diff --git a/.gitignore b/.gitignore index 60687d5a..b9eaf4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ node_modules # Build build +dist install package-lock.json yarn.lock diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bca7f82..28fa77f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,15 +4,35 @@ MIT License "CMake.js - a Node.js native addon build tool" https://github.com/cmake-js/cmake-js/blob/cmakejs_cmake_api/README.md + + + # THIS FILE IS INTENDED FOR CMAKEJS DEVELOPERS AND CONTRIBUTORS. + # + # IF YOU ARE NOT EITHER OF THE ABOVE, THEN YOU MAY SAFELY CONSIDER + # THIS FILE TO BE NON-EXISTENT. + # + # IF YOU ARE LOOKING TO BUILD YOUR OWN ADDON WITH OUR CMAKE API, PLEASE + # INSTEAD START A NEW NODEJS/CMAKE PROJECT AND ADD OUR JAVASCRIPT PACKAGE + # TO YOUR DEPENDENCIES. THEN, CONFIGURE YOUR PROJECT USING OUR CLI. + # YOU WILL BE READY TO INCLUDE NODE/NAPI HEADERS YOU REQUIRE + # AND BEGIN DEVELOPING! + # + # THE BELOW IS OUR INTERNAL PROJECT WE USE FOR TESTING OUR API ONLY + # + # THIS IS NOT INTENDED TO BE AN EXAMPLE OF HOW OUR API WORKS. + # + # PLEASE DO NOT ATTEMPT TO CREATE A NEW ADDON INSIDE THIS REPO :) + # + # PLEASE DO NOT ATTEMPT TO REPLICATE THIS CODE IN YOUR OWN ADDON :) + # + # IF YOU WOULD LIKE TO SEE AN EXAMPLE OF OUR API IN ACTION, + # PLEASE GO TO '/tests/api/hello' AND '/tests/api/hello_consumer' + # AND RUN THE COMMANDS YOU FIND IN THEIR package.json FILES. ]=============================================================================]# cmake_minimum_required (VERSION 3.15) cmake_policy (VERSION 3.15) -if (NOT DEFINED CMAKE_BUILD_TYPE) - set (CMAKE_BUILD_TYPE "Debug") -endif () - project (CMakeJS VERSION 7.3.3 DESCRIPTION "CMake.js - a Node.js native addon build tool" @@ -20,6 +40,49 @@ project (CMakeJS LANGUAGES C CXX ) +# PROOF OF CONCEPT: +# +# @Julusian +# +# CMakeJS's own targets are abstract 'INTERFACE' targets which only exist +# within CMake's build cache - so, there is nothing to compile, here. +# +# We instead offer a simple test/demonstration of our API, and reserve the +# CMakeJS vendor namespace in C++/CMake land. +# +# Everything below this point is *purely* for demonstration (and some testing) +# purposes. It makes sense that we should have a root-level CMakeLists.txt so +# so that we can make our own 'project()', but we didn't want to do this inside +# the API file because then our users would inheritently always be building +# *sub-projects* below us, which is immediately breaking expected behaviour +# before they can even begin. So... +# +# We establish a root-level CMakeLists with our own 'project()', and consume our +# own API. +# +# BUT, this file *only exists when you are sitting at this project directory*. +# Our API consumers are not *ever* being directed to this file; our package.json +# dependees are never calling these commands nor interfacing with this file. +# +# So, everything contained within this file is purely intended for running +# internal checks on our API before we ship updates to our users. This helps +# simply helps us to confirm that all is working as expected. I.e., it is +# *purely for demonstrative purposes!* +# +# THIS PROJECT IS INTENDED FOR CMAKEJS DEVELOPERS AND CONTRIBUTORS. +# +# YOU ARE WELCOME TO RUN THE SCRIPTS AND SEE HOW EVERYTHING WORKS. + +# PLEASE DO NOT ATTEMPT TO CREATE A NEW ADDON LIKE WE DID HERE :) +# SEE OUR DOCS AND OUR SAMPLE PROJECTS INSTEAD: +# +# - https://github.com/cmake-js/cmake-js/blob/cmakejs_cmake_api/README.md +# +# - '/tests/api/hello' +# - '/tests/api/hello_consumer' +# +# (RUN THE COMMANDS YOU FIND IN THEIR package.json FILES. + # (no building into our source tree is allowed) set (CMakeJS_IS_MASTER_PROJECT FALSE) if(NOT (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)) @@ -28,11 +91,6 @@ endif() include ("${CMAKE_CURRENT_LIST_DIR}/share/cmake/CMakeJS.cmake") -# CMakeJS's own targets are abstract 'INTERFACE' targets which only exist -# within CMake's build cache - so, there is nothing to compile, here. - -# We instead offer a simple test/demonstration of our API, and reserve the -# CMakeJS vendor namespace in C++/CMake land. # If this is not a subproject... if (CMakeJS_IS_TOP_LEVEL @@ -98,6 +156,74 @@ console.log(`Napi Version: ${demo.version()}`); -- -- ]==]) + + # offer a basic installer for our demo. + + # NOTE: this is part of the DEMO, I am NOT suggesting + # cmake-js should pack and ship any addons of it's own! + # this entire CMakeLists.txt is just purely about proof + # of concept. + + # the following demonstrates how users can + # easily ship either their source that automatically + # carry all dependencies (required headers), + # which is useful for e.g., offline machines, + # they can build-at-home or in a VM or wokflow run + # targeting a specific OS/platform/arch which + # they don't have access to, and can retrieve + # the built binaries in a tar file, then + # send their pre-built, pre-packaged addon(s) + # down to their remote servers, which don't + # have C++ compilers or CMake on them. + + include(GNUInstallDirs) # dont be alarmed by this - it wont install to your system! + + export ( + TARGETS demo + FILE share/cmake/DemoTargets.cmake + NAMESPACE CMakeJS:: + ) + + # install 'CMakeJSTargets' export file + install( + EXPORT DemoTargets + FILE DemoTargets.cmake + NAMESPACE CMakeJS:: + DESTINATION lib/cmake/CMakeJS + ) + + install(TARGETS demo + EXPORT DemoTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + ) + + + file (WRITE "${CMAKE_CURRENT_BINARY_DIR}/DemoConfig.cmake.in" [==[ +@PACKAGE_INIT@ + +include (${CMAKE_CURRENT_LIST_DIR}/DemoTargets.cmake) + +check_required_components (cmake-js) + +]==]) + + # create cmake config file + configure_package_config_file ( + "${CMAKE_CURRENT_BINARY_DIR}/DemoConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/share/cmake/DemoConfig.cmake" + INSTALL_DESTINATION + "${CMAKE_INSTALL_LIBDIR}/cmake/Demo" + ) + # generate the version file for the cmake config file + write_basic_package_version_file ( + "${CMAKE_CURRENT_BINARY_DIR}/share/cmake/DemoConfigVersion.cmake" + VERSION 7.3.3 + COMPATIBILITY AnyNewerVersion + ) + # copy the demo types file(COPY "${CMAKE_CURRENT_LIST_DIR}/lib/demo.node.js" @@ -107,25 +233,94 @@ console.log(`Napi Version: ${demo.version()}`); "${PROJECT_BINARY_DIR}/lib" ) - # copy the CMake API - file(COPY - "${CMAKE_CURRENT_LIST_DIR}/share/cmake/CMakeJS.cmake" + install(FILES + "${PROJECT_BINARY_DIR}/lib/demo.node.js" + "${PROJECT_BINARY_DIR}/lib/demo.node.ts" + "${PROJECT_BINARY_DIR}/lib/demo.node.d.ts" DESTINATION - "${PROJECT_BINARY_DIR}/share" + "${CMAKE_INSTALL_LIBDIR}" ) + # Still seems like a lot of code for builders, so why + # did cmake-js make the effort of defining export() + # and install() at API level? + # + # Because if I want to actually achieve any of those + # remote scenarios highlighted above, I'd have to do + # all this, for my addon(s!), AND for all the cmake-js + # targets. Since the kind cmake-js devs already + # did their job fully at API level, whoever builds + # an addon with it will find that... their entire + # addon project's dependancies are already fully + # relocatable. + # + # Considering those can be quite a lot of files + # (libnode-dev...), not to mention they can be + # tricky to locate - and to point your IDE at correctly, + # cmake-js users looking to implement CMake's export() + # and install() stuff on their addons - for whatever + # reasons they have (I mentioned a few) - will + # likely be delighted that so much heavy work has + # been done for them, by a slick combination of + # it's two API's: + # + # - cmake-js CLI controls their project with + # it's super handy alias-like commands + # + # - whatever they do and wherever they point + # their CMake outputs and inputs at, CMake itself + # will simply go "cmake-js::node-api? I got that + # right here." and just "sort it all out" for the user + # + # - one means of sorting it out" is, cmake-js CLI + # will kick in during config, determine if we need + # headers (and which, according to --link-level) + # and baiscally be a Node Addon API package manager + # for us, resolving our deps and handing them over + # to CMake to manage them + # + # - the CMake API will make building an addon a + # very straightforward affair, should they choose + # to either use our addon creation functions, or + # just link to a cmake-js:: target to get everything + # "sorted out" (the previous steps automate this) + # + # - if they want to pack and ship their addon as + # source, as a prebuilt binary, or some custom + # combo, our API is flexible enough to not burden + # them with manually resolving all those previous + # steps before they can take care of their own targets + # + # The beautiful part in the whole process is combining + # the npm-like workflow with the power of a whole C/C++ + # compiler/linker toolchain. Developers from JS and + # developers from C++ should both feel equally at home, + # whether they want to run CMake native commands or use + # the efficient CLI (and the obvious answer is...! :) ) + # + # The entire thing "just works", as long as they have + # cmake-js on their package.json and configure their builds + # with it. + # + # Thanks for reading! + # Nathan + + if(BUILD_TESTS) - # include(CTest) # If you want to see all the test dashboard targets, uncomment this include() + # include(CTest) # If you want to see all the test dashboard targets, uncomment this enable_testing() include("${CMAKE_CURRENT_LIST_DIR}/tests/demo/tests.cmake") endif() # offer a basic/messy CPack + # If it's distracting from the simplicity of the demo, remove it if you wish; + # it isn't part of the proposed API-to-be-shipped itself at all. + # set(CPACK_PACKAGE_CHECKSUM "${PROJECT_VERSION_TWEAK}") # git rev-parse is a good idea for this... set(CPACK_PACKAGE_VENDOR "cmake-js") - set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") - set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") # Compiled binary distribution - set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-Source") # No system spec as this is un-compiled source file distribution + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}-node_${NODE_VERSION}") + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}-node_${NODE_VERSION}") # Multi-platform CMake API distribution + set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-Source") # No system spec as this is un-compiled source file distribution (i.e., the Javascript API) set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION}) set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_VERSION_MINOR}) @@ -133,9 +328,9 @@ console.log(`Napi Version: ${demo.version()}`); set(CPACK_PACKAGE_VERSION_TWEAK ${PROJECT_VERSION_VERSION_TWEAK}) set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) - set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) - set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY OFF) - set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") + set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY ON) + set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY ON) + set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") # Check out CPack's 'NSIS' installer for Win32, and the others! set(CPACK_SOURCE_IGNORE_FILES _CPack_Packages /*.zip @@ -150,13 +345,14 @@ console.log(`Napi Version: ${demo.version()}`); /.cache /.config /.local + /dist /doc /docs #/bin #/lib /usr /out - /build + #/build /Release /Debug /MinSizeRel diff --git a/lib/cMake.js b/lib/cMake.js index 6ec0a79a..6e0a6b1a 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -13,6 +13,8 @@ const npmConfigData = require('rc')('npm') const Toolset = require('./toolset') const headers = require('node-api-headers') +const path_to_api = require('../share/cmake/api') + class CMake { get path() { return this.options.cmakePath || 'cmake' @@ -194,6 +196,30 @@ class CMake { D.push({ CMAKE_MAKE_PROGRAM: this.toolset.makePath }) } + // Provides a CLI switch for our targets (default = all 'ON', full API available). + // In this list, each target depends on the previous one; the next target requires + // that the previous one to be 'ON', because of the dependency chain. + // Most users probably won't interact with this, but some might only want + // for example the 'libnode_dev' stuff and nothing else... if they are brave :) + + // TODO: needs some cascading logic, ideally... + // but CMake should 'safely fail' if wrong settings anyway + // We should not be exposing all four options at a time, + // because that could lead to someone illogically de-activating + // say, node-api, while trying to build with node-addon-api (which isn't + // how the addon API works of course). + // Thus, I propose offering a new CLI arg for something like '--link-level=2', + // which would pick the third entry of our (zero-indexed) list below. + // Default value would be 3, providing cmake-js::cmake-js (our full API) + // as a silent default. + D.push({ CMAKEJS_USING_NODE_DEV: 'ON' }) // cmake-js::node-dev + D.push({ CMAKEJS_USING_NODE_API: 'ON' }) // cmake-js::node-api + D.push({ CMAKEJS_USING_NODE_ADDON_API: 'ON' }) // cmake-js::node-addon-pi + D.push({ CMAKEJS_USING_CMAKEJS: 'ON' }) // cmake-js::cmake-js (required for 'cmakejs_create_napi_addon()') + + // CMakeJS.cmake api (could use an arg to toggle on/off of you want) + D.push({ CMAKE_MODULE_PATH: path_to_api }) + // Load NPM config for (const [key, value] of Object.entries(npmConfigData)) { if (key.startsWith('cmake_')) { diff --git a/package.json b/package.json index 04160c22..af159c99 100644 --- a/package.json +++ b/package.json @@ -67,24 +67,14 @@ "scripts": { "test": "mocha tests", "lint": "eslint lib bin/cmake-js tests", - "demo": "cmake -S . -B ./build -DBUILD_TESTS:BOOL=TRUE && cmake --build ./build && cmake --install ./build --prefix ./install && ctest --test-dir ./build --rerun-failed --output-on-failure --verbose && cpack -B ./install --config ./build/CPackConfig.cmake && cpack -B ./install --config ./build/CPackSourceConfig.cmake", - "configure": "cmake -S . -B ./build -DBUILD_TESTS:BOOL=TRUE", - "reconfigure": "cmake --fresh -S . -B ./build -DBUILD_TESTS:BOOL=TRUE", - "build": "cmake --build ./build", - "rebuild": "cmake --fresh -S . -B ./build && cmake --build ./build", - "cmake:configure": "cmake -S . -B ./build -DBUILD_TESTS:BOOL=TRUE", - "cmake:reconfigure": "cmake --fresh -S . -B ./build -DBUILD_TESTS:BOOL=TRUE", - "cmake:build": "cmake --build ./build", - "cmake:rebuild": "cmake --fresh -S . -B ./build && cmake --build ./build", - "cmake:install": "cmake --install ./build --prefix ./install", - "cmake:help": "cmake --help", - "ctest": "ctest --test-dir ./build --rerun-failed --output-on-failure --verbose", - "ctest:help": "ctest --help", - "cpack:all": "cpack -B ./build --config ./build/CPackConfig.cmake", - "cpack:zip": "cpack -B ./build --config ./build/CPackConfig.cmake --generator ZIP", - "cpack:tar": "cpack -B ./build --config ./build/CPackConfig.cmake --generator TAR", - "cpack:source": "cpack -B ./build --config ./build/CPackSourceConfig.cmake", - "cpack:help": "cpack --help" + "demo:configure": "cmake --fresh -S . -B ./build -DBUILD_TESTS:BOOL=TRUE -DCMAKE_INSTALL_PREFIX:PATH=./install -G Ninja", + "demo:build": "cmake --build ./build", + "demo:install": "cmake --install ./build", + "demo:ctest": "ctest --test-dir ./build", + "demo:cpack": "cpack --config ./build/CPackSourceConfig.cmake -B ./install", + "demo:cdist": "cpack --config ./build/CPackConfig.cmake -B ./install", + "api:pack-js": "cpack --config ./build/CPackSourceConfig.cmake -B ./dist", + "api:pack-cmake": "cpack --config ./build/CPackConfig.cmake -B ./dist" }, "files": [ "lib", diff --git a/share/cmake/CMakeJS.cmake b/share/cmake/CMakeJS.cmake index db44f03b..2179c0d7 100644 --- a/share/cmake/CMakeJS.cmake +++ b/share/cmake/CMakeJS.cmake @@ -8,6 +8,8 @@ cmake_minimum_required(VERSION 3.15) cmake_policy(VERSION 3.15) include(CMakeParseArguments) +include(GNUInstallDirs) +include(CMakeDependentOption) if (DEFINED CMAKE_JS_VERSION) message(FATAL_ERROR "You cannot use the new cmake flow with the old cmake-js binary, instead you should use cmake-js2 or cmake") @@ -78,33 +80,92 @@ Setup optional targets dependency chain, e.g., for end-user specification with VCPKG_FEATURE_FLAGS or by passing for example '-DCMAKE_NODE_API:BOOL=FALSE' ]=============================================================================]# -set (CMAKEJS_TARGETS "") -include(CMakeDependentOption) -option (CMAKEJS_NODE_API "Supply cmake-js::node-api target for linkage" ON) -cmake_dependent_option(CMAKEJS_NODE_ADDON_API "Supply cmake-js::node-addon-api target for linkage" ON CMAKEJS_NODE_API OFF) -cmake_dependent_option(CMAKEJS_CMAKEJS "Supply cmake-js::cmake-js target for linkage" ON CMAKEJS_NODE_API OFF) +set (CMAKEJS_TARGETS "") # This list will auto-populate from --link-level +option (CMAKEJS_USING_NODE_API "Supply cmake-js::node-api target for linkage" ON) +cmake_dependent_option(CMAKEJS_USING_NODE_ADDON_API "Supply cmake-js::node-addon-api target for linkage" ON CMAKEJS_USING_NODE_API OFF) +cmake_dependent_option(CMAKEJS_USING_CMAKEJS "Supply cmake-js::cmake-js target for linkage" ON CMAKEJS_USING_NODE_ADDON_API OFF) if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" CACHE STRING "Select the MSVC runtime library for use by compilers targeting the MSVC ABI." FORCE) endif() -#[=======================================================================[ -FindCMakeJs.cmake --------- - -Find the native CMakeJs includes, source, and library - -(This codeblock typically belongs in a file named 'FindCMakeJS.cmake' for -distribution...) - -This module defines - -:: +#[=============================================================================[ +Provides CMAKE_JS_EXECUTABLE +]=============================================================================]# +function(cmakejs_find_cmakejs_executable) + # Check for cmake-js installations + find_program(CMAKE_JS_EXECUTABLE + NAMES "cmake-js" "cmake-js.exe" + PATHS "$ENV{PATH}" "$ENV{ProgramFiles}/cmake-js" + DOC "cmake-js system executable binary" + REQUIRED + ) + if(NOT CMAKE_JS_EXECUTABLE) + find_program(CMAKE_JS_EXECUTABLE + NAMES "cmake-js" "cmake-js.exe" + PATHS "${CMAKE_CURRENT_SOURCE_DIR}/node_modules/cmake-js/bin" + DOC "cmake-js project-local npm package binary" + REQUIRED + ) + if (NOT CMAKE_JS_EXECUTABLE) + message(FATAL_ERROR "cmake-js not found! Please run 'npm install' and try again.") + return() + endif() + endif() + _cmakejs_normalize_path(CMAKE_JS_EXECUTABLE) + string(REGEX REPLACE "[\r\n\"]" "" CMAKE_JS_EXECUTABLE "${CMAKE_JS_EXECUTABLE}") + set(CMAKE_JS_EXECUTABLE ${CMAKE_JS_EXECUTABLE} PARENT_SCOPE) # vars defined in functions only apply to their own scope, so this is needed! +endfunction() - CMAKEJS_HELPER_EXECUTABLE, the cmake-js helper binary +#[=============================================================================[ +Provides CMAKE_JS_INC +Requires CMAKE_JS_EXECUTABLE +(might accept a '--log-level' arg in future for more verbose output) +]=============================================================================]# +function(cmakejs_print_cmakejs_include) + execute_process( + COMMAND "${CMAKE_JS_EXECUTABLE}" "print-cmakejs-include" "--log-level error" "--generator ${CMAKE_GENERATOR}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE CMAKE_JS_INC + ) + # Strip the var of any unusual chars that might break the paths... + _cmakejs_normalize_path(CMAKE_JS_INC) + string(REGEX REPLACE "[\r\n\"]" "" CMAKE_JS_INC "${CMAKE_JS_INC}") + set(CMAKE_JS_INC "${CMAKE_JS_INC}" PARENT_SCOPE) +endfunction() -]=======================================================================]# +#[=============================================================================[ +Provides CMAKE_JS_SRC +Requires CMAKE_JS_EXECUTABLE +(might accept a '--log-level' arg in future for more verbose output) +]=============================================================================]# +function(cmakejs_print_cmakejs_src) + execute_process( + COMMAND "${CMAKE_JS_EXECUTABLE}" "print-cmakejs-src" "--log-level error" "--generator ${CMAKE_GENERATOR}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE CMAKE_JS_SRC + ) + # Strip the var of any unusual chars that might break the paths... + _cmakejs_normalize_path(CMAKE_JS_SRC) + string(REGEX REPLACE "[\r\n\"]" "" CMAKE_JS_SRC "${CMAKE_JS_SRC}") + set(CMAKE_JS_SRC "${CMAKE_JS_SRC}" PARENT_SCOPE) +endfunction() +#[=============================================================================[ +Provides CMAKE_JS_LIB +Requires CMAKE_JS_EXECUTABLE +(might accept a '--log-level' arg in future for more verbose output) +]=============================================================================]# +function(cmakejs_print_cmakejs_lib) + execute_process( + COMMAND "${CMAKE_JS_EXECUTABLE}" "print-cmakejs-lib" "--log-level error" "--generator ${CMAKE_GENERATOR}" + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE CMAKE_JS_LIB + ) + _cmakejs_normalize_path(CMAKE_JS_LIB) + string(REGEX REPLACE "[\r\n\"]" "" CMAKE_JS_LIB "${CMAKE_JS_LIB}") + set(CMAKE_JS_LIB "${CMAKE_JS_LIB}" PARENT_SCOPE) +endfunction() #[=============================================================================[ Get the in-use NodeJS binary for executing NodeJS commands in CMake scripts. @@ -118,29 +179,29 @@ Provides ]=============================================================================]# function(cmakejs_acquire_node_executable) - find_program(NODE_EXECUTABLE - NAMES "node" "node.exe" - PATHS "$ENV{PATH}" "$ENV{ProgramFiles}/nodejs" - DOC "NodeJs executable binary" - REQUIRED - ) - if (NOT NODE_EXECUTABLE) - message(FATAL_ERROR "NodeJS installation not found! Please check your paths and try again.") - return() - endif() + find_program(NODE_EXECUTABLE + NAMES "node" "node.exe" + PATHS "$ENV{PATH}" "$ENV{ProgramFiles}/nodejs" + DOC "NodeJs executable binary" + REQUIRED + ) + if (NOT NODE_EXECUTABLE) + message(FATAL_ERROR "NodeJS installation not found! Please check your paths and try again.") + return() + endif() - execute_process( - COMMAND "${NODE_EXECUTABLE}" "--version" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - OUTPUT_VARIABLE NODE_VERSION - ) - string(REGEX REPLACE "[\r\n\"]" "" NODE_VERSION "${NODE_VERSION}") - set(NODE_VERSION "${NODE_VERSION}" CACHE STRING "" FORCE) + execute_process( + COMMAND "${NODE_EXECUTABLE}" "--version" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE NODE_VERSION + ) + string(REGEX REPLACE "[\r\n\"]" "" NODE_VERSION "${NODE_VERSION}") + set(NODE_VERSION "${NODE_VERSION}" CACHE STRING "" FORCE) - if(VERBOSE) - message(STATUS "NODE_EXECUTABLE: ${NODE_EXECUTABLE}") - message(STATUS "NODE_VERSION: ${NODE_VERSION}") - endif() + if(VERBOSE) + message(STATUS "NODE_EXECUTABLE: ${NODE_EXECUTABLE}") + message(STATUS "NODE_VERSION: ${NODE_VERSION}") + endif() endfunction() #[=============================================================================[ @@ -150,29 +211,46 @@ Provides :: NODE_API_HEADERS_DIR, where to find node_api.h, etc. - NODE_API_INC_FILES, the headers required to use Node Addon API. + NODE_API_INC_FILES, the headers required to use Node API. ]=============================================================================]# function(cmakejs_acquire_napi_c_files) - execute_process( - COMMAND "${NODE_EXECUTABLE}" -p "require('node-api-headers').include_dir" - WORKING_DIRECTORY "${_CMAKEJS_DIR}" - OUTPUT_VARIABLE NODE_API_HEADERS_DIR - # COMMAND_ERROR_IS_FATAL ANY - ) - string(REGEX REPLACE "[\r\n\"]" "" NODE_API_HEADERS_DIR "${NODE_API_HEADERS_DIR}") - - # copy the headers to mitigate `Target "node-api-headers" INTERFACE_INCLUDE_DIRECTORIES property contains path which is prefixed in the source directory.` - file(GLOB _NODE_API_INC_FILES "${NODE_API_HEADERS_DIR}/*.h") - file(COPY ${_NODE_API_INC_FILES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/include/node-api-headers") - unset(_NODE_API_INC_FILES) - - # target include directories (as if 'node-api-headers' were an isolated CMake project...) - set(NODE_API_HEADERS_DIR - $ - $ - ) - set(NODE_API_HEADERS_DIR "${NODE_API_HEADERS_DIR}" CACHE PATH "Node API Headers directory." FORCE) + execute_process( + COMMAND "${NODE_EXECUTABLE}" -p "require('node-api-headers').include_dir" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE NODE_API_HEADERS_DIR + # COMMAND_ERROR_IS_FATAL ANY - crashes on ARM64 builds? unfortunate! + ) + string(REGEX REPLACE "[\r\n\"]" "" NODE_API_HEADERS_DIR "${NODE_API_HEADERS_DIR}") + + # relocate... + set(_NODE_API_INC_FILES "") + file(GLOB_RECURSE _NODE_API_INC_FILES "${NODE_API_HEADERS_DIR}/*.h") + file(COPY ${_NODE_API_INC_FILES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/include/node-api-headers") + unset(_NODE_API_INC_FILES) + + unset(NODE_API_HEADERS_DIR CACHE) + # target include directories (as if 'node-api-headers' were an isolated CMake project...) + set(NODE_API_HEADERS_DIR + $ + $ + ) + set(NODE_API_HEADERS_DIR ${NODE_API_HEADERS_DIR} PARENT_SCOPE) # dont wrap this one in quotes; it breaks! + + # this is just for IDE support only. Never pass globbed headers to 'target_sources()'! + set(NODE_API_INC_FILES "") + file(GLOB_RECURSE NODE_API_INC_FILES "${NODE_API_HEADERS_DIR}/*.h") + set(NODE_API_INC_FILES "${NODE_API_INC_FILES}" PARENT_SCOPE) + source_group("Node API (C)" FILES "${NODE_API_INC_FILES}") + # end IDE support codeblock + # it's not a 'source_group' for targets! + # VS users will see the above globbed headers as a filegroup named "Node API (C)" + # and that is literally all that this 'source_group' function does. + # it is not the same as 'target_sources' - so, globbing was ok here! + + if(VERBOSE) + message(STATUS "NODE_API_HEADERS_DIR: ${NODE_API_HEADERS_DIR}") + endif() endfunction() #[=============================================================================[ @@ -186,32 +264,85 @@ Provides ]=============================================================================]# function(cmakejs_acquire_napi_cpp_files) - execute_process( - COMMAND "${NODE_EXECUTABLE}" -p "require('node-addon-api').include_dir || require('node-addon-api').include" - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" - OUTPUT_VARIABLE NODE_ADDON_API_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE - # COMMAND_ERROR_IS_FATAL ANY - ) - string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR "${NODE_ADDON_API_DIR}") + execute_process( + COMMAND "${NODE_EXECUTABLE}" -p "require('node-addon-api').include" + WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" + OUTPUT_VARIABLE NODE_ADDON_API_DIR + # COMMAND_ERROR_IS_FATAL ANY -these vars seem to error on ARM64 builds ...? + ) + string(REGEX REPLACE "[\r\n\"]" "" NODE_ADDON_API_DIR "${NODE_ADDON_API_DIR}") + + # relocate... + set(_NODE_ADDON_API_INC_FILES "") + file(GLOB_RECURSE _NODE_ADDON_API_INC_FILES "${NODE_ADDON_API_DIR}/*.h") + file(COPY ${_NODE_ADDON_API_INC_FILES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/include/node-addon-api") + unset(_NODE_ADDON_API_INC_FILES) + + unset(NODE_ADDON_API_DIR CACHE) + # target include directories (as if 'node-addon-api' were an isolated CMake project...) + set(NODE_ADDON_API_DIR + $ + $ + ) + set(NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR} PARENT_SCOPE) - # copy the headers to mitigate `Target "node-addon-api" INTERFACE_INCLUDE_DIRECTORIES property contains path which is prefixed in the source directory.` - file(GLOB _NODE_ADDON_API_INC_FILES "${NODE_ADDON_API_DIR}/*.h") - file(COPY ${_NODE_ADDON_API_INC_FILES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/include/node-addon-api") - unset(_NODE_ADDON_API_INC_FILES) + # this is just for IDE support only. Never pass globbed headers to 'target_sources()'! + set(NODE_ADDON_API_INC_FILES "") + file(GLOB_RECURSE NODE_ADDON_API_INC_FILES "${NODE_ADDON_API_DIR}/*.h") + set(NODE_ADDON_API_INC_FILES ${NODE_ADDON_API_INC_FILES} PARENT_SCOPE) + source_group("Node Addon API (C++)" FILES "${NODE_ADDON_API_INC_FILES}") - # target include directories (as if 'node-addon-api' were an isolated CMake project...) - set(NODE_ADDON_API_DIR - $ - $ - ) - set(NODE_ADDON_API_DIR "${NODE_ADDON_API_DIR}" CACHE PATH "Node Addon API Headers directory." FORCE) + if(VERBOSE) + message(STATUS "NODE_ADDON_API_DIR: ${NODE_ADDON_API_DIR}") + endif() endfunction() +#[=============================================================================[ +Generate a Javascript bindings file to your built addon, at the root of your +build directory, providing a more predictable file to acquire your built addon +from instead of having to work out where your built addon went from the Javascript +side. + +(experimental) +]=============================================================================]# +function(cmakejs_create_addon_bindings addon_target) + + # Check that this is a Node Addon target + get_target_property(is_addon_lib ${name} ${name}_IS_NAPI_ADDON_LIBRARY) + if(NOT TARGET ${name} OR NOT is_addon_lib) + message(SEND_ERROR "'cmakejs_create_addon_bindings()' called on '${name}' which is not an existing napi addon library") + return() + endif() + + # Figure out the path from the build dir to wherever the built addon went + file(RELATIVE_PATH _bindings_rel_path "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}") + + # Use the addon name and relative path to create a 'configured' string (vars surrounded with @'s get evaluated) + string(CONFIGURE [[ +const @addon_target@ = require(`./@_bindings_rel_path@/@addon_target@.node`); +module.exports = @addon_target@; +]] + _bindings + @ONLY + ) + + # write the configured string to a file in the binary dir, providing a + # consistent binding point for every addon built! :) + file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${addon_target}.node.js" "${_bindings}") + + # Now, built addons can be found easier in Javascript: + # const my_addon = require('./build/') + + # If your CMake is not going into './build' then obvuously it should be + # changed; but, we should *never* write CMake-configured bindings file + # into anybody's source tree, as we might corrupt their work! ALWAYS + # put this kind of stuff into the binary dir! + message(STATUS "-- Created Javascript bindings: ${addon_target}.node.js") +endfunction() #[=============================================================================[ Silently create an interface library (no output) with all Addon API dependencies -resolved, for Addon targets to link with. +resolved, for each feature that we offer; this is for Addon targets to link with. (This should contain most of cmake-js globally-required configuration) @@ -229,85 +360,285 @@ if(NOT DEFINED NODE_EXECUTABLE) message(DEBUG "NODE_VERSION: ${NODE_VERSION}") endif() -if(CMAKEJS_NODE_API) +if(CMAKEJS_USING_NODE_DEV) # user did 'cmake-js configure --link-level=0' or higher + # NodeJS system installation headers + # cmake-js::node-dev + add_library (node-dev INTERFACE) + add_library (cmake-js::node-dev ALIAS node-dev) + target_sources (node-dev INTERFACE ${CMAKE_JS_SRC}) # tip: don't enclose this in strings! (or it won't be null if the file is nonexistent) + target_link_libraries (node-dev INTERFACE ${CMAKE_JS_LIB}) # tip: don't enclose this in strings! (or it won't be null if the file is nonexistent) + set_target_properties (node-dev PROPERTIES VERSION ${NODE_VERSION}) + target_include_directories (node-dev INTERFACE + $ + $ + ) + + # TODO: this list would be un-manageable for all iterations of NodeJS dev files + # across versions; even minor versions seem to have lots of different files! + # Fortunately for us, HEADERS should never really go to 'target_sources', + # because HEADERS are *not supposed to be compiled*, they are only used + # for lookup by the compiler. They are just symbolic, nothing more. + # + # The two 'correct' mechanisms for adding headers to a target in CMake are: + # + # Modern CMake: you can put them in 'target_sources()' - one by one, never + # by globbing expressions! - if you create a FILE_SET of TYPE HEADERS + # and follow the strict naming conventions and BASE_DIRS thing. + # + # Classic CMake: You don't actually pass any header files to your target + # explicitly; instead, you just put their path on 'target_include_directories()', + # and your compiler/CMake/IDE/intellisense will loop up the file relative to + # that path. So you can then '#include ' + # + # This manual listing of files maybe wouldnt be so bad if we just globbed + # everything recursively and dumped them all into one massive dir. But (aHa!), + # they have all been authored to '#include ', and for + # very good reason - these libnode-dev headers contain familiar libs such as + # openssl, but these ones are doctored by team NodeJS according to their custom + # needs, and that's why NodeJS ships these third-party copies under their own + # include line. + # + # We can glob the files; we can even have CMake just pick up the base dir and + # throw that around; but, we'd have to generate the entire FILESET below with + # a fully-maintained filetree and include line, specific to whichever version + # of NodeJS that cmake.js has picked up for us. + # + # So instead of taking on unthinkable complexity of maintaining this, we can + # just pass the relocatable '*_INCLUDE_DIR' from under these copied files that + # CMake is already passing around, and just pass that to our target's + # 'target_include_directories()' and let it do the 'Classical lookup-only header' + # approach. We lose nothing, really, since none of these files are supposed to be + # compiled in. CMake only ever just needed to know where the base dir is that + # it can instruct the compiler and linker do their symbol lookups from, and + # the dir we're passing in has been made CMake-relocatable thanks to it's + # BUILD_ and INSTALL_ interfaces and generator expressions. That is really + # the definition of an INTERFACE library in CMake parlance anyway - a CMake + # - formatted header-only library :) + + + # set(NODE_DEV_FILES "") + # list(APPEND NODE_DEV_FILES + # # NodeJS core + # "node_buffer.h" + # "node_object_wrap.h" + # "node_version.h" + # "node.h" + # # NodeJS addon + # "node_api.h" + # "node_api_types.h" + # "js_native_api.h" + # "js_native_api_types.h" + # # uv + # "uv.h" + # # v8 + # "v8config.h" + # "v8-array-buffer.h" + # "v8-callbacks.h" + # "v8-container.h" + # "v8-context.h" + # "v8-data.h" + # "v8-date.h" + # "v8-debug.h" + # "v8-embedder-heap.h" + # "v8-embedder-state-scope.h" + # "v8-exception.h" + # "v8-extension.h" + # "v8-forward.h" + # "v8-function-callback.h" + # "v8-function.h" + # "v8-initialization.h" + # "v8-internal.h" + # "v8-isolate.h" + # "v8-json.h" + # "v8-local-handle.h" + # "v8-locker.h" + # "v8-maybe.h" + # "v8-memory-span.h" + # "v8-message.h" + # "v8-microtask-queue.h" + # "v8-microtask.h" + # "v8-object.h" + # "v8-persistent-handle.h" + # "v8-platform.h" + # "v8-primitive-object.h" + # "v8-primitive.h" + # "v8-profiler.h" + # "v8-promise.h" + # "v8-proxy.h" + # "v8-regexp.h" + # "v8-script.h" + # "v8-snapshot.h" + # "v8-statistics.h" + # "v8-template.h" + # "v8-traced-handle.h" + # "v8-typed-array.h" + # "v8-unwinder.h" + # "v8-util.h" + # "v8-value-serializer-version.h" + # "v8-value-serializer.h" + # "v8-value.h" + # "v8-version.h" + # "v8-version-string.h" + # "v8-wasm.h" + # "v8-wasm-trap-handler-posix.h" + # "v8-wasm-trap-handler-win.h" + # "v8-weak-callback.h" + # "v8.h" + # "v8-config.h" + # # zlib + # "zconf.h" + # "zlib.h" + # ) + + # foreach(FILE IN LISTS NODE_DEV_FILES) + # if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/include/node/${FILE}") + # message(DEBUG "Found NodeJS developer header: ${FILE}") + # target_sources(node-dev INTERFACE + # FILE_SET node_dev_INTERFACE_HEADERS + # TYPE HEADERS + # BASE_DIRS + # $ + # $ + # FILES + # $ + # $ + # ) + # endif() + # endforeach() + set(_CMAKE_JS_INC_FILES "") + + # Not quite working, but not breaking anything... + foreach(input IN ITEMS "${CMAKE_JS_INC}") + + _cmakejs_normalize_path(input) + get_filename_component(file_abs_path "${input}" ABSOLUTE) + get_filename_component(file_name "${input}" NAME) + file(RELATIVE_PATH file_rel_path "${CMAKE_CURRENT_BINARY_DIR}" "${file_abs_path}") # /${file_name} + + message(DEBUG "Found NodeJS development header: ${file_name}") + message(DEBUG "file_abs_path: ${file_abs_path}") + message(DEBUG "file_rel_path: ${file_rel_path}") + target_sources(node-dev INTERFACE + FILE_SET node_dev_INTERFACE_HEADERS + TYPE HEADERS + BASE_DIRS + $ # /node + $ # /node + FILES + $ # /${file_name} + $ + ) + + endforeach() + unset(_CMAKE_JS_INC_FILES) + + list(APPEND CMAKEJS_TARGETS node-dev) +endif() + +if(CMAKEJS_USING_NODE_API) # user did 'cmake-js configure --link-level=1' or higher + # Acquire if needed... - if(NOT DEFINED NODE_API_HEADERS_DIR) + if(NOT DEFINED NODE_API_HEADERS_DIR) # Why the NODE_API_* namespace? Because 'node-api-headers' from vcpkg also provides this exact var, so we can help our users from vcpkg-land avoid picking up headers they already have ; but, we still need to process those headers into our target(s) for them! cmakejs_acquire_napi_c_files() + set(NODE_API_HEADERS_DIR ${NODE_API_HEADERS_DIR} CACHE PATH "Node API Headers directory." FORCE) message(DEBUG "NODE_API_HEADERS_DIR: ${NODE_API_HEADERS_DIR}") unset(NODE_API_INC_FILES) endif() if(NOT DEFINED NODE_API_INC_FILES) - file(GLOB NODE_API_INC_FILES "${NODE_API_HEADERS_DIR}/*.h") - set(NODE_API_INC_FILES "${NODE_API_INC_FILES}" CACHE FILEPATH "Node API Header files." FORCE) - source_group("Node Addon API (C)" FILES "${NODE_API_INC_FILES}") + file(GLOB_RECURSE NODE_API_INC_FILES "${NODE_API_HEADERS_DIR}/*.h") + source_group("Node Addon API (C)" FILES ${NODE_API_INC_FILES}) # IDE only, don't pass this to target_sources()! endif() + set(NODE_API_INC_FILES "${NODE_API_INC_FILES}" CACHE STRING "Node API Header files." FORCE) - # Node API (C) - requires NodeJS system installation headers + # Node API (C) - requires NodeJS developer headers target, cmake-js::node-dev # cmake-js::node-api add_library (node-api INTERFACE) add_library (cmake-js::node-api ALIAS node-api) - target_include_directories (node-api INTERFACE "${NODE_API_HEADERS_DIR}") - target_sources (node-api INTERFACE "${NODE_API_INC_FILES}") - set_target_properties (node-api PROPERTIES VERSION 6.1.0) - - # find the node api definition to generate into node.lib - if (MSVC) - target_sources (node-api INTERFACE "${_CMAKEJS_DIR}/lib/cpp/win_delay_load_hook.cc") - - execute_process(COMMAND ${NODE_PATH} -p "require('node-api-headers').def_paths.node_api_def" - WORKING_DIRECTORY ${_CMAKEJS_DIR} - OUTPUT_VARIABLE CMAKEJS_NODELIB_DEF - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - if (DEFINED CMAKEJS_NODELIB_DEF) - message(FATAL_ERROR "Failed to find `node-api-headers` api definition") - endif() - - set(CMAKEJS_NODELIB_TARGET "${CMAKE_BINARY_DIR}/node.lib") - execute_process(COMMAND ${CMAKE_AR} /def:${CMAKEJS_NODELIB_DEF} /out:${CMAKEJS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) - target_link_libraries (node-api INTERFACE "${CMAKEJS_NODELIB_TARGET}") - endif() + target_include_directories (node-api INTERFACE ${NODE_API_HEADERS_DIR}) # no string enclosure here! + target_link_libraries (node-api INTERFACE cmake-js::node-dev) + set_target_properties (node-api PROPERTIES VERSION 6.1.0) + set_target_properties (node-api PROPERTIES SOVERSION 6) + + set(NODE_API_FILES "") + list(APPEND NODE_API_FILES + "node_api.h" + "node_api_types.h" + "js_native_api.h" + "js_native_api_types.h" + ) - if (MSVC) - # setup delayload - target_link_options(node-api PRIVATE "/DELAYLOAD:NODE.EXE") - target_link_libraries(node-api PRIVATE delayimp) - if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)") - target_link_options(node-api PUBLIC "/SAFESEH:NO") + foreach(FILE IN LISTS NODE_API_FILES) + if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/include/node-api-headers/${FILE}") + message(DEBUG "Found Napi API C header: ${FILE}") + target_sources(node-api INTERFACE + FILE_SET node_api_INTERFACE_HEADERS + TYPE HEADERS + BASE_DIRS + $ + $ + FILES + $ + $ + ) endif() - endif() - - list(APPEND CMAKEJS_TARGETS node-api) + endforeach() + list(APPEND CMAKEJS_TARGETS node-api) endif() -if(CMAKEJS_NODE_ADDON_API) +if(CMAKEJS_USING_NODE_ADDON_API) # user did 'cmake-js configure --link-level=2' or higher + # Acquire if needed... - if(NOT DEFINED NODE_ADDON_API_DIR) + if(NOT DEFINED NODE_ADDON_API_DIR) # Why the NODE_ADDON_API_* namespace? Because 'node-addon-api' from vcpkg also provides this exact var, so we can help our users from vcpkg-land avoid picking up headers they already have ; but, we still need to process those headers into our target(s) for them! cmakejs_acquire_napi_cpp_files() + set(NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR} CACHE PATH "Node Addon API Headers directory." FORCE) message(DEBUG "NODE_ADDON_API_DIR: ${NODE_ADDON_API_DIR}") unset(NODE_ADDON_API_INC_FILES) endif() if(NOT DEFINED NODE_ADDON_API_INC_FILES) - file(GLOB NODE_ADDON_API_INC_FILES "${NODE_ADDON_API_DIR}/*.h") - set(NODE_ADDON_API_INC_FILES "${NODE_ADDON_API_INC_FILES}" CACHE FILEPATH "Node Addon API Header files." FORCE) - source_group("Node Addon API (C++)" FILES "${NODE_ADDON_API_INC_FILES}") + file(GLOB_RECURSE NODE_ADDON_API_INC_FILES "${NODE_ADDON_API_DIR}/*.h") + source_group("Node Addon API (C++)" FILES "${NODE_ADDON_API_INC_FILES}") # just for IDE support; another misleading function name! endif() + set(NODE_ADDON_API_INC_FILES "${NODE_ADDON_API_INC_FILES}" CACHE STRING "Node Addon API Header files." FORCE) - # Node Addon API (C++) - requires Node API (C) + # Node Addon API (C++) - requires Node API (C) target, cmake-js::node-api # cmake-js::node-addon-api add_library (node-addon-api INTERFACE) add_library (cmake-js::node-addon-api ALIAS node-addon-api) - target_include_directories (node-addon-api INTERFACE "${NODE_ADDON_API_DIR}") - target_sources (node-addon-api INTERFACE "${NODE_ADDON_API_INC_FILES}") + target_include_directories (node-addon-api INTERFACE ${NODE_ADDON_API_DIR}) target_link_libraries (node-addon-api INTERFACE cmake-js::node-api) - set_target_properties (node-addon-api PROPERTIES VERSION 1.1.0) + set_target_properties (node-addon-api PROPERTIES VERSION 1.1.0) + set_target_properties (node-addon-api PROPERTIES SOVERSION 1) + + set(NODE_ADDON_API_FILES "") + list(APPEND NODE_ADDON_API_FILES + "napi-inl.deprecated.h" + "napi-inl.h" + "napi.h" + ) + + foreach(FILE IN LISTS NODE_ADDON_API_FILES) + if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/include/node-addon-api/${FILE}") + message(DEBUG "Found Napi Addon API C++ header: ${FILE}") + target_sources(node-addon-api INTERFACE + FILE_SET node_addon_api_INTERFACE_HEADERS + TYPE HEADERS + BASE_DIRS + $ + $ + FILES + $ + $ + ) + endif() + endforeach() + list(APPEND CMAKEJS_TARGETS node-addon-api) endif() -if(CMAKEJS_CMAKEJS) - # CMakeJS API - requires Node Addon API (C++), resolves the full Napi Addon dependency chain +if(CMAKEJS_USING_CMAKEJS) # user did 'cmake-js configure --link-level=3', or did not specify a link level (fallback case) + + # CMakeJS API - requires Node Addon API (C++) target, cmake-js::node-api; resolves the full Napi Addon dependency chain # cmake-js::cmake-js add_library (cmake-js INTERFACE) add_library (cmake-js::cmake-js ALIAS cmake-js) @@ -319,21 +650,47 @@ if(CMAKEJS_CMAKEJS) target_compile_features (cmake-js INTERFACE cxx_nullptr) # Signal a basic C++11 feature to require C++11. set_target_properties (cmake-js PROPERTIES VERSION ${_CMAKEJS_VERSION}) set_target_properties (cmake-js PROPERTIES SOVERSION 7) - set_target_properties (cmake-js PROPERTIES COMPATIBLE_INTERFACE_STRING cmake-js_MAJOR_VERSION) + set_target_properties (cmake-js PROPERTIES COMPATIBLE_INTERFACE_STRING CMakeJS_MAJOR_VERSION) + + # # Generate definitions + # if(MSVC) + # set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$: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() + + # TODO: DELAY_HOOK thing - here? idea of this target is, 'automatically give me everything, all set up'. + # I propose that the remainder of the details of Node Addon CMake config which I've not explored yet + # should be well-considered as to which target we supply a pre-config for. Wherever you define this stuff, + # it will probably propogate down to this target level anyway, due to this one carrying a link line + # to all the other targets via it's logical dependency chain. I just think that if you're not using our + # nice 'create()' function, you're an intermediate/advanced CMake dev who would rather spec that + # stuff themselves. An additional cosideration is making sure we only apply stuff to our targets, + # and never just define things globally. Meaning, always use 'target_add_definitions()' and never ever + # use regular 'add_definitions()' when taking care of this. Why? Because that global function + # will propogate globally, downstream, and who knows where else (see also: never do 'using namespace ' + # in the global namespace scope in a public C++ header file... you don't know where it will propogate, + # what it might break, or even where the issue stems from when it does arise). + # Keep in mind that PUBLIC defs on a target are propogated to any other targets that link with it. + # PRIVATE does not propogate. Sometimes, INTERFACE libs insist you use INTERFACE specifiers. + # so whatever is decided, it must be very well considered i.e., not arbitrary, and if CMake + # insists something, don't fight it. CMake doggedly refuses to be beaten at it's own games :) list(APPEND CMAKEJS_TARGETS cmake-js) - # Node that the below function definitions are contained inside 'if(CMAKEJS_CMAKEJS)' (our main helper library).... +# Note that the below function definitions are contained inside +# 'if(CMAKEJS_CMAKEJS)' (because they require our main helper library).... - #[=============================================================================[ - Exposes a user-side helper function for creating a dynamic '*.node' library, - linked to the Addon API interface. +#[=============================================================================[ +Exposes a user-side helper function for creating a dynamic '*.node' library, +linked to the Addon API interface. - cmakejs_create_napi_addon( []) - cmakejs_create_napi_addon( [ALIAS ] [NAMESPACE ] [NAPI_VERSION ] []) +cmakejs_create_napi_addon( []) +cmakejs_create_napi_addon( [ALIAS ] [NAMESPACE ] [NAPI_VERSION ] []) - (This should wrap the CMakeLists.txt-side requirements for building a Napi Addon) - ]=============================================================================]# +(This should wrap the CMakeLists.txt-side requirements for building a Napi Addon) +]=============================================================================]# function(cmakejs_create_napi_addon name) # Avoid duplicate target names @@ -343,7 +700,7 @@ if(CMAKEJS_CMAKEJS) endif() set(options) - set(args ALIAS NAMESPACE NAPI_VERSION) + set(args ALIAS NAMESPACE NAPI_VERSION EXCEPTIONS) set(list_args) cmake_parse_arguments(ARG "${options}" "${args}" "${list_args}" "${ARGN}") @@ -364,7 +721,7 @@ if(CMAKEJS_CMAKEJS) endif() endif() - # Needs more validation... + # TODO: This needs more/better validation... if(DEFINED ARG_NAPI_VERSION AND (ARG_NAPI_VERSION LESS_EQUAL 0)) message(SEND_ERROR "NAPI_VERSION for ${name} is not a valid Integer number (${ARG_NAPI_VERSION})") return() @@ -384,6 +741,45 @@ if(CMAKEJS_CMAKEJS) set(name_alt "${ARG_NAMESPACE}") endif() + # TODO: How the exceptions are set in fallback cases can be very tricky + # to ascertain. There are numerous different '-D' flags for different + # compilers and platforms for either enabling or disabling exceptions; + # It is also not a good idea to use mixed exceptions policies, or + # link different libraries together with different exceptions policies; + # The user could call this nice new EXCEPTIONS arg in our function, which + # sets a PUBLIC definition (meaning, it propagates to anything that might + # be linked with it); our arg accepts YES, NO, or MAYBE as per . + # Default is MAYBE (as in, no opinion of our own...) + # But, this is not taking into account the users that would rather set + # '-D_UNWIND', '-DCPP_EXCEPTIONS', or some other flag specific to their + # system. If they did, and we are not honouring it, then we are risking + # breaking their global exceptions policy... + # I suggest taking a look at the header file that CMakeRC generates + # to understand how to grep a variety of different possiple exceptions flags + # all into a custom one which handles all cases. The Napi way of having + # three seperate args, that can each be defined against logic, is unfortunate + # and we don't want to break compatibility of existing users' projects. + # I have made one attempt at this in the past which I will revisit + # shortly... but definitely a case of, all ideas welcome! + if(NOT ARG_EXCEPTIONS) + set(ARG_EXCEPTIONS "MAYBE") # YES, NO, or MAYBE... + endif() + + if((NOT DEFINED NAPI_CPP_EXCEPTIONS) OR + (NOT DEFINED NAPI_DISABLE_CPP_EXCEPTIONS) OR + (NOT DEFINED NAPI_CPP_EXCEPTIONS_MAYBE) + ) + + if(ARG_EXCEPTIONS STREQUAL "YES") + set(_NAPI_GLOBAL_EXCEPTIONS_POLICY "NAPI_CPP_EXCEPTIONS") + elseif(ARG_EXCEPTIONS STREQUAL "NO") + set(_NAPI_GLOBAL_EXCEPTIONS_POLICY "NAPI_DISABLE_CPP_EXCEPTIONS") + else() + set(_NAPI_GLOBAL_EXCEPTIONS_POLICY "NAPI_CPP_EXCEPTIONS_MAYBE") + endif() + + endif() + if(VERBOSE) message(STATUS "Configuring Napi Addon: ${name}") endif() @@ -393,11 +789,52 @@ if(CMAKEJS_CMAKEJS) add_library(${name} SHARED) add_library(${name_alt}::${name} ALIAS ${name}) + # TODO: If we instead set up a var like 'CMAKEJS_LINK_LEVEL', + # it can carry an integer number corresponding to which + # dependency level the builder wants. The value of this + # integer can be determined somehow from the result of the + # 'CMakeDependentOption's at the top of this file. + # + # i.e. (psudeo code); + # + # if options = 0; set (CMAKEJS_LINK_LEVEL "cmake-js::node-dev") + # if options = 1; set (CMAKEJS_LINK_LEVEL "cmake-js::node-api") + # if options = 2; set (CMAKEJS_LINK_LEVEL "cmake-js::node-addon-api") + # if options = 3; set (CMAKEJS_LINK_LEVEL "cmake-js::cmake-js") + # + # target_link_libraries(${name} PRIVATE ${CMAKEJS_LINK_LEVEL}) + # + # Why? + # + # Because currently, our 'create_napi_addon()' depends on cmake-js::cmake-js, + # Which is why I had to wrap our nice custom functions inside of + # this 'CMAKEJS_USING_CMAKEJS=TRUE' block, for now. + # + # cmake-js cli users could then be offered a new flag for setting a + # preferred dependency level for their project, controlling the + # values on the JS side before being passed to the command line + # (default to 3 if not set): + # + # $ cmake-js configure --link-level=2 + # + # The above would provide dependency resolution up to cmake-js::node-adon-api level. + # + # If do we make our functions available at all times this way, + # we must also validate that all the possible configurations work + # (or fail safely, and with a prompt.) + # + # Testing (and supporting) the above could be exponentially complex. + # I think most people won't use the target toggles anyway, + # and those that do, won't have access to any broken/untested + # variations of our functions. + # + # Just my suggestion; do as you will :) + target_link_libraries(${name} PRIVATE cmake-js::cmake-js) set_property( TARGET ${name} - PROPERTY "${name}_IS_NAPI_ADDON_LIBRARY" TRUE + PROPERTY "${name}_IS_NAPI_ADDON_LIBRARY" TRUE # Custom property ) set_target_properties(${name} @@ -407,11 +844,12 @@ if(CMAKEJS_CMAKEJS) PREFIX "" SUFFIX ".node" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKEJS_BINARY_DIR}/lib" - LIBRARY_OUTPUT_DIRECTORY "${CMAKEJS_BINARY_DIR}/lib" - RUNTIME_OUTPUT_DIRECTORY "${CMAKEJS_BINARY_DIR}/bin" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKEJS_BINARY_DIR}/lib" # Actually we might not need to enforce an opinion here! + LIBRARY_OUTPUT_DIRECTORY "${CMAKEJS_BINARY_DIR}/lib" # Instead, we call 'cmakejs_create_addon_bindings()' + RUNTIME_OUTPUT_DIRECTORY "${CMAKEJS_BINARY_DIR}/bin" # on this target, and the user can just 'require()' that file! # # Conventional C++-style debug settings might be useful to have... + # Getting Javascript bindings to grep different paths is tricky, though! # LIBRARY_OUTPUT_NAME_DEBUG "d${name}" # ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${CMAKEJS_BINARY_DIR}/lib/Debug" # LIBRARY_OUTPUT_DIRECTORY_DEBUG "${CMAKEJS_BINARY_DIR}/lib/Debug" @@ -431,18 +869,25 @@ if(CMAKEJS_CMAKEJS) PUBLIC # These definitions are shared with anything that links to this addon "NAPI_VERSION=${ARG_NAPI_VERSION}" "BUILDING_NODE_EXTENSION" + "${_NAPI_GLOBAL_EXCEPTIONS_POLICY}" ) + # (experimental) :) + # cmakejs_create_addon_bindings(${name}) + + # Global exceptions policy + unset(_NAPI_GLOBAL_EXCEPTIONS_POLICY) + endfunction() - #[=============================================================================[ - Add source files to an existing Napi Addon target. +#[=============================================================================[ +Add source files to an existing Napi Addon target. - cmakejs_napi_addon_add_sources( [items1...]) - cmakejs_napi_addon_add_sources( [BASE_DIRS ] [items1...]) - cmakejs_napi_addon_add_sources( [ [items1...] [ [items2...] ...]]) - cmakejs_napi_addon_add_sources( [ [BASE_DIRS [...]] [items1...]...) - ]=============================================================================]# +cmakejs_napi_addon_add_sources( [items1...]) +cmakejs_napi_addon_add_sources( [BASE_DIRS ] [items1...]) +cmakejs_napi_addon_add_sources( [ [items1...] [ [items2...] ...]]) +cmakejs_napi_addon_add_sources( [ [BASE_DIRS [...]] [items1...]...) +]=============================================================================]# function(cmakejs_napi_addon_add_sources name) # Check that this is a Node Addon target @@ -464,9 +909,6 @@ if(CMAKEJS_CMAKEJS) _cmakejs_normalize_path(ARG_BASE_DIRS) get_filename_component(ARG_BASE_DIRS "${ARG_BASE_DIRS}" ABSOLUTE) - # Generate the identifier for the resource library's namespace - get_target_property(lib_namespace "${name}" ${name}_ADDON_NAMESPACE) - # All remaining unparsed args 'should' be source files for this target, so... foreach(input IN LISTS ARG_UNPARSED_ARGUMENTS) @@ -511,12 +953,12 @@ if(CMAKEJS_CMAKEJS) endfunction() - #[=============================================================================[ - Add pre-processor definitions to an existing Napi Addon target. +#[=============================================================================[ +Add pre-processor definitions to an existing Napi Addon target. - cmakejs_napi_addon_add_definitions( [items1...]) - cmakejs_napi_addon_add_definitions( [items1...] [ [items2...] ...]) - ]=============================================================================]# +cmakejs_napi_addon_add_definitions( [items1...]) +cmakejs_napi_addon_add_definitions( [items1...] [ [items2...] ...]) +]=============================================================================]# function(cmakejs_napi_addon_add_definitions name) # Check that this is a Node Addon target @@ -557,7 +999,8 @@ if(CMAKEJS_CMAKEJS) endif() # CMAKEJS_CMAKEJS -# This should enable each target to behave well with intellisense (in case they weren't already) +# This should enable each target to behave well with intellisense +# (in case they weren't already) foreach(TARGET IN LISTS CMAKEJS_TARGETS) target_include_directories(${TARGET} INTERFACE @@ -568,6 +1011,33 @@ endforeach() #[=============================================================================[ Collect targets and allow CMake to provide them + +Builders working with CMake at any level know how fussy CMake is about stuff +like filepaths, and how to resolve your project's dependencies. Enough people +went "agh if CMake is gonna be so fussy about my project's filepaths, why can't +it just look after that stuff by itself? Why have I got to do this?" and CMake +went "ok then, do these new 'export()' and 'install()' functions and I'll sort it +all out myself, for you. I'll also sort it out for your users, and their users too!" + +DISCLAIMER: the names 'export()' and 'install()' are just old CMake parlance - +very misleading, at first - try to not think about 'installing' in the traditional +system-level sense, nobody does that until much later downstream from here... + +Earlier, we scooped up all the different header files, logically arranged them into +seperate 'targets' (with a little bit of inter-dependency management), and copied +them into the binary dir. In doing so, we effectively 'chopped off' their +absolute paths; they now 'exist' (temporarily) on a path that *we have not +defined yet*, which is CMAKE_BINARY_DIR. + +In using the BUILD_ and INSTALL_ interfaces, we told CMake how to relocate those +files as it pleases. CMake will move them around as it pleases, but no matter +where those files end up, they will *always* be at 'CMAKE_BINARY_DIR/include/dir', +as far as CMake cares; it will put those files anywhere it needs to, at any time, +*but* we (and our consumers' CMake) can depend on *always* finding them at +'CMAKE_BINARY_DIR/include/dir', no matter what anybody sets their CMAKE_BINARY_DIR +to be. + +It's not quite over yet, but the idea should be becoming clear now... ]=============================================================================]# export ( @@ -578,12 +1048,58 @@ export ( include (CMakePackageConfigHelpers) -file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/CMakeJSConfig.cmake.in" [==[ +file (WRITE "${CMAKE_CURRENT_BINARY_DIR}/CMakeJSConfig.cmake.in" [==[ @PACKAGE_INIT@ -include(${CMAKE_CURRENT_LIST_DIR}/CMakeJSTargets.cmake) +include (${CMAKE_CURRENT_LIST_DIR}/CMakeJSTargets.cmake) + +check_required_components (cmake-js) + +# This codeblock make CMakeJS.cmake transportable, by +# also ensuring that anybody who picks up our CMake package +# will also have the CLI (which is a requirement of our CMake API) + +# Resolve NodeJS development headers +# TODO: This code block is quite problematic, since: +# 1 - it might trigger a build run, depending on how the builder has set up their package.json scripts... +# 2 - it also currently assumes a preference for yarn over npm (and the others)... +# 3 - finally, because of how cmake-js works, it might create Ninja-build artefacts, +# even when the CMake user specifies a different generator to CMake manually... +# We could use 'add_custom_target()' with a user-side ARG for which package manager to use... +if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/node_modules") + # re: package manager to use, we could check IF_EXISTS for a 'yarn.lock' + # in the CMAKE_CURRENT_SOURCE_DIR, else use npm? + execute_process( + COMMAND yarn install + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE NODE_MODULES_DIR + ) + if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/node_modules") + message(FATAL_ERROR "Something went wrong - NodeJS modules installation failed!") + return() + endif() +endif() + +# this would be just 'yarn/npm add cmake-js' if this API were on our 'master' branch +if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/node_modules/cmake-js") + execute_process( + COMMAND yarn "add https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE CMAKE_JS_EXECUTABLE + ) + if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/node_modules/cmake-js") + message(FATAL_ERROR "Something went wrong - cmake-js installation failed!") + return() + endif() +endif() + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/node_modules/cmake-js/share/cmake") -check_required_components(cmake-js) +# Tell the user what to do +message(STATUS "-- Appended cmake-js CMake API to your module path.") +message(STATUS "-- You may 'include(CMakeJS)' in your CMake project to use our API and/or relocatable targets.") +message(STATUS "-- Read more about our 'CMakeJS.cmake' API here:") +message(STATUS "-- https://github.com/cmake-js/cmake-js/blob/cmakejs_cmake_api/README.md") ]==]) # create cmake config file @@ -599,14 +1115,8 @@ write_basic_package_version_file ( VERSION ${_version} COMPATIBILITY AnyNewerVersion ) - -if(CMAKEJS_NODE_API) - install(FILES ${NODE_API_INC_FILES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/include/node-api-headers") -endif() - -if(CMAKEJS_NODE_ADDON_API) - install(FILES ${NODE_ADDON_API_INC_FILES} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/include/node-addon-api") -endif() +# pass our module along +file(COPY "${_CMAKEJS_SCRIPT}" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/share/cmake") # This whole block that follows, and the last changes I made to this file (re: 'file/directory reolcation') # is all predicated on the idea that our consumers want to control certain vars themselves: @@ -634,16 +1144,62 @@ endif() # To do this, they set '-DCMAKE_INSTALL_PREFIX=./install', configure, then build the # 'install' target. -include(GNUInstallDirs) - -# configure a 'CMakeJSTargets' export file for install -install(TARGETS ${CMAKEJS_TARGETS} - EXPORT CMakeJSTargets - LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" - ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" - RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" - INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" +unset(CMAKEJS_INC_DIR) +set(CMAKEJS_INC_DIR + $ + $ + CACHE PATH "Installation directory for include files, a relative path that will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path." + FORCE ) +# copy headers (and definitions?) to build dir for distribution +if(CMAKEJS_USING_NODE_DEV) + install(FILES ${CMAKE_JS_INC_FILES} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/node") + install(TARGETS node-dev + EXPORT CMakeJSTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + FILE_SET node_dev_INTERFACE_HEADERS + ) +endif() + +if(CMAKEJS_USING_NODE_API) + install(FILES ${NODE_API_INC_FILES} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/node-api-headers") + install(TARGETS node-api + EXPORT CMakeJSTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/node-api-headers" # Having trouble setting this correctly + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/node-api-headers" # this issue stems from their package.json, not our code... but guess who needs to fix it now :) + FILE_SET node_api_INTERFACE_HEADERS + ) +endif() + +if(CMAKEJS_USING_NODE_ADDON_API) + install(FILES ${NODE_ADDON_API_INC_FILES} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/node-addon-api") + install(TARGETS node-addon-api + EXPORT CMakeJSTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/node-addon-api" + PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/node-addon-api" + FILE_SET node_addon_api_INTERFACE_HEADERS + ) +endif() + +if(CMAKEJS_USING_CMAKEJS) + install(FILES ${_CMAKEJS_SCRIPT} DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/CMakeJS") + install(TARGETS cmake-js + EXPORT CMakeJSTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + ) +endif() # install config files install(FILES @@ -668,6 +1224,78 @@ foreach(TARGET IN LISTS CMAKEJS_TARGETS) endforeach() if(NOT CMakeJS_IS_TOP_LEVEL) + + # cmake-js --link-level=0 + if(CMAKEJS_USING_NODE_DEV AND (NOT CMAKEJS_USING_NODE_API)) + message(STATUS [==[ +-- +-- To build with the Node.js developer API, +-- +-- Add this to your CMakeLists.txt: +-- + +include(CMakeJS) + +add_library(my_library SHARED) +target_sources(my_library PRIVATE src//my_library.cc) +target_link_libraries(my_library PRIVATE cmake-js::node-dev) + +-- +-- You can include '' in 'my_library.cc' and start building +-- with the Node API, including v8, uv, and all its' dependencies. +-- +]==]) + endif() # CMAKEJS_USING_NODE_API + + # cmake-js --link-level=1 + if(CMAKEJS_USING_NODE_API AND (NOT CMAKEJS_USING_NODE_ADDON_API)) + message(STATUS [==[ +-- +-- To build a Node.js addon in C, +-- +-- Add this to your CMakeLists.txt: +-- + +include(CMakeJS) + +add_library(my_addon SHARED) +target_sources(my_addon PRIVATE src//my_addon.cc) +target_link_libraries(my_addon PRIVATE cmake-js::node-api) +set_target_properties(my_addon PROPERTIES PREFIX "" SUFFIX ".node") + +-- +-- You can include '' in 'my_addon.cc' and start building +-- with the Node Addon API in C. +-- +]==]) + endif() # CMAKEJS_USING_NODE_API + + # cmake-js --link-level=2 + if(CMAKEJS_USING_NODE_ADDON_API AND (NOT CMAKEJS_USING_CMAKEJS)) + message(STATUS [==[ +-- +-- To build a Node.js addon in C++, +-- +-- Add this to your CMakeLists.txt: +-- + +include(CMakeJS) + +add_library(my_addon SHARED) +target_sources(my_addon PRIVATE src//my_addon.cpp) +target_link_libraries(my_addon PRIVATE cmake-js::node-addon-api) +set_target_properties(my_addon PROPERTIES PREFIX "" SUFFIX ".node") +add_target_definitions(my_addon PRIVATE BUILDING_NODE_EXTENSION) + +-- +-- You can include '' in 'my_addon.cpp' and start building +-- with the Node Addon API in C++. +-- +]==]) + endif() # CMAKEJS_USING_NODE_ADDON_API + + # cmake-js --link-level=3 (default) + if(CMAKEJS_USING_CMAKEJS) message(STATUS [==[ -- -- To build a Node.js addon, @@ -675,6 +1303,8 @@ if(NOT CMakeJS_IS_TOP_LEVEL) -- Add this to your CMakeLists.txt: -- +include(CMakeJS) + cmakejs_create_napi_addon ( # CMAKEJS_ADDON_NAME my_addon @@ -684,16 +1314,18 @@ cmakejs_create_napi_addon ( NAMESPACE ) --- You will be able to load your addon in JavaScript code: --- - -const my_addon = require("./build/lib/my_addon.node"); +]==]) -console.log(`Napi Status: ${my_addon.hello()}`); -console.log(`Napi Version: ${my_addon.version()}`); + # cmake-js --link-level=4 (experimental) + if(CMAKEJS_USING_NODE_SEA_CONFIG) + # https://nodejs.org/api/single-executable-applications.html + endif() +# Global message (our CLI applies in all scenarios) +message(STATUS [==[ -- You may use either the regular CMake interface, or the cmake-js CLI, to build your addon! -- +-- Add this to your package.json: { "name": "@/my-addon", @@ -710,6 +1342,15 @@ console.log(`Napi Version: ${my_addon.version()}`); // ... } +-- You will be able to load your built addon in JavaScript code: +-- + +const my_addon = require("./build/lib/my_addon.node"); + +console.log(`Napi Status: ${my_addon.hello()}`); +console.log(`Napi Version: ${my_addon.version()}`); + + -- Make sure to register a module in your C/C++ code like official example does: -- https://github.com/nodejs/node-addon-examples/blob/main/src/1-getting-started/1_hello_world/node-addon-api/hello.cc -- @@ -720,6 +1361,7 @@ console.log(`Napi Version: ${my_addon.version()}`); -- https://github.com/nodejs/node-addon-examples -- -- ]==]) + endif() # CMAKEJS_USING_CMAKEJS endif() unset(_version) diff --git a/share/cmake/api.js b/share/cmake/api.js new file mode 100644 index 00000000..bf2f2fee --- /dev/null +++ b/share/cmake/api.js @@ -0,0 +1,3 @@ +// The CLI needs a portable path to the API +const path_to_api = __dirname; +module.exports = path_to_api; diff --git a/tests/api/README.md b/tests/api/README.md new file mode 100644 index 00000000..56992809 --- /dev/null +++ b/tests/api/README.md @@ -0,0 +1,11 @@ +# CMakeJS.cmake API examples + +The directories contained here demonstrate some of the various functionality that cmake-js provides. + +Everything is powered by a tidy combination of cmake-js' Javascript CLI and it's CMake API. + +```hello_consumer``` demonstrates how cmake-js - powered addons behave when they filter out into the wider world; the ```consumer``` in question should have NodeJS, CMake, and a C/C++ compiler toolchain installed, but they are not burdened with actually operating any of those tools in order to use somebody's addon like a regular NodeJS module. + +The remaining ```hello_*``` projects each represent various functionalities that our CLI/API combo provides Addon buiders with - see their package.json commands and README files for a better understanding of each demonstration and their purposes. + +An especially interesting example is ```hello_with_testing_and_packing```, which demonstrates not just the cmake-js CLI/API, but also leverages some of the deeper functionality we have provided our CMake targets with, such as providing Addon builders with fully transportable dependencies :) diff --git a/tests/api/hello/.gitignore b/tests/api/hello/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/api/hello/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/api/hello/CMakeLists.txt b/tests/api/hello/CMakeLists.txt new file mode 100644 index 00000000..65bc8354 --- /dev/null +++ b/tests/api/hello/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.15) + +project(hello) + +include(CMakeJS) + +cmakejs_create_napi_addon( + addon + "src/hello/addon.cpp" +) diff --git a/tests/api/hello/README.md b/tests/api/hello/README.md new file mode 100644 index 00000000..e5f46ff0 --- /dev/null +++ b/tests/api/hello/README.md @@ -0,0 +1,7 @@ +## I am an addon made with Napi Addon API in C++. + +You can build me, export me as a Javscript module, extend my functionality with your own code ideas, connecting Javascript and C++ functionality together in one binding package. Any importing consumers can get me from '@vendor/hello' in their package.json deps... see 'hello_consumer'! + +Please see my package.json to understand how I work. + +Powered by cmake-js CLI diff --git a/tests/api/hello/index.js b/tests/api/hello/index.js new file mode 100644 index 00000000..a4971b05 --- /dev/null +++ b/tests/api/hello/index.js @@ -0,0 +1,5 @@ +// 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`); +module.exports = addon; diff --git a/tests/api/hello/package.json b/tests/api/hello/package.json new file mode 100644 index 00000000..399be3ff --- /dev/null +++ b/tests/api/hello/package.json @@ -0,0 +1,23 @@ +{ + "name": "@vendor/hello", + "version": "1.0.0", + "description": "A test addon made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node -p \"const addon = require('./build/lib/addon.node'); console.log(addon.hello());\"", + "install": "cmake-js install", + "postinstall": "cmake-js compile", + "configure": "cmake-js configure", + "reconfigure": "cmake-js reconfigure", + "build": "cmake-js build", + "rebuild": "cmake-js rebuild", + "clean": "cmake-js clean", + "wipe": "cmake-js clean && rm -rvf ./node_modules" + }, + "dependencies": { + "cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api", + "node-addon-api": "^7.1.0", + "node-api-headers": "^1.1.0" + } +} diff --git a/tests/api/hello/src/hello/addon.cpp b/tests/api/hello/src/hello/addon.cpp new file mode 100644 index 00000000..806362d5 --- /dev/null +++ b/tests/api/hello/src/hello/addon.cpp @@ -0,0 +1,48 @@ +/** + * @file addon.cpp + * @brief A quick 'hello world' Napi Addon in C++ +*/ + +// Required header and C++ flag +#if __has_include() && BUILDING_NODE_EXTENSION + +#include + +#ifndef STRINGIFY +# define STRINGIFY_HELPER(n) #n +# define STRINGIFY(n) STRINGIFY_HELPER(n) +#endif + +Napi::Value Hello(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), STRINGIFY(CMAKEJS_ADDON_NAME)".node 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(CMAKEJS_ADDON_NAME, Init) // (name to use, initializer to use) + + +#else // !__has_include() || !BUILDING_NODE_EXTENSION + #warning "Warning: Cannot find '' - try running 'npm -g install cmake-js'..." +#endif diff --git a/tests/api/hello_consumer/.gitignore b/tests/api/hello_consumer/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/api/hello_consumer/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/api/hello_consumer/README.md b/tests/api/hello_consumer/README.md new file mode 100644 index 00000000..c34d5970 --- /dev/null +++ b/tests/api/hello_consumer/README.md @@ -0,0 +1,13 @@ +## I am an addon consumer, apparently. + +I'm not even sure what an addon or a CMake is. I know I have CMake, NodeJS, and a C++ compiler installed on this system (and this Ninja thing?), but honestly, I have never once had to touch any of that stuff. + +I just import addons from my neighbours and they all seem to "just work" for me. I am a happy consumer! :) + +Usually, I import these addons from online package repositories in ZIP or TAR format and they all work perfectly. The only reason I am not doing that today is ,because of something about a ```CMakeJS.cmake``` script not being on the 'master' branch yet! so I have to fetch the addon from next door until then, but I'm told that everything will work exactly the same for me, when that does happen. + +Something seems to be getting built in my node_modules dir whenever I ```npm/yarn install```.... + +Please see my package.json to understand how I work. + +Powered by cmake-js CLI diff --git a/tests/api/hello_consumer/index.js b/tests/api/hello_consumer/index.js new file mode 100644 index 00000000..b2f4d881 --- /dev/null +++ b/tests/api/hello_consumer/index.js @@ -0,0 +1,14 @@ +// I am a third-party, or maybe fourth or fifth... +// I've never heard of 'cmake-js' before +// I just have NodeJS and CMake installed +// and everything "just works" for me! + +const addon = require('@vendor/hello'); + +console.log(addon.hello()) + +// If I swap my package.json dependency for +// '@vendor/hello_with_types' and do +// 'require('@vendor/hello_with_types')' here instead, +// then my intellisense engine explains the +// functions to me :) diff --git a/tests/api/hello_consumer/package.json b/tests/api/hello_consumer/package.json new file mode 100644 index 00000000..a5e009f4 --- /dev/null +++ b/tests/api/hello_consumer/package.json @@ -0,0 +1,13 @@ +{ + "name": "@vendor/hello_consumer", + "version": "1.0.0", + "description": "A test addon consumer made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node ./index.js" + }, + "dependencies": { + "@vendor/hello": "../hello/" + } +} diff --git a/tests/api/hello_with_curl/.gitignore b/tests/api/hello_with_curl/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/api/hello_with_curl/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/api/hello_with_curl/CMakeLists.txt b/tests/api/hello_with_curl/CMakeLists.txt new file mode 100644 index 00000000..2fd050c1 --- /dev/null +++ b/tests/api/hello_with_curl/CMakeLists.txt @@ -0,0 +1,164 @@ +cmake_minimum_required(VERSION 3.15) + +project(hello VERSION 1.0.0.0) + +if(NOT DEFINED CMAKE_JS_VERSION) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/node_modules/cmake-js/share/cmake") +endif() + +# Get CURL::libcurl +find_package(CURL REQUIRED) + +if(hello_IS_INSTALLABLE) + include(GNUInstallDirs) + # cURL relocate... + if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/include/curl") + # Set up cURL for transport + file(COPY "${CURL_INCLUDE_DIR}/curl" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/include") + endif() + set (CURL_INCLUDE_DIR + $ + $ + CACHE PATH "Path to curl.h" FORCE + ) + install(FILES ${CURL_INCLUDE_DIR} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +endif() + +# Get cmake-js::cmake-js via 'cmakejs_create_napi_addon()' +include(CMakeJS) + +cmakejs_create_napi_addon( + addon + "src/hello/addon.cpp" + "src/hello/get.cpp" + "src/hello/post.cpp" + NAMESPACE vendor + EXCEPTIONS YES +) +# Move project headers... +configure_file( + "include/hello/addon.h" # from SOURCE_DIR... + "include/hello/addon.h" # ..to BINARY_DIR +) +target_include_directories(addon PUBLIC + $ + $ +) + +# Link Addon to cURl +target_link_libraries(addon PRIVATE CURL::libcurl) +target_include_directories(addon PRIVATE ${CURL_INCLUDE_DIR}) + +# TESTING +if(NOT DEFINED BUILD_TESTS AND hello_IS_TOP_LEVEL) + set(BUILD_TESTS ON) +endif() +if(BUILD_TESTS AND hello_IS_TOP_LEVEL) + # include(CTest) # uncomment this if you want to see the Test Dashboard targets (lots...) + enable_testing() + include("${CMAKE_CURRENT_LIST_DIR}/tests/hello/tests.cmake") +endif() + +# PACKING +if(hello_IS_INSTALLABLE AND hello_IS_TOP_LEVEL) + # CPack prep stuff... + include (CMakePackageConfigHelpers) + + file (WRITE "${CMAKE_CURRENT_BINARY_DIR}/AddonConfig.cmake.in" [==[ +@PACKAGE_INIT@ + +include (${CMAKE_CURRENT_LIST_DIR}/AddonTargets.cmake) + +check_required_components (cmake-js) + +]==]) + + # create cmake config file + configure_package_config_file ( + "${CMAKE_CURRENT_BINARY_DIR}/AddonConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/share/cmake/AddonConfig.cmake" + INSTALL_DESTINATION + "${CMAKE_INSTALL_LIBDIR}/cmake/Addon" + ) + # generate the version file for the cmake config file + write_basic_package_version_file ( + "${CMAKE_CURRENT_BINARY_DIR}/share/cmake/AddonConfigVersion.cmake" + VERSION hello_VERSION + COMPATIBILITY AnyNewerVersion + ) + # pass our build script along + file (WRITE "${CMAKE_CURRENT_BINARY_DIR}/share/cmake/Addon.cmake" "${CMAKE_CURRENT_LIST_FILE}") + export ( + TARGETS addon + FILE share/cmake/AddonTargets.cmake + NAMESPACE hello:: + ) + + install(TARGETS addon + EXPORT AddonTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + ) + + install( + EXPORT AddonTargets + FILE addonTargets.cmake + NAMESPACE vendor:: + DESTINATION lib/cmake/hello + ) + + # set(CPACK_PACKAGE_CHECKSUM "${PROJECT_VERSION_TWEAK}") # git rev-parse is a good idea for this... + set(CPACK_PACKAGE_VENDOR "hello") + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}-node_${NODE_VERSION}") + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}-node_${NODE_VERSION}") + set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-Source") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION}) + set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) + set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_VERSION_MINOR}) + set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_VERSION_PATCH}) + set(CPACK_PACKAGE_VERSION_TWEAK ${PROJECT_VERSION_VERSION_TWEAK}) + #set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) + set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) + set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY ON) + set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY ON) + set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") # Check out CPack's 'NSIS' installer for Win32, and the others! + set(CPACK_SOURCE_IGNORE_FILES + _CPack_Packages + /*.zip + /*.tar + /*.tar.* + /.env* + /.git/* + /.cmake + /.github + /.vs + /.vscode + /.cache + /.config + /.local + /dist + /doc + /docs + #/bin + #/lib + /usr + /out + #/build + /Release + /Debug + /MinSizeRel + /RelWithDebInfo + /downloads + /installed + /install + /node_modules + /vcpkg + /.*build.* + /package-lock.json + /yarn.lock + /\\\\.DS_Store + ) + include(CPack) +endif() diff --git a/tests/api/hello_with_curl/README.md b/tests/api/hello_with_curl/README.md new file mode 100644 index 00000000..eac612ae --- /dev/null +++ b/tests/api/hello_with_curl/README.md @@ -0,0 +1,9 @@ +## I am an addon with cURL API support. + +You can build me, export me as a Javscript module, and share your comments with importing consumers' users' intellisense engines; write descriptions of the cool functions and features that you build, so that your users will have an even better experience using your addon. + +Please see my package.json to understand how to make HTTP POST requests with me. + +More cURL API examples can be found at [https://curl.se/libcurl/c/example.html](https://curl.se/libcurl/c/example.html). + +Powered by cmake-js CLI diff --git a/tests/api/hello_with_curl/include/hello/addon.h b/tests/api/hello_with_curl/include/hello/addon.h new file mode 100644 index 00000000..0ca19d0b --- /dev/null +++ b/tests/api/hello_with_curl/include/hello/addon.h @@ -0,0 +1,20 @@ +/** + * +*/ + +#ifndef HELLO_ADDON_H_INCLUDED +#define HELLO_ADDON_H_INCLUDED + +#include + +#include + +#define HELLO_ADDON_VERSION @PROJECT_VERSION@ // CMake will evaluate this when 'configure_file()' runs! + +/** Adapted from https://curl.se/libcurl/c/simple.html */ +int hello_addon_get(const char* url, const bool& follow); + +/** Adapted from https://curl.se/libcurl/c/http-post.html */ +int hello_addon_post(const char* url, const char* data); + +#endif // HELLO_ADDON_H_INCLUDED diff --git a/tests/api/hello_with_curl/index.js b/tests/api/hello_with_curl/index.js new file mode 100644 index 00000000..2388022f --- /dev/null +++ b/tests/api/hello_with_curl/index.js @@ -0,0 +1,4 @@ +// This small codeblock in your root-level index.js allows others to consume +// your addon as any other NodeJS module +const hello_with_curl = require(`./lib/addon.node`); +module.exports = hello_with_curl; diff --git a/tests/api/hello_with_curl/lib/addon.node.d.ts b/tests/api/hello_with_curl/lib/addon.node.d.ts new file mode 100644 index 00000000..fbdecbbf --- /dev/null +++ b/tests/api/hello_with_curl/lib/addon.node.d.ts @@ -0,0 +1,9 @@ +declare interface addon { + hello(): string; + napi_version(): number; + curl_version(): number; + get(url: string, follow: boolean): string; + post(url: string, data: string): string; +} +declare const addon: addon; +export = addon; diff --git a/tests/api/hello_with_curl/lib/addon.node.js b/tests/api/hello_with_curl/lib/addon.node.js new file mode 100644 index 00000000..572207ec --- /dev/null +++ b/tests/api/hello_with_curl/lib/addon.node.js @@ -0,0 +1,8 @@ +const platform = process.platform; +var buildDir = "/build/lib/"; + +if(platform === "win32") + buildDir = "\\build\\bin\\Release\\"; + +const addon = require(`..${buildDir}addon.node`); +module.exports = addon; diff --git a/tests/api/hello_with_curl/lib/addon.node.ts b/tests/api/hello_with_curl/lib/addon.node.ts new file mode 100644 index 00000000..1df4fdf2 --- /dev/null +++ b/tests/api/hello_with_curl/lib/addon.node.ts @@ -0,0 +1,38 @@ +/** + * The 'addon' C++ addon interface. + */ +interface addon { + /** + * Returns a string, confirming the cmake-js addon is online. + * @returns string + */ + hello(): string; + /** + * Returns a number, confirming the Napi Addon API version number. + * @returns number + */ + napi_version(): number; + /** + * Returns a number, confirming the Curl API version number. + * @returns number + */ + curl_version(): number; + /** + * Sends an HTTP POST request using the CURL API. + * The request is sent to 'url'. Both the url and the + * data to be posted must be Javascript strings (for now). + * Returns a number, confirming the POST request status. + * @returns number + */ + get(url: string, follow: boolean): number; + /** + * Sends an HTTP POST request using the CURL API. + * The request is sent to 'url'. Both the url and the + * data to be posted must be Javascript strings (for now). + * Returns a number, confirming the POST request status. + * @returns number + */ + post(url: string, data: string): number; +} +const addon: addon = require('../build/lib/addon.node'); +export = addon; diff --git a/tests/api/hello_with_curl/package.json b/tests/api/hello_with_curl/package.json new file mode 100644 index 00000000..9b86fc61 --- /dev/null +++ b/tests/api/hello_with_curl/package.json @@ -0,0 +1,34 @@ +{ + "name": "@vendor/hello_with_types", + "version": "1.0.0", + "description": "A test addon with Typescript support made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node ./index.js", + "install": "cmake-js install --CDhello_IS_INSTALLABLE:BOOL=TRUE --CDBUILD_TESTS:BOOL=TRUE --CDCMAKE_INSTALL_PREFIX:PATH=./dist", + "postinstall": "cmake-js compile --CDhello_IS_INSTALLABLE:BOOL=TRUE --CDBUILD_TESTS:BOOL=TRUE --CDCMAKE_INSTALL_PREFIX:PATH=./dist", + "configure": "cmake-js configure --CDhello_IS_INSTALLABLE:BOOL=TRUE --CDBUILD_TESTS:BOOL=TRUE --CDCMAKE_INSTALL_PREFIX:PATH=./dist", + "reconfigure": "cmake-js reconfigure --CDhello_IS_INSTALLABLE:BOOL=TRUE --CDBUILD_TESTS:BOOL=TRUE --CDCMAKE_INSTALL_PREFIX:PATH=./dist", + "build": "cmake-js build", + "rebuild": "cmake-js rebuild", + "clean": "cmake-js clean && rm -rvf ./dist", + "wipe": "cmake-js clean && rm -rvf ./node_modules", + + "ctest": "ctest --test-dir ./build --rerun-failed --output-on-failure --verbose", + "cpack": "cpack --config ./build/CPackSourceConfig.cmake -B ./dist", + "cdist": "cpack --config ./build/CPackConfig.cmake -B ./dist", + + "curl:get": "node -p \"const addon = require('./index'); addon.get( 'https://httpbin.org/anything', /** follow redirects? */ true);\"", + "curl:post": "node -p \"const addon = require('./index'); addon.post('https://httpbin.org/anything', 'name=nathanjhood&project=cmake-js');\"" + }, + "dependencies": { + "cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api", + "node-addon-api": "^7.1.0", + "node-api-headers": "^1.1.0" + }, + "devDependencies": { + "@types/node": "^20.11.14", + "typescript": "^5.3.3" + } +} diff --git a/tests/api/hello_with_curl/src/hello/addon.cpp b/tests/api/hello_with_curl/src/hello/addon.cpp new file mode 100644 index 00000000..971dec02 --- /dev/null +++ b/tests/api/hello_with_curl/src/hello/addon.cpp @@ -0,0 +1,217 @@ +/** + * @file addon.cpp + * @brief A quick 'HTTP POST' Napi Addon in C++ with CURL +*/ + +#include + +// Required header and C++ flag +#if __has_include() && BUILDING_NODE_EXTENSION && NAPI_CPP_EXCEPTIONS + +#include + +#include "hello/addon.h" + +#ifndef STRINGIFY +# define STRINGIFY_HELPER(n) #n +# define STRINGIFY(n) STRINGIFY_HELPER(n) +#endif + +namespace Napi +{ +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +namespace NAPI_CPP_CUSTOM_NAMESPACE +{ +#endif + +Napi::Value Hello(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), STRINGIFY(CMAKEJS_ADDON_NAME)".node v." STRINGIFY(@PROJECT_VERSION@)" is online!"); +} + +Napi::Value NapiVersion(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), NAPI_VERSION); +} + +Napi::Value CurlVersion(const Napi::CallbackInfo& info) { + return Napi::Number::New(info.Env(), CURLVERSION_NOW); +} + +Napi::Value Get(const Napi::CallbackInfo& args) { + + const Napi::Env env = args.Env(); + + // Arguments required: at least one, and no more than two + if (args.Length() != 2) { + // NAPI_CPP_EXCEPTIONS = YES + Napi::TypeError::New(env, "Wrong number of arguments! Please supply a url string, and the string data to POST, in that order").ThrowAsJavaScriptException(); + return env.Null(); + } + + // Param 1 must be a url string + // Param 2 must be a redirect boolean + if (!args[0].IsString() || !args[1].IsBoolean()) { + // NAPI_CPP_EXCEPTIONS = YES + Napi::TypeError::New(env, "Wrong type of arguments! Please supply a url string, and a boolean whether to follow redirected url's, in that order").ThrowAsJavaScriptException(); + return env.Null(); + } + + /* First set the URL that is about to receive our POST. This URL can just as well be an https:// URL if that is what should receive the data. */ + std::string url = args[0].ToString().Utf8Value(); + + /* Now specify the redirect follow mode */ + bool follow = args[1].ToBoolean().Value(); + + int status; + + try { + + // Try to get the data from the url + status = hello_addon_get(url.data(), follow); + + NAPI_THROW_IF_FAILED(env, status) // This behaviour changes depending on EXCEPTIONS policy (can be YES, NO, or MAYBE) + + } catch (const std::exception &x) { + + // If there was an error... + std::string message(x.what()); + message += '\n'; + message += STRINGIFY(CMAKEJS_ADDON_NAME)".node: could not get the following request:\n"; + message += "url: "; + message += args[0].As(); + message += '\n'; + message += "follow redirects: "; + message += args[1].As(); + message += '\n'; + + std::cerr << message << std::endl; + // Throw a javascript-side exception + Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); + + // Clear the old string + url.clear(); + + message.clear(); + + // Return null + return env.Null(); + } + + // If the request did not cause an exception, + // return the status and exit. + return Napi::Number::New(env, status); +} + +Napi::Value Post(const Napi::CallbackInfo& args) { + + const Napi::Env env = args.Env(); + + // Arguments required: at least one, and no more than two + if (args.Length() != 2) { + // NAPI_CPP_EXCEPTIONS = YES + Napi::TypeError::New(env, "Wrong number of arguments! Please supply a url string, and the string data to POST, in that order").ThrowAsJavaScriptException(); + return env.Null(); + } + + // Param 1 must be a url string + // Param 2 must be a data string + if (!args[0].IsString() || !args[1].IsString()) { + // NAPI_CPP_EXCEPTIONS = YES + Napi::TypeError::New(env, "Wrong type of arguments! Please supply a url string, and the string data to POST, in that order").ThrowAsJavaScriptException(); + return env.Null(); + } + + /* First set the URL that is about to receive our POST. This URL can just as well be an https:// URL if that is what should receive the data. */ + std::string url = args[0].ToString().Utf8Value(); + + /* Now specify the POST data */ + std::string data = args[1].ToString().Utf8Value(); + + int status; + + try { + + // Try to post the data to the url + status = hello_addon_post(url.data(), data.data()); + + NAPI_THROW_IF_FAILED(env, status) // This behaviour changes depending on EXCEPTIONS policy (can be YES, NO, or MAYBE) + + } catch (const std::exception &x) { + + // If there was an error... + std::string message(x.what()); + message += '\n'; + message += STRINGIFY(CMAKEJS_ADDON_NAME)".node: could not post the following request:\n"; + message += "url: "; + message += args[0].As(); + message += '\n'; + message += "data: "; + message += args[1].As(); + message += '\n'; + // Throw a javascript-side exception + Napi::TypeError::New(env, message).ThrowAsJavaScriptException(); + + // Clear the old string + url.clear(); + + message.clear(); + + // Return null + return env.Null(); + } + + // If the request did not cause an exception, + // return the status and exit. + return Napi::Number::New(env, status); +} + +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, "napi_version"), + Napi::Function::New(env, NapiVersion) + ); + + exports.Set( + Napi::String::New(env, "curl_version"), + Napi::Function::New(env, CurlVersion) + ); + + exports.Set( + Napi::String::New(env, "get"), + Napi::Function::New(env, Get) + ); + + exports.Set( + Napi::String::New(env, "post"), + Napi::Function::New(env, Post) + ); + + // 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(CMAKEJS_ADDON_NAME, Init) // (name to use, initializer to use) + +#ifdef NAPI_CPP_CUSTOM_NAMESPACE +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +#endif +} // namespace Napi + +// Export your custom namespace to outside of the Napi namespace, providing an +// alias to the Napi Addon API; e.g., '::::Object()', along with the +// functions defined above, such as '::::Hello()'. +namespace NAPI_CPP_CUSTOM_NAMESPACE::CMAKEJS_ADDON_NAME { + using namespace Napi::NAPI_CPP_CUSTOM_NAMESPACE; +} + +#else // !__has_include() || !BUILDING_NODE_EXTENSION + #warning "Warning: Cannot find '' - try running 'npm -g install cmake-js'..." +#endif diff --git a/tests/api/hello_with_curl/src/hello/get.cpp b/tests/api/hello_with_curl/src/hello/get.cpp new file mode 100644 index 00000000..639084a9 --- /dev/null +++ b/tests/api/hello_with_curl/src/hello/get.cpp @@ -0,0 +1,55 @@ +/** + * @file post.cpp + * @brief A quick 'HTTP POST' Napi Addon in C++ with CURL +*/ + +#include + +#if __has_include() + +#include + +#include "hello/addon.h" + +/** + * @brief Adapted from https://curl.se/libcurl/c/simple.html + * + * @param url The HTTP URL to send the POST request to. + * @param follow Whether to follow redirected URL requests. + * @return int + */ +int hello_addon_get(const char* url, const bool& follow) +{ + CURL *curl; + CURLcode res; + + std::cout << "\n"; + std::cout << "Using cURL v" << CURLVERSION_NOW << std::endl; + std::cout << "\n"; + + std::cout << "\n"; + std::cout << "url: " << url << std::endl; + std::cout << "follow redirects: " << follow << std::endl; + std::cout << "\n"; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_URL, url); + /* example.com is redirected, so we tell libcurl to follow redirection */ + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // 'follow' + + /* Perform the request, res will get the return code */ + res = curl_easy_perform(curl); + /* Check for errors */ + if(res != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + return EXIT_SUCCESS; +} + +#else // !__has_include() + #warning "Warning: Cannot find '' - try installing cURL from your command line'..." +#endif diff --git a/tests/api/hello_with_curl/src/hello/post.cpp b/tests/api/hello_with_curl/src/hello/post.cpp new file mode 100644 index 00000000..79cedf5d --- /dev/null +++ b/tests/api/hello_with_curl/src/hello/post.cpp @@ -0,0 +1,53 @@ +/** + * @file post.cpp + * @brief A quick 'HTTP POST' Napi Addon in C++ with CURL +*/ + +#include + +#if __has_include() + +#include + +#include "hello/addon.h" + +int hello_addon_post(const char* url, const char* data) +{ + CURL *curl; + CURLcode res; + + std::cout << "\n"; + std::cout << "Using cURL v" << CURLVERSION_NOW << std::endl; + std::cout << "\n"; + + std::cout << "\n"; + std::cout << "url: " << url << std::endl; + std::cout << "data: " << data << std::endl; + std::cout << "\n"; + + /* In windows, this will init the winsock stuff */ + curl_global_init(CURL_GLOBAL_ALL); + + /* get a curl handle */ + curl = curl_easy_init(); + if(curl) { + /* First set the URL that is about to receive our POST. This URL can just as well be an https:// URL if that is what should receive the data. */ + curl_easy_setopt(curl, CURLOPT_URL, url); + /* Now specify the POST data */ + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); + + /* Perform the request, res will get the return code */ + res = curl_easy_perform(curl); + /* Check for errors */ + if(res != CURLE_OK) fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res)); + + /* always cleanup */ + curl_easy_cleanup(curl); + } + curl_global_cleanup(); + return EXIT_SUCCESS; +} + +#else // !__has_include() + #warning "Warning: Cannot find '' - try installing cURL from your command line'..." +#endif diff --git a/tests/api/hello_with_curl/tests/hello/curl_version.js b/tests/api/hello_with_curl/tests/hello/curl_version.js new file mode 100644 index 00000000..b527d0e7 --- /dev/null +++ b/tests/api/hello_with_curl/tests/hello/curl_version.js @@ -0,0 +1,30 @@ +function test_curl_version() { + + let status = false; + + try { + + const addon = require("../../lib/addon.node"); + + console.log(`Napi Version: ${addon.napi_version()}`); + + status = true; + + } catch(e) { + + console.log(`${e}`); + } + + return status; +}; + +const res_test_curl_version = test_curl_version(); + +if((!res_test_curl_version)) +{ + console.log("'test_curl_version()' failed."); + return false; +} + +console.log("'test_curl_version()' passed."); +return true; diff --git a/tests/api/hello_with_curl/tests/hello/get.js b/tests/api/hello_with_curl/tests/hello/get.js new file mode 100644 index 00000000..16ba47fd --- /dev/null +++ b/tests/api/hello_with_curl/tests/hello/get.js @@ -0,0 +1,30 @@ +function test_get() { + + let status = false; + + try { + + const addon = require("../../lib/addon.node"); + + addon.get('https://httpbin.org/anything', /** follow redirects? */ true); + + status = true; + + } catch(e) { + + console.log(`${e}`); + } + + return status; +}; + +const res_test_get = test_get(); + +if((!res_test_get)) +{ + console.log("'test_get()' failed."); + return false; +} + +console.log("'test_get()' passed."); +return true; diff --git a/tests/api/hello_with_curl/tests/hello/hello.js b/tests/api/hello_with_curl/tests/hello/hello.js new file mode 100644 index 00000000..624cd825 --- /dev/null +++ b/tests/api/hello_with_curl/tests/hello/hello.js @@ -0,0 +1,30 @@ +function test_hello() { + + let status = false; + + try { + + const addon = require("../../lib/addon.node"); + + console.log(addon.hello()); + + status = true; + + } catch(e) { + + console.log(`${e}`); + } + + return status; +}; + +const res_test_hello = test_hello(); + +if((!res_test_hello)) +{ + console.log("'test_hello()' failed."); + return false; +} + +console.log("'test_hello()' passed."); +return true; diff --git a/tests/api/hello_with_curl/tests/hello/napi_version.js b/tests/api/hello_with_curl/tests/hello/napi_version.js new file mode 100644 index 00000000..81b97082 --- /dev/null +++ b/tests/api/hello_with_curl/tests/hello/napi_version.js @@ -0,0 +1,30 @@ +function test_napi_version() { + + let status = false; + + try { + + const addon = require("../../lib/addon.node"); + + console.log(`Napi Version: ${addon.napi_version()}`); + + status = true; + + } catch(e) { + + console.log(`${e}`); + } + + return status; +}; + +const res_test_napi_version = test_napi_version(); + +if((!res_test_napi_version)) +{ + console.log("'test_napi_version()' failed."); + return false; +} + +console.log("'test_napi_version()' passed."); +return true; diff --git a/tests/api/hello_with_curl/tests/hello/post.js b/tests/api/hello_with_curl/tests/hello/post.js new file mode 100644 index 00000000..1bb2a868 --- /dev/null +++ b/tests/api/hello_with_curl/tests/hello/post.js @@ -0,0 +1,30 @@ +function test_post() { + + let status = false; + + try { + + const addon = require("../../lib/addon.node"); + + addon.post('https://httpbin.org/anything', 'name=nathanjhood&project=cmake-js'); + + status = true; + + } catch(e) { + + console.log(`${e}`); + } + + return status; +}; + +const res_test_post = test_post(); + +if((!res_test_post)) +{ + console.log("'test_post()' failed."); + return false; +} + +console.log("'test_post()' passed."); +return true; diff --git a/tests/api/hello_with_curl/tests/hello/tests.cmake b/tests/api/hello_with_curl/tests/hello/tests.cmake new file mode 100644 index 00000000..6e8fd5c2 --- /dev/null +++ b/tests/api/hello_with_curl/tests/hello/tests.cmake @@ -0,0 +1,25 @@ +# Make a list of tests to do (should match the test's filename) +list(APPEND TESTS + hello + napi_version + curl_version + get + post +) + +# define a function to simplify adding tests +function(do_test arg) + add_test( + NAME test_${arg} + COMMAND "${NODE_EXECUTABLE}" "./tests/hello/${arg}.js" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + ) + set_tests_properties(test_${arg} + PROPERTIES PASS_REGULAR_EXPRESSION " passed." + ) +endfunction(do_test) + +# run the tests +foreach(TEST IN LISTS TESTS) + do_test("${TEST}") +endforeach() diff --git a/tests/api/hello_with_curl/tsconfig.json b/tests/api/hello_with_curl/tsconfig.json new file mode 100644 index 00000000..7ee6464f --- /dev/null +++ b/tests/api/hello_with_curl/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "declaration": true, + "lib": [ "esnext" ], + "module": "commonjs", + "outDir": "./build", + "rootDir": ".", + "strict": true, + "target": "es6", + "types": [ + "node" + ], + "typeRoots": [ + "node_modules/@types" + ] + }, + "exclude": [ + "node_modules" + ] +} diff --git a/tests/api/hello_with_testing_and_packing/.gitignore b/tests/api/hello_with_testing_and_packing/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/api/hello_with_testing_and_packing/CMakeLists.txt b/tests/api/hello_with_testing_and_packing/CMakeLists.txt new file mode 100644 index 00000000..672674a6 --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/CMakeLists.txt @@ -0,0 +1,152 @@ +cmake_minimum_required(VERSION 3.15) + +project(vendor VERSION 1.0.0.0) + +# (must be top-level project to run tests and packing, like any other CMake project!) +set (vendor_IS_MASTER_PROJECT FALSE) +if(NOT (PROJECT_SOURCE_DIR STREQUAL PROJECT_BINARY_DIR)) + set (vendor_IS_MASTER_PROJECT TRUE) +endif() + +include(CMakeJS) + +cmakejs_create_napi_addon ( + # CMAKEJS_ADDON_NAME + addon + # SOURCES + src/hello/addon.cpp + # NAPI_CPP_CUSTOM_NAMESPACE + NAMESPACE vendor +) + +# If this is not a subproject... +if (vendor_IS_TOP_LEVEL + # (and we're not building into the source tree) + AND vendor_IS_MASTER_PROJECT +) + + include(GNUInstallDirs) # dont be alarmed by this - it wont install to your system! + + # CPack prep stuff... + export ( + TARGETS addon + FILE share/cmake/AddonTargets.cmake + NAMESPACE vendor:: + ) + + install( + EXPORT AddonTargets + FILE addonTargets.cmake + NAMESPACE vendor:: + DESTINATION lib/cmake/vendor + ) + + install(TARGETS addon + EXPORT AddonTargets + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" + INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + ) + + + file (WRITE "${CMAKE_CURRENT_BINARY_DIR}/AddonConfig.cmake.in" [==[ + @PACKAGE_INIT@ + + include (${CMAKE_CURRENT_LIST_DIR}/AddonTargets.cmake) + + check_required_components (vendor) + + ]==]) + + # create cmake config file + configure_package_config_file ( + "${CMAKE_CURRENT_BINARY_DIR}/AddonConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/share/cmake/AddonConfig.cmake" + INSTALL_DESTINATION + "${CMAKE_INSTALL_LIBDIR}/cmake/Addon" + ) + # generate the version file for the cmake config file + write_basic_package_version_file ( + "${CMAKE_CURRENT_BINARY_DIR}/share/cmake/AddonConfigVersion.cmake" + VERSION 1.0.0 + COMPATIBILITY AnyNewerVersion + ) + + # copy the addon types + file(COPY + "${CMAKE_CURRENT_LIST_DIR}/lib/addon.node.js" + "${CMAKE_CURRENT_LIST_DIR}/lib/addon.node.ts" + "${CMAKE_CURRENT_LIST_DIR}/lib/addon.node.d.ts" + DESTINATION + "${PROJECT_BINARY_DIR}/lib" + ) + + install(FILES + "${PROJECT_BINARY_DIR}/lib/addon.node.js" + "${PROJECT_BINARY_DIR}/lib/addon.node.ts" + "${PROJECT_BINARY_DIR}/lib/addon.node.d.ts" + DESTINATION + "${CMAKE_INSTALL_LIBDIR}" + ) + + if(BUILD_TESTS) + # include(CTest) # If you want to see all the test dashboard targets, uncomment this + enable_testing() + include("${CMAKE_CURRENT_LIST_DIR}/tests/hello/tests.cmake") + endif() + + # set(CPACK_PACKAGE_CHECKSUM "${PROJECT_VERSION_TWEAK}") # git rev-parse is a good idea for this... + set(CPACK_PACKAGE_VENDOR "vendor") + set(CPACK_PACKAGE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}-node_${NODE_VERSION}") + set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}-node_${NODE_VERSION}") # Multi-platform CMake API distribution + set(CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}-Source") # No system spec as this is un-compiled source file distribution (i.e., the Javascript API) + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION}) + set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) + set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_VERSION_MINOR}) + set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_VERSION_PATCH}) + set(CPACK_PACKAGE_VERSION_TWEAK ${PROJECT_VERSION_VERSION_TWEAK}) + #set(CPACK_RESOURCE_FILE_LICENSE ${PROJECT_SOURCE_DIR}/LICENSE) + set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md) + set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY ON) + set(CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY ON) + set(CPACK_SOURCE_GENERATOR "TGZ;ZIP") # Check out CPack's 'NSIS' installer for Win32, and the others! + set(CPACK_SOURCE_IGNORE_FILES + _CPack_Packages + /*.zip + /*.tar + /*.tar.* + /.env* + /.git/* + /.cmake + /.github + /.vs + /.vscode + /.cache + /.config + /.local + /dist + /doc + /docs + #/bin + #/lib + /usr + /out + #/build + /Release + /Debug + /MinSizeRel + /RelWithDebInfo + /downloads + /installed + /install + /node_modules + /vcpkg + /.*build.* + /package-lock.json + /yarn.lock + /\\\\.DS_Store + ) + include(CPack) + +endif () # *_IS_TOP_LEVEL_PROJECT diff --git a/tests/api/hello_with_testing_and_packing/README.md b/tests/api/hello_with_testing_and_packing/README.md new file mode 100644 index 00000000..ecf38815 --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/README.md @@ -0,0 +1,7 @@ +## I am an installable addon. + +You can build me, run some tests, and then package me up into an archive file for easy distribution, in various formats. + +Please see my package.json to understand how I work. + +Powered by cmake-js CLI diff --git a/tests/api/hello_with_testing_and_packing/index.js b/tests/api/hello_with_testing_and_packing/index.js new file mode 100644 index 00000000..25433f74 --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/index.js @@ -0,0 +1,4 @@ +// This small codeblock in your root-level index.js allows others to consume +// your addon as any other NodeJS module +const hello_with_testing_and_packing = require(`./lib/addon.node`); +module.exports = hello_with_testing_and_packing; diff --git a/tests/api/hello_with_testing_and_packing/lib/addon.node.d.ts b/tests/api/hello_with_testing_and_packing/lib/addon.node.d.ts new file mode 100644 index 00000000..89e5844c --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/lib/addon.node.d.ts @@ -0,0 +1,6 @@ +declare interface addon { + hello(): string; + version(): string; +} +declare const addon: addon; +export = addon; diff --git a/tests/api/hello_with_testing_and_packing/lib/addon.node.js b/tests/api/hello_with_testing_and_packing/lib/addon.node.js new file mode 100644 index 00000000..572207ec --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/lib/addon.node.js @@ -0,0 +1,8 @@ +const platform = process.platform; +var buildDir = "/build/lib/"; + +if(platform === "win32") + buildDir = "\\build\\bin\\Release\\"; + +const addon = require(`..${buildDir}addon.node`); +module.exports = addon; diff --git a/tests/api/hello_with_testing_and_packing/lib/addon.node.ts b/tests/api/hello_with_testing_and_packing/lib/addon.node.ts new file mode 100644 index 00000000..a7eef18a --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/lib/addon.node.ts @@ -0,0 +1,17 @@ +/** + * The 'addon' C++ addon interface. + */ +interface addon { + /** + * Returns a string, confirming the cmake-js addon is online. + * @returns string + */ + hello(): string; + /** + * Returns a number, confirming the Napi Addon API version number. + * @returns number + */ + version(): number; +} +const addon: addon = require('../build/lib/addon.node'); +export = addon; diff --git a/tests/api/hello_with_testing_and_packing/package.json b/tests/api/hello_with_testing_and_packing/package.json new file mode 100644 index 00000000..c62723be --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/package.json @@ -0,0 +1,30 @@ +{ + "name": "@vendor/hello_with_types", + "version": "1.0.0", + "description": "A test addon with CTest, CPack, and Typescript support made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node ./index.js", + "install": "cmake-js install", + "postinstall": "cmake-js compile --CDBUILD_TESTS:BOOL=TRUE --CDCMAKE_INSTALL_PREFIX:PATH=./dist", + "configure": "cmake-js configure --CDBUILD_TESTS:BOOL=TRUE --CDCMAKE_INSTALL_PREFIX:PATH=./dist", + "reconfigure": "cmake-js reconfigure --CDBUILD_TESTS:BOOL=TRUE --CDCMAKE_INSTALL_PREFIX:PATH=./dist", + "build": "cmake-js build", + "rebuild": "cmake-js rebuild", + "clean": "cmake-js clean", + "wipe": "cmake-js clean && rm -rvf ./node_modules", + "ctest": "ctest --test-dir ./build --rerun-failed --output-on-failure --verbose", + "cpack": "cpack --config ./build/CPackSourceConfig.cmake -B ./dist", + "cdist": "cpack --config ./build/CPackConfig.cmake -B ./dist" + }, + "dependencies": { + "cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api", + "node-addon-api": "^7.1.0", + "node-api-headers": "^1.1.0" + }, + "devDependencies": { + "@types/node": "^20.11.14", + "typescript": "^5.3.3" + } +} diff --git a/tests/api/hello_with_testing_and_packing/src/hello/addon.cpp b/tests/api/hello_with_testing_and_packing/src/hello/addon.cpp new file mode 100644 index 00000000..bd3e3e1b --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/src/hello/addon.cpp @@ -0,0 +1,62 @@ +/** + * @file addon.cpp + * @brief A quick 'hello world' Napi Addon in C++ +*/ + +// Required header and C++ flag +#if __has_include() && BUILDING_NODE_EXTENSION + +#include + +#ifndef STRINGIFY +# define STRINGIFY_HELPER(n) #n +# define STRINGIFY(n) STRINGIFY_HELPER(n) +#endif + +namespace Napi +{ +namespace NAPI_CPP_CUSTOM_NAMESPACE +{ + +Napi::Value Hello(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), STRINGIFY(CMAKEJS_ADDON_NAME)".node 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(CMAKEJS_ADDON_NAME, Init) // (name to use, initializer to use) + +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +} // namespace Napi + +// Export your custom namespace to outside of the Napi namespace, providing an +// alias to the Napi Addon API; e.g., '::::Object()', along with the +// functions defined above, such as '::::Hello()'. +namespace NAPI_CPP_CUSTOM_NAMESPACE::CMAKEJS_ADDON_NAME { + using namespace Napi::NAPI_CPP_CUSTOM_NAMESPACE; +} + +#else // !__has_include() || !BUILDING_NODE_EXTENSION + #warning "Warning: Cannot find '' - try running 'npm -g install cmake-js'..." +#endif diff --git a/tests/api/hello_with_testing_and_packing/tests/hello/hello.js b/tests/api/hello_with_testing_and_packing/tests/hello/hello.js new file mode 100644 index 00000000..624cd825 --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/tests/hello/hello.js @@ -0,0 +1,30 @@ +function test_hello() { + + let status = false; + + try { + + const addon = require("../../lib/addon.node"); + + console.log(addon.hello()); + + status = true; + + } catch(e) { + + console.log(`${e}`); + } + + return status; +}; + +const res_test_hello = test_hello(); + +if((!res_test_hello)) +{ + console.log("'test_hello()' failed."); + return false; +} + +console.log("'test_hello()' passed."); +return true; diff --git a/tests/api/hello_with_testing_and_packing/tests/hello/tests.cmake b/tests/api/hello_with_testing_and_packing/tests/hello/tests.cmake new file mode 100644 index 00000000..56f0435f --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/tests/hello/tests.cmake @@ -0,0 +1,22 @@ +# Make a list of tests to do (should match the test's filename) +list(APPEND TESTS + hello + version +) + +# define a function to simplify adding tests +function(do_test arg) + add_test( + NAME test_${arg} + COMMAND "${NODE_EXECUTABLE}" "./tests/hello/${arg}.js" + WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" + ) + set_tests_properties(test_${arg} + PROPERTIES PASS_REGULAR_EXPRESSION " passed." + ) +endfunction(do_test) + +# run the tests +foreach(TEST IN LISTS TESTS) + do_test("${TEST}") +endforeach() diff --git a/tests/api/hello_with_testing_and_packing/tests/hello/version.js b/tests/api/hello_with_testing_and_packing/tests/hello/version.js new file mode 100644 index 00000000..643baad2 --- /dev/null +++ b/tests/api/hello_with_testing_and_packing/tests/hello/version.js @@ -0,0 +1,30 @@ +function test_version() { + + let status = false; + + try { + + const addon = require("../../lib/addon.node"); + + console.log(`Napi Version: ${addon.version()}`); + + status = true; + + } catch(e) { + + console.log(`${e}`); + } + + return status; +}; + +const res_test_version = test_version(); + +if((!res_test_version)) +{ + console.log("'test_version()' failed."); + return false; +} + +console.log("'test_version()' passed."); +return true; diff --git a/tests/api/hello_with_types/.gitignore b/tests/api/hello_with_types/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/api/hello_with_types/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/api/hello_with_types/CMakeLists.txt b/tests/api/hello_with_types/CMakeLists.txt new file mode 100644 index 00000000..11b7170f --- /dev/null +++ b/tests/api/hello_with_types/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.15) + +project(hello) + +include(CMakeJS) + +cmakejs_create_napi_addon( + addon + src/hello/addon.cpp + NAMESPACE vendor +) diff --git a/tests/api/hello_with_types/README.md b/tests/api/hello_with_types/README.md new file mode 100644 index 00000000..7533ccdc --- /dev/null +++ b/tests/api/hello_with_types/README.md @@ -0,0 +1,7 @@ +## I am an addon with Typescript support. + +You can build me, export me as a Javscript module, and share your comments with importing consumers' users' intellisense engines; write descriptions of the cool functions and features that you build, so that your users will have an even better experience using your addon. + +Please see my package.json to understand how I work. + +Powered by cmake-js CLI diff --git a/tests/api/hello_with_types/index.js b/tests/api/hello_with_types/index.js new file mode 100644 index 00000000..a4f57180 --- /dev/null +++ b/tests/api/hello_with_types/index.js @@ -0,0 +1,4 @@ +// This small codeblock in your root-level index.js allows others to consume +// your addon as any other NodeJS module +const hello_with_types = require(`./lib/addon.node`); +module.exports = hello_with_types; diff --git a/tests/api/hello_with_types/lib/addon.node.d.ts b/tests/api/hello_with_types/lib/addon.node.d.ts new file mode 100644 index 00000000..89e5844c --- /dev/null +++ b/tests/api/hello_with_types/lib/addon.node.d.ts @@ -0,0 +1,6 @@ +declare interface addon { + hello(): string; + version(): string; +} +declare const addon: addon; +export = addon; diff --git a/tests/api/hello_with_types/lib/addon.node.js b/tests/api/hello_with_types/lib/addon.node.js new file mode 100644 index 00000000..572207ec --- /dev/null +++ b/tests/api/hello_with_types/lib/addon.node.js @@ -0,0 +1,8 @@ +const platform = process.platform; +var buildDir = "/build/lib/"; + +if(platform === "win32") + buildDir = "\\build\\bin\\Release\\"; + +const addon = require(`..${buildDir}addon.node`); +module.exports = addon; diff --git a/tests/api/hello_with_types/lib/addon.node.ts b/tests/api/hello_with_types/lib/addon.node.ts new file mode 100644 index 00000000..a7eef18a --- /dev/null +++ b/tests/api/hello_with_types/lib/addon.node.ts @@ -0,0 +1,17 @@ +/** + * The 'addon' C++ addon interface. + */ +interface addon { + /** + * Returns a string, confirming the cmake-js addon is online. + * @returns string + */ + hello(): string; + /** + * Returns a number, confirming the Napi Addon API version number. + * @returns number + */ + version(): number; +} +const addon: addon = require('../build/lib/addon.node'); +export = addon; diff --git a/tests/api/hello_with_types/package.json b/tests/api/hello_with_types/package.json new file mode 100644 index 00000000..a5800061 --- /dev/null +++ b/tests/api/hello_with_types/package.json @@ -0,0 +1,27 @@ +{ + "name": "@vendor/hello_with_types", + "version": "1.0.0", + "description": "A test addon with Typescript support made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node ./index.js", + "install": "cmake-js install", + "postinstall": "cmake-js compile", + "configure": "cmake-js configure", + "reconfigure": "cmake-js reconfigure", + "build": "cmake-js build", + "rebuild": "cmake-js rebuild", + "clean": "cmake-js clean", + "wipe": "cmake-js clean && rm -rvf ./node_modules" + }, + "dependencies": { + "cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api", + "node-addon-api": "^7.1.0", + "node-api-headers": "^1.1.0" + }, + "devDependencies": { + "@types/node": "^20.11.14", + "typescript": "^5.3.3" + } +} diff --git a/tests/api/hello_with_types/src/hello/addon.cpp b/tests/api/hello_with_types/src/hello/addon.cpp new file mode 100644 index 00000000..bd3e3e1b --- /dev/null +++ b/tests/api/hello_with_types/src/hello/addon.cpp @@ -0,0 +1,62 @@ +/** + * @file addon.cpp + * @brief A quick 'hello world' Napi Addon in C++ +*/ + +// Required header and C++ flag +#if __has_include() && BUILDING_NODE_EXTENSION + +#include + +#ifndef STRINGIFY +# define STRINGIFY_HELPER(n) #n +# define STRINGIFY(n) STRINGIFY_HELPER(n) +#endif + +namespace Napi +{ +namespace NAPI_CPP_CUSTOM_NAMESPACE +{ + +Napi::Value Hello(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), STRINGIFY(CMAKEJS_ADDON_NAME)".node 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(CMAKEJS_ADDON_NAME, Init) // (name to use, initializer to use) + +} // namespace NAPI_CPP_CUSTOM_NAMESPACE +} // namespace Napi + +// Export your custom namespace to outside of the Napi namespace, providing an +// alias to the Napi Addon API; e.g., '::::Object()', along with the +// functions defined above, such as '::::Hello()'. +namespace NAPI_CPP_CUSTOM_NAMESPACE::CMAKEJS_ADDON_NAME { + using namespace Napi::NAPI_CPP_CUSTOM_NAMESPACE; +} + +#else // !__has_include() || !BUILDING_NODE_EXTENSION + #warning "Warning: Cannot find '' - try running 'npm -g install cmake-js'..." +#endif diff --git a/tests/targets/README.md b/tests/targets/README.md new file mode 100644 index 00000000..f42b871f --- /dev/null +++ b/tests/targets/README.md @@ -0,0 +1,22 @@ +# CMakeJS.cmake targets + +The four directories here correspond to the four CMake targets we currently export: + +| dir | target | provides | link level | +| -------------- | ------------------------ | ------------------------------- | ----------- | +| node-dev | cmake-js::node-dev | \ | 0 | +| node-api | cmake-js::node-api | \ | 1 | +| node-addon-api | cmake-js::node-addon-api | \ | 2 | +| cmake-js | cmake-js::cmake-js | cmakejs_create_napi_addon() | 3 (default) | + +Each target in the above order *depends on* the one before it; they have also all been made relocatable. + +The first three targets, cmake-js::node-*, each carries it's own set of headers, as indicated by their given names, and further clarified in the table above. + +Our proposed ```--link-level``` CLI switch can control how far down the list they want to go. User who specify a ```--link-level=1``` on their cmake-js CLI command(s) will handily find that they are automatically linked with and provided the header sets provided by cmake-js::node-api (the C addon headers), *as well as* cmake-js::node-dev (which the C headers depend on). What they won't recieve or be linked with is any of the header set from cmake-js::node-addon-api (the C++ Addon wrappers). This is [desireable behaviour](). + +cmake-js::cmake-js is unique in that it doesn't carry any headers of it's own, but since it *does* carry all the other targets by being last in the consumer chain, users linking to this target will recieve the full set of developer headers, *and* will be able to use our very nice ```cmakejs_create_napi_addon()``` function in their CMakeLists.txt, which vastly reduces the amount of intermediate/advanced CMake config they might otherwise be faced with. + +Everything is powered by a tidy combination of cmake-js' Javascript CLI and it's CMake API. + +As a cherry on the cake, users will be able to call CPack on their addons, and find that their CMake source and binary dirs are bundled up along with the header sets that they requested via ```--link-level``` (and nothing more). diff --git a/tests/targets/cmake-js/.gitignore b/tests/targets/cmake-js/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/targets/cmake-js/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/targets/cmake-js/CMakeLists.txt b/tests/targets/cmake-js/CMakeLists.txt new file mode 100644 index 00000000..6c747613 --- /dev/null +++ b/tests/targets/cmake-js/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.15) + +project(hello) + +# For CMake IDE tools support (do yarn/install first and keep your node_modules folder around) +if(NOT DEFINED CMAKE_JS_VERSION) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/node_modules/cmake-js/share/cmake") +endif() + +include(CMakeJS) + +add_library(addon SHARED "src/hello/addon.cpp") +target_link_libraries(addon PRIVATE cmake-js::cmake-js) # relocatable deps resolved! +set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node") +set_target_properties(addon PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") +target_compile_definitions(addon PUBLIC BUILDING_NODE_EXTENSION) +target_compile_definitions(addon PUBLIC NAPI_VERSION=8) +target_compile_definitions(addon PUBLIC NAPI_CPP_EXCEPTIONS_MAYBE) + +# For Visual Studio generators +if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$: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() diff --git a/tests/targets/cmake-js/README.md b/tests/targets/cmake-js/README.md new file mode 100644 index 00000000..e5f46ff0 --- /dev/null +++ b/tests/targets/cmake-js/README.md @@ -0,0 +1,7 @@ +## I am an addon made with Napi Addon API in C++. + +You can build me, export me as a Javscript module, extend my functionality with your own code ideas, connecting Javascript and C++ functionality together in one binding package. Any importing consumers can get me from '@vendor/hello' in their package.json deps... see 'hello_consumer'! + +Please see my package.json to understand how I work. + +Powered by cmake-js CLI diff --git a/tests/targets/cmake-js/index.js b/tests/targets/cmake-js/index.js new file mode 100644 index 00000000..4e5b1cdb --- /dev/null +++ b/tests/targets/cmake-js/index.js @@ -0,0 +1,10 @@ +// This small codeblock in your root-level index.js allows others to consume +// your addon as any other NodeJS module +const platform = process.platform; +var buildDir = "/build/lib/"; + +if(platform === "win32") + buildDir = "\\build\\bin\\Release\\"; + +const hello = require(`.${buildDir}addon.node`); +module.exports = hello; diff --git a/tests/targets/cmake-js/package.json b/tests/targets/cmake-js/package.json new file mode 100644 index 00000000..c676b053 --- /dev/null +++ b/tests/targets/cmake-js/package.json @@ -0,0 +1,23 @@ +{ + "name": "@vendor/hello", + "version": "1.0.0", + "description": "A test addon made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node -p \"const addon = require('./index'); console.log(addon.hello());\"", + "install": "cmake-js install", + "postinstall": "cmake-js compile", + "configure": "cmake-js configure", + "reconfigure": "cmake-js reconfigure", + "build": "cmake-js build", + "rebuild": "cmake-js rebuild", + "clean": "cmake-js clean", + "wipe": "cmake-js clean && rm -rvf ./node_modules" + }, + "dependencies": { + "cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api", + "node-addon-api": "^7.1.0", + "node-api-headers": "^1.1.0" + } +} diff --git a/tests/targets/cmake-js/src/hello/addon.cpp b/tests/targets/cmake-js/src/hello/addon.cpp new file mode 100644 index 00000000..57c7c072 --- /dev/null +++ b/tests/targets/cmake-js/src/hello/addon.cpp @@ -0,0 +1,43 @@ +/** + * @file addon.cpp + * @brief A quick 'hello world' Napi Addon in C++ +*/ + +// Required header and C++ flag +#if __has_include() && BUILDING_NODE_EXTENSION + +#include + +Napi::Value Hello(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), "addon.node 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) + + +#else // !__has_include() || !BUILDING_NODE_EXTENSION + #warning "Warning: Cannot find '' - try running 'npm -g install cmake-js'..." +#endif diff --git a/tests/targets/node-addon-api/.gitignore b/tests/targets/node-addon-api/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/targets/node-addon-api/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/targets/node-addon-api/CMakeLists.txt b/tests/targets/node-addon-api/CMakeLists.txt new file mode 100644 index 00000000..17402452 --- /dev/null +++ b/tests/targets/node-addon-api/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.15) + +project(hello) + +set(CMAKEJS_USING_CMAKEJS FALSE) # this should have been done with '--link-level=2' on the CLI, testing this way for now + +# For CMake IDE tools support (do yarn/install first and keep your node_modules folder around) +if(NOT DEFINED CMAKE_JS_VERSION) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/node_modules/cmake-js/share/cmake") +endif() + +include(CMakeJS) + +add_library(addon SHARED "src/hello/addon.cpp") +target_link_libraries(addon PRIVATE cmake-js::node-addon-api) # relocatable deps resolved! +set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node") +set_target_properties(addon PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") +target_compile_definitions(addon PUBLIC BUILDING_NODE_EXTENSION) +target_compile_definitions(addon PUBLIC NAPI_VERSION=8) +target_compile_definitions(addon PUBLIC NAPI_CPP_EXCEPTIONS_MAYBE) + +# For Visual Studio generators +if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$: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() diff --git a/tests/targets/node-addon-api/README.md b/tests/targets/node-addon-api/README.md new file mode 100644 index 00000000..e5f46ff0 --- /dev/null +++ b/tests/targets/node-addon-api/README.md @@ -0,0 +1,7 @@ +## I am an addon made with Napi Addon API in C++. + +You can build me, export me as a Javscript module, extend my functionality with your own code ideas, connecting Javascript and C++ functionality together in one binding package. Any importing consumers can get me from '@vendor/hello' in their package.json deps... see 'hello_consumer'! + +Please see my package.json to understand how I work. + +Powered by cmake-js CLI diff --git a/tests/targets/node-addon-api/index.js b/tests/targets/node-addon-api/index.js new file mode 100644 index 00000000..4e5b1cdb --- /dev/null +++ b/tests/targets/node-addon-api/index.js @@ -0,0 +1,10 @@ +// This small codeblock in your root-level index.js allows others to consume +// your addon as any other NodeJS module +const platform = process.platform; +var buildDir = "/build/lib/"; + +if(platform === "win32") + buildDir = "\\build\\bin\\Release\\"; + +const hello = require(`.${buildDir}addon.node`); +module.exports = hello; diff --git a/tests/targets/node-addon-api/package.json b/tests/targets/node-addon-api/package.json new file mode 100644 index 00000000..399be3ff --- /dev/null +++ b/tests/targets/node-addon-api/package.json @@ -0,0 +1,23 @@ +{ + "name": "@vendor/hello", + "version": "1.0.0", + "description": "A test addon made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node -p \"const addon = require('./build/lib/addon.node'); console.log(addon.hello());\"", + "install": "cmake-js install", + "postinstall": "cmake-js compile", + "configure": "cmake-js configure", + "reconfigure": "cmake-js reconfigure", + "build": "cmake-js build", + "rebuild": "cmake-js rebuild", + "clean": "cmake-js clean", + "wipe": "cmake-js clean && rm -rvf ./node_modules" + }, + "dependencies": { + "cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api", + "node-addon-api": "^7.1.0", + "node-api-headers": "^1.1.0" + } +} diff --git a/tests/targets/node-addon-api/src/hello/addon.cpp b/tests/targets/node-addon-api/src/hello/addon.cpp new file mode 100644 index 00000000..57c7c072 --- /dev/null +++ b/tests/targets/node-addon-api/src/hello/addon.cpp @@ -0,0 +1,43 @@ +/** + * @file addon.cpp + * @brief A quick 'hello world' Napi Addon in C++ +*/ + +// Required header and C++ flag +#if __has_include() && BUILDING_NODE_EXTENSION + +#include + +Napi::Value Hello(const Napi::CallbackInfo& info) { + return Napi::String::New(info.Env(), "addon.node 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) + + +#else // !__has_include() || !BUILDING_NODE_EXTENSION + #warning "Warning: Cannot find '' - try running 'npm -g install cmake-js'..." +#endif diff --git a/tests/targets/node-api/.gitignore b/tests/targets/node-api/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/targets/node-api/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/targets/node-api/CMakeLists.txt b/tests/targets/node-api/CMakeLists.txt new file mode 100644 index 00000000..92659acd --- /dev/null +++ b/tests/targets/node-api/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.15) + +project(hello) + +set(CMAKEJS_USING_NODE_ADDON_API FALSE) # this should have been done with '--link-level=1' on the CLI, testing this way for now + +# For CMake IDE tools support (do yarn/install first and keep your node_modules folder around) +if(NOT DEFINED CMAKE_JS_VERSION) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/node_modules/cmake-js/share/cmake") +endif() + +include(CMakeJS) + +add_library(addon SHARED "src/hello/addon.cc") +target_link_libraries(addon PRIVATE cmake-js::node-api) # relocatable deps resolved! +set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node") +set_target_properties(addon PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + +# For Visual Studio generators +if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$: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() diff --git a/tests/targets/node-api/README.md b/tests/targets/node-api/README.md new file mode 100644 index 00000000..9928f18d --- /dev/null +++ b/tests/targets/node-api/README.md @@ -0,0 +1,7 @@ +## I am an addon made with Napi Addon API in C. + +You can build me, export me as a Javscript module, extend my functionality with your own code ideas, connecting Javascript and C functionality together in one binding package. Any importing consumers can get me from '@vendor/hello' in their package.json deps... see 'hello_consumer'! + +Please see my package.json to understand how I work. + +Powered by cmake-js CLI diff --git a/tests/targets/node-api/index.js b/tests/targets/node-api/index.js new file mode 100644 index 00000000..4e5b1cdb --- /dev/null +++ b/tests/targets/node-api/index.js @@ -0,0 +1,10 @@ +// This small codeblock in your root-level index.js allows others to consume +// your addon as any other NodeJS module +const platform = process.platform; +var buildDir = "/build/lib/"; + +if(platform === "win32") + buildDir = "\\build\\bin\\Release\\"; + +const hello = require(`.${buildDir}addon.node`); +module.exports = hello; diff --git a/tests/targets/node-api/package.json b/tests/targets/node-api/package.json new file mode 100644 index 00000000..02606770 --- /dev/null +++ b/tests/targets/node-api/package.json @@ -0,0 +1,22 @@ +{ + "name": "@vendor/hello", + "version": "1.0.0", + "description": "A test addon made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node -p \"const addon = require('./index'); console.log(addon.hello());\"", + "install": "cmake-js install", + "postinstall": "cmake-js compile", + "configure": "cmake-js configure", + "reconfigure": "cmake-js reconfigure", + "build": "cmake-js build", + "rebuild": "cmake-js rebuild", + "clean": "cmake-js clean", + "wipe": "cmake-js clean && rm -rvf ./node_modules" + }, + "dependencies": { + "cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api", + "node-api-headers": "^1.1.0" + } +} diff --git a/tests/targets/node-api/src/hello/addon.cc b/tests/targets/node-api/src/hello/addon.cc new file mode 100644 index 00000000..6ed16d48 --- /dev/null +++ b/tests/targets/node-api/src/hello/addon.cc @@ -0,0 +1,56 @@ +/** + * @file addon.cc + * @brief A quick 'hello world' Node Addon in C + * + * Please note that this example is from the NodeJS Addon + * official docs, and uses 'nullptr', which does not exist + * in 'pure' C. If you name your addon source file with an + * extension of just '.c', the compiler/generator will assume + * you are building in 'pure' C and this useage of 'nullptr' + * will cause the build to fail. + * + * To have a more 'C-like' experience building addons in C, + * we recommend using the extension '.cc' for your sources, + * because this extension does not differentiate between + * being a C file or a C++ file, unlike both '.c' and the + * various '.cpp/cxx' file extensions. +*/ + +// Required header +#if __has_include() + +#include + +napi_value vendor_addon_hello(napi_env env, napi_callback_info args) +{ + napi_value greeting; + napi_status status; + + status = napi_create_string_utf8(env, "addon.node is online!", NAPI_AUTO_LENGTH, &greeting); + if (status != napi_ok) return nullptr; + return greeting; +} + +napi_value vendor_addon_init(napi_env env, napi_value exports) +{ + napi_status status; + napi_value fn; + + // Export a chosen C function under a given Javascript key + + status = napi_create_function(env, nullptr, 0, vendor_addon_hello, nullptr, &fn); // Name of function on Javascript side... + if (status != napi_ok) return nullptr; + + status = napi_set_named_property(env, exports, "hello", fn); // Name of function on C side... + if (status != napi_ok) return nullptr; + + // The above expose the C function 'addon_hello' as a javascript function named '.hello', etc... + return exports; +} + +// Register a new addon with the intializer function defined above +NAPI_MODULE(addon, vendor_addon_init) // ( to use, initializer to use) + +#else // !__has_include() + #warning "Warning: Cannot find '' - try running 'npm -g install cmake-js'..." +#endif diff --git a/tests/targets/node-dev/.gitignore b/tests/targets/node-dev/.gitignore new file mode 100644 index 00000000..4c561b01 --- /dev/null +++ b/tests/targets/node-dev/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +install diff --git a/tests/targets/node-dev/CMakeLists.txt b/tests/targets/node-dev/CMakeLists.txt new file mode 100644 index 00000000..ccf87e11 --- /dev/null +++ b/tests/targets/node-dev/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.15) + +project(hello) + +set(CMAKEJS_USING_NODE_API FALSE) # this should have been done with '--link-level=0' on the CLI, testing this way for now + +# For CMake IDE tools support (do yarn/install first and keep your node_modules folder around) +if(NOT DEFINED CMAKE_JS_VERSION) + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/node_modules/cmake-js/share/cmake") +endif() + +include(CMakeJS) + +add_library(addon SHARED "src/hello/addon.cc") +target_link_libraries(addon PRIVATE cmake-js::node-dev) # relocatable deps resolved! +set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node") +set_target_properties(addon PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + +# For Visual Studio generators +if(MSVC) + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$: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() diff --git a/tests/targets/node-dev/README.md b/tests/targets/node-dev/README.md new file mode 100644 index 00000000..dbc35886 --- /dev/null +++ b/tests/targets/node-dev/README.md @@ -0,0 +1,7 @@ +## I am an addon made with Node API in C++. + +You can build me, export me as a Javscript module, extend my functionality with your own code ideas, connecting Javascript and C++ functionality together in one binding package. Any importing consumers can get me from '@vendor/hello' in their package.json deps... see 'hello_consumer'! + +Please see my package.json to understand how I work. + +Powered by cmake-js CLI diff --git a/tests/targets/node-dev/index.js b/tests/targets/node-dev/index.js new file mode 100644 index 00000000..4e5b1cdb --- /dev/null +++ b/tests/targets/node-dev/index.js @@ -0,0 +1,10 @@ +// This small codeblock in your root-level index.js allows others to consume +// your addon as any other NodeJS module +const platform = process.platform; +var buildDir = "/build/lib/"; + +if(platform === "win32") + buildDir = "\\build\\bin\\Release\\"; + +const hello = require(`.${buildDir}addon.node`); +module.exports = hello; diff --git a/tests/targets/node-dev/package.json b/tests/targets/node-dev/package.json new file mode 100644 index 00000000..2248627e --- /dev/null +++ b/tests/targets/node-dev/package.json @@ -0,0 +1,21 @@ +{ + "name": "@vendor/hello", + "version": "1.0.0", + "description": "A test addon made using CMakeJS.cmake", + "main": "index.js", + "license": "MIT", + "scripts": { + "start": "node -p \"const addon = require('./index'); console.log(addon.hello());\"", + "install": "cmake-js install", + "postinstall": "cmake-js compile", + "configure": "cmake-js configure", + "reconfigure": "cmake-js reconfigure", + "build": "cmake-js build", + "rebuild": "cmake-js rebuild", + "clean": "cmake-js clean", + "wipe": "cmake-js clean && rm -rvf ./node_modules" + }, + "dependencies": { + "cmake-js": "https://github.com/nathanjhood/cmake-js#cmakejs_cmake_api" + } +} diff --git a/tests/targets/node-dev/src/hello/addon.cc b/tests/targets/node-dev/src/hello/addon.cc new file mode 100644 index 00000000..ad8203e9 --- /dev/null +++ b/tests/targets/node-dev/src/hello/addon.cc @@ -0,0 +1,47 @@ +/** + * @file addon.cc + * @brief A quick 'hello world' Node Addon in C++ +*/ + +// Required header +#if __has_include() + +#include + +namespace vendor { + +void Hello(const v8::FunctionCallbackInfo& args) +{ + v8::Isolate* isolate = args.GetIsolate(); + args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "addon.node is online!").ToLocalChecked()); +} + +// void Version(const v8::FunctionCallbackInfo& args) +// { +// v8::Isolate* isolate = args.GetIsolate(); +// args.GetReturnValue().Set(NODE_VERSION); // should be a v8::Number... +// } + +// Expose the C++ function 'Hello' as a javascript function named 'hello', etc... +void Initialize(v8::Local exports) +{ + // Export a chosen C++ function under a given Javascript key + NODE_SET_METHOD(exports, + "hello", // Name of function on Javascript side... + Hello // Name of function on C++ side... + ); + + // NODE_SET_METHOD(exports, + // "version", + // Version + // ); +} + +// Register a new addon with the intializer function defined above +NODE_MODULE(addon, Initialize) // (name to use, initializer to use) + +} // namespace vendor + +#else // !__has_include() + #warning "Warning: Cannot find '' - try running 'npm -g install cmake-js'..." +#endif