Skip to content

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

Open
Julusian opened this issue Oct 29, 2023 · 18 comments
Open

cmake-js v8 ideas #310

Julusian opened this issue Oct 29, 2023 · 18 comments
Assignees
Milestone

Comments

@Julusian
Copy link
Collaborator

Julusian commented Oct 29, 2023

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 that cmake-js uses the 'new' flow.

Ideas I think should be done:

  • ✅ Consider updating minimum nodejs requirement. This needs some thought on what the new minimum should be, and if it should change at all. https://github.com/awslabs/aws-crt-nodejs is one of the larger users of this project and don't update their minimum often. As of writing, they have just dropped nodejs 10, 2.5 years after it went EOL. Their new minimum is nodejs 14, which was EOL 6 months ago. This should also be influenced by our dependencies, factoring what their current minimum is, and some thought on when will we get stuck on unsupported versions.
    • Builtin fetch support from node 18 would be nice to have to reduce the dependency footprint.
  • ✅ Projects should be assumed to be node-api based unless detected/specified otherwise. This flips the default behaviour, but will better indicate that node-api project should be preferred and are the future.
  • ✅ Rewrite the project in Typescript or ES classes. This would help with maintainability and readability of the code, but will likely be a breaking change for anyone using cmake-js programatically (is that even possible/supported?). As a sub portion of this, should we switch to ESM instead of commonjs? Maybe this could be done as a minor version, we don't claim to have a programmatic api...
  • ✅ Rework logic to be done via loadable cmake modules instead of injecting variables. The aim is to be able to perform the build with standard cmake, and to avoid the various snippets of cmake config we require users to add to their cmake files. This might help simplify the codebase, depending on how much logic we can conclude is no longer necessary.
  • ✅ Review the existing command line parameters. Can it be changed to use the same keys as cmake? When coupled with 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:

  • ⛔ Deprecate support for non node-api projects? As I see it, these projects are 'legacy' and there is no good reason to make a new one in this format. But there are likely many users who cannot justify the cost to port their project to node-api. Deprecating this could give them a needed nudge, or could just annoy them. If this is done, the earliest that support could be dropped is v9 unless there is enough objection.
  • ✅ I do not understand the usecase for much of the config field in package.json. Perhaps some work should be done to identify what portions are useful and clarify the intended use case in the documentation.
@Julusian Julusian self-assigned this Oct 29, 2023
@Julusian Julusian pinned this issue Oct 29, 2023
@audetto
Copy link

audetto commented Oct 30, 2023

Hi

I have finally managed to use this project and am glad to move away from gyp.

My observations are

  1. version of node headers. It is true that for node-api it does not download headers, but I was surprised by which ones are used. gyp seems to use the ones of the running node, while this project has fixed headers. It made a lot of difference to me as I need some 21-only experimental features.

  2. cmake-js vs cmake: being devil's advocate and I admit I understand little of the features provided. Why do we need cmake-js as an executable and not just as a cmake module? A bit like a find_package or a FindNode with a Node::API output that I can add as a target library. If something as complex as Qt can integrate natively in cmake, I don't think there are unsolvable obstacles. The advantages would be a better integration with cmake-based tools (VSCode).

In my current setup I have learnt a lot from cmake-js, but have moved away from it.
I ask node its version (node -v) and together with NVM_DIR I know the path of the header files.
I know this is very specific to my setup, but the advantage is that it is plain old cmake.

@Julusian
Copy link
Collaborator Author

version of node headers. It is true that for node-api it does not download headers, but I was surprised by which ones are used. gyp seems to use the ones of the running node, while this project has fixed headers. It made a lot of difference to me as I need some 21-only experimental features.

Do you mean you needed a newer version of the node-api headers?
You are right in that the version we provide is a little old, they were not using semver friendly version numbers until a couple of releases ago and I havent gotten around to releasing an update with the newer version

cmake-js vs cmake: being devil's advocate and I admit I understand little of the features provided. Why do we need cmake-js as an executable and not just as a cmake module?

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.
I have seen cmake-js do all kinds of things to handle certain arguments for cmake, such as the toolset and generator. But whether these are needed, I cannot say. But I like the idea of at least reviewing things, and trying to make it so that we provide some cmake modules that can be loaded instead of injecting various defines, and see how far we can get with stripping back the logic here.
I don't think we will be able to get rid of the command entirely, but maybe using it could become optional in certain scenarios.

@bretambrose
Copy link
Contributor

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.

@Julusian
Copy link
Collaborator Author

Julusian commented Nov 7, 2023

A developer may have a good reason to run their application on an EOL version of node, who am I to say no?

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.
That bug is also a good example of why to not support too far back in EOL versions, as it will result in needing to stick to old and possibly unsupported versions of dependencies.
But at the same time I know that too frequent major version bumps will fragment the userbase, so I don't want to do it unless there is a reason to.
So really that item in the list is a 'consider updating', and what version will be selected will be determined when it is done, based on both what users appear to be using and what seems realistic to be able to support for a few years.

@dsogari
Copy link

dsogari commented Jan 21, 2024

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.:

