-
Notifications
You must be signed in to change notification settings - Fork 146
cmake-js v8 ideas #310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hi I have finally managed to use this project and am glad to move away from gyp. My observations are
In my current setup I have learnt a lot from |
Do you mean you needed a newer version of the node-api headers?
honestly, I am not sure either. I took over this project a year ago with my initial focus being to update and prune the dependencies, and since then mostly just fixing bugs. I have yet to fully figure out how everything works. |
FWIW, aws-crt-nodejs has just updated to node 14. Repeating a comment I made on an issue over there, I personally do not like the idea of taking the decision of what runtime to use out of the developer's hands (we're both middleware libraries, so the language runtime a user app executes on doesn't seem like it should be our decision). A developer may have a good reason to run their application on an EOL version of node, who am I to say no? On the other hand, one of my colleagues pushed a bit to keep in sync with EOL because it's generally easier to keep CI/CD running on current runtimes rather than old, unsupported ones. For now, we're keeping things at 14, but with the notion that we'll more quickly bump towards current LTS if we run into CI/CD issues. |
True, but they could also stick to the previous major version. I have tried to fix any bugs reported for v6 of cmake-js, with #292 being the one which I have not been comfortable with the risk of fixing. |
Hello there. Thanks for making this project available, and forgive me in advance for the criticism. :P Let me share a small complaint about the differences in command-line options between cmake-js and plain CMake, e.g.:
The first distinction I came across was the use of Then I tried integrating with Emscripten to build Wasm targets, so I prepended the configure command with IMHO, a project that commits to porting an existing tool to another language should, as much as possible, try to remain faithful to the original, at least in terms of usage. |
That is a good suggestion, I've added it to the list. I think this ties quite closely into Now that I think about it, this could probably be experimented with a bit (along with some of the other ideas) in v7, by creating a new |
Hey there, I was just browsing and saw this discussion is currently active, and seemed quite interesting. Particularly, the notion of using some kind of CMake module to scaffold up a project and auto-resolve much of the CMake config, possibly in an extensible way. As it happens, I was kicking something like that around the past few days, inspired by some CMake programming I've encountered in projects like vcpkg, JUCE, CMakeRC and others; just making an interface library ('base') that acquires all the correct dev headers, library linkages, compiler definitions, and so forth, and then creating a CMake function a là A pseudo-example: # NapiAddon.cmake
# Create an interface library (no output) with all Addon API dependencies for linkage
add_library (napi-addon-base INTERFACE)
add_library (napi-addon::base ALIAS napi-addon-base)
target_include_directories (napi-addon-base INTERFACE ${CMAKE_JS_INC} ${NODE_API_HEADERS_DIR} ${NODE_ADDON_API_DIR})
target_sources (napi-addon-base INTERFACE ${CMAKE_JS_SRC})
target_link_libraries (napi-addon-base ${CMAKE_JS_LIB})
if (MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET)
execute_process (COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS})
endif ()
# Export a helper function for creating a dynamic ```*.node``` library, linked to the Addon API interface
function(add_napi_addon name)
# do some param validation
cmake_parse_args(...args)
# ...etc, then
add_library(${name} SHARED)
add_library(${NAPI_CPP_CUSTOM_NAMESPACE}::${name} ALIAS ${name})
# link this target to the Addon API interface library
target_link_libraries(${name} napi-addon::base)
# set up Addon config
set_target_properties (${name}
PROPERTIES
LIBRARY_OUTPUT_NAME "${name}"
PREFIX ""
SUFFIX ".node"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
# add source files to the target
napi_addon_add_sources(${name} ${ARG_UNPARSED_ARGUMENTS})
endfunction()
# Can extend the CMake interface with other useful functions...
function(napi_addon_add_sources name)
# do some param validation
cmake_parse_args(...args)
# ...etc, then
foreach(input IN LISTS ARG_SOURCES)
target_sources(${name} PRIVATE "${input}")
endoforeach()
endfunction() The builder then just makes sure that the above file is in their # CMakeLists.txt -DCMAKE_MODULE_PATH:PATH=/path/to/NapiAddon.cmake
project (demo)
include(NapiAddon)
add_napi_addon(addon
# SOURCES
src/demo/addon.cpp
)
# .and so forth! Note that this example actually builds if you import the script and source file attached below. No actual source code has been scaffolded for the builder in this process, but
...which I think is an acceptable bare minimum. I did play around further with generating a bunch of boilerplate source code, but IMO it just doesn't worth the hassle of setting up a project 'state' and working out when to generate the code and when not to, not causing unwanted re-writes in the source tree, etc. The builder need only do the usual Below I've attached an actual working example the suggested NapiAddon.cmake
I intend to keep playing with this over the next week or two, when free time permits. I'd quite like to experiment with things such as using I know it's a rough sketch of borrowed ideas - mostly from CMakeRC in this case - but maybe some of these thoughts might help stir the pot. In all, I really love how cmake-js promotes the productivity of the NodeJS-style workflow with the power of CMake, and if there is any opportunity to give back and see this thing continue to grow, it would be an honour to help in some way. Cheers |
I really like the idea of one-liners. As long as they don't change the meaning of command-line flags, they complement existing functionality and are a welcome addition.
That's a good idea. Actually, this could be the underlying executable called by |
@nathanjhood I started playing around with this myself over the weekend #325. I've gone a bit more monolithic with a single helper function so far, but I'm still in the stage of getting everything implemented and working
In #325, I have started down this route with a new Then I will make a |
Sounds good enough. Other names I can think of:
You might be interested in generating Node-API bindings for C++ using this pre-release version of SWIG, which adds support for TypeScript and WebAssembly. I'm using it in conjunction with emnapi. |
I think is worth keeping (or not losing) compatibility with IDEs supporting Maybe the first call is special and must be custom, but from there onward, I could even forget this is |
Thanks, I see the module you've begun on that tree. I do strongly suggest however that you expose a function that allows the builder to
I could specify more and more reasons for what is essentially my preference and I've no doubt that you have your reasons also. The main thing that jars with me in the implementation that you've started there, is that it makes assumptions about what the builder is trying to do, and in my case, those assumptions don't align with how I actually want to use cmake-js. For example, what if my project requires more than one Napi module? Just my 2 pence of course! Happy to raise a PR if you'd like further input from my side, beyond the verbiage.
Thanks, this does look pretty cool. Currently I'm just doing my bindings and typings manually, because again, I can't assume at the beginning of a project what functions I'll be exporting as development progresses. Perhaps this tool does some kind of grepping of source code and spits out the relevant typings? Fair play if so. Personally I am happy continue doing so manually for now. EDIT: Forgot to add that the whole 'bindings' part can be made much more manageable for obtainees of your addon, by using CMake to set a strict, predictable binary directory: function(cmakejs_create_addon _name)
add_library(${_name} STATIC)
target_link_libraries(${_name} PUBLIC cmake-js::base)
set_target_properties (${_name}
PROPERTIES
LIBRARY_OUTPUT_NAME "${_name}"
PREFIX ""
SUFFIX ".node"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
)
# ...
endfunction() Even better would be to replace |
One of the things that I disliked about
Personally, I think it's an awkward tool to use. CMake, on the other hand, is much more familiar to C++ developers. Plus, as @audetto mentioned, most C++ IDEs support CMake as build system. That's why I think this project deserves great significance in the JS Addon landscape.
Doing it manually has its appeal. The quality of the typings will be much better. Indeed, the TypeScript declaration output from the new SWIG is not perfect and is optional. On the other hand, generating the C++ binding code is very convenient, especially when one wants to port an existing library. |
As someone who started off in C++-with-CMake, and only encountered Web-with-NodeJS much later on, I fully concur. This tool has been the key in allowing me to bridge together my various interests and experiences; I even find the CLI to be useful on non-NodeJS projects in CMake!
Just one more note on the topic of boilerplate generation - readers are probably familiar with Another psuedo-example: string(CONFIGURE [==[
/**
* The '@NAME@' C++ addon interface.
*/
interface @NAME@ {
/**
* Returns a string, confirming the module is online.
* @returns string
*/
hello(): string;
/**
* Returns a number, confirming the Napi Addon version number.
* @returns number
*/
version(): number;
}
const @NAME@: @NAME@ = require('../@CMAKE_JS_BINARY_DIR@/lib/@NAME@.node');
export = @NAME@;
]==] typings_content @ONLY)
file(WRITE "${PROJECT_SOURCE_DIR}/lib/${NAME}.ts" "${typings_content}")
# Danger danger! *Never* overwrite the user's source tree contents! But as is clear from above, how can we know in advance what functions should be generated? Perhaps a seperate IMO the main hurdle for new-comers is more likely to be on the CMake side of things, so I am leaning my voice more in the direction of a strong yet simple CMake API, with no code generation. |
Ok, due to not wanting to pollute the thread any further with a singular voice, I took the bulk of my contributions to this discussion and made a quick demo project that presents a working example of my ramblings - a (quite scrappy) UPDATE: Added a routine to resolve all config/build steps using only CMake native commands; meaning, full IDE tooling integration (pending tests). It probably makes a better case than the lengthy posts; I shall continue to refine some of my suggestions further over there, rather than create a messy discussion. It currently takes very directly from the CMakeRC module; vcpkg also has lots of nice examples of Thanks for reading, over n' out. |
One useful feature that cmake-js should support is CMake presets. |
On the topic of binding code generation, I found this nbind. It uses |
I have encountered a slight issue regarding the existing implementation on the CMake side, that broaches on a breaking change proposal - and a solution. Here is some CMake that uses some of cmake-js' vars, mixed with CMake's own native vars: if(MSVC)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>" CACHE STRING "Select the MSVC runtime library for use by compilers targeting the MSVC ABI." FORCE)
if(CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET)
execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS})
endif()
endif() The problem? Well, it is not particularly clear which vars belong to which vendor's API. Most CMake users would expect any var that is prefixed as Obviously, nothing is broken, per se; everything builds and cmake-js made it to v7.x this way. But Kitware have actually been explicitly clear about not taking their namespace. There are some practical considerations that are not immediately obvious. These CMake definitions can be and often are used as C compiler pre-processor definitions, which are used to control logic within source files. Since C itself doesn't have namespaces, the convention is to use prefixed vars as a 'faux' namespace. Examples: Perhaps an acceptable compromise is for cmake-js to switch over to if(DEFINED CMAKE_JS_VERSION)
set(CMAKEJS_VERSION "${CMAKE_JS_VERSION}")
unset(CMAKE_JS_VERSION)
endif() @Julusian - while I haven't really incorporated the above, my proposed cmake-js CMake API is feeling presentable now; I copied the git workflows file from cmake-js and have my test project passing the same tests. Would you at all be interested in a PR on this? I certainly don't mean to oblige you at all, but I'd only be adding the EDIT: I also offer you the documentation and the demo project, if you might like to ship a working/tested example for newcomers. EDIT2: Opened on #326 Thanks |
I don't have a plan for when to do a v8 release, other than ideally not soon. I find that having semver breaking versions too frequently can cause pain for consumers, and many will often not update. Only 10% of users have updated to v7 after 12 months, while 75% have updated to the latest patch release of v6 in the 16 months after it was released.
Feel free to suggest any ideas of breaking changes you would like to see. Either as a short idea that could be added to this list or a link to an issue if it needs more explanation.
Some of this could be done by adding a second
cmake-js2
executable to v7, so that it can be done sooner without needing the v8 bump. In v8 the executables would then be renamed so thatcmake-js
uses the 'new' flow.Ideas I think should be done:
Rework logic to be done via loadable cmake modules
, could cmake-js simply be a helper to locate and invoke cmake with a sensible generator with all other arguments passed through?Ideas I am not sure about:
The text was updated successfully, but these errors were encountered: