diff --git a/.eslintrc.json b/.eslintrc.json index af263b4e..bdfb5bb5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,11 +1,21 @@ { - "parserOptions": { - "ecmaVersion": 2019, - "sourceType": "module" - }, - "rules": { - "semi": 2, - "no-var": 2, - "prefer-const": 2 - } -} \ No newline at end of file + "extends": ["eslint:recommended", "prettier"], + "parserOptions": { + "ecmaVersion": 2023, + "sourceType": "commonjs" + }, + "rules": {}, + "env": { + "node": true, + "es6": true, + "es2023": true + }, + "overrides": [ + { + "files": "tests/**/*.js", + "env": { + "mocha": true + } + } + ] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..c880f4dc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' + + - package-ecosystem: 'npm' + directory: '/' + schedule: + interval: 'weekly' + # Disable version updates for npm dependencies (we only want security updates) + open-pull-requests-limit: 0 diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index 7e6d77b3..97055c2a 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -2,6 +2,7 @@ name: Test on: push: + pull_request: jobs: test: @@ -13,33 +14,35 @@ jobs: matrix: include: # windows - - os: windows-2019 - arch: x64 - os: windows-2022 arch: x64 - - os: windows-2019 + - os: windows-2025 + arch: x64 + - os: windows-2022 arch: x86 + - os: windows-11-arm + arch: arm64 + node-version: 20.x # macos - - os: macos-12 - arch: x64 - # - os: macos-11 - # arch: arm64 - - os: macos-10.15 + - os: macos-13 arch: x64 + - os: macos-14 + arch: arm64 + node-version: 16.x # linux - os: ubuntu-22.04 arch: x64 - - os: ubuntu-18.04 + - os: ubuntu-24.04 arch: x64 - # linux-libc - - os: ubuntu-latest + - os: ubuntu-22.04-arm arch: arm64 - docker-arch: linux/arm64 - docker-image: node:14-buster + - os: ubuntu-24.04-arm + arch: arm64 + # linux-libc - os: ubuntu-latest arch: arm docker-arch: linux/arm/v7 - docker-image: node:14-buster + docker-image: node:14-bullseye # linux-musl - os: ubuntu-latest arch: x64 @@ -48,14 +51,14 @@ jobs: libc: musl steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - name: Use Node.js 14.x + - name: Use Node.js if: ${{ !matrix.docker-arch }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: architecture: ${{ matrix.arch }} - node-version: 14.x + node-version: ${{ matrix.node-version || '14.x' }} - name: run tests if: ${{ !matrix.docker-arch }} @@ -68,7 +71,7 @@ jobs: npm_config_build_from_source: true - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 if: matrix.docker-arch - name: run tests (in docker) uses: addnab/docker-run-action@v3 diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 00000000..1687e196 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,9 @@ +{ + "arrowParens": "always", + "bracketSpacing": true, + "printWidth": 120, + "semi": false, + "singleQuote": true, + "useTabs": true, + "endOfLine": "lf" +} diff --git a/README.md b/README.md index 4d7b9eba..b7d49524 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,10 @@ [![npm](https://img.shields.io/npm/v/cmake-js)](https://www.npmjs.com/package/cmake-js) ## About -CMake.js is a Node.js native addon build tool which works (almost) *exactly* like [node-gyp](https://github.com/TooTallNate/node-gyp), but instead of [gyp](http://en.wikipedia.org/wiki/GYP_%28software%29), it is based on [CMake](http://cmake.org) build system. It's compatible with the following runtimes: -- Node.js 14.15+ since CMake.js v7.0.0 (for older runtimes please use an earlier version of CMake.js). Newer versions can produce builds targetting older runtimes +CMake.js is a Node.js native addon build tool which works (almost) _exactly_ like [node-gyp](https://github.com/TooTallNate/node-gyp), but instead of [gyp](http://en.wikipedia.org/wiki/GYP_%28software%29), it is based on [CMake](http://cmake.org) build system. It's compatible with the following runtimes: + +- Node.js 14.15+ since CMake.js v7.0.0 (for older runtimes please use an earlier version of CMake.js). Newer versions can produce builds targeting older runtimes - [NW.js](https://github.com/nwjs/nw.js): all CMake.js based native modules are compatible with NW.js out-of-the-box, there is no [nw-gyp like magic](https://github.com/nwjs/nw.js/wiki/Using-Node-modules#3rd-party-modules-with-cc-addons) required - [Electron](https://github.com/electron/electron): out-of-the-box build support, [no post build steps required](https://github.com/electron/electron/blob/main/docs/tutorial/using-native-node-modules.md) @@ -28,20 +29,27 @@ cmake-js --help Usage: cmake-js [] [options] Commands: - install Install Node.js distribution files if needed - configure Configure CMake project - print-configure Print the configuration command - build Build the project (will configure first if required) - print-build Print the build command - clean Clean the project directory - print-clean Print the clean command - reconfigure Clean the project directory then configure the project - rebuild Clean the project directory then build the project - compile Build the project, and if build fails, try a full rebuild + cmake-js install Install Node.js distribution files if needed + cmake-js configure Configure CMake project + cmake-js print-configure Print the configuration command + cmake-js print-cmakejs-src Print the value of the CMAKE_JS_SRC variable + cmake-js print-cmakejs-include Print the value of the CMAKE_JS_INC variable + cmake-js print-cmakejs-lib Print the value of the CMAKE_JS_LIB variable + cmake-js build Build the project (will configure first if + required) + cmake-js print-build Print the build command + cmake-js clean Clean the project directory + cmake-js print-clean Print the clean command + cmake-js reconfigure Clean the project directory then configure the + project + cmake-js rebuild Clean the project directory then build the + project + cmake-js compile Build the project, and if build fails, try a + full rebuild Options: - --version Show version number [boolean] - -h, --help show this screen [boolean] + --version Show version number [boolean] + -h, --help Show help [boolean] -l, --log-level set log level (silly, verbose, info, http, warn, error), default is info [string] -d, --directory specify CMake project's directory (where CMakeLists.txt @@ -61,13 +69,13 @@ Options: -T, --target only build the specified target [string] -C, --prefer-clang use Clang compiler instead of default CMake compiler, if available (Posix) [boolean] - --cc use the specified C compiler [string] - --cxx use the specified C++ compiler [string] + --cc use the specified C compiler [string] + --cxx use the specified C++ compiler [string] -r, --runtime the runtime to use [string] -v, --runtime-version the runtime version to use [string] -a, --arch the architecture to build in [string] -p, --parallel the number of threads cmake can use [number] - --CD Custom argument passed to CMake in format: + --CD Custom argument passed to CMake in format: -D [string] -i, --silent Prevents CMake.js to print to the stdio [boolean] -O, --out Specify the output directory to compile to, default is @@ -78,13 +86,13 @@ Options: - [CMake](http://www.cmake.org/download/) - A proper C/C++ compiler toolchain of the given platform - - **Windows**: - - [Visual C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/). If you installed nodejs with the installer, you can install these when prompted. - - An alternate way is to install the [Chocolatey package manager](https://chocolatey.org/install), and run `choco install visualstudio2017-workload-vctools` in an Administrator Powershell - - If you have multiple versions installed, you can select a specific version with `npm config set msvs_version 2017` (Note: this will also affect `node-gyp`) - - **Unix/Posix**: - - Clang or GCC - - Ninja or Make (Ninja will be picked if both present) + - **Windows**: + - [Visual C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/). If you installed nodejs with the installer, you can install these when prompted. + - An alternate way is to install the [Chocolatey package manager](https://chocolatey.org/install), and run `choco install visualstudio2017-workload-vctools` in an Administrator Powershell + - If you have multiple versions installed, you can select a specific version with `npm config set msvs_version 2017` (Note: this will also affect `node-gyp`) + - **Unix/Posix**: + - Clang or GCC + - Ninja or Make (Ninja will be picked if both present) ## Usage @@ -92,27 +100,22 @@ Options: It is advised to use Node-API for new projects instead of NAN. It provides ABI stability making usage simpler and reducing maintainance. -In a nutshell. *(For more complete documentation please see [the first tutorial](https://github.com/unbornchikken/cmake-js/wiki/TUTORIAL-01-Creating-a-native-module-by-using-CMake.js-and-NAN).)* - - Install cmake-js for your module `npm install --save cmake-js` - Put a CMakeLists.txt file into your module root with this minimal required content: ```cmake -cmake_minimum_required(VERSION 3.15) -cmake_policy(SET CMP0091 NEW) -cmake_policy(SET CMP0042 NEW) - -project (your-addon-name-here) +cmake_minimum_required(VERSION 3.15...3.31) +project(your-addon-name-here) -add_definitions(-DNAPI_VERSION=4) - -include_directories(${CMAKE_JS_INC}) +add_compile_definitions(-DNAPI_VERSION=4) file(GLOB SOURCE_FILES "your-source files-location-here") add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC}) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") -target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_JS_INC}) +target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_JS_LIB}) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET) # Generate node.lib @@ -128,6 +131,14 @@ endif() } ``` +- Add the following into your package.json, using the same NAPI_VERSION value you provided to cmake + +```json +"binary": { + "napi_versions": [7] + }, +``` + #### Commandline With cmake-js installed as a depdendency or devDependency of your module, you can access run commands directly with: @@ -213,6 +224,7 @@ You can add custom CMake options by beginning option name with `CD`. #### Example In command prompt: + ``` cmake-js compile --CDFOO="bar" ``` @@ -243,27 +255,26 @@ You can configure runtimes for compiling target for all depending CMake.js modul ```json { - "name": "ta-taram-taram", - "description": "pa-param-pam-pam", - "version": "1.0.0", - "main": "app.js", - "cmake-js": { - "runtime": "node", - "runtimeVersion": "0.12.0", - "arch": "ia32" - } + "name": "ta-taram-taram", + "description": "pa-param-pam-pam", + "version": "1.0.0", + "main": "app.js", + "cmake-js": { + "runtime": "node", + "runtimeVersion": "0.12.0", + "arch": "ia32" + } } ``` Available settings: - **runtime**: application's target runtime, possible values are: - - `node`: Node.js - - `nw`: nw.js - - `electron`: Electron + - `node`: Node.js + - `nw`: nw.js + - `electron`: Electron - **runtimeVersion**: version of the application's target runtime, for example: `0.12.1` -- **arch**: architecture of application's target runtime (eg: `x64`, `ia32`, `arm64`, `arm`). *Notice: on non-Windows systems the C++ toolset's architecture's gonna be used despite this setting. If you don't specify this on Windows, then architecture of the main node runtime is gonna be used, so you have to choose a matching nw.js runtime.* - +- **arch**: architecture of application's target runtime (eg: `x64`, `ia32`, `arm64`, `arm`). _Notice: on non-Windows systems the C++ toolset's architecture's gonna be used despite this setting. If you don't specify this on Windows, then architecture of the main node runtime is gonna be used, so you have to choose a matching nw.js runtime._ #### Node-API and `node-addon-api` @@ -272,13 +283,14 @@ Available settings: which was previously known as N-API, supplies a set of C APIs that allow to compilation and loading of native modules by different versions of Node.js that support Node-API which includes -all versions of Node.js v10.x and later. +all versions of Node.js v10.x and later. To compile a native module that uses only the [plain `C` Node-API calls](https://nodejs.org/api/n-api.html#n_api_node_api), follow the directions for plain `node` native modules. You must also add the following lines to your CMakeLists.txt, to allow for building on windows + ``` if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET) # Generate node.lib @@ -295,6 +307,13 @@ you need to make your package depend on it with: cmake-js will then add it to the include search path automatically +You should add the following to your package.json, with the correct version number, so that cmake-js knows the module is node-api and that it can skip downloading the nodejs headers + +```json +"binary": { + "napi_versions": [7] + }, +``` #### Electron @@ -317,18 +336,18 @@ To make compatible your NW.js application with any NAN CMake.js based modules, w ```json { - "cmake-js": { - "runtime": "nw", - "runtimeVersion": "nw.js-version-here", - "arch": "whatever-setting-is-appropriate-for-your-application's-windows-build" - } + "cmake-js": { + "runtime": "nw", + "runtimeVersion": "nw.js-version-here", + "arch": "whatever-setting-is-appropriate-for-your-application's-windows-build" + } } ``` -That's it. There is nothing else to do either on the application's or on the module's side, CMake.js modules are compatible with NW.js out-of-the-box. For more complete documentation please see [the third tutorial](https://github.com/unbornchikken/cmake-js/wiki/TUTORIAL-03-Using-CMake.js-based-native-modules-with-nw.js). - +That's it. There is nothing else to do either on the application's or on the module's side, CMake.js modules are compatible with NW.js out-of-the-box. #### Heroku + [Heroku](https://heroku.com) uses the concept of a [buildpack](https://devcenter.heroku.com/articles/buildpacks) to define how an application should be prepared to run in a [dyno](https://devcenter.heroku.com/articles/dynos). The typical buildpack for note-based applications, @@ -339,30 +358,37 @@ but not [CMake](http://cmake.org). The least "painful" way of addressing this is to use heroku's multipack facility: - Set the applications' buildpack to -[https://github.com/heroku/heroku-buildpack-multi.git](https://github.com/heroku/heroku-buildpack-multi.git) + [https://github.com/heroku/heroku-buildpack-multi.git](https://github.com/heroku/heroku-buildpack-multi.git) - In the root directory of the application, -create a file called `.buildpacks` with these two lines: + create a file called `.buildpacks` with these two lines: - https://github.com/brave/heroku-cmake-buildpack.git - https://github.com/heroku/heroku-buildpack-nodejs.git + https://github.com/brave/heroku-cmake-buildpack.git + https://github.com/heroku/heroku-buildpack-nodejs.git - Deploy the application to have the changes take effect The `heroku-buildpack-multi` will run each buildpack in order allowing the node application to reference CMake in the Heroku build environment. +## Using external C/C++ libraries -## Tutorials +Because you are using CMake, there are many ways to load libraries in your CMakeLists.txt. +Various places on the internet and in the CMake docs will suggest various approaches you can take. Common ones are: +* [conan](https://vcpkg.io/) (This may not work properly currently, we hope to improve support in a future release) +* [vcpkg](https://conan.io/) (This may not work properly currently, we hope to improve support in a future release) +* [hunter](https://github.com/cpp-pm/hunter) +* [CMake ExternalProject](https://cmake.org/cmake/help/latest/module/ExternalProject.html) +* If on linux, using system libraries from the system package-manager +* Importing as a git submodule -- [TUTORIAL 01 Creating a native module by using CMake.js and NAN](https://github.com/unbornchikken/cmake-js/wiki/TUTORIAL-01-Creating-a-native-module-by-using-CMake.js-and-NAN) -- [TUTORIAL 02 Creating CMake.js based native addons with Qt Creator](https://github.com/unbornchikken/cmake-js/wiki/TUTORIAL-02-Creating-CMake.js-based-native-addons-with-QT-Creator) -- [TUTORIAL 03 Using CMake.js based native modules with NW.js](https://github.com/unbornchikken/cmake-js/wiki/TUTORIAL-03-Using-CMake.js-based-native-modules-with-nw.js) -- [TUTORIAL 04 Creating CMake.js based native modules with Boost dependency](https://github.com/unbornchikken/cmake-js/wiki/TUTORIAL-04-Creating-CMake.js-based-native-modules-with-Boost-dependency) +We aim to be agnostic about how to use CMake, so it should be possible to use whatever approach you desire. ## Real examples -* [@julusian/jpeg-turbo](https://github.com/julusian/node-jpeg-turbo) - A Node-API wrapping around libjpeg-turbo. cmake-js was a good fit here, as libjpeg-turbo provides cmake files that can be used, and would be hard to replicate correctly in node-gyp +- [@julusian/jpeg-turbo](https://github.com/julusian/node-jpeg-turbo) - A Node-API wrapping around libjpeg-turbo. cmake-js was a good fit here, as libjpeg-turbo provides cmake files that can be used, and would be hard to replicate correctly in node-gyp +- [node-datachannel](https://github.com/murat-dogan/node-datachannel) - Easy to use WebRTC data channels and media transport +- [aws-iot-device-sdk-v2](https://github.com/aws/aws-iot-device-sdk-js-v2) AWS IoT Device SDK for JavaScript v2 Open a PR to add your own project here. diff --git a/bin/cmake-js b/bin/cmake-js index aecf12f3..fdea4c5c 100755 --- a/bin/cmake-js +++ b/bin/cmake-js @@ -1,311 +1,343 @@ #!/usr/bin/env node -"use strict"; +'use strict' -const log = require("npmlog"); -const BuildSystem = require("../").BuildSystem; -const util = require("util"); -const version = require("../package").version; -const logLevels = ["silly", "verbose", "info", "http", "warn", "error"]; +const log = require('npmlog') +const BuildSystem = require('../').BuildSystem +const util = require('util') +const version = require('../package').version +const logLevels = ['silly', 'verbose', 'info', 'http', 'warn', 'error'] -const npmConfigData = require("rc")("npm"); +const npmConfigData = require('rc')('npm') for (const [key, value] of Object.entries(npmConfigData)) { - if (key.startsWith("cmake_js_")) { - const option = key.substr(9); - if (option.length === 1) { - process.argv.push("-" + option); - } - else { - process.argv.push("--" + option); - } - if (value) { - process.argv.push(value); - } - } + if (key.startsWith('cmake_js_')) { + const option = key.substr(9) + if (option.length === 1) { + process.argv.push('-' + option) + } else { + process.argv.push('--' + option) + } + if (value) { + process.argv.push(value) + } + } } -console.log(process.argv); - -const yargs = require("yargs") - .usage("CMake.js " + version + "\n\nUsage: $0 [] [options]") - .version(version) - .command("install", "Install Node.js distribution files if needed") - .command("configure", "Configure CMake project") - .command("print-configure", "Print the configuration command") - .command("build", "Build the project (will configure first if required)") - .command("print-build", "Print the build command") - .command("clean", "Clean the project directory") - .command("print-clean", "Print the clean command") - .command("reconfigure", "Clean the project directory then configure the project") - .command("rebuild", "Clean the project directory then build the project") - .command("compile", "Build the project, and if build fails, try a full rebuild") - .options({ - h: { - alias: "help", - demand: false, - describe: "show this screen", - type: "boolean" - }, - l: { - alias: "log-level", - demand: false, - describe: "set log level (" + logLevels.join(", ") + "), default is info", - type: "string" - }, - d: { - alias: "directory", - demand: false, - describe: "specify CMake project's directory (where CMakeLists.txt located)", - type: "string" - }, - D: { - alias: "debug", - demand: false, - describe: "build debug configuration", - type: "boolean" - }, - B: { - alias: "config", - demand: false, - describe: "specify build configuration (Debug, RelWithDebInfo, Release), will ignore '--debug' if specified", - type: "string" - }, - c: { - alias: "cmake-path", - demand: false, - describe: "path of CMake executable", - type: "string" - }, - m: { - alias: "prefer-make", - demand: false, - describe: "use Unix Makefiles even if Ninja is available (Posix)", - type: "boolean" - }, - x: { - alias: "prefer-xcode", - demand: false, - describe: "use Xcode instead of Unix Makefiles", - type: "boolean" - }, - g: { - alias: "prefer-gnu", - demand: false, - describe: "use GNU compiler instead of default CMake compiler, if available (Posix)", - type: "boolean" - }, - G: { - alias: "generator", - demand: false, - describe: "use specified generator", - type: "string" - }, - t: { - alias: "toolset", - demand: false, - describe: "use specified toolset", - type: "string" - }, - A: { - alias: "platform", - demand: false, - describe: "use specified platform name", - type: "string" - }, - T: { - alias: "target", - demand: false, - describe: "only build the specified target", - type: "string" - }, - C: { - alias: "prefer-clang", - demand: false, - describe: "use Clang compiler instead of default CMake compiler, if available (Posix)", - type: "boolean" - }, - cc: { - demand: false, - describe: "use the specified C compiler", - type: "string" - }, - cxx: { - demand: false, - describe: "use the specified C++ compiler", - type: "string" - }, - r: { - alias: "runtime", - demand: false, - describe: "the runtime to use", - type: "string" - }, - v: { - alias: "runtime-version", - demand: false, - describe: "the runtime version to use", - type: "string" - }, - a: { - alias: "arch", - demand: false, - describe: "the architecture to build in", - type: "string" - }, - p: { - alias: "parallel", - demand: false, - describe: "the number of threads cmake can use", - type: "number" - }, - "CD": { - demand: false, - describe: "Custom argument passed to CMake in format: -D", - type: "string" - }, - i: { - alias: "silent", - describe: "Prevents CMake.js to print to the stdio", - type: "boolean" - }, - O: { - alias: "out", - describe: "Specify the output directory to compile to, default is projectRoot/build", - type: "string" - } - }); -const argv = yargs.argv; +const yargs = require('yargs') + .usage('CMake.js ' + version + '\n\nUsage: $0 [] [options]') + .version(version) + .command('install', 'Install Node.js distribution files if needed') + .command('configure', 'Configure CMake project') + .command('print-configure', 'Print the configuration command') + .command('print-cmakejs-src', 'Print the value of the CMAKE_JS_SRC variable') + .command('print-cmakejs-include', 'Print the value of the CMAKE_JS_INC variable') + .command('print-cmakejs-lib', 'Print the value of the CMAKE_JS_LIB variable') + .command('build', 'Build the project (will configure first if required)') + .command('print-build', 'Print the build command') + .command('clean', 'Clean the project directory') + .command('print-clean', 'Print the clean command') + .command('reconfigure', 'Clean the project directory then configure the project') + .command('rebuild', 'Clean the project directory then build the project') + .command('compile', 'Build the project, and if build fails, try a full rebuild') + .options({ + h: { + alias: 'help', + demand: false, + describe: 'show this screen', + type: 'boolean', + }, + l: { + alias: 'log-level', + demand: false, + describe: 'set log level (' + logLevels.join(', ') + '), default is info', + type: 'string', + }, + d: { + alias: 'directory', + demand: false, + describe: "specify CMake project's directory (where CMakeLists.txt located)", + type: 'string', + }, + D: { + alias: 'debug', + demand: false, + describe: 'build debug configuration', + type: 'boolean', + }, + B: { + alias: 'config', + demand: false, + describe: "specify build configuration (Debug, RelWithDebInfo, Release), will ignore '--debug' if specified", + type: 'string', + }, + c: { + alias: 'cmake-path', + demand: false, + describe: 'path of CMake executable', + type: 'string', + }, + m: { + alias: 'prefer-make', + demand: false, + describe: 'use Unix Makefiles even if Ninja is available (Posix)', + type: 'boolean', + }, + x: { + alias: 'prefer-xcode', + demand: false, + describe: 'use Xcode instead of Unix Makefiles', + type: 'boolean', + }, + g: { + alias: 'prefer-gnu', + demand: false, + describe: 'use GNU compiler instead of default CMake compiler, if available (Posix)', + type: 'boolean', + }, + G: { + alias: 'generator', + demand: false, + describe: 'use specified generator', + type: 'string', + }, + t: { + alias: 'toolset', + demand: false, + describe: 'use specified toolset', + type: 'string', + }, + A: { + alias: 'platform', + demand: false, + describe: 'use specified platform name', + type: 'string', + }, + T: { + alias: 'target', + demand: false, + describe: 'only build the specified target', + type: 'string', + }, + C: { + alias: 'prefer-clang', + demand: false, + describe: 'use Clang compiler instead of default CMake compiler, if available (Posix)', + type: 'boolean', + }, + cc: { + demand: false, + describe: 'use the specified C compiler', + type: 'string', + }, + cxx: { + demand: false, + describe: 'use the specified C++ compiler', + type: 'string', + }, + r: { + alias: 'runtime', + demand: false, + describe: 'the runtime to use', + type: 'string', + }, + v: { + alias: 'runtime-version', + demand: false, + describe: 'the runtime version to use', + type: 'string', + }, + a: { + alias: 'arch', + demand: false, + describe: 'the architecture to build in', + type: 'string', + }, + p: { + alias: 'parallel', + demand: false, + describe: 'the number of threads cmake can use', + type: 'number', + }, + CD: { + demand: false, + describe: 'Custom argument passed to CMake in format: -D', + type: 'string', + }, + i: { + alias: 'silent', + describe: 'Prevents CMake.js to print to the stdio', + type: 'boolean', + }, + O: { + alias: 'out', + describe: 'Specify the output directory to compile to, default is projectRoot/build', + type: 'string', + }, + }) +const argv = yargs.argv // If help, then print and exit: if (argv.h) { - console.info(yargs.help()); - process.exit(0); + console.info(yargs.help()) + process.exit(0) } // Setup log level: if (argv.l && logLevels.includes(argv.l)) { - log.level = argv.l; - log.resume(); + log.level = argv.l + log.resume() } -log.silly("CON", "argv:"); -log.silly("CON", util.inspect(argv)); +log.silly('CON', 'argv:') +log.silly('CON', util.inspect(argv)) -log.verbose("CON", "Parsing arguments"); +log.verbose('CON', 'Parsing arguments') // Extract custom cMake options -const customOptions = {}; -for (const [key, value] of Object.entries(argv)) { - if (value && key.startsWith("CD")) { - customOptions[key.substr(2)] = value; - } +const customOptions = {} +for (const arg of process.argv) { + if (arg.startsWith('--CD')) { + const separator = arg.indexOf('=') + if (separator < 5) continue + const key = arg.substring(4, separator) + const value = arg.substring(separator + 1) + if (!value) continue + customOptions[key] = value + } } const options = { - directory: argv.directory || null, - debug: argv.debug, - cmakePath: argv.c || null, - generator: argv.G, - toolset: argv.t, - platform: argv.A, - target: argv.T, - preferMake: argv.m, - preferXcode: argv.x, - preferGnu: argv.g, - preferClang: argv.C, - cCompilerPath: argv.cc, - cppCompilerPath: argv.cxx, - runtime: argv.r, - runtimeVersion: argv.v, - arch: argv.a, - cMakeOptions: customOptions, - silent: argv.i, - out: argv.O, - config: argv.B, - parallel: argv.p -}; + directory: argv.directory || null, + debug: argv.debug, + cmakePath: argv.c || null, + generator: argv.G, + toolset: argv.t, + platform: argv.A, + target: argv.T, + preferMake: argv.m, + preferXcode: argv.x, + preferGnu: argv.g, + preferClang: argv.C, + cCompilerPath: argv.cc, + cppCompilerPath: argv.cxx, + runtime: argv.r, + runtimeVersion: argv.v, + arch: argv.a, + cMakeOptions: customOptions, + silent: argv.i, + out: argv.O, + config: argv.B, + parallel: argv.p, + extraCMakeArgs: argv._.slice(1), +} -log.verbose("CON", "options:"); -log.verbose("CON", util.inspect(options)); +log.verbose('CON', 'options:') +log.verbose('CON', util.inspect(options)) -const command = argv._[0] || "build"; +const command = argv._[0] || 'build' -log.verbose("CON", "Running command: " + command); +log.verbose('CON', 'Running command: ' + command) -const buildSystem = new BuildSystem(options); +const buildSystem = new BuildSystem(options) function ifCommand(c, f) { - if (c === command) { - f(); - return true; - } - return false; + if (c === command) { + f() + return true + } + return false } function exitOnError(promise) { - promise.catch(function () { - process.exit(1); - }); + promise.catch(function () { + process.exit(1) + }) } function install() { - exitOnError(buildSystem.install()); + exitOnError(buildSystem.install()) } function configure() { - exitOnError(buildSystem.configure()); + exitOnError(buildSystem.configure()) } function printConfigure() { - exitOnError(buildSystem.getConfigureCommand() - .then(function (command) { - console.info(command); - })); + exitOnError( + buildSystem.getConfigureCommand().then(function (command) { + console.info(command) + }), + ) +} +function printCmakeJsLib() { + exitOnError( + buildSystem.getCmakeJsLibString().then(function (command) { + console.info(command) + }), + ) +} +function printCmakeJsInclude() { + exitOnError( + buildSystem.getCmakeJsIncludeString().then(function (command) { + console.info(command) + }), + ) +} +function printCmakeJsSrc() { + exitOnError( + buildSystem.getCmakeJsSrcString().then(function (command) { + console.info(command) + }), + ) } function build() { - exitOnError(buildSystem.build()); + exitOnError(buildSystem.build()) } function printBuild() { - exitOnError(buildSystem.getBuildCommand() - .then(function (command) { - console.info(command); - })); + exitOnError( + buildSystem.getBuildCommand().then(function (command) { + console.info(command) + }), + ) } function clean() { - exitOnError(buildSystem.clean()); + exitOnError(buildSystem.clean()) } function printClean() { - exitOnError(buildSystem.getCleanCommand() - .then(function (command) { - console.info(command); - })); + exitOnError( + buildSystem.getCleanCommand().then(function (command) { + console.info(command) + }), + ) } function reconfigure() { - exitOnError(buildSystem.reconfigure()); + exitOnError(buildSystem.reconfigure()) } function rebuild() { - exitOnError(buildSystem.rebuild()); + exitOnError(buildSystem.rebuild()) } function compile() { - exitOnError(buildSystem.compile()); + exitOnError(buildSystem.compile()) } -let done = ifCommand("install", install); -done = done || ifCommand("configure", configure); -done = done || ifCommand("print-configure", printConfigure); -done = done || ifCommand("build", build); -done = done || ifCommand("print-build", printBuild); -done = done || ifCommand("clean", clean); -done = done || ifCommand("print-clean", printClean); -done = done || ifCommand("reconfigure", reconfigure); -done = done || ifCommand("rebuild", rebuild); -done = done || ifCommand("compile", compile); +let done = ifCommand('install', install) +done = done || ifCommand('configure', configure) +done = done || ifCommand('print-configure', printConfigure) +done = done || ifCommand('print-cmakejs-src', printCmakeJsSrc) +done = done || ifCommand('print-cmakejs-include', printCmakeJsInclude) +done = done || ifCommand('print-cmakejs-lib', printCmakeJsLib) +done = done || ifCommand('build', build) +done = done || ifCommand('print-build', printBuild) +done = done || ifCommand('clean', clean) +done = done || ifCommand('print-clean', printClean) +done = done || ifCommand('reconfigure', reconfigure) +done = done || ifCommand('rebuild', rebuild) +done = done || ifCommand('compile', compile) if (!done) { - if (command) { - log.error("COM", "Unknown command: " + command); - process.exit(1); - } - else { - build(); - } + if (command) { + log.error('COM', 'Unknown command: ' + command) + process.exit(1) + } else { + build() + } } diff --git a/changelog.md b/changelog.md index a8c1a41c..0675981d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,238 +1,230 @@ -v7.0.0 - 08/10/22 -========== - -- update dependencies -- replace some dependencies with modern language features -- follow node-gyp behaviour for visual-studio version detection and selection -- automatically locate node-addon-api and add to include paths -- avoid downloads when building for node-api -- encourage use of MT builds with MSVC, rather than MD - -v6.3.1 - 05/06/22 -========== - -- add missing bluebird dependency -- fix platform detection for visual studio 2019 and newer -- fix platform detection for macos - -v6.3.0 - 26/11/21 -========== - -- add offline mode: https://github.com/cmake-js/cmake-js/pull/260 -- handle missing buildSystem.log: https://github.com/cmake-js/cmake-js/pull/259 -- Add config flag: https://github.com/cmake-js/cmake-js/pull/251 -- Remove escaped quotes from windows registry queries: https://github.com/cmake-js/cmake-js/pull/250 - -v6.2.1 - 20/07/21 -========== - -- EOL hotfix (Thx Windows!) - -v6.2.0 - 19/07/21 -========== - -- various fixes - -v6.1.0 - 27/02/20 -========== - -- Add support for "-A/--platform" option to make target platform selectable for Visual Studio 2019 generator: https://github.com/cmake-js/cmake-js/pull/201 - -v6.0.0 - 30/09/19 -================= - -- Dropped compatibility of old Node.js runtimes (<10.0.0) -- --cc and --cxx flags for overriding compiler detection: https://github.com/cmake-js/cmake-js/pull/191 - -v5.3.2 - 21/08/19 -================= - -- Visual Studio detection fixes - -v5.3.1 - 18/07/19 -================= - -- VS 2019 Support fix: https://github.com/cmake-js/cmake-js/pull/187 - -v5.3.0 - 09/07/19 -================= - -- VS 2019 Support: https://github.com/cmake-js/cmake-js/pull/178/, https://github.com/cmake-js/cmake-js/pull/184/ - -v5.2.1 - 10/04/19 -================= - -- Win delay load hook: https://github.com/cmake-js/cmake-js/pull/165/ - -v5.1.1 - 02/04/19 -================= - -- CMake 3.14 support fixed - https://github.com/cmake-js/cmake-js/pull/161 - -v5.1.0 - 14/02/19 -================= - -- CMake 3.14 support - https://github.com/cmake-js/cmake-js/pull/159 - -v5.0.1 - 24/01/19 -================= - -- Linux line ending hotfix (I hate Windows!) - -v5.0.0 - 24/01/19 -================= - -- [semver major] Add case sensitive NPM config integration https://github.com/cmake-js/cmake-js/pull/151 -- better npm config integration, all CMake.js commandline argument could be set by using npm config: https://github.com/cmake-js/cmake-js#npm-config-integration -- support for Electron v4+ https://github.com/cmake-js/cmake-js/pull/152 - -v4.0.1 - 03/10/18 -================= - -- log argument hotfix https://github.com/cmake-js/cmake-js/pull/145 - -v4.0.0 - 14/09/18 -================= - -BREAKING CHANGES: - -- -s/--std (along with -o/--prec11 option removed, you have to specify compiler standard in CMakeLists.txt files https://github.com/cmake-js/cmake-js/issues/72 -- Implicit -w compiler flag doesn't get added on OSX https://github.com/cmake-js/cmake-js/pull/133 - -v3.7.3 - 16/05/18 -================= - -- npm config hotfix https://github.com/cmake-js/cmake-js/pull/123 - -v3.7.2 - 16/05/18 -================= - -- do not use, breaks ES5 compatibility - -v3.7.1 - 07/05/18 -================= - -- Linux line ending hotfix (wat) - -v3.7.0 - 07/05/18 -================= - -- PR: replace unzip with unzipper https://github.com/cmake-js/cmake-js/pull/120 -- PR: replace npmconf with rc https://github.com/cmake-js/cmake-js/pull/119 -- PR: update to modern fs-extras https://github.com/cmake-js/cmake-js/pull/118 -- PR: Adds toolset command line flag https://github.com/cmake-js/cmake-js/pull/115 - -v3.6.2 - 17/02/18 -================= - -- use https distribution download urls -- custom cmake options made case sensitive - -v3.6.1 - 11/01/18 -================= - -- Detect 2017 Windows Build Tools - -v3.6.0 - 11/27/17 -================= - -- "T" option for building specified target: https://github.com/cmake-js/cmake-js/pull/98 - -v3.5.0 - 06/21/17 -================= - -- Added Visual Studio 2017 compatibility: https://github.com/cmake-js/cmake-js/pull/78 - -v3.4.1 - 02/4/17 -================= - -- FIX: test output instead of guessing by platform: https://github.com/cmake-js/cmake-js/pull/77 - -v3.4.0 - 01/12/17 -================= - -- "G" option to set custom generators: https://github.com/cmake-js/cmake-js/pull/64 - -v3.3.1 - 09/13/16 -================= - -- fix of default parameters: https://github.com/cmake-js/cmake-js/pull/57 - -v3.3.0 - 09/02/16 -================= - -- silent option (https://github.com/cmake-js/cmake-js/pull/54) -- out option (https://github.com/cmake-js/cmake-js/pull/53) - -v3.2.3 - 08/17/16 -================= - -- Line endings - -v3.2.2 - 12/08/16 -================= - -- Multi directory support for Windows/MSVC build - -v3.2.1 - 25/04/16 -================= - -- Linux line ending hotfix - -v3.2.0 - 25/04/16 -================= - -- Added NW.js 0.13+ compatibility -- Node v0.10.x support fixed (https://github.com/cmake-js/cmake-js/pull/45, https://github.com/cmake-js/cmake-js/issues/50) -- CMAKE_JS_VERSION defined (https://github.com/cmake-js/cmake-js/issues/48) - -v3.1.2 - 03/02/16 -================= - -- Fixed cmake-js binary ES5 compatibility. - -v3.1.1 - 03/02/16 -================= - -- Fixed line endings - -v3.1.0 - 03/02/16 -================= - -- Custom CMake parameter support (https://github.com/gerhardberger) - -v3.0.0 - 20/11/15 -================= - -- Visual C++ Build Tools support -- std option introduced -- better unit test coverage - -v2.1.0 - 29/10/15 -================= - -- explicit options for use GNU or Clang compiler instead of CMake's default (see --help for details) - -v2.0.2 - 22/10/15 -================= - -- Fix: print-* commands report "undefined" - -v2.0.0 - 17/10/15 -================= - -- Fix: distribution files only gets downloaded if needed (4.0.0+) -- option to generate Xcode project (-x, --prefer-xcode) - by https://github.com/javedulu -- compile command for fast module compilation during npm updates (instead of rebuild) -- codebase switched to ECMAScript 2015 - -v1.1.1 - 06/10/15 -================= - -- Hotfix for build NW.js correctly. - -v1.1.0 - 05/10/15 -================= - -- Node.js 4.0.0+ support -- Downloads the small, header only tarball for Node.js 4+ +# v7.3.1 - 17/04/25 + +- fix(windows): support windows arm64 (Thanks to @jaycex) +- fix(windows): support newer visual studio installations + +# v7.3.0 - 15/01/24 + +- feat(windows): replace custom libnode.def generation with version from node-api-headers +- fix: support for vs2015 with nodejs 18 and older (#317) +- fix(windows): always remove Path if PATH is also defined (#319) +- fix: Cmake arguments got converted to numbers (#314) +- fix: update node-api-headers +- chore: update dependencies + +# v7.2.1 - 14/02/23 + +- fix: support Windows11SDK + +# v7.2.0 - 12/02/23 + +- fix: `-DCMAKE_JS_VERSION=undefined` (#298) +- fix: Only add build type to `CMAKE_LIBRARY_OUTPUT_DIRECTORY` if needed (#299) +- feat: Forward extra arguments to CMake commands (#297) + +# v7.1.1 - 15/12/22 + +- fix build errors on windows + +# v7.1.0 - 14/12/22 + +- add commands for retrieving cmake-js include and lib directories +- fix win delay hook issues with electron +- fix missing js_native_api_symbols in windows node.lib + +# v7.0.0 - 08/10/22 + +- update dependencies +- replace some dependencies with modern language features +- follow node-gyp behaviour for visual-studio version detection and selection +- automatically locate node-addon-api and add to include paths +- avoid downloads when building for node-api +- encourage use of MT builds with MSVC, rather than MD + +# v6.3.1 - 05/06/22 + +- add missing bluebird dependency +- fix platform detection for visual studio 2019 and newer +- fix platform detection for macos + +# v6.3.0 - 26/11/21 + +- add offline mode: https://github.com/cmake-js/cmake-js/pull/260 +- handle missing buildSystem.log: https://github.com/cmake-js/cmake-js/pull/259 +- Add config flag: https://github.com/cmake-js/cmake-js/pull/251 +- Remove escaped quotes from windows registry queries: https://github.com/cmake-js/cmake-js/pull/250 + +# v6.2.1 - 20/07/21 + +- EOL hotfix (Thx Windows!) + +# v6.2.0 - 19/07/21 + +- various fixes + +# v6.1.0 - 27/02/20 + +- Add support for "-A/--platform" option to make target platform selectable for Visual Studio 2019 generator: https://github.com/cmake-js/cmake-js/pull/201 + +# v6.0.0 - 30/09/19 + +- Dropped compatibility of old Node.js runtimes (<10.0.0) +- --cc and --cxx flags for overriding compiler detection: https://github.com/cmake-js/cmake-js/pull/191 + +# v5.3.2 - 21/08/19 + +- Visual Studio detection fixes + +# v5.3.1 - 18/07/19 + +- VS 2019 Support fix: https://github.com/cmake-js/cmake-js/pull/187 + +# v5.3.0 - 09/07/19 + +- VS 2019 Support: https://github.com/cmake-js/cmake-js/pull/178/, https://github.com/cmake-js/cmake-js/pull/184/ + +# v5.2.1 - 10/04/19 + +- Win delay load hook: https://github.com/cmake-js/cmake-js/pull/165/ + +# v5.1.1 - 02/04/19 + +- CMake 3.14 support fixed - https://github.com/cmake-js/cmake-js/pull/161 + +# v5.1.0 - 14/02/19 + +- CMake 3.14 support - https://github.com/cmake-js/cmake-js/pull/159 + +# v5.0.1 - 24/01/19 + +- Linux line ending hotfix (I hate Windows!) + +# v5.0.0 - 24/01/19 + +- [semver major] Add case sensitive NPM config integration https://github.com/cmake-js/cmake-js/pull/151 +- better npm config integration, all CMake.js commandline argument could be set by using npm config: https://github.com/cmake-js/cmake-js#npm-config-integration +- support for Electron v4+ https://github.com/cmake-js/cmake-js/pull/152 + +# v4.0.1 - 03/10/18 + +- log argument hotfix https://github.com/cmake-js/cmake-js/pull/145 + +# v4.0.0 - 14/09/18 + +BREAKING CHANGES: + +- -s/--std (along with -o/--prec11 option removed, you have to specify compiler standard in CMakeLists.txt files https://github.com/cmake-js/cmake-js/issues/72 +- Implicit -w compiler flag doesn't get added on OSX https://github.com/cmake-js/cmake-js/pull/133 + +# v3.7.3 - 16/05/18 + +- npm config hotfix https://github.com/cmake-js/cmake-js/pull/123 + +# v3.7.2 - 16/05/18 + +- do not use, breaks ES5 compatibility + +# v3.7.1 - 07/05/18 + +- Linux line ending hotfix (wat) + +# v3.7.0 - 07/05/18 + +- PR: replace unzip with unzipper https://github.com/cmake-js/cmake-js/pull/120 +- PR: replace npmconf with rc https://github.com/cmake-js/cmake-js/pull/119 +- PR: update to modern fs-extras https://github.com/cmake-js/cmake-js/pull/118 +- PR: Adds toolset command line flag https://github.com/cmake-js/cmake-js/pull/115 + +# v3.6.2 - 17/02/18 + +- use https distribution download urls +- custom cmake options made case sensitive + +# v3.6.1 - 11/01/18 + +- Detect 2017 Windows Build Tools + +# v3.6.0 - 11/27/17 + +- "T" option for building specified target: https://github.com/cmake-js/cmake-js/pull/98 + +# v3.5.0 - 06/21/17 + +- Added Visual Studio 2017 compatibility: https://github.com/cmake-js/cmake-js/pull/78 + +# v3.4.1 - 02/4/17 + +- FIX: test output instead of guessing by platform: https://github.com/cmake-js/cmake-js/pull/77 + +# v3.4.0 - 01/12/17 + +- "G" option to set custom generators: https://github.com/cmake-js/cmake-js/pull/64 + +# v3.3.1 - 09/13/16 + +- fix of default parameters: https://github.com/cmake-js/cmake-js/pull/57 + +# v3.3.0 - 09/02/16 + +- silent option (https://github.com/cmake-js/cmake-js/pull/54) +- out option (https://github.com/cmake-js/cmake-js/pull/53) + +# v3.2.3 - 08/17/16 + +- Line endings + +# v3.2.2 - 12/08/16 + +- Multi directory support for Windows/MSVC build + +# v3.2.1 - 25/04/16 + +- Linux line ending hotfix + +# v3.2.0 - 25/04/16 + +- Added NW.js 0.13+ compatibility +- Node v0.10.x support fixed (https://github.com/cmake-js/cmake-js/pull/45, https://github.com/cmake-js/cmake-js/issues/50) +- CMAKE_JS_VERSION defined (https://github.com/cmake-js/cmake-js/issues/48) + +# v3.1.2 - 03/02/16 + +- Fixed cmake-js binary ES5 compatibility. + +# v3.1.1 - 03/02/16 + +- Fixed line endings + +# v3.1.0 - 03/02/16 + +- Custom CMake parameter support (https://github.com/gerhardberger) + +# v3.0.0 - 20/11/15 + +- Visual C++ Build Tools support +- std option introduced +- better unit test coverage + +# v2.1.0 - 29/10/15 + +- explicit options for use GNU or Clang compiler instead of CMake's default (see --help for details) + +# v2.0.2 - 22/10/15 + +- Fix: print-\* commands report "undefined" + +# v2.0.0 - 17/10/15 + +- Fix: distribution files only gets downloaded if needed (4.0.0+) +- option to generate Xcode project (-x, --prefer-xcode) - by https://github.com/javedulu +- compile command for fast module compilation during npm updates (instead of rebuild) +- codebase switched to ECMAScript 2015 + +# v1.1.1 - 06/10/15 + +- Hotfix for build NW.js correctly. + +# v1.1.0 - 05/10/15 + +- Node.js 4.0.0+ support +- Downloads the small, header only tarball for Node.js 4+ diff --git a/lib/appCMakeJSConfig.js b/lib/appCMakeJSConfig.js index 23f602b7..bee96119 100644 --- a/lib/appCMakeJSConfig.js +++ b/lib/appCMakeJSConfig.js @@ -1,63 +1,58 @@ -"use strict"; -const path = require("path"); -const isPlainObject = require("lodash.isplainobject"); +'use strict' +const path = require('path') function getConfig(lookPath, log) { - const pjsonPath = path.join(lookPath, "package.json"); - log.silly("CFG", "Looking for package.json in: '" + pjsonPath + "'."); - try { - const json = require(pjsonPath); - log.silly("CFG", "Loaded:\n" + JSON.stringify(json)); - if (isPlainObject(json) && isPlainObject(json["cmake-js"])) { - log.silly("CFG", "Config found."); - return json["cmake-js"]; - } - else { - log.silly("CFG", "Config not found."); - return null; - } - } - catch (e) { - log.silly("CFG", "'package.json' not found."); - return null; - } + const pjsonPath = path.join(lookPath, 'package.json') + log.silly('CFG', "Looking for package.json in: '" + pjsonPath + "'.") + try { + const json = require(pjsonPath) + log.silly('CFG', 'Loaded:\n' + JSON.stringify(json)) + if (json && json['cmake-js'] && typeof json['cmake-js'] === 'object') { + log.silly('CFG', 'Config found.') + return json['cmake-js'] + } else { + log.silly('CFG', 'Config not found.') + return null + } + } catch (e) { + log.silly('CFG', "'package.json' not found.") + return null + } } module.exports = function (projectPath, log) { - log.verbose("CFG", "Looking for application level CMake.js config in '" + projectPath + "."); - let currPath = projectPath; - let lastConfig = null; - let currConfig; - for (; ;) { - currConfig = getConfig(currPath, log); - if (currConfig) { - lastConfig = currConfig; - } - try { - log.silly("CFG", "Looking for parent path."); - const lastPath = currPath; - currPath = path.normalize(path.join(currPath, "..")); - if (lastPath === currPath) { - currPath = null; // root - } - if (currPath) { - log.silly("CFG", "Parent path: '" + currPath + "'."); - } - } - catch (e) { - log.silly("CFG", "Exception:\n" + e.stack); - break; - } - if (currPath === null) { - log.silly("CFG", "Parent path with package.json file doesn't exists. Done."); - break; - } - } - if (lastConfig) { - log.verbose("CFG", "Application level CMake.js config found:\n" + JSON.stringify(lastConfig)); - } - else { - log.verbose("CFG", "Application level CMake.js config doesn't exists."); - } - return lastConfig; -}; + log.verbose('CFG', "Looking for application level CMake.js config in '" + projectPath + '.') + let currPath = projectPath + let lastConfig = null + let currConfig + for (;;) { + currConfig = getConfig(currPath, log) + if (currConfig) { + lastConfig = currConfig + } + try { + log.silly('CFG', 'Looking for parent path.') + const lastPath = currPath + currPath = path.normalize(path.join(currPath, '..')) + if (lastPath === currPath) { + currPath = null // root + } + if (currPath) { + log.silly('CFG', "Parent path: '" + currPath + "'.") + } + } catch (e) { + log.silly('CFG', 'Exception:\n' + e.stack) + break + } + if (currPath === null) { + log.silly('CFG', "Parent path with package.json file doesn't exists. Done.") + break + } + } + if (lastConfig) { + log.verbose('CFG', 'Application level CMake.js config found:\n' + JSON.stringify(lastConfig)) + } else { + log.verbose('CFG', "Application level CMake.js config doesn't exists.") + } + return lastConfig +} diff --git a/lib/buildSystem.js b/lib/buildSystem.js index e1690df7..63c93842 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -1,130 +1,123 @@ -"use strict"; -const CMake = require("./cMake"); -const Dist = require("./dist"); -const CMLog = require("./cmLog"); -const appCMakeJSConfig = require("./appCMakeJSConfig"); -const npmConfig = require("./npmConfig"); -const path = require("path"); -const isPlainObject = require("lodash.isplainobject"); -const Toolset = require("./toolset"); +'use strict' +const CMake = require('./cMake') +const Dist = require('./dist') +const CMLog = require('./cmLog') +const appCMakeJSConfig = require('./appCMakeJSConfig') +const npmConfig = require('./npmConfig') +const path = require('path') +const Toolset = require('./toolset') function isNodeApi(log, projectRoot) { - try { - const projectPkgJson = require(path.join(projectRoot, 'package.json')) - // Make sure the property exists - return !!projectPkgJson?.binary?.napi_versions - } catch (e) { - log.silly("CFG", "'package.json' not found."); - return false - } + try { + const projectPkgJson = require(path.join(projectRoot, 'package.json')) + // Make sure the property exists + return !!projectPkgJson?.binary?.napi_versions + } catch (e) { + log.silly('CFG', "'package.json' not found.") + return false + } } -function BuildSystem(options) { - this.options = options || {}; - this.options.directory = path.resolve(this.options.directory || process.cwd()); - this.options.out = path.resolve(this.options.out || path.join(this.options.directory, "build")); - this.log = new CMLog(this.options); - this.options.isNodeApi = isNodeApi(this.log, this.options.directory) - const appConfig = appCMakeJSConfig(this.options.directory, this.log); - const npmOptions = npmConfig(this.log); - - if (isPlainObject(npmOptions) && Object.keys(npmOptions).length) { - this.options.runtimeDirectory = npmOptions["nodedir"]; - this.options.msvsVersion = npmOptions['msvs_version'] - } - if (isPlainObject(appConfig)) { - if (Object.keys(appConfig).length) { - this.log.verbose("CFG", "Applying CMake.js config from root package.json:"); - this.log.verbose("CFG", JSON.stringify(appConfig)); - // Applying applications's config, if there is no explicit runtime related options specified - this.options.runtime = this.options.runtime || appConfig.runtime; - this.options.runtimeVersion = this.options.runtimeVersion || appConfig.runtimeVersion; - this.options.arch = this.options.arch || appConfig.arch; - } - } - this.log.verbose("CFG", "Build system options:"); - this.log.verbose("CFG", JSON.stringify(this.options)); - this.cmake = new CMake(this.options); - this.dist = new Dist(this.options); - this.toolset = new Toolset(this.options); +class BuildSystem { + constructor(options) { + this.options = options || {} + this.options.directory = path.resolve(this.options.directory || process.cwd()) + this.options.out = path.resolve(this.options.out || path.join(this.options.directory, 'build')) + this.log = new CMLog(this.options) + this.options.isNodeApi = isNodeApi(this.log, this.options.directory) + const appConfig = appCMakeJSConfig(this.options.directory, this.log) + const npmOptions = npmConfig(this.log) + + if (npmOptions && typeof npmOptions === 'object' && Object.keys(npmOptions).length) { + this.options.runtimeDirectory = npmOptions['nodedir'] + this.options.msvsVersion = npmOptions['msvs_version'] + } + if (appConfig && typeof appConfig === 'object' && Object.keys(appConfig).length) { + this.log.verbose('CFG', 'Applying CMake.js config from root package.json:') + this.log.verbose('CFG', JSON.stringify(appConfig)) + // Applying applications's config, if there is no explicit runtime related options specified + this.options.runtime = this.options.runtime || appConfig.runtime + this.options.runtimeVersion = this.options.runtimeVersion || appConfig.runtimeVersion + this.options.arch = this.options.arch || appConfig.arch + } + + this.log.verbose('CFG', 'Build system options:') + this.log.verbose('CFG', JSON.stringify(this.options)) + this.cmake = new CMake(this.options) + this.dist = new Dist(this.options) + this.toolset = new Toolset(this.options) + } + async _ensureInstalled() { + try { + await this.toolset.initialize(true) + if (!this.options.isNodeApi) { + await this.dist.ensureDownloaded() + } + } catch (e) { + this._showError(e) + throw e + } + } + _showError(e) { + if (this.log === undefined) { + // handle internal errors (init failed) + console.error('OMG', e.stack) + return + } + if (this.log.level === 'verbose' || this.log.level === 'silly') { + this.log.error('OMG', e.stack) + } else { + this.log.error('OMG', e.message) + } + } + install() { + return this._ensureInstalled() + } + async _invokeCMake(method) { + try { + await this._ensureInstalled() + return await this.cmake[method]() + } catch (e) { + this._showError(e) + throw e + } + } + getConfigureCommand() { + return this._invokeCMake('getConfigureCommand') + } + getCmakeJsLibString() { + return this._invokeCMake('getCmakeJsLibString') + } + getCmakeJsIncludeString() { + return this._invokeCMake('getCmakeJsIncludeString') + } + getCmakeJsSrcString() { + return this._invokeCMake('getCmakeJsSrcString') + } + configure() { + return this._invokeCMake('configure') + } + getBuildCommand() { + return this._invokeCMake('getBuildCommand') + } + build() { + return this._invokeCMake('build') + } + getCleanCommand() { + return this._invokeCMake('getCleanCommand') + } + clean() { + return this._invokeCMake('clean') + } + reconfigure() { + return this._invokeCMake('reconfigure') + } + rebuild() { + return this._invokeCMake('rebuild') + } + compile() { + return this._invokeCMake('compile') + } } -BuildSystem.prototype._ensureInstalled = async function () { - try { - await this.toolset.initialize(true); - if (!this.options.isNodeApi) { - await this.dist.ensureDownloaded(); - } - } - catch (e) { - this._showError(e); - throw e; - } -}; - -BuildSystem.prototype._showError = function (e) { - if (this.log === undefined) { - // handle internal errors (init failed) - console.error("OMG", e.stack); - return; - } - if (this.log.level === "verbose" || this.log.level === "silly") { - this.log.error("OMG", e.stack); - } - else { - this.log.error("OMG", e.message); - } -}; - -BuildSystem.prototype.install = function () { - return this._ensureInstalled(); -}; - -BuildSystem.prototype._invokeCMake = async function (method) { - try { - await this._ensureInstalled(); - return await this.cmake[method](); - } - catch (e) { - this._showError(e); - throw e; - } -}; - -BuildSystem.prototype.getConfigureCommand = function () { - return this._invokeCMake("getConfigureCommand"); -}; - -BuildSystem.prototype.configure = function () { - return this._invokeCMake("configure"); -}; - -BuildSystem.prototype.getBuildCommand = function () { - return this._invokeCMake("getBuildCommand"); -}; - -BuildSystem.prototype.build = function () { - return this._invokeCMake("build"); -}; - -BuildSystem.prototype.getCleanCommand = function () { - return this._invokeCMake("getCleanCommand"); -}; - -BuildSystem.prototype.clean = function () { - return this._invokeCMake("clean"); -}; - -BuildSystem.prototype.reconfigure = function () { - return this._invokeCMake("reconfigure"); -}; - -BuildSystem.prototype.rebuild = function () { - return this._invokeCMake("rebuild"); -}; - -BuildSystem.prototype.compile = function () { - return this._invokeCMake("compile"); -}; - -module.exports = BuildSystem; +module.exports = BuildSystem diff --git a/lib/cMake.js b/lib/cMake.js index c73f4bce..6ec0a79a 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -1,387 +1,362 @@ -"use strict"; -const which = require("which"); -const fs = require("fs-extra"); -const path = require("path"); -const environment = require("./environment"); -const Dist = require("./dist"); -const CMLog = require("./cmLog"); -const TargetOptions = require("./targetOptions"); -const processHelpers = require("./processHelpers"); -const locateNAN = require("./locateNAN"); -const locateNodeApi = require("./locateNodeApi"); -const npmConfigData = require("rc")("npm"); -const Toolset = require("./toolset"); -const headers = require('node-api-headers'); - -function CMake(options) { - this.options = options || {}; - this.log = new CMLog(this.options); - this.dist = new Dist(this.options); - this.projectRoot = path.resolve(this.options.directory || process.cwd()); - this.workDir = path.resolve(this.options.out || path.join(this.projectRoot, "build")); - this.config = this.options.config || (this.options.debug ? "Debug" : "Release"); - this.buildDir = path.join(this.workDir, this.config); - this._isAvailable = null; - this.targetOptions = new TargetOptions(this.options); - this.toolset = new Toolset(this.options); - this.cMakeOptions = this.options.cMakeOptions || {}; - this.silent = !!options.silent; -} +'use strict' +const which = require('which') +const fs = require('fs-extra') +const path = require('path') +const environment = require('./environment') +const Dist = require('./dist') +const CMLog = require('./cmLog') +const TargetOptions = require('./targetOptions') +const processHelpers = require('./processHelpers') +const locateNAN = require('./locateNAN') +const locateNodeApi = require('./locateNodeApi') +const npmConfigData = require('rc')('npm') +const Toolset = require('./toolset') +const headers = require('node-api-headers') + +class CMake { + get path() { + return this.options.cmakePath || 'cmake' + } + get isAvailable() { + if (this._isAvailable === null) { + this._isAvailable = CMake.isAvailable(this.options) + } + return this._isAvailable + } + + constructor(options) { + this.options = options || {} + this.log = new CMLog(this.options) + this.dist = new Dist(this.options) + this.projectRoot = path.resolve(this.options.directory || process.cwd()) + this.workDir = path.resolve(this.options.out || path.join(this.projectRoot, 'build')) + this.config = this.options.config || (this.options.debug ? 'Debug' : 'Release') + this.buildDir = path.join(this.workDir, this.config) + this._isAvailable = null + this.targetOptions = new TargetOptions(this.options) + this.toolset = new Toolset(this.options) + this.cMakeOptions = this.options.cMakeOptions || {} + this.extraCMakeArgs = this.options.extraCMakeArgs || [] + this.silent = !!options.silent + } + static isAvailable(options) { + options = options || {} + try { + if (options.cmakePath) { + const stat = fs.lstatSync(options.cmakePath) + return !stat.isDirectory() + } else { + which.sync('cmake') + return true + } + } catch (e) { + // Ignore + } + return false + } + static async getGenerators(options, log) { + const arch = ' [arch]' + options = options || {} + const gens = [] + if (CMake.isAvailable(options)) { + // try parsing machine-readable capabilities (available since CMake 3.7) + try { + const stdout = await processHelpers.execFile([options.cmakePath || 'cmake', '-E', 'capabilities']) + const capabilities = JSON.parse(stdout) + return capabilities.generators.map((x) => x.name) + } catch (error) { + if (log) { + log.verbose('TOOL', 'Failed to query CMake capabilities (CMake is probably older than 3.7)') + } + } + + // fall back to parsing help text + const stdout = await processHelpers.execFile([options.cmakePath || 'cmake', '--help']) + const hasCr = stdout.includes('\r\n') + const output = hasCr ? stdout.split('\r\n') : stdout.split('\n') + let on = false + output.forEach(function (line, i) { + if (on) { + const parts = line.split('=') + if ( + (parts.length === 2 && parts[0].trim()) || + (parts.length === 1 && i !== output.length - 1 && output[i + 1].trim()[0] === '=') + ) { + let gen = parts[0].trim() + if (gen.endsWith(arch)) { + gen = gen.substr(0, gen.length - arch.length) + } + gens.push(gen) + } + } + if (line.trim() === 'Generators') { + on = true + } + }) + } else { + throw new Error('CMake is not installed. Install CMake.') + } + return gens + } + verifyIfAvailable() { + if (!this.isAvailable) { + throw new Error( + "CMake executable is not found. Please use your system's package manager to install it, or you can get installers from there: http://cmake.org.", + ) + } + } + async getConfigureCommand() { + // Create command: + let command = [this.path, this.projectRoot, '--no-warn-unused-cli'] + + const D = [] + + // CMake.js watermark + D.push({ CMAKE_JS_VERSION: environment.cmakeJsVersion }) + + // Build configuration: + D.push({ CMAKE_BUILD_TYPE: this.config }) + if (environment.isWin) { + D.push({ CMAKE_RUNTIME_OUTPUT_DIRECTORY: this.workDir }) + } else if (this.workDir.endsWith(this.config)) { + D.push({ CMAKE_LIBRARY_OUTPUT_DIRECTORY: this.workDir }) + } else { + D.push({ CMAKE_LIBRARY_OUTPUT_DIRECTORY: this.buildDir }) + } + + // In some configurations MD builds will crash upon attempting to free memory. + // This tries to encourage MT builds which are larger but less likely to have this crash. + D.push({ CMAKE_MSVC_RUNTIME_LIBRARY: 'MultiThreaded$<$:Debug>' }) + + // Includes: + const includesString = await this.getCmakeJsIncludeString() + D.push({ CMAKE_JS_INC: includesString }) + + // Sources: + const srcsString = this.getCmakeJsSrcString() + D.push({ CMAKE_JS_SRC: srcsString }) + + // Runtime: + D.push({ NODE_RUNTIME: this.targetOptions.runtime }) + D.push({ NODE_RUNTIMEVERSION: this.targetOptions.runtimeVersion }) + D.push({ NODE_ARCH: this.targetOptions.arch }) + + if (environment.isOSX) { + if (this.targetOptions.arch) { + let xcodeArch = this.targetOptions.arch + if (xcodeArch === 'x64') xcodeArch = 'x86_64' + D.push({ CMAKE_OSX_ARCHITECTURES: xcodeArch }) + } + } + + // Custom options + for (const [key, value] of Object.entries(this.cMakeOptions)) { + D.push({ [key]: value }) + } + + // Toolset: + await this.toolset.initialize(false) + + const libsString = this.getCmakeJsLibString() + D.push({ CMAKE_JS_LIB: libsString }) + + if (environment.isWin) { + const nodeLibDefPath = this.getNodeLibDefPath() + if (nodeLibDefPath) { + const nodeLibPath = path.join(this.workDir, 'node.lib') + D.push({ CMAKE_JS_NODELIB_DEF: nodeLibDefPath }) + D.push({ CMAKE_JS_NODELIB_TARGET: nodeLibPath }) + } + } + + if (this.toolset.generator) { + command.push('-G', this.toolset.generator) + } + if (this.toolset.platform) { + command.push('-A', this.toolset.platform) + } + if (this.toolset.toolset) { + command.push('-T', this.toolset.toolset) + } + if (this.toolset.cppCompilerPath) { + D.push({ CMAKE_CXX_COMPILER: this.toolset.cppCompilerPath }) + } + if (this.toolset.cCompilerPath) { + D.push({ CMAKE_C_COMPILER: this.toolset.cCompilerPath }) + } + if (this.toolset.compilerFlags.length) { + D.push({ CMAKE_CXX_FLAGS: this.toolset.compilerFlags.join(' ') }) + } + if (this.toolset.linkerFlags.length) { + D.push({ CMAKE_SHARED_LINKER_FLAGS: this.toolset.linkerFlags.join(' ') }) + } + if (this.toolset.makePath) { + D.push({ CMAKE_MAKE_PROGRAM: this.toolset.makePath }) + } + + // Load NPM config + for (const [key, value] of Object.entries(npmConfigData)) { + if (key.startsWith('cmake_')) { + const sk = key.substr(6) + if (sk && value) { + D.push({ [sk]: value }) + } + } + } + + command = command.concat( + D.map(function (p) { + return '-D' + Object.keys(p)[0] + '=' + Object.values(p)[0] + }), + ) + + return command.concat(this.extraCMakeArgs) + } + getCmakeJsLibString() { + const libs = [] + if (environment.isWin) { + const nodeLibDefPath = this.getNodeLibDefPath() + if (nodeLibDefPath) { + libs.push(path.join(this.workDir, 'node.lib')) + } else { + libs.push(...this.dist.winLibs) + } + } + return libs.join(';') + } + async getCmakeJsIncludeString() { + let incPaths = [] + if (!this.options.isNodeApi) { + // Include and lib: + if (this.dist.headerOnly) { + incPaths = [path.join(this.dist.internalPath, '/include/node')] + } else { + const nodeH = path.join(this.dist.internalPath, '/src') + const v8H = path.join(this.dist.internalPath, '/deps/v8/include') + const uvH = path.join(this.dist.internalPath, '/deps/uv/include') + incPaths = [nodeH, v8H, uvH] + } + + // NAN + const nanH = await locateNAN(this.projectRoot) + if (nanH) { + incPaths.push(nanH) + } + } else { + // Base headers + const apiHeaders = require('node-api-headers') + incPaths.push(apiHeaders.include_dir) + + // Node-api + const napiH = await locateNodeApi(this.projectRoot) + if (napiH) { + incPaths.push(napiH) + } + } + + return incPaths.join(';') + } + getCmakeJsSrcString() { + const srcPaths = [] + if (environment.isWin) { + const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')) + + srcPaths.push(delayHook.replace(/\\/gm, '/')) + } + + return srcPaths.join(';') + } + getNodeLibDefPath() { + return environment.isWin && this.options.isNodeApi ? headers.def_paths.node_api_def : undefined + } + async configure() { + this.verifyIfAvailable() + + this.log.info('CMD', 'CONFIGURE') + const listPath = path.join(this.projectRoot, 'CMakeLists.txt') + const command = await this.getConfigureCommand() + + try { + await fs.lstat(listPath) + } catch (e) { + throw new Error("'" + listPath + "' not found.") + } + + try { + await fs.ensureDir(this.workDir) + } catch (e) { + // Ignore + } + + const cwd = process.cwd() + process.chdir(this.workDir) + try { + await this._run(command) + } finally { + process.chdir(cwd) + } + } + async ensureConfigured() { + try { + await fs.lstat(path.join(this.workDir, 'CMakeCache.txt')) + } catch (e) { + await this.configure() + } + } + getBuildCommand() { + const command = [this.path, '--build', this.workDir, '--config', this.config] + if (this.options.target) { + command.push('--target', this.options.target) + } + if (this.options.parallel) { + command.push('--parallel', this.options.parallel) + } + return Promise.resolve(command.concat(this.extraCMakeArgs)) + } + async build() { + this.verifyIfAvailable() -Object.defineProperties(CMake.prototype, { - path: { - get: function () { - return this.options.cmakePath || "cmake"; - } - }, - isAvailable: { - get: function () { - if (this._isAvailable === null) { - this._isAvailable = CMake.isAvailable(this.options); - } - return this._isAvailable; - } - } -}); - -CMake.isAvailable = function (options) { - options = options || {}; - try { - if (options.cmakePath) { - const stat = fs.lstatSync(options.cmakePath); - return !stat.isDirectory(); - } - else { - which.sync("cmake"); - return true; - } - } - catch (e) { - // Ignore - } - return false; -}; - -CMake.getGenerators = async function (options, log) { - const arch = " [arch]"; - options = options || {}; - const gens = []; - if (CMake.isAvailable(options)) { - // try parsing machine-readable capabilities (available since CMake 3.7) - try { - const stdout = await processHelpers.execFile([options.cmakePath || "cmake", "-E", "capabilities"]); - const capabilities = JSON.parse(stdout); - return capabilities.generators.map(x => x.name); - } - catch (error) { - if (log) { - log.verbose("TOOL", "Failed to query CMake capabilities (CMake is probably older than 3.7)"); - } - } - - // fall back to parsing help text - const stdout = await processHelpers.execFile([options.cmakePath || "cmake", "--help"]); - const hasCr = stdout.includes("\r\n"); - const output = hasCr ? stdout.split("\r\n") : stdout.split("\n"); - let on = false; - output.forEach(function (line, i) { - if (on) { - const parts = line.split("="); - if ((parts.length === 2 && parts[0].trim()) || - (parts.length === 1 && i !== output.length - 1 && output[i + 1].trim()[0] === "=")) { - let gen = parts[0].trim(); - if (gen.endsWith(arch)) { - gen = gen.substr(0, gen.length - arch.length); - } - gens.push(gen); - } - } - if (line.trim() === "Generators") { - on = true; - } - }); - } - else { - throw new Error("CMake is not installed. Install CMake."); - } - return gens; -}; - -CMake.prototype.getGenerators = function () { - return CMake.getGenerators(this.options, this.log); -}; - -CMake.prototype.verifyIfAvailable = function () { - if (!this.isAvailable) { - throw new Error("CMake executable is not found. Please use your system's package manager to install it, or you can get installers from there: http://cmake.org."); - } -}; - -CMake.prototype.getConfigureCommand = async function (nodeLibDefPath) { - // Create command: - let command = [this.path, this.projectRoot, "--no-warn-unused-cli"]; - - const D = []; - - // CMake.js watermark - D.push({"CMAKE_JS_VERSION": environment.moduleVersion}); - - // Build configuration: - D.push({"CMAKE_BUILD_TYPE": this.config}); - if (environment.isWin) { - D.push({"CMAKE_RUNTIME_OUTPUT_DIRECTORY": this.workDir}); + await this.ensureConfigured() + const buildCommand = await this.getBuildCommand() + this.log.info('CMD', 'BUILD') + await this._run(buildCommand) + } + getCleanCommand() { + return [this.path, '-E', 'remove_directory', this.workDir].concat(this.extraCMakeArgs) } - else { - D.push({"CMAKE_LIBRARY_OUTPUT_DIRECTORY": this.buildDir}); + clean() { + this.verifyIfAvailable() + + this.log.info('CMD', 'CLEAN') + return this._run(this.getCleanCommand()) + } + async reconfigure() { + this.extraCMakeArgs = [] + await this.clean() + await this.configure() + } + async rebuild() { + this.extraCMakeArgs = [] + await this.clean() + await this.build() + } + async compile() { + this.extraCMakeArgs = [] + try { + await this.build() + } catch (e) { + this.log.info('REP', 'Build has been failed, trying to do a full rebuild.') + await this.rebuild() + } + } + _run(command) { + this.log.info('RUN', command) + return processHelpers.run(command, { silent: this.silent }) + } + + async getGenerators() { + return CMake.getGenerators(this.options, this.log) } - - // In some configurations MD builds will crash upon attempting to free memory. - // This tries to encourage MT builds which are larger but less likely to have this crash. - D.push({"CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreaded$<$:Debug>"}) - - let incPaths = []; - if (!this.options.isNodeApi) { - // Include and lib: - if (this.dist.headerOnly) { - incPaths = [path.join(this.dist.internalPath, "/include/node")]; - } - else { - const nodeH = path.join(this.dist.internalPath, "/src"); - const v8H = path.join(this.dist.internalPath, "/deps/v8/include"); - const uvH = path.join(this.dist.internalPath, "/deps/uv/include"); - incPaths = [nodeH, v8H, uvH]; - } - - // NAN - const nanH = await locateNAN(this.projectRoot); - if (nanH) { - incPaths.push(nanH); - } - } else { - // Base headers - const apiHeaders = require('node-api-headers') - incPaths.push(apiHeaders.include_dir) - - // Node-api - const napiH = await locateNodeApi(this.projectRoot) - if (napiH) { - incPaths.push(napiH) - } - } - - // Includes: - D.push({"CMAKE_JS_INC": incPaths.join(";")}); - - // Sources: - const srcPaths = []; - if (environment.isWin) { - const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')); - - srcPaths.push(delayHook.replace(/\\/gm, '/')); - } - - D.push({"CMAKE_JS_SRC": srcPaths.join(";")}); - - // Runtime: - D.push({"NODE_RUNTIME": this.targetOptions.runtime}); - D.push({"NODE_RUNTIMEVERSION": this.targetOptions.runtimeVersion}); - D.push({"NODE_ARCH": this.targetOptions.arch}); - - if (environment.isOSX) { - if (this.targetOptions.arch) { - let xcodeArch = this.targetOptions.arch - if (xcodeArch === 'x64') xcodeArch = 'x86_64' - D.push({CMAKE_OSX_ARCHITECTURES: xcodeArch}) - } - } - - // Custom options - for (const [key, value] of Object.entries(this.cMakeOptions)) { - D.push({ [key]: value }); - } - - // Toolset: - await this.toolset.initialize(false); - - if (environment.isWin) { - // Win - const libs = [] - if (nodeLibDefPath) { - const nodeLibPath = path.join(this.workDir, 'node.lib') - D.push({ CMAKE_JS_NODELIB_DEF: nodeLibDefPath }) - D.push({ CMAKE_JS_NODELIB_TARGET: nodeLibPath }) - libs.push(nodeLibPath) - } else { - libs.push(...this.dist.winLibs) - } - if (libs.length) { - D.push({"CMAKE_JS_LIB": libs.join(";")}); - } - } - - if (this.toolset.generator) { - command.push("-G", this.toolset.generator); - } - if (this.toolset.platform) { - command.push("-A", this.toolset.platform); - } - if (this.toolset.toolset) { - command.push("-T", this.toolset.toolset); - } - if (this.toolset.cppCompilerPath) { - D.push({"CMAKE_CXX_COMPILER": this.toolset.cppCompilerPath}); - } - if (this.toolset.cCompilerPath) { - D.push({"CMAKE_C_COMPILER": this.toolset.cCompilerPath}); - } - if (this.toolset.compilerFlags.length) { - D.push({"CMAKE_CXX_FLAGS": this.toolset.compilerFlags.join(" ")}); - } - if (this.toolset.linkerFlags.length) { - D.push({"CMAKE_SHARED_LINKER_FLAGS": this.toolset.linkerFlags.join(" ")}); - } - if (this.toolset.makePath) { - D.push({"CMAKE_MAKE_PROGRAM": this.toolset.makePath}); - } - - // Load NPM config - for (const [key, value] of Object.entries(npmConfigData)) { - if (key.startsWith("cmake_")) { - const sk = key.substr(6); - if (sk && value) { - D.push({ [sk]: value }); - } - } - } - - command = command.concat(D.map(function (p) { - return "-D" + Object.keys(p)[0] + "=" + Object.values(p)[0]; - })); - - return command; -}; - -CMake.prototype._generateNodeLibDef = async function (targetFile) { - try { - // Compile a Set of all the symbols that could be exported - const allSymbols = new Set() - for (const ver of Object.values(headers.symbols)) { - for (const sym of ver.node_api_symbols) { - allSymbols.add(sym) - } - } - - // Write a 'def' file for NODE.EXE - const allSymbolsArr = Array.from(allSymbols) - await fs.writeFile(targetFile, 'NAME NODE.EXE\nEXPORTS\n' + allSymbolsArr.join('\n')) - - return targetFile - } catch(e) { - // It most likely wasn't found - throw new Error(`Failed to generate def for node.lib`) - } } -CMake.prototype.configure = async function () { - this.verifyIfAvailable(); - - const nodeLibDefPath = environment.isWin && this.options.isNodeApi ? path.join(this.options.out, 'node-lib.def') : undefined - - this.log.info("CMD", "CONFIGURE"); - const listPath = path.join(this.projectRoot, "CMakeLists.txt"); - const command = await this.getConfigureCommand(nodeLibDefPath); - - try { - await fs.lstat(listPath); - } - catch (e) { - throw new Error("'" + listPath + "' not found."); - } - - try { - await fs.ensureDir(this.workDir); - } - catch (e) { - // Ignore - } - - const cwd = process.cwd(); - process.chdir(this.workDir); - try { - if (environment.isWin && nodeLibDefPath) { - await this._generateNodeLibDef(nodeLibDefPath) - } - - await this._run(command); - } - finally { - process.chdir(cwd); - } -}; - -CMake.prototype.ensureConfigured = async function () { - try { - await fs.lstat(path.join(this.workDir, "CMakeCache.txt")); - } - catch (e) { - await this.configure(); - } -}; - -CMake.prototype.getBuildCommand = function () { - const command = [this.path, "--build", this.workDir, "--config", this.config]; - if (this.options.target) { - command.push("--target", this.options.target); - } - if (this.options.parallel) { - command.push("--parallel", this.options.parallel); - } - return Promise.resolve(command); -}; - -CMake.prototype.build = async function () { - this.verifyIfAvailable(); - - await this.ensureConfigured(); - const buildCommand = await this.getBuildCommand(); - this.log.info("CMD", "BUILD"); - await this._run(buildCommand); -}; - -CMake.prototype.getCleanCommand = function () { - return [this.path, "-E", "remove_directory", this.workDir]; -}; - -CMake.prototype.clean = function () { - this.verifyIfAvailable(); - - this.log.info("CMD", "CLEAN"); - return this._run(this.getCleanCommand()); -}; - -CMake.prototype.reconfigure = async function () { - await this.clean(); - await this.configure(); -}; - -CMake.prototype.rebuild = async function () { - await this.clean(); - await this.build(); -}; - -CMake.prototype.compile = async function () { - try { - await this.build(); - } - catch (e) { - this.log.info("REP", "Build has been failed, trying to do a full rebuild."); - await this.rebuild(); - } -}; - -CMake.prototype._run = function (command) { - this.log.info("RUN", command); - return processHelpers.run(command, {silent: this.silent}); -}; - -module.exports = CMake; +module.exports = CMake diff --git a/lib/cmLog.js b/lib/cmLog.js index f1e85a60..58fbdfa6 100644 --- a/lib/cmLog.js +++ b/lib/cmLog.js @@ -1,76 +1,61 @@ -"use strict"; -const log = require("npmlog"); - -function CMLog(options) { - this.options = options || {}; - this.debug = require("debug")(this.options.logName || "cmake-js"); +'use strict' +const log = require('npmlog') + +class CMLog { + get level() { + if (this.options.noLog) { + return 'silly' + } else { + return log.level + } + } + + constructor(options) { + this.options = options || {} + this.debug = require('debug')(this.options.logName || 'cmake-js') + } + silly(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.silly(cat, msg) + } + } + verbose(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.verbose(cat, msg) + } + } + info(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.info(cat, msg) + } + } + warn(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.warn(cat, msg) + } + } + http(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.http(cat, msg) + } + } + error(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.error(cat, msg) + } + } } -Object.defineProperties(CMLog.prototype, { - level: { - get: function() { - if (this.options.noLog) { - return "silly"; - } - else { - return log.level; - } - } - } -}); - -CMLog.prototype.silly = function(cat, msg) { - if (this.options.noLog) { - this.debug(cat + ": " + msg); - } - else { - log.silly(cat, msg); - } -}; - -CMLog.prototype.verbose = function(cat, msg) { - if (this.options.noLog) { - this.debug(cat + ": " + msg); - } - else { - log.verbose(cat, msg); - } -}; - -CMLog.prototype.info = function(cat, msg) { - if (this.options.noLog) { - this.debug(cat + ": " + msg); - } - else { - log.info(cat, msg); - } -}; - -CMLog.prototype.warn = function(cat, msg) { - if (this.options.noLog) { - this.debug(cat + ": " + msg); - } - else { - log.warn(cat, msg); - } -}; - -CMLog.prototype.http = function(cat, msg) { - if (this.options.noLog) { - this.debug(cat + ": " + msg); - } - else { - log.http(cat, msg); - } -}; - -CMLog.prototype.error = function(cat, msg) { - if (this.options.noLog) { - this.debug(cat + ": " + msg); - } - else { - log.error(cat, msg); - } -}; - -module.exports = CMLog; \ No newline at end of file +module.exports = CMLog diff --git a/lib/cpp/win_delay_load_hook.cc b/lib/cpp/win_delay_load_hook.cc index 72dca35d..8add2927 100644 --- a/lib/cpp/win_delay_load_hook.cc +++ b/lib/cpp/win_delay_load_hook.cc @@ -40,6 +40,9 @@ static FARPROC WINAPI load_exe_hook(unsigned int event, DelayLoadInfo* info) { if (_stricmp(info->szDll, "node.exe") != 0) return NULL; + + // Fall back to the current process + if(!node_dll) node_dll = GetModuleHandleA(NULL); return (FARPROC) node_dll; } diff --git a/lib/dist.js b/lib/dist.js index a019e017..6bfbd320 100644 --- a/lib/dist.js +++ b/lib/dist.js @@ -1,195 +1,176 @@ -"use strict"; -const environment = require("./environment"); -const path = require("path"); -const urljoin = require("url-join"); -const fs = require("fs-extra"); -const CMLog = require("./cmLog"); -const TargetOptions = require("./targetOptions"); -const runtimePaths = require("./runtimePaths"); -const Downloader = require("./downloader"); +'use strict' +const environment = require('./environment') +const path = require('path') +const urljoin = require('url-join') +const fs = require('fs-extra') +const CMLog = require('./cmLog') +const TargetOptions = require('./targetOptions') +const runtimePaths = require('./runtimePaths') +const Downloader = require('./downloader') +const os = require('os') function testSum(sums, sum, fPath) { - const serverSum = sums.find(function (s) { - return s.getPath === fPath; - }); - if (serverSum && serverSum.sum === sum) { - return; - } - throw new Error("SHA sum of file '" + fPath + "' mismatch!"); + const serverSum = sums.find(function (s) { + return s.getPath === fPath + }) + if (serverSum && serverSum.sum === sum) { + return + } + throw new Error("SHA sum of file '" + fPath + "' mismatch!") } -function Dist(options) { - this.options = options || {}; - this.log = new CMLog(this.options); - this.targetOptions = new TargetOptions(this.options); - this.downloader = new Downloader(this.options); +class Dist { + get internalPath() { + const cacheDirectory = '.cmake-js' + const runtimeArchDirectory = this.targetOptions.runtime + '-' + this.targetOptions.arch + const runtimeVersionDirectory = 'v' + this.targetOptions.runtimeVersion + + return ( + this.options.runtimeDirectory || + path.join(os.homedir(), cacheDirectory, runtimeArchDirectory, runtimeVersionDirectory) + ) + } + get externalPath() { + return runtimePaths.get(this.targetOptions).externalPath + } + get downloaded() { + let headers = false + let libs = true + let stat = getStat(this.internalPath) + if (stat.isDirectory()) { + if (this.headerOnly) { + stat = getStat(path.join(this.internalPath, 'include/node/node.h')) + headers = stat.isFile() + } else { + stat = getStat(path.join(this.internalPath, 'src/node.h')) + if (stat.isFile()) { + stat = getStat(path.join(this.internalPath, 'deps/v8/include/v8.h')) + headers = stat.isFile() + } + } + if (environment.isWin) { + for (const libPath of this.winLibs) { + stat = getStat(libPath) + libs = libs && stat.isFile() + } + } + } + return headers && libs + + function getStat(path) { + try { + return fs.statSync(path) + } catch (e) { + return { + isFile: () => false, + isDirectory: () => false, + } + } + } + } + get winLibs() { + const libs = runtimePaths.get(this.targetOptions).winLibs + const result = [] + for (const lib of libs) { + result.push(path.join(this.internalPath, lib.dir, lib.name)) + } + return result + } + get headerOnly() { + return runtimePaths.get(this.targetOptions).headerOnly + } + + constructor(options) { + this.options = options || {} + this.log = new CMLog(this.options) + this.targetOptions = new TargetOptions(this.options) + this.downloader = new Downloader(this.options) + } + + async ensureDownloaded() { + if (!this.downloaded) { + await this.download() + } + } + async download() { + const log = this.log + log.info('DIST', 'Downloading distribution files to: ' + this.internalPath) + await fs.ensureDir(this.internalPath) + const sums = await this._downloadShaSums() + await Promise.all([this._downloadLibs(sums), this._downloadTar(sums)]) + } + async _downloadShaSums() { + if (this.targetOptions.runtime === 'node') { + const sumUrl = urljoin(this.externalPath, 'SHASUMS256.txt') + const log = this.log + log.http('DIST', '\t- ' + sumUrl) + return (await this.downloader.downloadString(sumUrl)) + .split('\n') + .map(function (line) { + const parts = line.split(/\s+/) + return { + getPath: parts[1], + sum: parts[0], + } + }) + .filter(function (i) { + return i.getPath && i.sum + }) + } else { + return null + } + } + async _downloadTar(sums) { + const log = this.log + const self = this + const tarLocalPath = runtimePaths.get(self.targetOptions).tarPath + const tarUrl = urljoin(self.externalPath, tarLocalPath) + log.http('DIST', '\t- ' + tarUrl) + + const sum = await this.downloader.downloadTgz(tarUrl, { + hash: sums ? 'sha256' : null, + cwd: self.internalPath, + strip: 1, + filter: function (entryPath) { + if (entryPath === self.internalPath) { + return true + } + const ext = path.extname(entryPath) + return ext && ext.toLowerCase() === '.h' + }, + }) + + if (sums) { + testSum(sums, sum, tarLocalPath) + } + } + async _downloadLibs(sums) { + const log = this.log + const self = this + if (!environment.isWin) { + return + } + + const paths = runtimePaths.get(self.targetOptions) + for (const dirs of paths.winLibs) { + const subDir = dirs.dir + const fn = dirs.name + const fPath = subDir ? urljoin(subDir, fn) : fn + const libUrl = urljoin(self.externalPath, fPath) + log.http('DIST', '\t- ' + libUrl) + + await fs.ensureDir(path.join(self.internalPath, subDir)) + + const sum = await this.downloader.downloadFile(libUrl, { + path: path.join(self.internalPath, fPath), + hash: sums ? 'sha256' : null, + }) + + if (sums) { + testSum(sums, sum, fPath) + } + } + } } -// Props -Object.defineProperties(Dist.prototype, { - internalPath: { - get: function () { - const cacheDirectory = ".cmake-js"; - const runtimeArchDirectory = (this.targetOptions.runtime) + "-" + this.targetOptions.arch; - const runtimeVersionDirectory = "v" + this.targetOptions.runtimeVersion; - - return this.options.runtimeDirectory || - path.join(environment.home, - cacheDirectory, - runtimeArchDirectory, - runtimeVersionDirectory); - } - }, - externalPath: { - get: function () { - return runtimePaths.get(this.targetOptions).externalPath; - } - }, - downloaded: { - get: function () { - let headers = false; - let libs = true; - let stat = getStat(this.internalPath); - if (stat.isDirectory()) { - if (this.headerOnly) { - stat = getStat(path.join(this.internalPath, "include/node/node.h")); - headers = stat.isFile(); - } - else { - stat = getStat(path.join(this.internalPath, "src/node.h")); - if (stat.isFile()) { - stat = getStat(path.join(this.internalPath, "deps/v8/include/v8.h")); - headers = stat.isFile(); - } - } - if (environment.isWin) { - for (const libPath of this.winLibs) { - stat = getStat(libPath); - libs = libs && stat.isFile(); - } - } - } - return headers && libs; - - function getStat(path) { - try { - return fs.statSync(path); - } - catch (e) { - return { - isFile: () => false, - isDirectory: () => false - }; - } - } - } - }, - winLibs: { - get: function () { - const libs = runtimePaths.get(this.targetOptions).winLibs; - const result = []; - for (const lib of libs) { - result.push(path.join(this.internalPath, lib.dir, lib.name)); - } - return result; - } - }, - headerOnly: { - get: function () { - return runtimePaths.get(this.targetOptions).headerOnly; - } - } -}); - -// Methods -Dist.prototype.ensureDownloaded = async function () { - if (!this.downloaded) { - await this.download(); - } -}; - -Dist.prototype.download = async function () { - const log = this.log; - log.info("DIST", "Downloading distribution files to: " + this.internalPath); - await fs.ensureDir(this.internalPath); - const sums = await this._downloadShaSums(); - await Promise.all([this._downloadLibs(sums), this._downloadTar(sums)]); -}; - -Dist.prototype._downloadShaSums = async function () { - if (this.targetOptions.runtime === "node") { - const sumUrl = urljoin(this.externalPath, "SHASUMS256.txt"); - const log = this.log; - log.http("DIST", "\t- " + sumUrl); - return (await this.downloader.downloadString(sumUrl)) - .split("\n") - .map(function (line) { - const parts = line.split(/\s+/); - return { - getPath: parts[1], - sum: parts[0] - }; - }) - .filter(function (i) { - return i.getPath && i.sum; - }); - } - else { - return null; - } -}; - -Dist.prototype._downloadTar = async function (sums) { - const log = this.log; - const self = this; - const tarLocalPath = runtimePaths.get(self.targetOptions).tarPath; - const tarUrl = urljoin(self.externalPath, tarLocalPath); - log.http("DIST", "\t- " + tarUrl); - - const sum = await this.downloader.downloadTgz(tarUrl, { - hash: sums ? "sha256" : null, - cwd: self.internalPath, - strip: 1, - filter: function (entryPath) { - if (entryPath === self.internalPath) { - return true; - } - const ext = path.extname(entryPath); - return ext && ext.toLowerCase() === ".h"; - } - }); - - if (sums) { - testSum(sums, sum, tarLocalPath); - } -}; - -Dist.prototype._downloadLibs = async function (sums) { - const log = this.log; - const self = this; - if (!environment.isWin) { - return; - } - - const paths = runtimePaths.get(self.targetOptions); - for (const dirs of paths.winLibs) { - const subDir = dirs.dir; - const fn = dirs.name; - const fPath = subDir ? urljoin(subDir, fn) : fn; - const libUrl = urljoin(self.externalPath, fPath); - log.http("DIST", "\t- " + libUrl); - - await fs.ensureDir(path.join(self.internalPath, subDir)); - - const sum = await this.downloader.downloadFile(libUrl, { - path: path.join(self.internalPath, fPath), - hash: sums ? "sha256" : null - }); - - if (sums) { - testSum(sums, sum, fPath); - } - } -}; - -module.exports = Dist; +module.exports = Dist diff --git a/lib/downloader.js b/lib/downloader.js index 2c109ac6..182789ec 100644 --- a/lib/downloader.js +++ b/lib/downloader.js @@ -1,95 +1,92 @@ -"use strict"; -const crypto = require("crypto"); -const axios = require("axios"); -const MemoryStream = require("memory-stream"); -const zlib = require("zlib"); -const tar = require("tar"); -const fs = require("fs"); -const CMLog = require("./cmLog"); +'use strict' +const crypto = require('crypto') +const axios = require('axios') +const MemoryStream = require('memory-stream') +const zlib = require('zlib') +const tar = require('tar') +const fs = require('fs') +const CMLog = require('./cmLog') -function Downloader(options) { - this.options = options || {}; - this.log = new CMLog(this.options); -} - -Downloader.prototype.downloadToStream = function(url, stream, hash) { - const self = this; - const shasum = hash ? crypto.createHash(hash) : null; - return new Promise(function (resolve, reject) { - let length = 0; - let done = 0; - let lastPercent = 0; - axios - .get(url, { responseType: "stream" }) - .then(function (response) { - length = parseInt(response.headers["content-length"]); - if (typeof length !== 'number') { - length = 0; - } - - response.data.on('data', function (chunk) { - if (shasum) { - shasum.update(chunk); - } - if (length) { - done += chunk.length; - let percent = done / length * 100; - percent = Math.round(percent / 10) * 10 + 10; - if (percent > lastPercent) { - self.log.verbose("DWNL", "\t" + lastPercent + "%"); - lastPercent = percent; - } - } - }); - - response.data.pipe(stream); - }) - .catch(function (err) { - reject(err); - }); +class Downloader { + constructor(options) { + this.options = options || {} + this.log = new CMLog(this.options) + } + downloadToStream(url, stream, hash) { + const self = this + const shasum = hash ? crypto.createHash(hash) : null + return new Promise(function (resolve, reject) { + let length = 0 + let done = 0 + let lastPercent = 0 + axios + .get(url, { responseType: 'stream' }) + .then(function (response) { + length = parseInt(response.headers['content-length']) + if (typeof length !== 'number') { + length = 0 + } - stream.once("error", function (err) { - reject(err); - }); + response.data.on('data', function (chunk) { + if (shasum) { + shasum.update(chunk) + } + if (length) { + done += chunk.length + let percent = (done / length) * 100 + percent = Math.round(percent / 10) * 10 + 10 + if (percent > lastPercent) { + self.log.verbose('DWNL', '\t' + lastPercent + '%') + lastPercent = percent + } + } + }) - stream.once("finish", function () { - resolve(shasum ? shasum.digest("hex") : undefined); - }); - }); -}; + response.data.pipe(stream) + }) + .catch(function (err) { + reject(err) + }) -Downloader.prototype.downloadString = async function (url) { - const result = new MemoryStream(); - await this.downloadToStream(url, result); - return result.toString(); -}; + stream.once('error', function (err) { + reject(err) + }) -Downloader.prototype.downloadFile = async function (url, options) { - if (typeof options === 'string') { - options.path = options; - } - const result = fs.createWriteStream(options.path); - const sum = await this.downloadToStream(url, result, options.hash); - this.testSum(url, sum, options); - return sum; -}; - -Downloader.prototype.downloadTgz = async function (url, options) { - if (typeof options === 'string') { - options.cwd = options; - } - const gunzip = zlib.createGunzip(); - const extractor = tar.extract(options); - gunzip.pipe(extractor); - const sum = await this.downloadToStream(url, gunzip, options.hash); - this.testSum(url, sum, options); - return sum; -}; - -Downloader.prototype.testSum = function(url, sum, options) { - if (options.hash && sum && options.sum && options.sum !== sum) { - throw new Error(options.hash.toUpperCase() + " sum of download '" + url + "' mismatch!"); - } -}; + stream.once('finish', function () { + resolve(shasum ? shasum.digest('hex') : undefined) + }) + }) + } + async downloadString(url) { + const result = new MemoryStream() + await this.downloadToStream(url, result) + return result.toString() + } + async downloadFile(url, options) { + if (typeof options === 'string') { + options.path = options + } + const result = fs.createWriteStream(options.path) + const sum = await this.downloadToStream(url, result, options.hash) + this.testSum(url, sum, options) + return sum + } + async downloadTgz(url, options) { + if (typeof options === 'string') { + options.cwd = options + } + const gunzip = zlib.createGunzip() + const extractor = tar.extract(options) + gunzip.pipe(extractor) + const sum = await this.downloadToStream(url, gunzip, options.hash) + this.testSum(url, sum, options) + return sum + } + testSum(url, sum, options) { + if (options.hash && sum && options.sum && options.sum !== sum) { + throw new Error(options.hash.toUpperCase() + " sum of download '" + url + "' mismatch!") + } + } +} -module.exports = Downloader; +module.exports = Downloader diff --git a/lib/environment.js b/lib/environment.js index d8e32d5c..401b0bac 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -1,107 +1,97 @@ -"use strict"; -const os = require("os"); -const which = require("which"); +'use strict' +const os = require('os') +const which = require('which') -const environment = module.exports = { - cmakeJsVersion: require("../package.json").version, - platform: os.platform(), - isWin: os.platform() === "win32", - isLinux: os.platform() === "linux", - isOSX: os.platform() === "darwin", - arch: os.arch(), - isX86: os.arch() === "ia32" || os.arch() === "x86", - isX64: os.arch() === "x64", - isArm: os.arch() === "arm", - runtime: "node", - runtimeVersion: process.versions.node, - home: process.env[(os.platform() === "win32") ? "USERPROFILE" : "HOME"], - EOL: os.EOL -}; +const environment = (module.exports = { + cmakeJsVersion: require('../package.json').version, + platform: os.platform(), + isWin: os.platform() === 'win32', + isLinux: os.platform() === 'linux', + isOSX: os.platform() === 'darwin', + arch: os.arch(), + isX86: os.arch() === 'ia32' || os.arch() === 'x86', + isX64: os.arch() === 'x64', + isArm: os.arch() === 'arm', + isArm64: os.arch() === 'arm64', + runtime: 'node', + runtimeVersion: process.versions.node, +}) Object.defineProperties(environment, { - isPosix: { - get: function () { - return !this.isWin; - } - }, - _isNinjaAvailable: { - value: null, - writable: true - }, - isNinjaAvailable: { - get: function() { - if (this._isNinjaAvailable === null) { - this._isNinjaAvailable = false; - try { - if (which.sync("ninja")) { - this._isNinjaAvailable = true; - } - } - catch (e) { - // Ignore - } - } - return this._isNinjaAvailable; - } - }, - _isMakeAvailable: { - value: null, - writable: true - }, - isMakeAvailable: { - get: function() { - if (this._isMakeAvailable === null) { - this._isMakeAvailable = false; - try { - if (which.sync("make")) { - this._isMakeAvailable = true; - } - } - catch (e) { - // Ignore - } - } - return this._isMakeAvailable; - } - }, - _isGPPAvailable: { - value: null, - writable: true - }, - isGPPAvailable: { - get: function() { - if (this._isGPPAvailable === null) { - this._isGPPAvailable = false; - try { - if (which.sync("g++")) { - this._isGPPAvailable = true; - } - } - catch (e) { - // Ignore - } - } - return this._isGPPAvailable; - } - }, - _isClangAvailable: { - value: null, - writable: true - }, - isClangAvailable: { - get: function() { - if (this._isClangAvailable === null) { - this._isClangAvailable = false; - try { - if (which.sync("clang++")) { - this._isClangAvailable = true; - } - } - catch (e) { - // Ignore - } - } - return this._isClangAvailable; - } - } -}); \ No newline at end of file + _isNinjaAvailable: { + value: null, + writable: true, + }, + isNinjaAvailable: { + get: function () { + if (this._isNinjaAvailable === null) { + this._isNinjaAvailable = false + try { + if (which.sync('ninja')) { + this._isNinjaAvailable = true + } + } catch (e) { + // Ignore + } + } + return this._isNinjaAvailable + }, + }, + _isMakeAvailable: { + value: null, + writable: true, + }, + isMakeAvailable: { + get: function () { + if (this._isMakeAvailable === null) { + this._isMakeAvailable = false + try { + if (which.sync('make')) { + this._isMakeAvailable = true + } + } catch (e) { + // Ignore + } + } + return this._isMakeAvailable + }, + }, + _isGPPAvailable: { + value: null, + writable: true, + }, + isGPPAvailable: { + get: function () { + if (this._isGPPAvailable === null) { + this._isGPPAvailable = false + try { + if (which.sync('g++')) { + this._isGPPAvailable = true + } + } catch (e) { + // Ignore + } + } + return this._isGPPAvailable + }, + }, + _isClangAvailable: { + value: null, + writable: true, + }, + isClangAvailable: { + get: function () { + if (this._isClangAvailable === null) { + this._isClangAvailable = false + try { + if (which.sync('clang++')) { + this._isClangAvailable = true + } + } catch (e) { + // Ignore + } + } + return this._isClangAvailable + }, + }, +}) diff --git a/lib/import/README b/lib/import/README index 9c246cdd..55d97301 100644 --- a/lib/import/README +++ b/lib/import/README @@ -1,6 +1,6 @@ -This is a copy of some files from node-gyp, with some minor modifications.a +This is a copy of some files from node-gyp, with some minor modifications. -Currently based on v9.0.0 with support for vs2013 removed +Currently based on v10.0.1 node-gyp has a decent strategy for finding Visual Studio, and has a lot more developer time behind them to make a good and robust solution. -We may as well benefit from their solution than pointlessly reinvent it +We may as well benefit from their solution than reinvent it diff --git a/lib/import/find-visualstudio.js b/lib/import/find-visualstudio.js index 4d1dfa5d..d162c2f0 100644 --- a/lib/import/find-visualstudio.js +++ b/lib/import/find-visualstudio.js @@ -1,425 +1,592 @@ 'use strict' const log = require('npmlog') -const execFile = require('child_process').execFile -const fs = require('fs') -const path = require('path').win32 -const logWithPrefix = require('./util').logWithPrefix -const regSearchKeys = require('./util').regSearchKeys - -function findVisualStudio (configMsvsVersion, callback) { - const finder = new VisualStudioFinder(configMsvsVersion, - callback) - finder.findVisualStudio() +const { existsSync } = require('fs') +const { win32: path } = require('path') +const { regSearchKeys, execFile, logWithPrefix } = require('./util') +const semver = require('semver') + +class VisualStudioFinder { + static findVisualStudio = (...args) => new VisualStudioFinder(...args).findVisualStudio() + + log = logWithPrefix(log, 'find VS') + + regSearchKeys = regSearchKeys + + constructor(nodeSemver, configMsvsVersion) { + this.nodeSemver = nodeSemver + this.configMsvsVersion = configMsvsVersion + this.errorLog = [] + this.validVersions = [] + } + + // Logs a message at verbose level, but also saves it to be displayed later + // at error level if an error occurs. This should help diagnose the problem. + addLog(message) { + this.log.verbose(message) + this.errorLog.push(message) + } + + async findVisualStudio() { + this.configVersionYear = null + this.configPath = null + if (this.configMsvsVersion) { + this.addLog('msvs_version was set from command line or npm config') + if (this.configMsvsVersion.match(/^\d{4}$/)) { + this.configVersionYear = parseInt(this.configMsvsVersion, 10) + this.addLog(`- looking for Visual Studio version ${this.configVersionYear}`) + } else { + this.configPath = path.resolve(this.configMsvsVersion) + this.addLog(`- looking for Visual Studio installed in "${this.configPath}"`) + } + } else { + this.addLog('msvs_version not set from command line or npm config') + } + + if (process.env.VCINSTALLDIR) { + this.envVcInstallDir = path.resolve(process.env.VCINSTALLDIR, '..') + this.addLog( + 'running in VS Command Prompt, installation path is:\n' + + `"${this.envVcInstallDir}"\n- will only use this version`, + ) + } else { + this.addLog('VCINSTALLDIR not set, not running in VS Command Prompt') + } + + const checks = [ + () => this.findVisualStudio2019OrNewerFromSpecifiedLocation(), + () => this.findVisualStudio2019OrNewerUsingSetupModule(), + () => this.findVisualStudio2019OrNewer(), + () => this.findVisualStudio2017FromSpecifiedLocation(), + () => this.findVisualStudio2017UsingSetupModule(), + () => this.findVisualStudio2017(), + () => this.findVisualStudio2015(), + () => this.findVisualStudio2013(), + ] + + for (const check of checks) { + const info = await check() + if (info) { + return this.succeed(info) + } + } + + return this.fail() + } + + succeed(info) { + this.log.info( + `using VS${info.versionYear} (${info.version}) found at:` + + `\n"${info.path}"` + + '\nrun with --verbose for detailed information', + ) + return info + } + + fail() { + if (this.configMsvsVersion && this.envVcInstallDir) { + this.errorLog.push('msvs_version does not match this VS Command Prompt or the', 'installation cannot be used.') + } else if (this.configMsvsVersion) { + // If msvs_version was specified but finding VS failed, print what would + // have been accepted + this.errorLog.push('') + if (this.validVersions) { + this.errorLog.push('valid versions for msvs_version:') + this.validVersions.forEach((version) => { + this.errorLog.push(`- "${version}"`) + }) + } else { + this.errorLog.push('no valid versions for msvs_version were found') + } + } + + const errorLog = this.errorLog.join('\n') + + // For Windows 80 col console, use up to the column before the one marked + // with X (total 79 chars including logger prefix, 62 chars usable here): + // X + const infoLog = [ + '**************************************************************', + 'You need to install the latest version of Visual Studio', + 'including the "Desktop development with C++" workload.', + 'For more information consult the documentation at:', + 'https://github.com/nodejs/node-gyp#on-windows', + '**************************************************************', + ].join('\n') + + this.log.error(`\n${errorLog}\n\n${infoLog}\n`) + throw new Error('Could not find any Visual Studio installation to use') + } + + async findVisualStudio2019OrNewerFromSpecifiedLocation() { + return this.findVSFromSpecifiedLocation([2019, 2022]) + } + + async findVisualStudio2017FromSpecifiedLocation() { + if (semver.gte(this.nodeSemver, '22.0.0')) { + this.addLog('not looking for VS2017 as it is only supported up to Node.js 21') + return null + } + return this.findVSFromSpecifiedLocation([2017]) + } + + async findVSFromSpecifiedLocation(supportedYears) { + if (!this.envVcInstallDir) { + return null + } + const info = { + path: path.resolve(this.envVcInstallDir), + // Assume the version specified by the user is correct. + // Since Visual Studio 2015, the Developer Command Prompt sets the + // VSCMD_VER environment variable which contains the version information + // for Visual Studio. + // https://learn.microsoft.com/en-us/visualstudio/ide/reference/command-prompt-powershell?view=vs-2022 + version: process.env.VSCMD_VER, + packages: [ + 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', + 'Microsoft.VisualStudio.Component.VC.Tools.ARM64', + // Assume MSBuild exists. It will be checked in processing. + 'Microsoft.VisualStudio.VC.MSBuild.Base', + ], + } + + // Is there a better way to get SDK information? + const envWindowsSDKVersion = process.env.WindowsSDKVersion + const sdkVersionMatched = envWindowsSDKVersion?.match(/^(\d+)\.(\d+)\.(\d+)\..*/) + if (sdkVersionMatched) { + info.packages.push(`Microsoft.VisualStudio.Component.Windows10SDK.${sdkVersionMatched[3]}.Desktop`) + } + // pass for further processing + return this.processData([info], supportedYears) + } + + async findVisualStudio2019OrNewerUsingSetupModule() { + return this.findNewVSUsingSetupModule([2019, 2022]) + } + + async findVisualStudio2017UsingSetupModule() { + if (semver.gte(this.nodeSemver, '22.0.0')) { + this.addLog('not looking for VS2017 as it is only supported up to Node.js 21') + return null + } + return this.findNewVSUsingSetupModule([2017]) + } + + async findNewVSUsingSetupModule(supportedYears) { + const ps = path.join(process.env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe') + const vcInstallDir = this.envVcInstallDir + + const checkModuleArgs = [ + '-NoProfile', + '-Command', + '&{@(Get-Module -ListAvailable -Name VSSetup).Version.ToString()}', + ] + this.log.silly('Running', ps, checkModuleArgs) + const [cErr] = await this.execFile(ps, checkModuleArgs) + if (cErr) { + this.addLog( + 'VSSetup module doesn\'t seem to exist. You can install it via: "Install-Module VSSetup -Scope CurrentUser"', + ) + this.log.silly('VSSetup error = %j', cErr && (cErr.stack || cErr)) + return null + } + const filterArg = vcInstallDir !== undefined ? `| where {$_.InstallationPath -eq '${vcInstallDir}' }` : '' + const psArgs = ['-NoProfile', '-Command', `&{Get-VSSetupInstance ${filterArg} | ConvertTo-Json -Depth 3}`] + + this.log.silly('Running', ps, psArgs) + const [err, stdout, stderr] = await this.execFile(ps, psArgs) + let parsedData = this.parseData(err, stdout, stderr) + if (parsedData === null) { + return null + } + this.log.silly('Parsed data', parsedData) + if (!Array.isArray(parsedData)) { + // if there are only 1 result, then Powershell will output non-array + parsedData = [parsedData] + } + // normalize output + parsedData = parsedData.map((info) => { + info.path = info.InstallationPath + info.version = `${info.InstallationVersion.Major}.${info.InstallationVersion.Minor}.${info.InstallationVersion.Build}.${info.InstallationVersion.Revision}` + info.packages = info.Packages.map((p) => p.Id) + return info + }) + // pass for further processing + return this.processData(parsedData, supportedYears) + } + + // Invoke the PowerShell script to get information about Visual Studio 2019 + // or newer installations + async findVisualStudio2019OrNewer() { + return this.findNewVS([2019, 2022]) + } + + // Invoke the PowerShell script to get information about Visual Studio 2017 + async findVisualStudio2017() { + if (semver.gte(this.nodeSemver, '22.0.0')) { + this.addLog('not looking for VS2017 as it is only supported up to Node.js 21') + return null + } + return this.findNewVS([2017]) + } + + // Invoke the PowerShell script to get information about Visual Studio 2017 + // or newer installations + async findNewVS(supportedYears) { + const ps = path.join(process.env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe') + const csFile = path.join(__dirname, 'Find-VisualStudio.cs') + const psArgs = [ + '-ExecutionPolicy', + 'Unrestricted', + '-NoProfile', + '-Command', + "&{Add-Type -Path '" + csFile + "';" + '[VisualStudioConfiguration.Main]::PrintJson()}', + ] + + this.log.silly('Running', ps, psArgs) + const [err, stdout, stderr] = await this.execFile(ps, psArgs) + const parsedData = this.parseData(err, stdout, stderr, { checkIsArray: true }) + if (parsedData === null) { + return null + } + return this.processData(parsedData, supportedYears) + } + + // Parse the output of the PowerShell script, make sanity checks + parseData(err, stdout, stderr, sanityCheckOptions) { + const defaultOptions = { + checkIsArray: false, + } + + // Merging provided options with the default options + const sanityOptions = { ...defaultOptions, ...sanityCheckOptions } + + this.log.silly('PS stderr = %j', stderr) + + const failPowershell = (failureDetails) => { + this.addLog( + `could not use PowerShell to find Visual Studio 2017 or newer, try re-running with '--loglevel silly' for more details. \n + Failure details: ${failureDetails}`, + ) + return null + } + + if (err) { + this.log.silly('PS err = %j', err && (err.stack || err)) + return failPowershell(`${err}`.substring(0, 40)) + } + + let vsInfo + try { + vsInfo = JSON.parse(stdout) + } catch (e) { + this.log.silly('PS stdout = %j', stdout) + this.log.silly(e) + return failPowershell() + } + + if (sanityOptions.checkIsArray && !Array.isArray(vsInfo)) { + this.log.silly('PS stdout = %j', stdout) + return failPowershell('Expected array as output of the PS script') + } + return vsInfo + } + + // Process parsed data containing information about VS installations + // Look for the required parts, extract and output them back + processData(vsInfo, supportedYears) { + vsInfo = vsInfo.map((info) => { + this.log.silly(`processing installation: "${info.path}"`) + info.path = path.resolve(info.path) + const ret = this.getVersionInfo(info) + ret.path = info.path + ret.msBuild = this.getMSBuild(info, ret.versionYear) + ret.toolset = this.getToolset(info, ret.versionYear) + ret.sdk = this.getSDK(info) + return ret + }) + this.log.silly('vsInfo:', vsInfo) + + // Remove future versions or errors parsing version number + // Also remove any unsupported versions + vsInfo = vsInfo.filter((info) => { + if (info.versionYear && supportedYears.indexOf(info.versionYear) !== -1) { + return true + } + this.addLog(`${info.versionYear ? 'unsupported' : 'unknown'} version "${info.version}" found at "${info.path}"`) + return false + }) + + // Sort to place newer versions first + vsInfo.sort((a, b) => b.versionYear - a.versionYear) + + for (let i = 0; i < vsInfo.length; ++i) { + const info = vsInfo[i] + this.addLog(`checking VS${info.versionYear} (${info.version}) found ` + `at:\n"${info.path}"`) + + if (info.msBuild) { + this.addLog('- found "Visual Studio C++ core features"') + } else { + this.addLog('- "Visual Studio C++ core features" missing') + continue + } + + if (info.toolset) { + this.addLog(`- found VC++ toolset: ${info.toolset}`) + } else { + this.addLog('- missing any VC++ toolset') + continue + } + + if (info.sdk) { + this.addLog(`- found Windows SDK: ${info.sdk}`) + } else { + this.addLog('- missing any Windows SDK') + continue + } + + if (!this.checkConfigVersion(info.versionYear, info.path)) { + continue + } + + return info + } + + this.addLog('could not find a version of Visual Studio 2017 or newer to use') + return null + } + + // Helper - process version information + getVersionInfo(info) { + const match = /^(\d+)\.(\d+)(?:\..*)?/.exec(info.version) + if (!match) { + this.log.silly('- failed to parse version:', info.version) + return {} + } + this.log.silly('- version match = %j', match) + const ret = { + version: info.version, + versionMajor: parseInt(match[1], 10), + versionMinor: parseInt(match[2], 10), + } + if (ret.versionMajor === 15) { + ret.versionYear = 2017 + return ret + } + if (ret.versionMajor === 16) { + ret.versionYear = 2019 + return ret + } + if (ret.versionMajor === 17) { + ret.versionYear = 2022 + return ret + } + this.log.silly('- unsupported version:', ret.versionMajor) + return {} + } + + msBuildPathExists(path) { + return existsSync(path) + } + + // Helper - process MSBuild information + getMSBuild(info, versionYear) { + const pkg = 'Microsoft.VisualStudio.VC.MSBuild.Base' + const msbuildPath = path.join(info.path, 'MSBuild', 'Current', 'Bin', 'MSBuild.exe') + const msbuildPathArm64 = path.join(info.path, 'MSBuild', 'Current', 'Bin', 'arm64', 'MSBuild.exe') + if (info.packages.indexOf(pkg) !== -1) { + this.log.silly('- found VC.MSBuild.Base') + if (versionYear === 2017) { + return path.join(info.path, 'MSBuild', '15.0', 'Bin', 'MSBuild.exe') + } + if (versionYear === 2019) { + if (process.arch === 'arm64' && this.msBuildPathExists(msbuildPathArm64)) { + return msbuildPathArm64 + } else { + return msbuildPath + } + } + } + /** + * Visual Studio 2022 doesn't have the MSBuild package. + * Support for compiling _on_ ARM64 was added in MSVC 14.32.31326, + * so let's leverage it if the user has an ARM64 device. + */ + if (process.arch === 'arm64' && this.msBuildPathExists(msbuildPathArm64)) { + return msbuildPathArm64 + } else if (this.msBuildPathExists(msbuildPath)) { + return msbuildPath + } + return null + } + + // Helper - process toolset information + getToolset(info, versionYear) { + const vcToolsArm64 = 'VC.Tools.ARM64' + const pkgArm64 = `Microsoft.VisualStudio.Component.${vcToolsArm64}` + const vcToolsX64 = 'VC.Tools.x86.x64' + const pkgX64 = `Microsoft.VisualStudio.Component.${vcToolsX64}` + const express = 'Microsoft.VisualStudio.WDExpress' + + if (process.arch === 'arm64' && info.packages.includes(pkgArm64)) { + this.log.silly(`- found ${vcToolsArm64}`) + } else if (info.packages.includes(pkgX64)) { + if (process.arch === 'arm64') { + this.addLog( + `- found ${vcToolsX64} on ARM64 platform. Expect less performance and/or link failure with ARM64 binary.`, + ) + } else { + this.log.silly(`- found ${vcToolsX64}`) + } + } else if (info.packages.includes(express)) { + this.log.silly('- found Visual Studio Express (looking for toolset)') + } else { + return null + } + + if (versionYear === 2017) { + return 'v141' + } else if (versionYear === 2019) { + return 'v142' + } else if (versionYear === 2022) { + return 'v143' + } + this.log.silly('- invalid versionYear:', versionYear) + return null + } + + // Helper - process Windows SDK information + getSDK(info) { + const win8SDK = 'Microsoft.VisualStudio.Component.Windows81SDK' + const win10SDKPrefix = 'Microsoft.VisualStudio.Component.Windows10SDK.' + const win11SDKPrefix = 'Microsoft.VisualStudio.Component.Windows11SDK.' + + let Win10or11SDKVer = 0 + info.packages.forEach((pkg) => { + if (!pkg.startsWith(win10SDKPrefix) && !pkg.startsWith(win11SDKPrefix)) { + return + } + const parts = pkg.split('.') + if (parts.length > 5 && parts[5] !== 'Desktop') { + this.log.silly('- ignoring non-Desktop Win10/11SDK:', pkg) + return + } + const foundSdkVer = parseInt(parts[4], 10) + if (isNaN(foundSdkVer)) { + // Microsoft.VisualStudio.Component.Windows10SDK.IpOverUsb + this.log.silly('- failed to parse Win10/11SDK number:', pkg) + return + } + this.log.silly('- found Win10/11SDK:', foundSdkVer) + Win10or11SDKVer = Math.max(Win10or11SDKVer, foundSdkVer) + }) + + if (Win10or11SDKVer !== 0) { + return `10.0.${Win10or11SDKVer}.0` + } else if (info.packages.indexOf(win8SDK) !== -1) { + this.log.silly('- found Win8SDK') + return '8.1' + } + return null + } + + // Find an installation of Visual Studio 2015 to use + async findVisualStudio2015() { + if (semver.gte(this.nodeSemver, '19.0.0')) { + this.addLog('not looking for VS2015 as it is only supported up to Node.js 18') + return null + } + return this.findOldVS({ + version: '14.0', + versionMajor: 14, + versionMinor: 0, + versionYear: 2015, + toolset: 'v140', + }) + } + + // Find an installation of Visual Studio 2013 to use + async findVisualStudio2013() { + if (semver.gte(this.nodeSemver, '9.0.0')) { + this.addLog('not looking for VS2013 as it is only supported up to Node.js 8') + return null + } + return this.findOldVS({ + version: '12.0', + versionMajor: 12, + versionMinor: 0, + versionYear: 2013, + toolset: 'v120', + }) + } + + // Helper - common code for VS2013 and VS2015 + async findOldVS(info) { + const regVC7 = [ + 'HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7', + 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7', + ] + const regMSBuild = 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions' + + this.addLog(`looking for Visual Studio ${info.versionYear}`) + try { + let res = await this.regSearchKeys(regVC7, info.version, []) + const vsPath = path.resolve(res, '..') + this.addLog(`- found in "${vsPath}"`) + const msBuildRegOpts = process.arch === 'ia32' ? [] : ['/reg:32'] + + try { + res = await this.regSearchKeys([`${regMSBuild}\\${info.version}`], 'MSBuildToolsPath', msBuildRegOpts) + } catch (err) { + this.addLog('- could not find MSBuild in registry for this version') + return null + } + + const msBuild = path.join(res, 'MSBuild.exe') + this.addLog(`- MSBuild in "${msBuild}"`) + + if (!this.checkConfigVersion(info.versionYear, vsPath)) { + return null + } + + info.path = vsPath + info.msBuild = msBuild + info.sdk = null + return info + } catch (err) { + this.addLog('- not found') + return null + } + } + + // After finding a usable version of Visual Studio: + // - add it to validVersions to be displayed at the end if a specific + // version was requested and not found; + // - check if this is the version that was requested. + // - check if this matches the Visual Studio Command Prompt + checkConfigVersion(versionYear, vsPath) { + this.validVersions.push(versionYear) + this.validVersions.push(vsPath) + + if (this.configVersionYear && this.configVersionYear !== versionYear) { + this.addLog('- msvs_version does not match this version') + return false + } + if (this.configPath && path.relative(this.configPath, vsPath) !== '') { + this.addLog('- msvs_version does not point to this installation') + return false + } + if (this.envVcInstallDir && path.relative(this.envVcInstallDir, vsPath) !== '') { + this.addLog('- does not match this Visual Studio Command Prompt') + return false + } + + return true + } + + async execFile(exec, args) { + return await execFile(exec, args, { encoding: 'utf8' }) + } } -function VisualStudioFinder (configMsvsVersion, callback) { - this.configMsvsVersion = configMsvsVersion - this.callback = callback - this.errorLog = [] - this.validVersions = [] -} - -VisualStudioFinder.prototype = { - log: logWithPrefix(log, 'find VS'), - - regSearchKeys: regSearchKeys, - - // Logs a message at verbose level, but also saves it to be displayed later - // at error level if an error occurs. This should help diagnose the problem. - addLog: function addLog (message) { - this.log.verbose(message) - this.errorLog.push(message) - }, - - findVisualStudio: function findVisualStudio () { - this.configVersionYear = null - this.configPath = null - if (this.configMsvsVersion) { - this.addLog('msvs_version was set from command line or npm config') - if (this.configMsvsVersion.match(/^\d{4}$/)) { - this.configVersionYear = parseInt(this.configMsvsVersion, 10) - this.addLog( - `- looking for Visual Studio version ${this.configVersionYear}`) - } else { - this.configPath = path.resolve(this.configMsvsVersion) - this.addLog( - `- looking for Visual Studio installed in "${this.configPath}"`) - } - } else { - this.addLog('msvs_version not set from command line or npm config') - } - - if (process.env.VCINSTALLDIR) { - this.envVcInstallDir = - path.resolve(process.env.VCINSTALLDIR, '..') - this.addLog('running in VS Command Prompt, installation path is:\n' + - `"${this.envVcInstallDir}"\n- will only use this version`) - } else { - this.addLog('VCINSTALLDIR not set, not running in VS Command Prompt') - } - - this.findVisualStudio2017OrNewer((info) => { - if (info) { - return this.succeed(info) - } - this.findVisualStudio2015((info) => { - if (info) { - return this.succeed(info) - } - - this.fail() - }) - }) - }, - - succeed: function succeed (info) { - this.log.info(`using VS${info.versionYear} (${info.version}) found at:` + - `\n"${info.path}"` + - '\nrun with --verbose for detailed information') - process.nextTick(this.callback.bind(null, null, info)) - }, - - fail: function fail () { - if (this.configMsvsVersion && this.envVcInstallDir) { - this.errorLog.push( - 'msvs_version does not match this VS Command Prompt or the', - 'installation cannot be used.') - } else if (this.configMsvsVersion) { - // If msvs_version was specified but finding VS failed, print what would - // have been accepted - this.errorLog.push('') - if (this.validVersions) { - this.errorLog.push('valid versions for msvs_version:') - this.validVersions.forEach((version) => { - this.errorLog.push(`- "${version}"`) - }) - } else { - this.errorLog.push('no valid versions for msvs_version were found') - } - } - - const errorLog = this.errorLog.join('\n') - - // For Windows 80 col console, use up to the column before the one marked - // with X (total 79 chars including logger prefix, 62 chars usable here): - // X - const infoLog = [ - '**************************************************************', - 'You need to install the latest version of Visual Studio', - 'including the "Desktop development with C++" workload.', - 'For more information consult the documentation at:', - 'https://github.com/nodejs/node-gyp#on-windows', - '**************************************************************' - ].join('\n') - - this.log.error(`\n${errorLog}\n\n${infoLog}\n`) - process.nextTick(this.callback.bind(null, new Error( - 'Could not find any Visual Studio installation to use'))) - }, - - // Invoke the PowerShell script to get information about Visual Studio 2017 - // or newer installations - findVisualStudio2017OrNewer: function findVisualStudio2017OrNewer (cb) { - var ps = path.join(process.env.SystemRoot, 'System32', - 'WindowsPowerShell', 'v1.0', 'powershell.exe') - var csFile = path.join(__dirname, 'Find-VisualStudio.cs') - var psArgs = [ - '-ExecutionPolicy', - 'Unrestricted', - '-NoProfile', - '-Command', - '&{Add-Type -Path \'' + csFile + '\';' + '[VisualStudioConfiguration.Main]::PrintJson()}' - ] - - this.log.silly('Running', ps, psArgs) - var child = execFile(ps, psArgs, { encoding: 'utf8' }, - (err, stdout, stderr) => { - this.parseData(err, stdout, stderr, cb) - }) - child.stdin.end() - }, - - // Parse the output of the PowerShell script and look for an installation - // of Visual Studio 2017 or newer to use - parseData: function parseData (err, stdout, stderr, cb) { - this.log.silly('PS stderr = %j', stderr) - - const failPowershell = () => { - this.addLog( - 'could not use PowerShell to find Visual Studio 2017 or newer, try re-running with \'--loglevel silly\' for more details') - cb(null) - } - - if (err) { - this.log.silly('PS err = %j', err && (err.stack || err)) - return failPowershell() - } - - var vsInfo - try { - vsInfo = JSON.parse(stdout) - } catch (e) { - this.log.silly('PS stdout = %j', stdout) - this.log.silly(e) - return failPowershell() - } - - if (!Array.isArray(vsInfo)) { - this.log.silly('PS stdout = %j', stdout) - return failPowershell() - } - - vsInfo = vsInfo.map((info) => { - this.log.silly(`processing installation: "${info.path}"`) - info.path = path.resolve(info.path) - var ret = this.getVersionInfo(info) - ret.path = info.path - ret.msBuild = this.getMSBuild(info, ret.versionYear) - ret.toolset = this.getToolset(info, ret.versionYear) - ret.sdk = this.getSDK(info) - return ret - }) - this.log.silly('vsInfo:', vsInfo) - - // Remove future versions or errors parsing version number - vsInfo = vsInfo.filter((info) => { - if (info.versionYear) { - return true - } - this.addLog(`unknown version "${info.version}" found at "${info.path}"`) - return false - }) - - // Sort to place newer versions first - vsInfo.sort((a, b) => b.versionYear - a.versionYear) - - for (var i = 0; i < vsInfo.length; ++i) { - const info = vsInfo[i] - this.addLog(`checking VS${info.versionYear} (${info.version}) found ` + - `at:\n"${info.path}"`) - - if (info.msBuild) { - this.addLog('- found "Visual Studio C++ core features"') - } else { - this.addLog('- "Visual Studio C++ core features" missing') - continue - } - - if (info.toolset) { - this.addLog(`- found VC++ toolset: ${info.toolset}`) - } else { - this.addLog('- missing any VC++ toolset') - continue - } - - if (info.sdk) { - this.addLog(`- found Windows SDK: ${info.sdk}`) - } else { - this.addLog('- missing any Windows SDK') - continue - } - - if (!this.checkConfigVersion(info.versionYear, info.path)) { - continue - } - - return cb(info) - } - - this.addLog( - 'could not find a version of Visual Studio 2017 or newer to use') - cb(null) - }, - - // Helper - process version information - getVersionInfo: function getVersionInfo (info) { - const match = /^(\d+)\.(\d+)\..*/.exec(info.version) - if (!match) { - this.log.silly('- failed to parse version:', info.version) - return {} - } - this.log.silly('- version match = %j', match) - var ret = { - version: info.version, - versionMajor: parseInt(match[1], 10), - versionMinor: parseInt(match[2], 10) - } - if (ret.versionMajor === 15) { - ret.versionYear = 2017 - return ret - } - if (ret.versionMajor === 16) { - ret.versionYear = 2019 - return ret - } - if (ret.versionMajor === 17) { - ret.versionYear = 2022 - return ret - } - this.log.silly('- unsupported version:', ret.versionMajor) - return {} - }, - - // Helper - process MSBuild information - getMSBuild: function getMSBuild (info, versionYear) { - const pkg = 'Microsoft.VisualStudio.VC.MSBuild.Base' - const msbuildPath = path.join(info.path, 'MSBuild', 'Current', 'Bin', 'MSBuild.exe') - if (info.packages.indexOf(pkg) !== -1) { - this.log.silly('- found VC.MSBuild.Base') - if (versionYear === 2017) { - return path.join(info.path, 'MSBuild', '15.0', 'Bin', 'MSBuild.exe') - } - if (versionYear === 2019) { - return msbuildPath - } - } - // visual studio 2022 don't has msbuild pkg - if (fs.existsSync(msbuildPath)) { - return msbuildPath - } - return null - }, - - // Helper - process toolset information - getToolset: function getToolset (info, versionYear) { - const pkg = 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64' - const express = 'Microsoft.VisualStudio.WDExpress' - - if (info.packages.indexOf(pkg) !== -1) { - this.log.silly('- found VC.Tools.x86.x64') - } else if (info.packages.indexOf(express) !== -1) { - this.log.silly('- found Visual Studio Express (looking for toolset)') - } else { - return null - } - - if (versionYear === 2017) { - return 'v141' - } else if (versionYear === 2019) { - return 'v142' - } else if (versionYear === 2022) { - return 'v143' - } - this.log.silly('- invalid versionYear:', versionYear) - return null - }, - - // Helper - process Windows SDK information - getSDK: function getSDK (info) { - const win8SDK = 'Microsoft.VisualStudio.Component.Windows81SDK' - const win10SDKPrefix = 'Microsoft.VisualStudio.Component.Windows10SDK.' - - var Win10SDKVer = 0 - info.packages.forEach((pkg) => { - if (!pkg.startsWith(win10SDKPrefix)) { - return - } - const parts = pkg.split('.') - if (parts.length > 5 && parts[5] !== 'Desktop') { - this.log.silly('- ignoring non-Desktop Win10SDK:', pkg) - return - } - const foundSdkVer = parseInt(parts[4], 10) - if (isNaN(foundSdkVer)) { - // Microsoft.VisualStudio.Component.Windows10SDK.IpOverUsb - this.log.silly('- failed to parse Win10SDK number:', pkg) - return - } - this.log.silly('- found Win10SDK:', foundSdkVer) - Win10SDKVer = Math.max(Win10SDKVer, foundSdkVer) - }) - - if (Win10SDKVer !== 0) { - return `10.0.${Win10SDKVer}.0` - } else if (info.packages.indexOf(win8SDK) !== -1) { - this.log.silly('- found Win8SDK') - return '8.1' - } - return null - }, - - // Find an installation of Visual Studio 2015 to use - findVisualStudio2015: function findVisualStudio2015 (cb) { - return this.findOldVS({ - version: '14.0', - versionMajor: 14, - versionMinor: 0, - versionYear: 2015, - toolset: 'v140' - }, cb) - }, - - // Helper - common code for VS2013 and VS2015 - findOldVS: function findOldVS (info, cb) { - const regVC7 = ['HKLM\\Software\\Microsoft\\VisualStudio\\SxS\\VC7', - 'HKLM\\Software\\Wow6432Node\\Microsoft\\VisualStudio\\SxS\\VC7'] - const regMSBuild = 'HKLM\\Software\\Microsoft\\MSBuild\\ToolsVersions' - - this.addLog(`looking for Visual Studio ${info.versionYear}`) - this.regSearchKeys(regVC7, info.version, [], (err, res) => { - if (err) { - this.addLog('- not found') - return cb(null) - } - - const vsPath = path.resolve(res, '..') - this.addLog(`- found in "${vsPath}"`) - - const msBuildRegOpts = process.arch === 'ia32' ? [] : ['/reg:32'] - this.regSearchKeys([`${regMSBuild}\\${info.version}`], - 'MSBuildToolsPath', msBuildRegOpts, (err, res) => { - if (err) { - this.addLog( - '- could not find MSBuild in registry for this version') - return cb(null) - } - - const msBuild = path.join(res, 'MSBuild.exe') - this.addLog(`- MSBuild in "${msBuild}"`) - - if (!this.checkConfigVersion(info.versionYear, vsPath)) { - return cb(null) - } - - info.path = vsPath - info.msBuild = msBuild - info.sdk = null - cb(info) - }) - }) - }, - - // After finding a usable version of Visual Studio: - // - add it to validVersions to be displayed at the end if a specific - // version was requested and not found; - // - check if this is the version that was requested. - // - check if this matches the Visual Studio Command Prompt - checkConfigVersion: function checkConfigVersion (versionYear, vsPath) { - this.validVersions.push(versionYear) - this.validVersions.push(vsPath) - - if (this.configVersionYear && this.configVersionYear !== versionYear) { - this.addLog('- msvs_version does not match this version') - return false - } - if (this.configPath && - path.relative(this.configPath, vsPath) !== '') { - this.addLog('- msvs_version does not point to this installation') - return false - } - if (this.envVcInstallDir && - path.relative(this.envVcInstallDir, vsPath) !== '') { - this.addLog('- does not match this Visual Studio Command Prompt') - return false - } - - return true - } -} - -module.exports = findVisualStudio -module.exports.test = { - VisualStudioFinder: VisualStudioFinder, - findVisualStudio: findVisualStudio -} \ No newline at end of file +module.exports = VisualStudioFinder diff --git a/lib/import/util.js b/lib/import/util.js index 9e78a819..4afc10a0 100644 --- a/lib/import/util.js +++ b/lib/import/util.js @@ -1,64 +1,70 @@ 'use strict' const log = require('npmlog') -const execFile = require('child_process').execFile +const cp = require('child_process') const path = require('path') -function logWithPrefix (log, prefix) { - function setPrefix (logFunction) { - return (...args) => logFunction.apply(null, [ prefix, ...args ]) // eslint-disable-line - } - return { - silly: setPrefix(log.silly), - verbose: setPrefix(log.verbose), - info: setPrefix(log.info), - warn: setPrefix(log.warn), - error: setPrefix(log.error) - } +const execFile = async (...args) => + new Promise((resolve) => { + const child = cp.execFile(...args, (...a) => resolve(a)) + child.stdin.end() + }) + +function logWithPrefix(log, prefix) { + function setPrefix(logFunction) { + return (...args) => logFunction.apply(null, [prefix, ...args]) // eslint-disable-line + } + return { + silly: setPrefix(log.silly), + verbose: setPrefix(log.verbose), + info: setPrefix(log.info), + warn: setPrefix(log.warn), + error: setPrefix(log.error), + } } -function regGetValue (key, value, addOpts, cb) { - const outReValue = value.replace(/\W/g, '.') - const outRe = new RegExp(`^\\s+${outReValue}\\s+REG_\\w+\\s+(\\S.*)$`, 'im') - const reg = path.join(process.env.SystemRoot, 'System32', 'reg.exe') - const regArgs = ['query', key, '/v', value].concat(addOpts) +async function regGetValue(key, value, addOpts) { + const outReValue = value.replace(/\W/g, '.') + const outRe = new RegExp(`^\\s+${outReValue}\\s+REG_\\w+\\s+(\\S.*)$`, 'im') + const reg = path.join(process.env.SystemRoot, 'System32', 'reg.exe') + const regArgs = ['query', key, '/v', value].concat(addOpts) + + log.silly('reg', 'running', reg, regArgs) + const [err, stdout, stderr] = await execFile(reg, regArgs, { encoding: 'utf8' }) - log.silly('reg', 'running', reg, regArgs) - const child = execFile(reg, regArgs, { encoding: 'utf8' }, - function (err, stdout, stderr) { - log.silly('reg', 'reg.exe stdout = %j', stdout) - if (err || stderr.trim() !== '') { - log.silly('reg', 'reg.exe err = %j', err && (err.stack || err)) - log.silly('reg', 'reg.exe stderr = %j', stderr) - return cb(err, stderr) - } + log.silly('reg', 'reg.exe stdout = %j', stdout) + if (err || stderr.trim() !== '') { + log.silly('reg', 'reg.exe err = %j', err && (err.stack || err)) + log.silly('reg', 'reg.exe stderr = %j', stderr) + if (err) { + throw err + } + throw new Error(stderr) + } - const result = outRe.exec(stdout) - if (!result) { - log.silly('reg', 'error parsing stdout') - return cb(new Error('Could not parse output of reg.exe')) - } - log.silly('reg', 'found: %j', result[1]) - cb(null, result[1]) - }) - child.stdin.end() + const result = outRe.exec(stdout) + if (!result) { + log.silly('reg', 'error parsing stdout') + throw new Error('Could not parse output of reg.exe') + } + + log.silly('reg', 'found: %j', result[1]) + return result[1] } -function regSearchKeys (keys, value, addOpts, cb) { - var i = 0 - const search = () => { - log.silly('reg-search', 'looking for %j in %j', value, keys[i]) - regGetValue(keys[i], value, addOpts, (err, res) => { - ++i - if (err && i < keys.length) { return search() } - cb(err, res) - }) - } - search() +async function regSearchKeys(keys, value, addOpts) { + for (const key of keys) { + try { + return await regGetValue(key, value, addOpts) + } catch { + continue + } + } } module.exports = { - logWithPrefix: logWithPrefix, - regGetValue: regGetValue, - regSearchKeys: regSearchKeys -} \ No newline at end of file + logWithPrefix: logWithPrefix, + regGetValue: regGetValue, + regSearchKeys: regSearchKeys, + execFile: execFile, +} diff --git a/lib/index.js b/lib/index.js index 945db7c4..3773db00 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,12 +1,12 @@ -"use strict"; +'use strict' module.exports = { - BuildSystem: require("./buildSystem"), - CMLog: require("./cmLog"), - environment: require("./environment"), - TargetOptions: require("./targetOptions"), - Dist: require("./dist"), - CMake: require("./cMake"), - downloader: require("./downloader"), - Toolset: require("./toolset"), -}; + BuildSystem: require('./buildSystem'), + CMLog: require('./cmLog'), + environment: require('./environment'), + TargetOptions: require('./targetOptions'), + Dist: require('./dist'), + CMake: require('./cMake'), + downloader: require('./downloader'), + Toolset: require('./toolset'), +} diff --git a/lib/locateNAN.js b/lib/locateNAN.js index 0776c7d0..c8d0b0bf 100644 --- a/lib/locateNAN.js +++ b/lib/locateNAN.js @@ -1,65 +1,63 @@ -"use strict"; -const fs = require("fs-extra"); -const path = require("path"); +'use strict' +const fs = require('fs-extra') +const path = require('path') const isNANModule = async function (dir) { - const h = path.join(dir, "nan.h"); - try { - const stat = await fs.stat(h); - return stat.isFile(); - } - catch (e) { - return false; - } -}; + const h = path.join(dir, 'nan.h') + try { + const stat = await fs.stat(h) + return stat.isFile() + } catch (e) { + return false + } +} -async function isNodeJSProject (dir) { - const pjson = path.join(dir, "package.json"); - const node_modules = path.join(dir, "node_modules"); - try { - let stat = await fs.stat(pjson); - if (stat.isFile()) { - return true; - } - stat = await fs.stat(node_modules); - if (stat.isDirectory()) { - return true; - } - } - catch (e) { - // Ignore - } - return false; -}; +async function isNodeJSProject(dir) { + const pjson = path.join(dir, 'package.json') + const node_modules = path.join(dir, 'node_modules') + try { + let stat = await fs.stat(pjson) + if (stat.isFile()) { + return true + } + stat = await fs.stat(node_modules) + if (stat.isDirectory()) { + return true + } + } catch (e) { + // Ignore + } + return false +} -const locateNAN = module.exports = async function (projectRoot) { - if (locateNAN.__projectRoot) { - // Override for unit tests - projectRoot = locateNAN.__projectRoot; - } +const locateNAN = (module.exports = async function (projectRoot) { + if (locateNAN.__projectRoot) { + // Override for unit tests + projectRoot = locateNAN.__projectRoot + } - let result = await isNodeJSProject(projectRoot); - if (!result) { - return null; - } + let result = await isNodeJSProject(projectRoot) + if (!result) { + return null + } - const nanModulePath = path.join(projectRoot, "node_modules", "nan"); - result = await isNANModule(nanModulePath); - if (result) { - return nanModulePath; - } + const nanModulePath = path.join(projectRoot, 'node_modules', 'nan') + result = await isNANModule(nanModulePath) + if (result) { + return nanModulePath + } - // Goto upper level: - return await locateNAN(goUp(projectRoot)); -}; + // Goto upper level: + return await locateNAN(goUp(projectRoot)) +}) function goUp(dir) { - const items = dir.split(path.sep); - const scopeItem = items[items.length - 2]; - if (scopeItem && scopeItem[0] === "@") { - // skip scope - dir = path.join(dir, ".."); - } - dir = path.join(dir, "..", ".."); - return path.normalize(dir); + const items = dir.split(path.sep) + const scopeItem = items[items.length - 2] + if (scopeItem && scopeItem[0] === '@') { + // skip scope + dir = path.join(dir, '..') + } + dir = path.join(dir, '..', '..') + return path.normalize(dir) } diff --git a/lib/locateNodeApi.js b/lib/locateNodeApi.js index ee1fbf30..04c6f084 100644 --- a/lib/locateNodeApi.js +++ b/lib/locateNodeApi.js @@ -1,18 +1,18 @@ -"use strict"; -const path = require("path"); +'use strict' +const path = require('path') -const locateNodeApi = module.exports = async function (projectRoot) { - if (locateNodeApi.__projectRoot) { - // Override for unit tests - projectRoot = locateNodeApi.__projectRoot; - } +const locateNodeApi = (module.exports = async function (projectRoot) { + if (locateNodeApi.__projectRoot) { + // Override for unit tests + projectRoot = locateNodeApi.__projectRoot + } - try { - const tmpRequire = require('module').createRequire(path.join(projectRoot, 'package.json')) - const inc = tmpRequire('node-addon-api') - return inc.include.replace(/"/g, '') - } catch (e) { - // It most likely wasn't found - return null; - } -}; \ No newline at end of file + try { + const tmpRequire = require('module').createRequire(path.join(projectRoot, 'package.json')) + const inc = tmpRequire('node-addon-api') + return inc.include.replace(/"/g, '') + } catch (e) { + // It most likely wasn't found + return null + } +}) diff --git a/lib/npmConfig.js b/lib/npmConfig.js index 6b17badf..0fb6cf2d 100644 --- a/lib/npmConfig.js +++ b/lib/npmConfig.js @@ -1,31 +1,31 @@ -"use strict"; +'use strict' function getNpmConfig() { - const npmOptions = {}; - const npmConfigPrefix = 'npm_config_'; - Object.keys(process.env).forEach(function (name) { - if (name.indexOf(npmConfigPrefix) !== 0) { - return; - } - const value = process.env[name]; - name = name.substring(npmConfigPrefix.length); - if (name) { - npmOptions[name] = value; - } - }, this); + const npmOptions = {} + const npmConfigPrefix = 'npm_config_' + Object.keys(process.env).forEach(function (name) { + if (name.indexOf(npmConfigPrefix) !== 0) { + return + } + const value = process.env[name] + name = name.substring(npmConfigPrefix.length) + if (name) { + npmOptions[name] = value + } + }, this) - return npmOptions; + return npmOptions } module.exports = function (log) { - log.verbose("CFG", "Looking for NPM config."); - const options = getNpmConfig(); + log.verbose('CFG', 'Looking for NPM config.') + const options = getNpmConfig() - if (options) { - log.silly("CFG", "NPM options:", options); - }else { - log.verbose("CFG", "There are no NPM options available."); - } + if (options) { + log.silly('CFG', 'NPM options:', options) + } else { + log.verbose('CFG', 'There are no NPM options available.') + } - return options; -}; + return options +} diff --git a/lib/processHelpers.js b/lib/processHelpers.js index eb9ae574..1f237ad5 100644 --- a/lib/processHelpers.js +++ b/lib/processHelpers.js @@ -1,53 +1,53 @@ -"use strict"; -const spawn = require("child_process").spawn; -const execFile = require("child_process").execFile; +'use strict' +const spawn = require('child_process').spawn +const execFile = require('child_process').execFile const processHelpers = { - run: function (command, options) { - if (!options) options = {}; + run: function (command, options) { + if (!options) options = {} - return new Promise(function (resolve, reject) { - const env = Object.assign({}, process.env); - if (env.Path && env.PATH && env.Path !== env.PATH) { - env.PATH = env.Path + ';' + env.PATH; - delete env.Path; - } - const child = spawn(command[0], command.slice(1), { - stdio: options.silent ? "ignore" : "inherit", - env - }); - let ended = false; - child.on("error", function (e) { - if (!ended) { - reject(e); - ended = true; - } - }); - child.on("exit", function (code, signal) { - if (!ended) { - if (code === 0) { - resolve(); - } - else { - reject(new Error("Process terminated: " + code || signal)); - } - ended = true; - } - }); - }); - }, - execFile: function(command) { - return new Promise(function (resolve, reject) { - execFile(command[0], command.slice(1), function (err, stdout, stderr) { - if (err) { - reject(new Error(err.message + "\n" + (stdout || stderr))); - } - else { - resolve(stdout); - } - }); - }); - } -}; + return new Promise(function (resolve, reject) { + const env = Object.assign({}, process.env) + if (env.Path && env.PATH) { + if (env.Path !== env.PATH) { + env.PATH = env.Path + ';' + env.PATH + } + delete env.Path + } + const child = spawn(command[0], command.slice(1), { + stdio: options.silent ? 'ignore' : 'inherit', + env, + }) + let ended = false + child.on('error', function (e) { + if (!ended) { + reject(e) + ended = true + } + }) + child.on('exit', function (code, signal) { + if (!ended) { + if (code === 0) { + resolve() + } else { + reject(new Error('Process terminated: ' + code || signal)) + } + ended = true + } + }) + }) + }, + execFile: function (command) { + return new Promise(function (resolve, reject) { + execFile(command[0], command.slice(1), function (err, stdout, stderr) { + if (err) { + reject(new Error(err.message + '\n' + (stdout || stderr))) + } else { + resolve(stdout) + } + }) + }) + }, +} -module.exports = processHelpers; +module.exports = processHelpers diff --git a/lib/runtimePaths.js b/lib/runtimePaths.js index ccde788f..4fc9f177 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -1,86 +1,95 @@ -"use strict"; -const assert = require("assert"); -const semver = require("semver"); -const isPlainObject = require("lodash.isplainobject"); +'use strict' +const assert = require('assert') +const semver = require('semver') -const NODE_MIRROR = process.env.NVM_NODEJS_ORG_MIRROR || "https://nodejs.org/dist"; -const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || "https://artifacts.electronjs.org/headers/dist"; +const NODE_MIRROR = process.env.NVM_NODEJS_ORG_MIRROR || 'https://nodejs.org/dist' +const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || 'https://artifacts.electronjs.org/headers/dist' const runtimePaths = { - node: function (targetOptions) { - if (semver.lt(targetOptions.runtimeVersion, "4.0.0")) { - return { - externalPath: NODE_MIRROR + "/v" + targetOptions.runtimeVersion + "/", - winLibs: [{ - dir: targetOptions.isX64 ? "x64" : "", - name: targetOptions.runtime + ".lib" - }], - tarPath: targetOptions.runtime + "-v" + targetOptions.runtimeVersion + ".tar.gz", - headerOnly: false - }; - } - else { - return { - externalPath: NODE_MIRROR + "/v" + targetOptions.runtimeVersion + "/", - winLibs: [{ - dir: targetOptions.isX64 ? "win-x64" : "win-x86", - name: targetOptions.runtime + ".lib" - }], - tarPath: targetOptions.runtime + "-v" + targetOptions.runtimeVersion + "-headers.tar.gz", - headerOnly: true - }; - } - }, - nw: function (targetOptions) { - if (semver.gte(targetOptions.runtimeVersion, "0.13.0")) { - return { - externalPath: "https://node-webkit.s3.amazonaws.com/v" + targetOptions.runtimeVersion + "/", - winLibs: [ - { - dir: targetOptions.isX64 ? "x64" : "", - name: targetOptions.runtime + ".lib" - }, - { - dir: targetOptions.isX64 ? "x64" : "", - name: "node.lib" - } - ], - tarPath: "nw-headers-v" + targetOptions.runtimeVersion + ".tar.gz", - headerOnly: false - }; - } - return { - externalPath: "https://node-webkit.s3.amazonaws.com/v" + targetOptions.runtimeVersion + "/", - winLibs: [{ - dir: targetOptions.isX64 ? "x64" : "", - name: targetOptions.runtime + ".lib" - }], - tarPath: "nw-headers-v" + targetOptions.runtimeVersion + ".tar.gz", - headerOnly: false - }; - }, - electron: function (targetOptions) { - return { - externalPath: ELECTRON_MIRROR + "/v" + targetOptions.runtimeVersion + "/", - winLibs: [{ - dir: targetOptions.isX64 ? "x64" : "", - name: "node.lib" - }], - tarPath: "node" + "-v" + targetOptions.runtimeVersion + ".tar.gz", - headerOnly: semver.gte(targetOptions.runtimeVersion, '4.0.0-alpha') - }; - }, - get: function (targetOptions) { - assert(targetOptions && typeof targetOptions === 'object'); + node: function (targetOptions) { + if (semver.lt(targetOptions.runtimeVersion, '4.0.0')) { + return { + externalPath: NODE_MIRROR + '/v' + targetOptions.runtimeVersion + '/', + winLibs: [ + { + dir: targetOptions.isX64 ? 'x64' : '', + name: targetOptions.runtime + '.lib', + }, + ], + tarPath: targetOptions.runtime + '-v' + targetOptions.runtimeVersion + '.tar.gz', + headerOnly: false, + } + } else { + return { + externalPath: NODE_MIRROR + '/v' + targetOptions.runtimeVersion + '/', + winLibs: [ + { + dir: targetOptions.isArm64 ? 'win-arm64' : targetOptions.isX64 ? 'win-x64' : 'win-x86', + name: targetOptions.runtime + '.lib', + }, + ], + tarPath: targetOptions.runtime + '-v' + targetOptions.runtimeVersion + '-headers.tar.gz', + headerOnly: true, + } + } + }, + nw: function (targetOptions) { + if (semver.gte(targetOptions.runtimeVersion, '0.13.0')) { + return { + externalPath: 'https://node-webkit.s3.amazonaws.com/v' + targetOptions.runtimeVersion + '/', + winLibs: [ + { + dir: targetOptions.isX64 ? 'x64' : '', + name: targetOptions.runtime + '.lib', + }, + { + dir: targetOptions.isX64 ? 'x64' : '', + name: 'node.lib', + }, + ], + tarPath: 'nw-headers-v' + targetOptions.runtimeVersion + '.tar.gz', + headerOnly: false, + } + } + return { + externalPath: 'https://node-webkit.s3.amazonaws.com/v' + targetOptions.runtimeVersion + '/', + winLibs: [ + { + dir: targetOptions.isX64 ? 'x64' : '', + name: targetOptions.runtime + '.lib', + }, + ], + tarPath: 'nw-headers-v' + targetOptions.runtimeVersion + '.tar.gz', + headerOnly: false, + } + }, + electron: function (targetOptions) { + return { + externalPath: ELECTRON_MIRROR + '/v' + targetOptions.runtimeVersion + '/', + winLibs: [ + { + dir: targetOptions.isArm64 ? 'arm64' : targetOptions.isX64 ? 'x64' : '', + name: 'node.lib', + }, + ], + tarPath: 'node' + '-v' + targetOptions.runtimeVersion + '.tar.gz', + headerOnly: semver.gte(targetOptions.runtimeVersion, '4.0.0-alpha'), + } + }, + get: function (targetOptions) { + assert(targetOptions && typeof targetOptions === 'object') - const runtime = targetOptions.runtime; - const func = runtimePaths[runtime]; - let paths; - if (typeof func === 'function' && isPlainObject(paths = func(targetOptions))) { - return paths; - } - throw new Error("Unknown runtime: " + runtime); - } -}; + const runtime = targetOptions.runtime + const func = runtimePaths[runtime] + let paths + if (typeof func === 'function') { + paths = func(targetOptions) + if (paths && typeof paths === 'object') { + return paths + } + } + throw new Error('Unknown runtime: ' + runtime) + }, +} -module.exports = runtimePaths; +module.exports = runtimePaths diff --git a/lib/targetOptions.js b/lib/targetOptions.js index b611f12d..70c3d135 100644 --- a/lib/targetOptions.js +++ b/lib/targetOptions.js @@ -1,42 +1,33 @@ -"use strict"; +'use strict' -const environment = require("./environment"); +const environment = require('./environment') -function TargetOptions(options) { - this.options = options || {}; -} +class TargetOptions { + get arch() { + return this.options.arch || environment.arch + } + get isX86() { + return this.arch === 'ia32' || this.arch === 'x86' + } + get isX64() { + return this.arch === 'x64' + } + get isArm() { + return this.arch === 'arm' + } + get isArm64() { + return this.arch === 'arm64' + } + get runtime() { + return this.options.runtime || environment.runtime + } + get runtimeVersion() { + return this.options.runtimeVersion || environment.runtimeVersion + } -Object.defineProperties(TargetOptions.prototype, { - arch: { - get: function () { - return this.options.arch || environment.arch; - } - }, - isX86: { - get: function () { - return this.arch === "ia32" || this.arch === "x86"; - } - }, - isX64: { - get: function () { - return this.arch === "x64"; - } - }, - isArm: { - get: function () { - return this.arch === "arm"; - } - }, - runtime: { - get: function () { - return this.options.runtime || environment.runtime; - } - }, - runtimeVersion: { - get: function () { - return this.options.runtimeVersion || environment.runtimeVersion; - } - } -}); + constructor(options) { + this.options = options || {} + } +} -module.exports = TargetOptions; \ No newline at end of file +module.exports = TargetOptions diff --git a/lib/toolset.js b/lib/toolset.js index 9156feb5..3d9c034d 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -1,227 +1,224 @@ -"use strict"; -const TargetOptions = require("./targetOptions"); -const environment = require("./environment"); -const assert = require("assert"); -const CMLog = require("./cmLog"); -const util = require('util') -const findVisualStudioRaw = require('./import/find-visualstudio'); - -const findVisualStudio = util.promisify(findVisualStudioRaw) - -function Toolset(options) { - this.options = options || {}; - this.targetOptions = new TargetOptions(this.options); - this.generator = options.generator; - this.toolset = options.toolset; - this.platform = options.platform; - this.target = options.target; - this.cCompilerPath = options.cCompilerPath; - this.cppCompilerPath = options.cppCompilerPath; - this.compilerFlags = []; - this.linkerFlags = []; - this.makePath = null; - this.log = new CMLog(this.options); - this._initialized = false; +'use strict' +const TargetOptions = require('./targetOptions') +const environment = require('./environment') +const assert = require('assert') +const CMLog = require('./cmLog') +const { findVisualStudio } = environment.isWin ? require('./import/find-visualstudio') : {} + +class Toolset { + constructor(options) { + this.options = options || {} + this.targetOptions = new TargetOptions(this.options) + this.generator = options.generator + this.toolset = options.toolset + this.platform = options.platform + this.target = options.target + this.cCompilerPath = options.cCompilerPath + this.cppCompilerPath = options.cppCompilerPath + this.compilerFlags = [] + this.linkerFlags = [] + this.makePath = null + this.log = new CMLog(this.options) + this._initialized = false + } + async initialize(install) { + if (!this._initialized) { + if (environment.isWin) { + await this.initializeWin(install) + } else { + this.initializePosix(install) + } + this._initialized = true + } + } + initializePosix(install) { + if (!this.cCompilerPath || !this.cppCompilerPath) { + // 1: Compiler + if (!environment.isGPPAvailable && !environment.isClangAvailable) { + if (environment.isOSX) { + throw new Error( + "C++ Compiler toolset is not available. Install Xcode Commandline Tools from Apple Dev Center, or install Clang with homebrew by invoking: 'brew install llvm --with-clang --with-asan'.", + ) + } else { + throw new Error( + "C++ Compiler toolset is not available. Install proper compiler toolset with your package manager, eg. 'sudo apt-get install g++'.", + ) + } + } + + if (this.options.preferClang && environment.isClangAvailable) { + if (install) { + this.log.info('TOOL', 'Using clang++ compiler, because preferClang option is set, and clang++ is available.') + } + this.cppCompilerPath = this.cppCompilerPath || 'clang++' + this.cCompilerPath = this.cCompilerPath || 'clang' + } else if (this.options.preferGnu && environment.isGPPAvailable) { + if (install) { + this.log.info('TOOL', 'Using g++ compiler, because preferGnu option is set, and g++ is available.') + } + this.cppCompilerPath = this.cppCompilerPath || 'g++' + this.cCompilerPath = this.cCompilerPath || 'gcc' + } + } + // if it's already set because of options... + if (this.generator) { + if (install) { + this.log.info('TOOL', 'Using ' + this.generator + ' generator, as specified from commandline.') + } + } + + // 2: Generator + else if (environment.isOSX) { + if (this.options.preferXcode) { + if (install) { + this.log.info('TOOL', 'Using Xcode generator, because preferXcode option is set.') + } + this.generator = 'Xcode' + } else if (this.options.preferMake && environment.isMakeAvailable) { + if (install) { + this.log.info( + 'TOOL', + 'Using Unix Makefiles generator, because preferMake option is set, and make is available.', + ) + } + this.generator = 'Unix Makefiles' + } else if (environment.isNinjaAvailable) { + if (install) { + this.log.info('TOOL', 'Using Ninja generator, because ninja is available.') + } + this.generator = 'Ninja' + } else { + if (install) { + this.log.info('TOOL', 'Using Unix Makefiles generator.') + } + this.generator = 'Unix Makefiles' + } + } else { + if (this.options.preferMake && environment.isMakeAvailable) { + if (install) { + this.log.info( + 'TOOL', + 'Using Unix Makefiles generator, because preferMake option is set, and make is available.', + ) + } + this.generator = 'Unix Makefiles' + } else if (environment.isNinjaAvailable) { + if (install) { + this.log.info('TOOL', 'Using Ninja generator, because ninja is available.') + } + this.generator = 'Ninja' + } else { + if (install) { + this.log.info('TOOL', 'Using Unix Makefiles generator.') + } + this.generator = 'Unix Makefiles' + } + } + + // 3: Flags + if (environment.isOSX) { + if (install) { + this.log.verbose('TOOL', 'Setting default OSX compiler flags.') + } + + this.compilerFlags.push('-D_DARWIN_USE_64_BIT_INODE=1') + this.compilerFlags.push('-D_LARGEFILE_SOURCE') + this.compilerFlags.push('-D_FILE_OFFSET_BITS=64') + this.linkerFlags.push('-undefined dynamic_lookup') + } + + this.compilerFlags.push('-DBUILDING_NODE_EXTENSION') + + // 4: Build target + if (this.options.target) { + this.log.info('TOOL', 'Building only the ' + this.options.target + ' target, as specified from the command line.') + } + } + async initializeWin(install) { + if (!this.generator) { + const foundVsInfo = await this._getTopSupportedVisualStudioGenerator() + if (foundVsInfo) { + if (install) { + this.log.info('TOOL', `Using ${foundVsInfo.generator} generator.`) + } + this.generator = foundVsInfo.generator + + const isAboveVS16 = foundVsInfo.versionMajor >= 16 + + // The CMake Visual Studio Generator does not support the Win64 or ARM suffix on + // the generator name. Instead the generator platform must be set explicitly via + // the platform parameter + if (!this.platform && isAboveVS16) { + switch (this.targetOptions.arch) { + case 'ia32': + case 'x86': + this.platform = 'Win32' + break + case 'x64': + this.platform = 'x64' + break + case 'arm': + this.platform = 'ARM' + break + case 'arm64': + this.platform = 'ARM64' + break + default: + this.log.warn('TOOL', 'Unknown NodeJS architecture: ' + this.targetOptions.arch) + break + } + } + } else { + throw new Error('There is no Visual C++ compiler installed. Install Visual C++ Build Toolset or Visual Studio.') + } + } else { + // if it's already set because of options... + if (install) { + this.log.info('TOOL', 'Using ' + this.options.generator + ' generator, as specified from commandline.') + } + } + + this.linkerFlags.push('/DELAYLOAD:NODE.EXE') + + if (this.targetOptions.isX86) { + if (install) { + this.log.verbose('TOOL', 'Setting SAFESEH:NO linker flag.') + } + this.linkerFlags.push('/SAFESEH:NO') + } + } + async _getTopSupportedVisualStudioGenerator() { + const CMake = require('./cMake') + assert(environment.isWin) + + const selectedVs = await findVisualStudio(environment.runtimeVersion, this.options.msvsVersion) + if (!selectedVs) return null + + const list = await CMake.getGenerators(this.options, this.log) + for (const gen of list) { + const found = gen.startsWith(`Visual Studio ${selectedVs.versionMajor}`) + if (!found) { + continue + } + + // unlike previous versions "Visual Studio 16 2019" and onwards don't end with arch name + const isAboveVS16 = selectedVs.versionMajor >= 16 + if (!isAboveVS16) { + const is64Bit = gen.endsWith('Win64') + if ((this.targetOptions.isX86 && is64Bit) || (this.targetOptions.isX64 && !is64Bit)) { + continue + } + } + + return { + ...selectedVs, + generator: gen, + } + } + + // Nothing matched + return null + } } -Toolset.prototype.initialize = async function (install) { - if (!this._initialized) { - if (environment.isWin) { - await this.initializeWin(install); - } - else { - this.initializePosix(install); - } - this._initialized = true; - } -}; - -Toolset.prototype.initializePosix = function (install) { - if (!this.cCompilerPath || !this.cppCompilerPath) { - // 1: Compiler - if (!environment.isGPPAvailable && !environment.isClangAvailable) { - if (environment.isOSX) { - throw new Error("C++ Compiler toolset is not available. Install Xcode Commandline Tools from Apple Dev Center, or install Clang with homebrew by invoking: 'brew install llvm --with-clang --with-asan'."); - } - else { - throw new Error("C++ Compiler toolset is not available. Install proper compiler toolset with your package manager, eg. 'sudo apt-get install g++'."); - } - } - - if (this.options.preferClang && environment.isClangAvailable) { - if (install) { - this.log.info("TOOL", "Using clang++ compiler, because preferClang option is set, and clang++ is available."); - } - this.cppCompilerPath = this.cppCompilerPath || "clang++"; - this.cCompilerPath = this.cCompilerPath || "clang"; - } - else if (this.options.preferGnu && environment.isGPPAvailable) { - if (install) { - this.log.info("TOOL", "Using g++ compiler, because preferGnu option is set, and g++ is available."); - } - this.cppCompilerPath = this.cppCompilerPath || "g++"; - this.cCompilerPath = this.cCompilerPath || "gcc"; - } - } - // if it's already set because of options... - if (this.generator) { - if (install) { - this.log.info("TOOL", "Using " + this.generator + " generator, as specified from commandline."); - } - } - // 2: Generator - else if (environment.isOSX) { - if (this.options.preferXcode) { - if (install) { - this.log.info("TOOL", "Using Xcode generator, because preferXcode option is set."); - } - this.generator = "Xcode"; - } - else if (this.options.preferMake && environment.isMakeAvailable) { - if (install) { - this.log.info("TOOL", "Using Unix Makefiles generator, because preferMake option is set, and make is available."); - } - this.generator = "Unix Makefiles"; - } - else if (environment.isNinjaAvailable) { - if (install) { - this.log.info("TOOL", "Using Ninja generator, because ninja is available."); - } - this.generator = "Ninja"; - } - else { - if (install) { - this.log.info("TOOL", "Using Unix Makefiles generator."); - } - this.generator = "Unix Makefiles"; - } - } - else { - if (this.options.preferMake && environment.isMakeAvailable) { - if (install) { - this.log.info("TOOL", "Using Unix Makefiles generator, because preferMake option is set, and make is available."); - } - this.generator = "Unix Makefiles"; - } - else if (environment.isNinjaAvailable) { - if (install) { - this.log.info("TOOL", "Using Ninja generator, because ninja is available."); - } - this.generator = "Ninja"; - } - else { - if (install) { - this.log.info("TOOL", "Using Unix Makefiles generator."); - } - this.generator = "Unix Makefiles"; - } - } - - // 3: Flags - if (environment.isOSX) { - if (install) { - this.log.verbose("TOOL", "Setting default OSX compiler flags."); - } - - this.compilerFlags.push("-D_DARWIN_USE_64_BIT_INODE=1"); - this.compilerFlags.push("-D_LARGEFILE_SOURCE"); - this.compilerFlags.push("-D_FILE_OFFSET_BITS=64"); - this.linkerFlags.push("-undefined dynamic_lookup"); - } - - this.compilerFlags.push("-DBUILDING_NODE_EXTENSION"); - - // 4: Build target - if (this.options.target) { - this.log.info("TOOL", "Building only the " + this.options.target + " target, as specified from the command line."); - } -}; - -Toolset.prototype.initializeWin = async function (install) { - if (!this.generator) { - const foundVsInfo = await this._getTopSupportedVisualStudioGenerator(); - if (foundVsInfo) { - if (install) { - this.log.info("TOOL", `Using ${foundVsInfo.generator} generator.`); - } - this.generator = foundVsInfo.generator; - - const isAboveVS16 = foundVsInfo.versionMajor >= 16; - - // The CMake Visual Studio Generator does not support the Win64 or ARM suffix on - // the generator name. Instead the generator platform must be set explicitly via - // the platform parameter - if (!this.platform && isAboveVS16) { - switch(this.targetOptions.arch) { - case "ia32": - case "x86": - this.platform = "Win32"; - break; - case "x64": - this.platform = "x64"; - break; - case "arm": - this.platform = "ARM"; - break; - case "arm64": - this.platform = "ARM64"; - break; - default: - this.log.warn("TOOL", "Unknown NodeJS architecture: " + this.targetOptions.arch); - break; - } - } - } else { - throw new Error("There is no Visual C++ compiler installed. Install Visual C++ Build Toolset or Visual Studio."); - } - } else { - // if it's already set because of options... - if (install) { - this.log.info("TOOL", "Using " + this.options.generator + " generator, as specified from commandline."); - } - } - - this.linkerFlags.push("/DELAYLOAD:NODE.EXE"); - - if (this.targetOptions.isX86) { - if (install) { - this.log.verbose("TOOL", "Setting SAFESEH:NO linker flag."); - } - this.linkerFlags.push("/SAFESEH:NO"); - } -}; - -Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () { - const CMake = require("./cMake"); - assert(environment.isWin); - - const selectedVs = await findVisualStudio(this.options.msvsVersion) - if (!selectedVs) return null; - - const list = await CMake.getGenerators(this.options, this.log); - for (const gen of list) { - const found = gen.startsWith(`Visual Studio ${selectedVs.versionMajor}`) - if (!found) { - continue; - } - - // unlike previous versions "Visual Studio 16 2019" and onwards don't end with arch name - const isAboveVS16 = selectedVs.versionMajor >= 16; - if (!isAboveVS16) { - const is64Bit = gen.endsWith("Win64"); - if ((this.targetOptions.isX86 && is64Bit) || (this.targetOptions.isX64 && !is64Bit)) { - continue; - } - } - - return { - ...selectedVs, - generator: gen - } - } - - // Nothing matched - return null; -}; - -module.exports = Toolset; +module.exports = Toolset diff --git a/package.json b/package.json index eb6849f7..aa131376 100644 --- a/package.json +++ b/package.json @@ -1,74 +1,77 @@ -{ - "name": "cmake-js", - "description": "CMake.js - a Node.js native addon build tool", - "license": "MIT", - "keywords": [ - "native", - "addon", - "module", - "c", - "c++", - "bindings", - "build", - "buildtools", - "cmake", - "nw.js", - "electron", - "boost", - "nan", - "napi", - "node-api", - "node-addon-api" - ], - "main": "lib", - "version": "7.0.0", - "author": "Gábor Mező aka unbornchikken", - "maintainers": [ - { - "name": "Julian Waller", - "email": "git@julusian.co.uk", - "url": "https://github.com/julusian/" - } - ], - "repository": { - "type": "git", - "url": "git://github.com/cmake-js/cmake-js.git" - }, - "bin": { - "cmake-js": "./bin/cmake-js" - }, - "engines": { - "node": ">= 14.15.0" - }, - "dependencies": { - "axios": "^0.27.2", - "debug": "^4", - "fs-extra": "^10.1.0", - "lodash.isplainobject": "^4.0.6", - "memory-stream": "^1.0.0", - "node-api-headers": "^0.0.1", - "npmlog": "^6.0.2", - "rc": "^1.2.7", - "semver": "^7.3.8", - "tar": "^6.1.11", - "url-join": "^4.0.1", - "which": "^2.0.2", - "yargs": "^17.6.0" - }, - "devDependencies": { - "mocha": "*", - "nan": "^2.16.0", - "node-addon-api": "^5.0.0" - }, - "scripts": { - "test": "mocha tests", - "lint": "eslint lib bin/cmake-js tests" - }, - "files": [ - "lib", - "bin", - "*.md", - "bindings.js", - "bindings.d.ts" - ] -} +{ + "name": "cmake-js", + "description": "CMake.js - a Node.js native addon build tool", + "license": "MIT", + "keywords": [ + "native", + "addon", + "module", + "c", + "c++", + "bindings", + "build", + "buildtools", + "cmake", + "nw.js", + "electron", + "boost", + "nan", + "napi", + "node-api", + "node-addon-api" + ], + "main": "lib", + "version": "7.3.1", + "author": "Gábor Mező aka unbornchikken", + "maintainers": [ + { + "name": "Julian Waller", + "email": "git@julusian.co.uk", + "url": "https://github.com/julusian/" + } + ], + "repository": { + "type": "git", + "url": "git://github.com/cmake-js/cmake-js.git" + }, + "bin": { + "cmake-js": "./bin/cmake-js" + }, + "engines": { + "node": ">= 14.15.0" + }, + "dependencies": { + "axios": "^1.6.5", + "debug": "^4", + "fs-extra": "^11.2.0", + "memory-stream": "^1.0.0", + "node-api-headers": "^1.1.0", + "npmlog": "^6.0.2", + "rc": "^1.2.7", + "semver": "^7.5.4", + "tar": "^6.2.0", + "url-join": "^4.0.1", + "which": "^2.0.2", + "yargs": "^17.7.2" + }, + "devDependencies": { + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "mocha": "*", + "nan": "^2.22.2", + "node-addon-api": "^6.1.0", + "prettier": "^3.2.2" + }, + "scripts": { + "test": "mocha tests", + "lint": "eslint lib bin/cmake-js tests" + }, + "files": [ + "lib", + "bin", + "*.md", + "bindings.js", + "bindings.d.ts" + ], + "packageManager": "yarn@1.22.22+sha256.c17d3797fb9a9115bf375e31bfd30058cac6bc9c3b8807a3d8cb2094794b51ca" +} diff --git a/tests/es6/buildSystem.js b/tests/es6/buildSystem.js index b6989a8d..1fe4df42 100644 --- a/tests/es6/buildSystem.js +++ b/tests/es6/buildSystem.js @@ -1,50 +1,62 @@ -"use strict"; -/* global describe,it,before */ - -const assert = require("assert"); -const lib = require("../../"); -const locateNAN = require("../../lib/locateNAN") -const CMake = lib.CMake; -const path = require("path"); -const log = require("npmlog"); -const testRunner = require("./testRunner"); -const testCases = require("./testCases"); - -describe("BuildSystem", function () { - this.timeout(300000); - - before(function() { - if (process.env.UT_LOG_LEVEL) { - log.level = process.env.UT_LOG_LEVEL; - log.resume(); - } - locateNAN.__projectRoot = path.resolve(path.join(__dirname, "../../")); - }); - - after(function() { - locateNAN.__projectRoot = undefined; - }); - - describe("Build with various options", function() { - testRunner.runCase(testCases.buildPrototypeWithDirectoryOption); - }); - - it("should provide list of generators", async function () { - const gens = await CMake.getGenerators(); - assert(Array.isArray(gens)); - assert(gens.length > 0); - assert.equal(gens.filter(function (g) { return g.length; }).length, gens.length); - }); - - it("should rebuild prototype if cwd is the source directory", async function () { - await testCases.buildPrototype2WithCWD(); - }); - - it("should run with old GNU compilers", async function () { - await testCases.shouldConfigurePreC11Properly(); - }); - - it("should configure with custom option", async function () { - await testCases.configureWithCustomOptions(); - }); -}); +'use strict' + +const assert = require('assert') +const lib = require('../../') +const locateNAN = require('../../lib/locateNAN') +const CMake = lib.CMake +const path = require('path') +const log = require('npmlog') +const testRunner = require('./testRunner') +const testCases = require('./testCases') + +describe('BuildSystem', function () { + this.timeout(300000) + + before(function () { + if (process.env.UT_LOG_LEVEL) { + log.level = process.env.UT_LOG_LEVEL + log.resume() + } + locateNAN.__projectRoot = path.resolve(path.join(__dirname, '../../')) + }) + + after(function () { + locateNAN.__projectRoot = undefined + }) + + describe('Build with various options', function () { + testRunner.runCase(testCases.buildPrototypeWithDirectoryOption) + }) + + it('should provide list of generators', async function () { + const gens = await CMake.getGenerators() + assert(Array.isArray(gens)) + assert(gens.length > 0) + assert.equal( + gens.filter(function (g) { + return g.length + }).length, + gens.length, + ) + }) + + it('should rebuild prototype if cwd is the source directory', async function () { + await testCases.buildPrototype2WithCWD() + }) + + it('should build prototpye with nodeapi', async function () { + await testCases.buildPrototypeNapi() + }) + + it('should run with old GNU compilers', async function () { + await testCases.shouldConfigurePreC11Properly() + }) + + it('should configure with custom option', async function () { + await testCases.configureWithCustomOptions() + }) + + it('should forward extra arguments to CMake', async function () { + await testCases.shouldForwardExtraCMakeArgs() + }) +}) diff --git a/tests/es6/dist.js b/tests/es6/dist.js index fe3ff610..f9822136 100644 --- a/tests/es6/dist.js +++ b/tests/es6/dist.js @@ -1,26 +1,23 @@ -"use strict"; -/* global describe,it */ +'use strict' -const fs = require("fs-extra"); -const Dist = require("../../").Dist; -const assert = require("assert"); +const fs = require('fs-extra') +const Dist = require('../../').Dist +const assert = require('assert') -const testDownload = process.env.TEST_DOWNLOAD === "1"; +const testDownload = process.env.TEST_DOWNLOAD === '1' -describe("dist", function () { - it("should download dist files if needed", async function () { - this.timeout(60000); +describe('dist', function () { + it('should download dist files if needed', async function () { + this.timeout(60000) - const dist = new Dist(); - if (testDownload) { - await fs.remove(dist.internalPath); - assert(dist.downloaded === false); - await dist.ensureDownloaded(); - assert(dist.downloaded); - } - else { - await dist.ensureDownloaded(); - } - }); - -}); + const dist = new Dist() + if (testDownload) { + await fs.remove(dist.internalPath) + assert(dist.downloaded === false) + await dist.ensureDownloaded() + assert(dist.downloaded) + } else { + await dist.ensureDownloaded() + } + }) +}) diff --git a/tests/es6/index.js b/tests/es6/index.js index 3bfa0254..c7af0097 100644 --- a/tests/es6/index.js +++ b/tests/es6/index.js @@ -1,4 +1,4 @@ -"use strict"; -require("./dist"); -require("./buildSystem"); -require("./locateNAN"); +'use strict' +require('./dist') +require('./buildSystem') +require('./locateNAN') diff --git a/tests/es6/locateNAN.js b/tests/es6/locateNAN.js index aa633db4..661742f8 100644 --- a/tests/es6/locateNAN.js +++ b/tests/es6/locateNAN.js @@ -1,9 +1,8 @@ -"use strict"; -/* global describe,it */ +'use strict' -const locateNAN = require("../../lib/locateNAN"); -const path = require("path"); -const assert = require("assert"); +const locateNAN = require('../../lib/locateNAN') +const path = require('path') +const assert = require('assert') /* @@ -16,29 +15,28 @@ fixtures/project */ -describe("locateNAN", function () { - const PROJECT_DIR = path.resolve(__dirname, "..", "fixtures", "project"); - const NAN_DIR = path.join(PROJECT_DIR, "node_modules", "nan"); +describe('locateNAN', function () { + const PROJECT_DIR = path.resolve(__dirname, '..', 'fixtures', 'project') + const NAN_DIR = path.join(PROJECT_DIR, 'node_modules', 'nan') - it("should locate NAN from dependency", async function () { - const dir = path.join(PROJECT_DIR, "node_modules", "dep-1"); - - const nan = await locateNAN(dir); - assert.equal(nan, NAN_DIR); - }); + it('should locate NAN from dependency', async function () { + const dir = path.join(PROJECT_DIR, 'node_modules', 'dep-1') - it("should locate NAN from nested dependency", async function () { - const dir = path.join(PROJECT_DIR, "node_modules", "dep-1", "node_modules", "dep-3"); + const nan = await locateNAN(dir) + assert.equal(nan, NAN_DIR) + }) - const nan = await locateNAN(dir); - assert.equal(nan, NAN_DIR); - - }); + it('should locate NAN from nested dependency', async function () { + const dir = path.join(PROJECT_DIR, 'node_modules', 'dep-1', 'node_modules', 'dep-3') - it("should locate NAN from scoped dependency", async function () { - const dir = path.join(PROJECT_DIR, "node_modules", "@scope", "dep-2"); + const nan = await locateNAN(dir) + assert.equal(nan, NAN_DIR) + }) - const nan = await locateNAN(dir); - assert.equal(nan, NAN_DIR); - }); -}); + it('should locate NAN from scoped dependency', async function () { + const dir = path.join(PROJECT_DIR, 'node_modules', '@scope', 'dep-2') + + const nan = await locateNAN(dir) + assert.equal(nan, NAN_DIR) + }) +}) diff --git a/tests/es6/prototype-napi/CMakeLists.txt b/tests/es6/prototype-napi/CMakeLists.txt new file mode 100644 index 00000000..4579529f --- /dev/null +++ b/tests/es6/prototype-napi/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.15) +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0042 NEW) + +project (addon_napi) + +if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") +endif() + +include_directories(${CMAKE_JS_INC}) + +add_library(${PROJECT_NAME} SHARED src/addon.cpp) + +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") + +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) + +if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET) + # Generate node.lib + execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) +endif() + +if (NODE_RUNTIME STREQUAL "node" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 20) + set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) +elseif (NODE_RUNTIME STREQUAL "electron" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 32) + set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) + elseif (NODE_RUNTIME STREQUAL "electron" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 29) + set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) +endif() diff --git a/tests/es6/prototype-napi/package.json b/tests/es6/prototype-napi/package.json new file mode 100644 index 00000000..ace6167f --- /dev/null +++ b/tests/es6/prototype-napi/package.json @@ -0,0 +1,5 @@ +{ + "binary": { + "napi_versions": 7 + } +} diff --git a/tests/es6/prototype-napi/src/addon.cpp b/tests/es6/prototype-napi/src/addon.cpp new file mode 100644 index 00000000..6e777a9b --- /dev/null +++ b/tests/es6/prototype-napi/src/addon.cpp @@ -0,0 +1,14 @@ +#include + +Napi::Value Mul(const Napi::CallbackInfo &info) +{ + double value = info[0].As().DoubleValue() * info[1].As().DoubleValue(); + return Napi::Number::New(info.Env(), value); +} + +Napi::Object Init(Napi::Env env, Napi::Object exports) +{ + exports.Set("mul", Napi::Function::New(env, Mul)); +} + +NODE_API_MODULE(addon_napi, Init) diff --git a/tests/es6/prototype/CMakeLists.txt b/tests/es6/prototype/CMakeLists.txt index 4c29d9bc..21c78254 100644 --- a/tests/es6/prototype/CMakeLists.txt +++ b/tests/es6/prototype/CMakeLists.txt @@ -1,8 +1,10 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.15) +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0042 NEW) project (addon) -if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")) +if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") endif() @@ -13,3 +15,11 @@ add_library(addon SHARED src/addon.cpp) set_target_properties(addon PROPERTIES PREFIX "" SUFFIX ".node") target_link_libraries(addon ${CMAKE_JS_LIB}) + +if (NODE_RUNTIME STREQUAL "node" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 20) + set_property(TARGET addon PROPERTY CXX_STANDARD 17) +elseif (NODE_RUNTIME STREQUAL "electron" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 32) + set_property(TARGET addon PROPERTY CXX_STANDARD 20) +elseif (NODE_RUNTIME STREQUAL "electron" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 29) + set_property(TARGET addon PROPERTY CXX_STANDARD 17) +endif() \ No newline at end of file diff --git a/tests/es6/prototype2/CMakeLists.txt b/tests/es6/prototype2/CMakeLists.txt index 0189ddc4..0978135d 100644 --- a/tests/es6/prototype2/CMakeLists.txt +++ b/tests/es6/prototype2/CMakeLists.txt @@ -1,8 +1,10 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.15) +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0042 NEW) project (addon2) -if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")) +if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") endif() @@ -12,4 +14,12 @@ add_library(${PROJECT_NAME} SHARED src/addon.cpp) set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") -target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) \ No newline at end of file +target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) + +if (NODE_RUNTIME STREQUAL "node" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 20) + set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) +elseif (NODE_RUNTIME STREQUAL "electron" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 32) + set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20) +elseif (NODE_RUNTIME STREQUAL "electron" AND NODE_RUNTIMEVERSION VERSION_GREATER_EQUAL 29) + set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) +endif() diff --git a/tests/es6/testCases.js b/tests/es6/testCases.js index 29d77fa5..c359cec0 100644 --- a/tests/es6/testCases.js +++ b/tests/es6/testCases.js @@ -1,57 +1,85 @@ -"use strict"; -const assert = require("assert"); -const lib = require("../../"); -const BuildSystem = lib.BuildSystem; -const path = require("path"); -const fs = require("fs-extra"); +'use strict' +const assert = require('assert') +const lib = require('../../') +const BuildSystem = lib.BuildSystem +const path = require('path') +const fs = require('fs-extra') const testCases = { - buildPrototypeWithDirectoryOption: async function(options) { - options = { - directory: path.resolve(path.join(__dirname, "./prototype")), - ...options - }; - const buildSystem = new BuildSystem(options); - await buildSystem.rebuild(); - assert.ok((await fs.stat(path.join(__dirname, "prototype/build/Release/addon.node"))).isFile()); - }, - buildPrototype2WithCWD: async function(options) { - const cwd = process.cwd(); - process.chdir(path.resolve(path.join(__dirname, "./prototype2"))); - const buildSystem = new BuildSystem(options); - try { - await buildSystem.rebuild(); - assert.ok((await fs.stat(path.join(__dirname, "prototype2/build/Release/addon2.node"))).isFile()); - } - finally { - process.chdir(cwd); - } - }, - shouldConfigurePreC11Properly: async function(options) { - options = { - directory: path.resolve(path.join(__dirname, "./prototype")), - std: "c++98", - ...options - }; - const buildSystem = new BuildSystem(options); - if (!/visual studio/i.test(buildSystem.toolset.generator)) { - const command = await buildSystem.getConfigureCommand(); - assert.equal(command.indexOf("-std=c++"), -1, "c++ version still forced"); - } - }, - configureWithCustomOptions: async function(options) { - options = { - directory: path.resolve(path.join(__dirname, "./prototype")), - cMakeOptions: { - foo: "bar" - }, - ...options - }; - const buildSystem = new BuildSystem(options); + buildPrototypeWithDirectoryOption: async function (options) { + options = { + directory: path.resolve(path.join(__dirname, './prototype')), + ...options, + } + const buildSystem = new BuildSystem(options) + await buildSystem.rebuild() + assert.ok((await fs.stat(path.join(__dirname, 'prototype/build/Release/addon.node'))).isFile()) + }, + buildPrototype2WithCWD: async function (options) { + const cwd = process.cwd() + process.chdir(path.resolve(path.join(__dirname, './prototype2'))) + const buildSystem = new BuildSystem(options) + try { + await buildSystem.rebuild() + assert.ok((await fs.stat(path.join(__dirname, 'prototype2/build/Release/addon2.node'))).isFile()) + } finally { + process.chdir(cwd) + } + }, + buildPrototypeNapi: async function (options) { + const cwd = process.cwd() + process.chdir(path.resolve(path.join(__dirname, './prototype-napi'))) + const buildSystem = new BuildSystem(options) + try { + await buildSystem.rebuild() + assert.ok((await fs.stat(path.join(__dirname, 'prototype-napi/build/Release/addon_napi.node'))).isFile()) + } finally { + process.chdir(cwd) + } + }, + shouldConfigurePreC11Properly: async function (options) { + options = { + directory: path.resolve(path.join(__dirname, './prototype')), + std: 'c++98', + ...options, + } + const buildSystem = new BuildSystem(options) + if (!/visual studio/i.test(buildSystem.toolset.generator)) { + const command = await buildSystem.getConfigureCommand() + assert.equal(command.indexOf('-std=c++'), -1, 'c++ version still forced') + } + }, + configureWithCustomOptions: async function (options) { + options = { + directory: path.resolve(path.join(__dirname, './prototype')), + cMakeOptions: { + foo: 'bar', + }, + ...options, + } + const buildSystem = new BuildSystem(options) - const command = await buildSystem.getConfigureCommand(); - assert.notEqual(command.indexOf("-Dfoo=bar"), -1, "custom options added"); - } -}; + const command = await buildSystem.getConfigureCommand() + assert.notEqual(command.indexOf('-Dfoo=bar'), -1, 'custom options added') + }, + shouldForwardExtraCMakeArgs: async function (options) { + options = { + directory: path.resolve(path.join(__dirname, './prototype')), + ...options, + } -module.exports = testCases; + options.extraCMakeArgs = ['--debug-find-pkg=Boost', '--trace-source=FindBoost.cmake'] + const configure = await new BuildSystem(options).getConfigureCommand() + assert.deepEqual(configure.slice(-2), options.extraCMakeArgs, 'extra CMake args appended') + + options.extraCMakeArgs = ['--', 'CMakeFiles/x.dir/y.cpp.o'] + const build = await new BuildSystem(options).getBuildCommand() + assert.deepEqual(build.slice(-2), options.extraCMakeArgs, 'extra CMake args appended') + + options.extraCMakeArgs = ['.cache', '/tmp/jest_rs'] + const clean = await new BuildSystem(options).getCleanCommand() + assert.deepEqual(clean.slice(-2), options.extraCMakeArgs, 'extra CMake args appended') + }, +} + +module.exports = testCases diff --git a/tests/es6/testRunner.js b/tests/es6/testRunner.js index c9616bc0..16e8788c 100644 --- a/tests/es6/testRunner.js +++ b/tests/es6/testRunner.js @@ -1,105 +1,123 @@ -"use strict"; -/* global it */ -const lib = require("../../"); -const environment = lib.environment; -const log = require("npmlog"); -const util = require("util"); +'use strict' + +const lib = require('../../') +const environment = lib.environment +const log = require('npmlog') +const util = require('util') function* generateRuntimeOptions() { - function* generateForNode(arch) { - // Old: - yield { - runtime: "node", - runtimeVersion: "14.0.0", - arch: arch - }; - - // LTS: - yield { - runtime: "node", - runtimeVersion: "16.0.0", - arch: arch - }; - - // Current: - yield { - runtime: "node", - runtimeVersion: "18.3.0", - arch: arch - }; - } - - function* generateForNWJS(arch) { - // Latest: - yield { - runtime: "nw", - runtimeVersion: "0.64.0", - arch: arch - }; - } - - function* generateForElectron(arch) { - // Latest: - yield { - runtime: "electron", - runtimeVersion: "18.2.1", - arch: arch - }; - } - - function* generateForArch(arch) { - yield* generateForNode(arch); - yield* generateForNWJS(arch); - yield* generateForElectron(arch); - } - - if (environment.isWin) { - yield* generateForArch("x64"); - yield* generateForArch("ia32"); - } - else { - yield* generateForArch(); - } - - // Actual: - yield {}; + function* generateForNode(arch) { + if (arch !== 'arm64') { + yield { + runtime: 'node', + runtimeVersion: '14.0.0', + arch: arch, + } + + yield { + runtime: 'node', + runtimeVersion: '16.0.0', + arch: arch, + } + + yield { + runtime: 'node', + runtimeVersion: '18.13.0', + arch: arch, + } + } + + yield { + runtime: 'node', + runtimeVersion: '20.19.0', + arch: arch, + } + + // Current: + yield { + runtime: 'node', + runtimeVersion: '22.9.0', + arch: arch, + } + } + + function* generateForNWJS(arch) { + // Latest: + yield { + runtime: 'nw', + runtimeVersion: '0.64.0', + arch: arch, + } + } + + function* generateForElectron(arch) { + yield { + runtime: 'electron', + runtimeVersion: '18.2.1', + arch: arch, + } + + yield { + runtime: 'electron', + runtimeVersion: '31.7.7', + arch: arch, + } + } + + function* generateForArch(arch) { + yield* generateForNode(arch) + if (!environment.isWin || arch !== 'arm64') { + yield* generateForNWJS(arch) + } + yield* generateForElectron(arch) + } + + if (environment.isWin) { + yield* generateForArch('x64') + yield* generateForArch('ia32') + yield* generateForArch('arm64') + } else { + yield* generateForArch() + } + + // Actual: + yield {} } function* generateOptions() { - for (const runtimeOptions of generateRuntimeOptions()) { - if (environment.isWin) { - // V C++: - yield runtimeOptions; - } - else { - // Clang, Make - yield { ...runtimeOptions, preferClang: true, preferMake: true }; - - // Clang, Ninja - yield { ...runtimeOptions, preferClang: true }; - - // g++, Make - yield { ...runtimeOptions, preferGnu: true, preferMake: true }; - - // g++, Ninja - yield { ...runtimeOptions, preferGnu: true }; - - // Default: - yield runtimeOptions; - } - } + for (const runtimeOptions of generateRuntimeOptions()) { + if (environment.isWin) { + // V C++: + yield runtimeOptions + } else { + // Clang, Make + yield { ...runtimeOptions, preferClang: true, preferMake: true } + + // Clang, Ninja + yield { ...runtimeOptions, preferClang: true } + + // g++, Make + yield { ...runtimeOptions, preferGnu: true, preferMake: true } + + // g++, Ninja + yield { ...runtimeOptions, preferGnu: true } + + // Default: + yield runtimeOptions + } + } } const testRunner = { - runCase: function (testCase, options) { - for (const testOptions of generateOptions()) { - const currentOptions = { ...testOptions, ...(options || {}) }; - it("should build with: " + util.inspect(currentOptions), async function () { - log.info("TEST", "Running case for options of: " + util.inspect(currentOptions)); - await testCase(currentOptions); - }); - } - } -}; - -module.exports = testRunner; \ No newline at end of file + runCase: function (testCase, options) { + for (const testOptions of generateOptions()) { + const currentOptions = { ...testOptions, ...(options || {}) } + it('should build with: ' + util.inspect(currentOptions), async function () { + log.info('TEST', 'Running case for options of: ' + util.inspect(currentOptions)) + await testCase(currentOptions) + }) + } + }, +} + +module.exports = testRunner diff --git a/tests/fixtures/project/package.json b/tests/fixtures/project/package.json index e69de29b..0967ef42 100644 --- a/tests/fixtures/project/package.json +++ b/tests/fixtures/project/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/index.js b/tests/index.js index 7e14f01f..1b7069c8 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1 +1 @@ -require("./es6"); \ No newline at end of file +require('./es6')