Option CMake cmake-js
source directory -S -d
build directory -B -O
toolset name -T -t
cmake variable -D --CD
Flag CMake cmake-js
-T toolset name target name
-D cmake variable debug config
-B build directory build config
-C initial cache prefer clang

The first distinction I came across was the use of -O instead of -B for the build output directory. While this did not block me in any way, it was a bit annoying, since I expected the same options to be specified in the same way.

Then I tried integrating with Emscripten to build Wasm targets, so I prepended the configure command with emcmake. What this helper does is add the appropriate -DCMAKE_TOOLCHAIN_FILE and -DCMAKE_CROSSCOMPILING_EMULATOR arguments to the command. However, cmake-js treats -D as a flag for debug configuration, whereas it expects --CD for custom arguments. You can guess how frustrating it was for me to hit upon this issue, though I was able to get around it by parsing the result of emcmake echo and forwarding it to cmake-js.

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.

@Julusian
Copy link
Collaborator Author

Julusian commented Jan 22, 2024

@diegosogari 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 Rework logic to be done via loadable cmake modules instead of injecting variables., as they both want `cmake-js to be very minimal and be as close to proper cmake as possible.
I think this will require some thought on how to approach, as one of the nicer things cmake-js does is provides a few simple 'one-liners' which would otherwise be multiple commands. So I think it becomes a question of how minimal/transparent should the cmake-js command be?

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 cmake-js2 executable. I've started having a play around with this #325

@nathanjhood
Copy link

nathanjhood commented Jan 22, 2024

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à create_napi_addon() which itself calls add_library(${name} SHARED), sets up the target, and then links to 'base'. Thus, the intended interface is just a simple CMake function that sets up a regular target with all the appropriate CMake config to be a Napi addon, and which silently links with everything you need.

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 CMAKE_MODULE_PATH, so they can include() it and set up the Addon target using the wrapped function in as little as:

# 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 <napi.h> is obtainable in the above-located source file, so getting started is as easy as:

// @file src/demo/addon.cpp

// Required header and C++ flag
#if __has_include(<napi.h>) && BUILDING_NODE_EXTENSION

#include <napi.h>

namespace NAPI_CPP_CUSTOM_NAMESPACE
{

Napi::Value Hello(const Napi::CallbackInfo& info) {
  return Napi::String::New(info.Env(), "addon is online!");
}

Napi::Value Version(const Napi::CallbackInfo& info) {
  return Napi::Number::New(info.Env(), NAPI_VERSION);
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {

  // Export a chosen C++ function under a given Javascript key
  exports.Set(
    Napi::String::New(env, "hello"), // Name of function on Javascript side...
    Napi::Function::New(env, Hello)  // Name of function on C++ side...
  );

  exports.Set(
    Napi::String::New(env, "version"),
    Napi::Function::New(env, Version)
  );

  // The above will expose the C++ function 'Hello' as a javascript function
  // named 'hello', etc...
  return exports;
}

// Register a new addon with the intializer function defined above
NODE_API_MODULE(addon, Init) // (name to use, initializer to use)

} // namespace NAPI_CPP_CUSTOM_NAMESPACE

#else
 #warning "Warning: Cannot find '<napi.h>' - try running 'npm -g install cmake-js'..."
#endif // __has_include(<napi.h>) && BUILDING_NODE_EXTENSION

...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 #include <napi.h> and NODE_API_MODULE and such themselves, which is reasonable enough IMO.

Below I've attached an actual working example the suggested NapiAddon.cmake which actually builds and runs the above demo CMakeLists.txt and source file under cmake-js 7.3.x. It's really just a quick draft with bits missing, ideas not yet in place etc, but it might encourage some other ideas in this direction, I might hope! :)

NapiAddon.cmake
EDIT: The linked file has been renamed, but the repo makes it's example usage clear.

  • The generated target is a regular CMake target, so the builder can continue to target_include_directories() and such forth, if they want.
  • They can also add_napi_addon(whatever) as many times as they like, as long as they use different source files and names.
  • The extra function napi_addon_add_sources(<target> <source/file.cpp>) can also be used to freely append additional sources.

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 NAPI_CPP_CUSTOM_NAMESPACE as the name of the alias library target, and how to manage the exceptions specifications macros, and such forth.

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
Nathan

@dsogari
Copy link

dsogari commented Jan 23, 2024

@Julusian

I think this will require some thought on how to approach, as one of the nicer things cmake-js does is provides a few simple 'one-liners' which would otherwise be multiple commands. So I think it becomes a question of how minimal/transparent should the cmake-js command be?

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.

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 cmake-js2 executable. I've started having a play around with this #325

That's a good idea. Actually, this could be the underlying executable called by cmake-js, which would then be just a top-level wrapper. For users who want a more "original" experience, or need complete compatibility with plain CMake, they could bypass the wrapper.

@Julusian
Copy link
Collaborator Author

@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

@diegosogari That's a good idea. Actually, this could be the underlying executable called by cmake-js, which would then be just a top-level wrapper. For users who want a more "original" experience, or need complete compatibility with plain CMake, they could bypass the wrapper.

In #325, I have started down this route with a new cmake-js-cmake (yeah I don't like the name either, I'm open to ideas) which is intended to use some logic to find cmake, then passes through all parameters unmodified.
There is a lib.cmake which provides some util to replace the things that cmake-js usually injects via parameters.
With these two bits, you get a minimal form of the 'magic' that this library provides.

Then I will make a cmake-js2 which will provide various one-liners for an 'easy' experience, which will share the finding cmake logic, and will try to match parameter naming as much as possible. Maybe not every parameter type will be supported, we shall see

@dsogari
Copy link

dsogari commented Jan 23, 2024

@Julusian

In #325, I have started down this route with a new cmake-js-cmake (yeah I don't like the name either, I'm open to ideas) which is intended to use some logic to find cmake, then passes through all parameters unmodified.

Sounds good enough. Other names I can think of: cmake-js-bare, cmake-js-invoke. Maybe even just cmake, if not for the fact that a user might install it with -g, in which case it could cause problems for other projects using the system's CMake.

@nathanjhood

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.

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.

@audetto
Copy link

audetto commented Jan 23, 2024

I think is worth keeping (or not losing) compatibility with IDEs supporting cmake.

Maybe the first call is special and must be custom, but from there onward, I could even forget this is cmake-js and just use it as a plain cmake project.

@nathanjhood
Copy link

nathanjhood commented Jan 23, 2024

@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

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 create_cmakejs_module(name) instead of stealing and forcing the PROJECT_NAME namespace, because;

  • Adoption is entirely optional by the user. If they don't call the CMake function, nothing gets built unknowingly, and you can even ship it to current users without affecting them (see your main points in the OP)
  • Personally, and I feel I am probably not alone, I reserve PROJECT_NAME for my main implementation, which is not guaranteed to be the Napi module itself. My Napi modules are consumers of whatever I'm building under PROJECT_NAME
  • By providing a base interface library, it is possible to link any type of target to the Node Addon dependencies, without forcing that target to actually be a module

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.

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.

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 CMAKE_BINARY_DIR with a new var such as CMAKE_JS_BINARY_DIR which the user may modify if they wish, and which otherwise defaults to CMAKE_BINARY_DIR.

@dsogari
Copy link

dsogari commented Jan 23, 2024

One of the things that I disliked about node-gyp is that it's very opinionated. For instance:

  • You can see all kinds of additional compiler options that it adds here. This doesn't seem to work well with Emscripten's emmake, such that I had to negate some compiler flags (e.g., 'cflags!': ['-m64']).
  • It doesn't let you specify the build output directory, because it's hardcoded as build. This makes it a pain to build the same target for multiple platforms/arch.
  • The clean command just removes the build directory.

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.


@nathanjhood Currently I'm just doing my 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.

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.

@nathanjhood
Copy link

nathanjhood commented Jan 23, 2024

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.

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!

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.

Just one more note on the topic of boilerplate generation - readers are probably familiar with yo napi-module for scaffolding with node-gyp. This has typescript support out of the box, by generating some typings using vars from the build script. I was examining the source code, and it seems obviously quite easy to use CMake's configure functionality to do exactly the same thing.

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 cmake-js init command would be a safer bet (EDIT: and might also save a few recurring "where is the header I need?" tickets); or, IMO better yet, reserve such functionality for something like a yarn create cmake-js-addon script... But I hesitate to contribute further to the discussion of code generation within the context of cmake-js doing it's standard configure routines or anything like that.

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.

@nathanjhood
Copy link

nathanjhood commented Jan 23, 2024

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) CMakeJS.cmake module, being consumed by a simple hello-world style Addon project (including typings and other little things).

My suggested implementation.

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 cmake_arse_pargs() that might make an appearance in future revisions.

Thanks for reading, over n' out.
Nathan

@dsogari
Copy link

dsogari commented Jan 25, 2024

One useful feature that cmake-js should support is CMake presets.

@dsogari
Copy link

dsogari commented Jan 25, 2024

On the topic of binding code generation, I found this nbind. It uses node-gyp and doesn't appear to be currently maintained, but is interesting nonetheless.

@nathanjhood
Copy link

nathanjhood commented Jan 28, 2024

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 CMAKE_ to belong to CMake itself; i.e., Kitware. Due to some slightly unfortunate choice of naming, however, the cmake-js vars are encroaching into the CMake vendor namespace.

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: napi_*, and quite possibly cmake_*. By being 'in' the CMake namespace, this might present issues from Kitware's perspective, particularly to their source code.

Perhaps an acceptable compromise is for cmake-js to switch over to CMAKEJS_* for it's CMake vars prefixes. Doing so would obviously break all existing cmake-js projects, so I additionally propose that this could be easily handled on the CMake side; if the 'old cmake-js' vars are defined, cast them to the new ones. Else, just take the new ones:

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 CMakeJS.cmake module I created and tested, and nothing more. I haven't touched any of the existing cmake-js source code at all, just done a bunch of CMake and done a bunch of useage scenarios with it. You could do with it whatever you wish, the choice is yours.

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
Nathan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants