-
Notifications
You must be signed in to change notification settings - Fork 137
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
'CMakeJS.cmake' API proposal - initial #326
base: master
Are you sure you want to change the base?
Conversation
Thanks for helping with this. I've been meaning to properly look over what you have done, its similar but probably better written than my attempt #325. Honestly, my cmake knowledge is not amazing. It has mostly been from figuring out a few existing projects and improving them, so I am probably lacking a lot of knowledge especially in advanced things and in conventions. One key thing is that I don't think that this new cmake file should pay any attention to the My first question, what is the reason for doing:
instead of the simpler: To me it feels nicer to have it as a single line, but perhaps there is a good reason I am not aware of to do it the other way? |
Hey, thanks for getting back to me! I'm more than happy to answer the bunch of things best I can. There will surely be things! There is no real reason for the 'share' dir. I was just trying to offer a clean presentation to make it more enticing for you. One slight consideration is that, often with CMake modules, the mechanism might be split across more than one file: there is often a Along with the above, I don't know if you might like to hold things like the documentation, and possibly some example/test project(s) alongside the module, all in one out-of-the-way dir. It's your prerogative, in the end. End-users can append the module to their path, or just include it directly. At the end of the day, it will be up to them, no need to force anyone to do it a certain way. Perhaps it might actually be more 'conventional' to place a CMake has a little bit of an art to it; when it's right, it is really flexible. Otherwise, it's really brittle and seems like every little change breaks something. You have absolutely the right idea with the beginnings or your CMake module but there are "bits" to get right, and I saw a chance to give back here. Keep the questions coming, Best |
share/CMakeJS.cmake
Outdated
endif() | ||
|
||
# Resolve NodeJS development headers | ||
# TODO: This code block is quite problematic, since: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I don't think this should be done. In my mind, if node_modules
is missing that means that cmake-js
and therefore this cmake file will not be available and so is a non-issue, or for safety it should simply fail here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair - if the logic holds up with the codeblock removed, better than adding complexity with the add_custom_target()
idea.
I'll let you know if any issues do arise in my tests, but I see what you mean!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In deeper reflection (sorry not at my machine rn):
That codeblock is the bit that makes it all work without running from cmake-js.
Users can build everything with just normal CMake CLI commands - it works because of that hacky line. I obviously feel the solution needs a lot of work, but it does build without cmake-js!
Check my demo commands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is ultimately going to come down to how you install cmake-js, its not necessary to be able to use plain cmake
If you install it globally on your machine, then this block might be useful but as you point out risks performing a build as part of the yarn install
.
If you install it as a dependency of the project, then this block is not needed as this file has already been loaded from the node_modules so it must exist.
Ideally we should support both cases, so the question becomes how much to do automatically (in the first iteration).
I personally would be ok with simply failing saying that the directory doesnt exist. This is no different to other cmake projects which rely on system dependencies to be installed.
And if someone feels strongly opposed, then this could easily be added later on. Perhaps the package manager problem can be solved by reading it from a variable? I don't know how to solve it might trigger another build, my first thought was using an environment variable which would tell cmake-js to skip the build but that would cause issues if there was another cmake-js based dependency to be installed.
Side note, I have never been a fan of installing things like this globally. Wouldn't everyone end up having issues once different projects require different versions of whatever you installed globally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note, I have never been a fan of installing things like this globally. Wouldn't everyone end up having issues once different projects require different versions of whatever you installed globally?
Globally installing with cmake-js "just works" for me, and usually fixes any missing headers etc that I've ever encountered. Gladly, it works perfectly in sync with nvm
- so, my cmake-js libnode-dev filetree version is in sync with my runtime node version, without any effort from my side. It does what it does well, but one slight issue with this is that I don't necessarily want a '.cmakejs' dir in my home folder, containing multiple filetrees. Would be nicer if these could all be kept project-local, either in 'node_modules' or the build dir, or anywhere where we can easily just blow them away when closing down the project.
But yeah, nvm and cmake-js always worked well here!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does what it does well, but one slight issue with this is that I don't necessarily want a '.cmakejs' dir in my home folder, containing multiple filetrees.
I believe that the .cmakejs
folder contains a cache of the 'full' headers that have been downloaded, to avoid needing to constantly redownload them. If they are only cached inside of the project folder, then there will be a lot more cache misses. (when switching project, when doing a new clone, when cleaning out all uncommitted files)
But these days this is only done when not using node-api
share/CMakeJS.cmake
Outdated
# Generate the identifier for the resource library's namespace | ||
set(ns_re "[a-zA-Z_][a-zA-Z0-9_]*") | ||
|
||
if(NOT DEFINED ARG_NAMESPACE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this require the NODE_API_MODULE
to be called inside a c++ namespace
? Thats not something I've done before, so might need to be optional
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think NODE_API_MODULE
actually cares about what namespace it is in. In fact, if you were to define it in a different namespace than the Addon functions, it might even fail to build unless the module specifier carries the namespaces too:
NODE_API_MODULE(addon, demo::addon::Init)
NAPI_CPP_CUSTOM_NAMESPACE
is part of the Node Addon API (defined in napi.h
). It is demonstrated in the source file attached. Implementing it is entirely up to the user (I personally never knew about it 'til recently).
One of these bug-bears about namespaces, is that your code and your CMake targets should have consistent namespaces. You might be compiling a class found in vendor::library::class
, in which case the CMake target should also be vendor::library::class
(usually done as an ALIAS to the class
target).
I mostly tested within the defined namespaces and getting it right was tricky :)
Will try a test project with no namespace in the source code and see if I can break it with the NAMESPACE arg, and also vice-versa.
share/CMakeJS.cmake
Outdated
node-addon-api | ||
cmake-js | ||
) | ||
export ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont have any idea what this and below is doing right now. Not things I have encountered before
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loving this feedback, thanks for your time and thoughts so far @Julusian ! I'll step away for a minute so we don't keep cross posting. Meanwhile, if you haven't done so before, do check out MS's vcpkg, the C++ package manager which is kind-of taking on an npm-like role in C++ world. You just clone it to HOME (or use it as a git submodule), and it contains a (DISCLAIMER: I "borrowed" some vcpkg ideas to work on adding the full MSYS2 toolchain to vcpkg... maybe I'll finish it one day) The entire premise works because all of the packages in the vcpkg registry all contain a CMakeLists.txt which defines and exports it's targets. There actually already are some packages in there - Part of my proposal of this The potential of getting cmake-js into the CMake-sphere is pretty exciting. I keep saying there are things that need to be considered, and mostly I'm thinking about "how to ship cmake-js to vcpkg" :) EDIT: It's also about allowing cmake-js users to ship their addon projects to vcpkg, if they wish. cmake-js has to do it's part, like probably exporting some interface libraries, for that to work for end-users. Hope that may give some context to some of the suggestions I've made! |
Those last two commits are just a quick vcpkg demo - as a demo you may add I suggest I remove this for the moment and hopefully revisit vcpkg much later, to keep a clear focus on the actual API proposal for now. This hopefully just gives a bit more insight into all my concerns about "convention" - it would pay off immensely in the long run to understand now, early on, what to work towards, IMO. It's up to you how much of this do you feel is worth taking on, potentially bringing in an influx of C++/CMake-side visibility, etc. Those users would be able to just add cmake-js to their Over to you @Julusian , I'll be around! |
A quick word on the CMake-side interface for how these types of modules might usually be implemented. Below is a 'typical' end-user's CMake project, consuming the CMakeJS.cmake API, constructed according to the user guide on dependencies cmake_minimum_required(VERSION 3.15)
project(MyAddonProject VERSION 1.0.0)
find_package(CMakeJS REQUIRED)
add_library(MyAddon SHARED addon.cpp)
target_link_libraries(MyAddon PRIVATE cmake-js::cmake-js) # deps are resolved!
set_target_properties(MyAddon PROPERTIES PREFIX "" SUFFIX ".node")
While I don't really feel a strong preference for any particular inclusion mechanism, I think it would be sensible to align with expected behaviours. I will take a look at a few CMake modules in vcpkg (some examples I like are fmt, ableton-link, curl...) to get a generalized idea of any concepts missing or misplaced in the currently-proposed API design. Perhaps a test or set of tests should be established, built around the boundaries of what you feel should be supported at this time, and what should not. |
Won't some guidance need to given to cmake on where to find cmakejs still? From reading those docs it is looking at In another pure c++ with cmake project, I never entirely got along with cmake using Perhaps I should clarify that I am primarily thinking about libraries which will be published to npm. While prebuilds mean that in most cases compiling can be avoided, if it does end up being compiled I don't want users to have to think about anything (other than installing cmake on their system). |
I wrote most of this on saturday, and haven't looked over any commits/comments made since then, so some of it could be commenting on things that have been changed already.
By 'install' I mean the various cmake calls to
I've been mostly ignoring the demo. So far I've only really read the CMakeJS.cmake and that is what I have been focussed on. This could mean I am missing some context for things. But it seems like a chunk of this file is setting things up for users to be able to use cpack or 'install' or whatever.
Again, I think I am just missing examples I can run/inspect of how this might be used in practise. I have had a couple of npm libraries using cmake-js for years, and other than people not knowing what cmake is and that they might need to install it, everything else has worked. and there isn't anything to do with For example https://github.com/Julusian/node-jpeg-turbo/blob/main/CMakeLists.txt, where the cmake file is less than 50 lines long. I dont remember having any issues reported there which were caused by cmake-js or the naive cmake usage.
For now you are correct, but in cmake-js v8 this will be the only supported way to do things.
This could well be the root of the issue of that I don't think I know anyone experience in cmake, nor many who write much c++. My c++ usage could almost be labelled as hobby usage given how little of it I do.
Sure, but the aim of this library/package is to help bridge c++ and js, so it should be expected to have some js-ification of some c++ concepts (eg, every type node-api uses). In particular, seeing as I (as a user of cmake-js) added node-addon-api to my package.json, it would then be installed into my node_modules (so far normal js things), it then seems a little odd to copy that to the build dir. Will these copied headers be updated correctly when updating npm packages, followed by a re-run of
As I said somewhere earlier, those But yes, I was going to comment the other day on how it seemed weird to be copying the includes like it does, until disabling it revealed that error. So while I don't particularly like that it does a copy, it has a purpose and is necessary so is fine.
No, you do not need them at all. cmake-js tries to detect if the project is node-api (there is no good and 100% reliable way to do this though, one of the points on my v8 list was to swap the default behaviour to assume node-api unless proven otherwise), and depending on what it concludes, it sets different paths into Lines 227 to 258 in 3922fb8
cmake-js install , it won't even download the headers Lines 53 to 54 in 3922fb8
They are needed for 'old style' native modules (not using node-api), but that is another different challenge to tackle. For those we should also be providing the
Yeah, but yarn/npm does put a cache in there somewhere, so this is not entirely inconsistent ;)
I agree, and that is how it works today, other than needing cmake to be installed. |
Thinking about the globbing of headers, from what I managed to find its a problem mostly because it wont detect new files being added. In this scenario I am envisioning that they add a new header file which is added a dependency of an existing header file. (ie Maybe there are other problems with globbing that google didnt reveal (the first few answers I found didnt explain why it was bad practise), but I think it is something worth thinking about. I am also wondering about having
maybe these node-api and node-dev should depend on cmake-js? that way we can be sure that the users who don't know they need to link the delayhook will get it implicitly a I feel like this would make it easier to understand what link targets are available, as only the ones you call the Perhaps it would make sense to move the root cmakelists and demo code to be a project under the tests with the others? That will help clarify that it is only for testing purposes of this project |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've had a read through the tests/api and tests/demo projects.
Ignoring the tests/targets for now, as they will need to change when reworking --link-levels
|
||
#else // !__has_include(<napi.h>) || !BUILDING_NODE_EXTENSION | ||
#warning "Warning: Cannot find '<napi.h>' - try running 'npm -g install cmake-js'..." | ||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I'm a fan of this pattern. But equally, it doesn't matter, these are example projects.
That should say to do npm install node-addon-api
though, as that is where the header comes from
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi! Sure, I really don't mind. I thought it may be useful, when newcomers pull an addon project off git and open it in their IDE, to not greet them with a page full of red intellisense warnings about stuff missing. C++ is garish enough as is! Of course it does not fix or break anything. The 'good' stuff is just to get some function examples lighting up for them, this is no biggie for me.
"dependencies": { | ||
"cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api", | ||
"node-addon-api": "^7.1.0", | ||
"node-api-headers": "^1.1.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't technically needed, as this is already a dependency of cmake-js, but perhaps it should be moved so that projects which use cmake-js should also be providing it as a dependency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I haven't quite had the fortitude to remove these lines, for fear that it all breaks! But in theory I had thought as you say. If we can drop deps (visual complexity) and not break stuff, then even better!
// This small codeblock in your root-level index.js allows others to consume | ||
// your addon as any other NodeJS module | ||
|
||
const addon = require(`./build/lib/addon.node`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This path is different to before, which will be problematic. Most people use a library to do this loading, to ensure it is cross-platform and handles other variances. The most common one is https://www.npmjs.com/package/bindings which looks like it will not find this.
Other tooling for distributing prebuilds of libraries on npm will also likely not find these
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I dont like the bindings package a whole lot myself. I mean, it's well made sure, but I've found myself needing to overwrite it's implementation in some builds and that became such a huge headache that I turned to using custom bindings.
There is a custom bindings generator function in the API now; it works fantastic until it doesn't (experimental) - it's a good demo of doing 'configured' code generation with CMake, which I am sure might inspire more ideas in you. The trick is the @ONLY
var, which means that the vars surrounded by @@
will get evaluated when the function runs, but the regular-looking CMake vars will remain as string content.
Do as you please of course; my focus is mostly the CMake.
|
||
console.log(addon.hello()) | ||
|
||
// If I swap my package.json dependency for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps this demo should do both, to show this off as well as talk about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure!
namespace Napi | ||
{ | ||
namespace NAPI_CPP_CUSTOM_NAMESPACE | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a good idea to do?
This looks to me like you are defining your methods inside the Napi::hello
namespace, which is also where the Napi
types reside. Meaning this could result in collisions if you try to use names they are already using.
I could be wrong, I didn't read their header that closely
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I copied it from napi.h
It's their definition, not mine, and this is what it is for. It isn't necessary but the header makes it's usage quite clear.
What I will say is: napi.h provides this definition, so we should offer a hook into it, IMO. Optional, of course, which it is within napi.h as well.
I actually believe from the notes in their header, if I'm not reading it badly, that they semi-suggest using the namespace precisely because stuff is already defined in there, so you will not be able to create a function named 'Object()', since that already exists and is very difficult to overload. I probably am wrong, but napi.h appears to be suggesting that this is what this optional var is for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Annoyingly they have no documentation on this other than some comments in the header which dont explain how to use it.
But this part:
https://github.com/nodejs/node-addon-api/blob/ea712094e3cfd0ce973631683a1c3275e451fa32/napi.h#L163-L166
It reads to me as they are creating the symbols under a namespace, then they are reexporting the symbols under the Napi namespace. In other words, usage in user code is identical to if it isnt set.
I have tried this, and I didn't need to be inside any namespace or use any 'using' for Napi values to still work
// Export your custom namespace to outside of the Napi namespace, providing an | ||
// alias to the Napi Addon API; e.g., '<vendor>::<addon>::Object()', along with the | ||
// functions defined above, such as '<vendor>::<addon>::Hello()'. | ||
namespace NAPI_CPP_CUSTOM_NAMESPACE::CMAKEJS_ADDON_NAME { | ||
using namespace Napi::NAPI_CPP_CUSTOM_NAMESPACE; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Im curious, what is this solving?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I have Nathan::Addon::Hello()
.
Let's say I put a second addon on my CMakeLists.txt, with it's own source file. Link the first one to the second one. Now, Nathan::Addon::Hello()
is available in both source files.
I might even call that function in my second addon and pass one Napi::Env
to another.
Is this possible? I really don't know, haven't tried yet, but I think context-aware addons probably can do this.
Again, it will not break my heart if this line of code was wiped. Just more "stuff folks can do".
@Julusian one wider remark tied our overall convo and not a specific code change: I believe we can make a decent compromise about "relocatable targets" and whether we should be moving stuff around or not. The API was working just as nicely (as far as 'config/build') when we had our targets staying in one place. cmake-js has always done this; and not only do I see no reason to fix what isn't broken, I am also unnerved about breaking something for someone somewhere as a result of them expecting some behaviour which we changed drastically. I believe lots of CMake projects carry a few universal-ish vars, things like There is a var like this called something like We can probably quite easily "walk back" our relocatability far enough to retro-fit an option/logic switch around it. We dont want to define or rely on I have no qualms setting default to 'off' i.e., old behaviour, as long as it's in there somewhere. This default will do more to ensure existing users are protected from potentially breaking changes (and more "where's napi.h?" tickets) which is really crucial from my side of this proposal. I can have a go at retrofitting such an option in the coming days, but by all means, and particularly if you have a specific thought or idea on this, be welcome to tear into the task yourself if you wish. It is reassuring to know that you're seeing what I am getting at here, whether or not you see the merit in supporting it. As code owner, I will respect whatever you decide of course, but I am starting to feel like the case has been made and trust that whatever you do decide to do, you will have weighed your decisions from the perspective of users such as me also, even if we don't have like-for-like workflow, needs, and end goals. What was really getting me anxious was the idea of seeing cmake-js turning away from a whole lot of possibilities that it wasn't even fully aware of. And once you get so far into your CMake project, detangling is such a nightmare that you really only want to try do this once and do it well enough to be rock solid for years to come. I am deeply sorry for the massive PR here (I'm a bit green in some regards...), but I do feel we have a really tactile developer experience on our hands, a merging not just of two languages but two workflows, and the CLI already did so much lifting that I've been able to discover new ideas in the API as I've gone (i.e., the link level arg... feel free to rename this if you like, maybe 'API level'? up to you...) I'm feeling more confident now that I know you are at least aware of what untapped potential cmake-js still has in it. |
I will give a bit more detail on the remainder of this post from my perspective - after giving you a chance to digest this round of commentary (no API commits though! Feeling good). I just want to highlight something about a certain strategy that you really must avoid, from a security point of view. I beg your pardon that you might surely know much of this, but as always, a bit of context makes it all the more clear. Context:
The elements prefixed with The elements passed in with the
That is extremely slow. CMake also keeps a thing in the binary dir like 'verifyglobs.txt' which keeps a tab on things changing, and runs the entire globbing expression again whenever it evaluates to be necessary. If that means you're globbing a huge header fileset within your own source dir, and make one tiny change like a typo, the entire globbing regex might re-run when the change is detected by CMake. It's slow... I had it working on the That is actually not my biggest concern here though. What if your remote server is running cmake-js and building your addon, but a malicious user has injected a file into your compile line? Something like How? $ yarn cmake-js configure --CDNODE_ADDON_API_FILES=malicious_in_disguide.exe.cpp If that file is being Uh oh. It might not quite be that easy, but I'd be willing to bet that someone will try it. It might not happen to CMake users at home with no internet, but in NodeJS land? You should see my public server logs... I'm not prepared to let anything have a chance. So you can see I've done battle with that implementation in order to be as sure as possible that things are being well-processed and managed to the best of CMake's functionalities. Oh, I'm passing all the headers into target_sources(), I know... but that's what FILESET HEADERS takes care of - CMake itself will go, "oh ok, those ones go on
Now that I hope this is a bit more clear, you can probably feel a bit more comfortable breaking my work down as you see fit, knowing a bit better about why I did certain things in certain ways (even when other easier means seemed to be available). CMake offers these newer functionalities for reasons. I say use them, let kitware do what they know is best with it. Once you're in their API, they are taking care of it "all working properly, all the time, everywhere" so that we don't have to. You just have to do their dance a little bit to get those benefits. |
Tbh we may remove the root CMakeLists.txt, my demo.js/.ts/.d.ts under 'lib', my commands from package.json, and also the typescript/node types dep I added (that was purely to activate the intellisense for the demo, obviously not something we truly need). Now that we have example projects, we can test stuff out on those. It is rather handy, though, to use that CMakeLists to quickly jot down a function you just added to API, and give it a few quick tests right there. I don't know if you have thoughts on any 'actual' CMakeLists project under the cmake-js name. I mean, there is scope for anything really; 'add_executable' could put together a really simple native CLI for adding a bit of control over the header package downloads... perhaps? Perhaps not. I'm just pointing out the cmake-js still has scope for it's own CMake project, should the need ever arise. That was the intent behind sticking our API off to the side under 'share' instead of being a root-level file. It can stay out of the way when not needed. IMO, pre-existing projects should probably be shielded from unintentional contact with the new API, ideally. In any case, everything under the 'cmake-js' CMake project space in the current proposal is fully removable without breaking anything in the API. Your ideas for more functions sounds very welcome IMO. Keep in mind that includers of our API will also gain these functions for their own customary usage, if they wish. project(my_custom_addon)
include(CMakeJS)
# NOTE: If we add a 'project()' call in the API, then it becomes imperitave that users only include us *after* their own 'project()' call, otherwise we are forcing them into building a sub-project of cmake-js, which is really not the correct thing to force on builders. This is why I never defined our own 'project()' in the API, but instead in an off-side file for internal usage.
cmakejs_acquire_napi_cpp_files()
cmakejs_setup_napi_things()
cmakejs_create_napi_addon()
# ... The above was my reasoning for refactoring some logic into separate functions. Not to mention, that by functionising it's gonna be much easier to debug individual things in isolation. Not to mention, to isolate the changes we inevitably make to these functions, over time. Also note the usage of PARENT_SCOPE when inside a function. I'm not sure if it may be better to swap functions for As long as the functionality is quite clear then I think those functions are quite cool and handy to have around, as a user. Especially for stuff like tricky CI/CD runs with permissions hell going on. Perhaps one platform just needs the little extra helper in order to build... About link-level and optional targets... I honestly haven't even thought about NaN (where on earth do they get these names??) I spy a new ticket on the issues page which may provide us some further insights into things people want to do with cmake-js. We totally have the opportunity right now to account for these types of users. You may be totally right about not needing link level, but do be weary about how our create_addon function is currently expecting to link to cmake-js::cmake-js, and of course be sure to account for that in any changes you make. The inter-dependencies between targets is something I tried to hard-code away from end-users, and alleviating the strictness of this may lead to further tickets from confused users. For consideration! Ciao for now * (Be aware that CMake projects do not exist side by side. Once the first 'project()' is called, everything hance forth is a sub-project of that project, and so on. This is why CMake generates a <project>IS_TOP_LEVEL var on every run. CMAKE_SOURCE_DIR is the 'top-level' master project, and CMAKE_CURRENT_SOURCE_DIR is whatever the most recent 'project()' call was. We definitely do not want to use PROJECT* in our API! It is for downstream users to control, not for us) |
@nathanjhood @Julusian pardon me for crashing this party, I am the author of [SWIG JSE] (https://github.com/mmomtchev/swig) which generates JS bindings from C/C++ headers. It supports dual build setups where it generates identical bindings for Node.js native and browser/WASM. Currently, my example project uses I have some additional requirements:
I will try to follow this discussion (so much prose...), but me neither I do not know CMake well enough to design the |
Possibly. I'm tempted to see how it goes with the relocatable stuff first, that behaviour can always be added later if it is actually a problem. Re globbing: OK. Im not convinced that is a realistic concern (if they were able to get a new file into the globbed path, wouldnt they be able to modify the CMakeJS.cmake to inject the file too?) But I'm fine with leaving it with them manually specified for now. |
re: globbing, just want to check you saw my edit re: Or, if we are not validating the collected headers, someone might possibly sneak this in using something like Uh oh! It seems that the idea of calling Not calling May I demonstrate in psuedo CMake, someone making an addon with our API: project(i_am_a_cmakejs_user)
include(CMakeJS) # imagine this contains a 'project(cmakejs)'
create_addon(my addon ....)
message(STATUS "PROJECT_NAME is ${PROJECT_NAME}") # output: "PROJECT_NAME is cmakejs" Not only are all of the - ```CMAKE_BINARY_DIR``` - ouch!!!
- ```CMAKE_SOURCE_DIR``` - users cannot build addons if this is not pointing at their dir!
- ```CMAKE_CURRENT_SOURCE_DIR``` - this would *also* not be pointing at their dir, but ours! I admit that I have not attempted to try this and see if everything breaks, but I want to make sure that it is understood, why calling Remember that CMake users know how to use CMake, and know how I expect those vars to behave in their CMake projects. Nobody else breaks these, I believe cmake-js should also not break them. Consider that vcpkg is an entire C++ package manager, written almost entirely in CMake. They never once call re: cmake_min_required Without this, you have to use Nobody in the developer world has a logical reason to use anything other than the latest Release version of CMake. Kitware are very clear on this. CMake is one of the golden examples of backwards compatibility in the software world, even CMake users complain that kitware refuse to break anything, even if for the greater good of today's developers. Banks are running COBOL and ancient compiler instructions, somebody is enabling them to do it. The above also pertains to why it is definitely worth going along with their tricks. By plugging in to their full interface feature set, we get all those benefits too. |
No, no intention of doing that
I use the version that is installable from my system package manager 🤷 (currently 3.25, Im using ubuntu 22.04) |
@nathanjhood I'm currently doing some reshuffling, mostly to get it to be using the function based feature flow. There is some renaming (they prefer calling things node-api instead of napi these days).
In other words, by using This is largely preparation so that |
Cool, what I'm just up to is new branches on my other repos, where I'm trying 'migration' from my old CMakeLists over to the new API, so thus I can have fun with it while staying off your commit radar :) As per my own last commit on this PR, every single project under
Actually that very last one is probably the crux of this entire 'relocatable' business. How can we addon builders send a CPack archive of our addon project files out, if it contains absolute paths? Broken record here, sorry - I am pretty sure this is all making sense now. Phew :)
Are any of these lines user requirements for the implementation to work? I don't object to passing simple function calls to newcomers, but we probably agree that we don't want to ask people to do things that they don't fully understand, we don't want to need to write lots of documentation just to get a newbie's addon online. But if it's a demo of what intermediate/customary workflows can achieve? I am all for providing these functions, yeah. Especially helpfully where somebody has a weird edge-case of CMake build failure, due to being on some weird edge-case platform. They can use logic to optionally do additional config like that. Additionally, I gave existing users a little extra protection by wrapping everything in those options at the top. I think there is some rationale to providing an 'easy' target level which just sorts out everything, and the others are more customary for people who only need a specific thing. The question is, which level of user should have which things provided to them, and which things should they also not have? Obviously, you as codeowner will have the best vantage point on this, in the end.
I did already build some of these from the current API - did you check the source files under As long as none of those example projects ever break from their functional state of my last commit here, then I don't really have any opinion on how you implement anything. Those projects are the experience I want from a cmake-js CMake API, and if that stuff all keeps working that way (maybe you will still improve on it somehow too) then I can go back to being a happy builder, who is now shipping my addons to my happy consumers, and everything is really sussed out for us all. Let me know if you want me to confirm anything per the above regarding any changes you make or propose. I appreciate your patience on this mega journey but I am quite happy to hand over the reigns, as long as my examples don't break (without a very compelling reason). My commit reference containing my completed proposal is: $ git rev-parse cmakejs_cmake_api
f427db8f706da4f02a4aacc1e46763306d9d3a51 I guess it will be preserved on my fork at I will probably deprecate the above fork in due course. I'd like to have it around for preservation though. Cheers and good luck! |
Yeah I did, but having removed the I have fixed most of them up, but until I fixup the
Whatever state that branch is in when it gets deleted, will be preserved as part of this PR. |
Right? CMake will fight you every step of the way, if you don't quite go along with it. This is why I can't always give you a better reason other than "CMake convention"... you sort of learn that once you just go along with it's ways, that is when suddenly a CMake project, no matter how complex, "just works". Even if you fix it for yourself in your own test addon, does it work for 'hello_consumer'? Obviously they should have a silent build experience The dependencies chapter, cmake-js already took care of this with its CLI. We didn't do any of this in the API because cmake-js already retrieves our dependencies (headers etc) but it's a good short little reference to skim, for context. Importing and exporting. Modern CMake. So, the CMakeJS API is exporting itself. This allows builders to import it. The import section is what users will expect to do with our API. The exporting is to be provided by the API... else, the user must export export it themselves to leverage this. no need at all to read them all - more rather, when you have specific queries, if isn't about dependencies or importing/exporting/relocating, then there is probably a nice digestible chapter on it in one of the other two links. At a pinch, just refer to 'Mastering CMake's chapters, the others for reference. Best of luck, I'll stay tuned in! |
Related #310
Here is my
CMakeJS.cmake
API proposal (MIT), more or less "complete", in as much as I feel it is presentable and also (potentially) solves many issues raised in other discussions and tickets.Constructed over on my demo project; just dropping it into a new 'share' directory right here. Could go at the project root, or anywhere really, as long as builders are informed so that they may append the location to their
CMAKE_MODULE_PATH
and theninclude(CMakeJS)
.It doesn't interfere with any existing source code, since there currently isn't any other CMake in this codebase to interfere with.
The most simple example of how this works for end-users in it's current presentation (as per the provided docs, and pretty much as per CMake conventions):
The above should build and run, as long as the consuming project has a corresponding
package.json
with cmake-js as a dependency:This demo project gives a full example; only difference is the location of
CMakeJS.cmake
. My suggestion is that it should be shipped in the cmake-js package tree (as presented), so that end-users will always find it under${PROJECT_SOURCE_DIR}/node_modules/cmake-js/share
- or, wherever.cmake-js' CLI could also add another flag to it's configure runs, making the API instantly available when running from cmake-js. The native CMake CLI arg would obviously be something like
"-DCMAKE_MODULE_PATH:PATH=path/to/cmake-js/share"
, but I have no suggestions on how to implement that in the context of cmake-js. Currently, it isn't actually necessary to make any such changes anyway, as long as the user adds the module to their path manually, somehow (like in the example).Those of us who are really familiar with CMake will usually expect that a module offers some targets. This one offers four interface targets (no compilation units of their own), with each one depending on the previous one, per the Addon API dependency chain. With this, builders can choose to manually link with a specific level of the whole dependency API chain (dev files, C API, C++ API, and CMakeJS API):
Or, they may ignore these libs entirely and just call
cmakejs_create_napi_addon()
like we did above (which links withcmake-js::cmake-js
, under the hood).There are examples of the extended API's functionality in the demo project. There also tests and typings, corresponding to the built addon.
The provided documentation, as well as the demo project linked above, should hopefully provide further insights into some of the design considerations that have been carefully planned, tested, and thought about.
I mentioned on the linked discussion (v8 ideas) that there are some issues around namespacing clashes. There are several means of addressing that, some which could be worked into this design to handle the whole issue (I believe). I will add a further note that vendors usually reserve their namespaces in CMake target land as well, so this API should not aim to create either a
cmake::js
nor anode::api
; but,cmake-js::node-api
is likely going to be acceptable, per the proposed API.I really look forward to getting some feedback from @Julusian and/or any other interested parties in how to make an even more powerful, elegant experience out of building with this API - or something like it - as a proposed part of cmake-js.
Thanks for reading!
Nathan J. Hood