From dab3f728eebe9d1a0bdbb7675af3d4967caf57c5 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 9 May 2022 07:21:19 +0100 Subject: [PATCH 01/84] Various tidying (#275) * chore: remove support for iojs * chore: remove unused dependency * chore: remove unused zip download support * chore: replace bluebird coroutine with async methods, and builtin promises * chore: fix tests * chore: make npm package contents more predictable * chore: update most dependencies * chore: remove unused imports * chore: replace some uses of lodash * chore: remove dependency on full lodash * chore: update documentation --- .gitignore | 1 + .npmignore | 2 -- README.md | 51 ++++++++++++--------------- bin/cmake-js | 27 +++++++-------- lib/appCMakeJSConfig.js | 4 +-- lib/buildSystem.js | 8 ++--- lib/cMake.js | 29 ++++++---------- lib/dist.js | 11 +++--- lib/downloader.js | 19 ++-------- lib/environment.js | 12 +++---- lib/locateNAN.js | 4 +-- lib/processHelpers.js | 6 ++-- lib/runtimePaths.js | 18 ++-------- lib/targetOptions.js | 1 - lib/toolset.js | 5 +-- lib/vsDetect.js | 10 +++--- package.json | 36 +++++++++---------- tests/es6/buildSystem.js | 28 ++++++--------- tests/es6/dist.js | 27 +++++++-------- tests/es6/locateNAN.js | 30 +++++++--------- tests/es6/prototype/CMakeLists.txt | 2 +- tests/es6/prototype/src/addon.cpp | 2 +- tests/es6/prototype2/CMakeLists.txt | 2 +- tests/es6/prototype2/src/addon.cpp | 2 +- tests/es6/testCases.js | 54 ++++++++++++++--------------- tests/es6/testRunner.js | 41 +++++++++------------- 26 files changed, 177 insertions(+), 255 deletions(-) delete mode 100644 .npmignore diff --git a/.gitignore b/.gitignore index 7ccb1a33..d841f14b 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ node_modules # Build build package-lock.json +yarn.lock # Users Environment Variables .lock-wscript diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 145e32d0..00000000 --- a/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -tests/ -.idea/ \ No newline at end of file diff --git a/README.md b/README.md index 10dc779f..11b5d491 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,16 @@ # CMake.js (MIT) ## About -CMake.js is a Node.js/io.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: +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 10+ since CMake.js v6.0.0 (for older runtimes please use CMake.js 5) +- Node.js 10+ since CMake.js v6.0.0 (for older runtimes please use CMake.js 5). Newer version can produce builds targetting 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/atom/electron) (formerly known as atom-shell): out-of-the-box build support, [no post build steps required](https://github.com/atom/electron/blob/master/docs/tutorial/using-native-node-modules.md) +- [Electron](https://github.com/electron/electron) (formerly known as atom-shell): 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) ## Installation ```bash -npm install -g cmake-js +npm install cmake-js ``` **Help:** @@ -23,7 +23,7 @@ cmake-js --help Usage: cmake-js [] [options] Commands: - install Install Node.js/io.js distribution files if needed + 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) @@ -90,10 +90,15 @@ In a nutshell. *(For more complete documentation please see [the first tutorial] ```cmake project (your-addon-name-here) + include_directories(${CMAKE_JS_INC}) + 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}) ``` @@ -206,6 +211,12 @@ This will print during configure: ### Runtimes +#### Important + +It is important to understand that this setting is to be configured in the **application's root package.json file**. If you're creating a native module targeting nw.js for example, then **do not specify anything** in your module's package.json. It's the actual application's decision to specify its runtime, your module's just compatible anything that was mentioned in the [About chapter](#about). Actually defining `cmake-js` key in your module's package.json file may lead to an error. Why? If you set it up to use nw.js 0.12.1 for example, then when it gets compiled during development time (to run its unit tests for example) it's gonna be compiled against io.js 1.2 runtime. But if you're having io.js 34.0.1 at the command line then, which is binary incompatible with 1.2, then your unit tests will fail for sure. So it is advised to not use cmake-js target settings in your module's package.json, because that way CMake.js will use that you have, and your tests will pass. + +#### Configuration + If any of the `runtime`, `runtimeVersion`, or `arch` configuration parameters is not explicitly configured, sensible defaults will be auto-detected based on the JavaScript environment where CMake.js runs within. You can configure runtimes for compiling target for all depending CMake.js modules in an application. Define a `cmake-js` key in the application's root `package.json` file, eg.: @@ -231,7 +242,7 @@ Available settings: - `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/io.js 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.* #### Runtime options in CMakeLists.txt @@ -257,31 +268,11 @@ To make compatible your NW.js application with any CMake.js based modules, write 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). -#### Electron - -To make compatible your Electron application with any CMake.js based modules, write the following to your application's package.json file: - -```json -{ - "cmake-js": { - "runtime": "electron", - "runtimeVersion": "electron-runtime-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 Electron out-of-the-box. - ##### Note Currently Electron (V1.4.x+) can only call modules built using CMake.js from the main process. To call such a module from a render process use the Electron [remote](https://github.com/electron/electron/blob/master/docs/api/remote.md) module in your require statement: ```var yourModule = require('electron').remote.require('pathToYourModule/cmakeModuleName.node')``` -#### Important - -It is important to understand that this setting is to be configured in the **application's root package.json file**. If you're creating a native module targeting nw.js for example, then **do not specify anything** in your module's package.json. It's the actual application's decision to specify its runtime, your module's just compatible anything that was mentioned in the [About chapter](#about). Actually defining `cmake-js` key in your module's package.json file may lead to an error. Why? If you set it up to use nw.js 0.12.1 for example, then when it gets compiled during development time (to run its unit tests for example) it's gonna be compiled against io.js 1.2 runtime. But if you're having io.js 34.0.1 at the command line then, which is binary incompatible with 1.2, then your unit tests will fail for sure. So it is advised to not use cmake-js target settings in your module's package.json, because that way CMake.js will use that you have, and your tests will pass. - #### 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). @@ -325,7 +316,7 @@ classes provided by [`node-addon-api`](https://github.com/nodejs/node-addon-api), you need to make your package depend on it with: - npm install --save-dev node-addon-api + npm install --save node-addon-api and add it to the include directories of your *CMake* project file `CMakeLists.txt`: @@ -349,9 +340,11 @@ target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) - [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) -## Use case in the works - ArrayFire.js +## 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 -I'm working on the Node.js port of the awesome [ArrayFire](http://arrayfire.com/) CPU/GPU computing library, please follow its status in its repo: [ArrayFire.js](https://github.com/arrayfire/arrayfire_js). +Open a PR to add your own project here. ## Changelog diff --git a/bin/cmake-js b/bin/cmake-js index 39a4aadd..681ea2c9 100755 --- a/bin/cmake-js +++ b/bin/cmake-js @@ -2,15 +2,14 @@ "use strict"; var log = require("npmlog"); -var _ = require("lodash"); var BuildSystem = require("../").BuildSystem; var util = require("util"); var version = require("../package").version; var logLevels = ["silly", "verbose", "info", "http", "warn", "error"]; var npmConfigData = require("rc")("npm"); -for (var key of _.keys(npmConfigData)) { - if (_.startsWith(key, "cmake_js_")) { +for (const [key, value] of Object.entries(npmConfigData)) { + if (key.startsWith("cmake_js_")) { var option = key.substr(9); if (option.length === 1) { process.argv.push("-" + option); @@ -18,8 +17,8 @@ for (var key of _.keys(npmConfigData)) { else { process.argv.push("--" + option); } - if (npmConfigData[key]) { - process.argv.push(npmConfigData[key]); + if (value) { + process.argv.push(value); } } } @@ -28,10 +27,8 @@ console.log(process.argv); var yargs = require("yargs") .usage("CMake.js " + version + "\n\nUsage: $0 [] [options]") - .version(function () { - return version; - }) - .command("install", "Install Node.js/io.js distribution files if needed") + .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)") @@ -181,7 +178,7 @@ if (argv.h) { // Setup log level: -if (argv.l && _.includes(logLevels, argv.l)) { +if (argv.l && logLevels.includes(argv.l)) { log.level = argv.l; log.resume(); } @@ -193,11 +190,11 @@ log.verbose("CON", "Parsing arguments"); // Extract custom cMake options var customOptions = {}; -_.keys(argv).forEach(function (key) { - if (argv[key] && _.startsWith(key, "CD")) { - customOptions[key.substr(2)] = argv[key]; +for (const [key, value] of Object.entries(argv)) { + if (value && key.startsWith("CD")) { + customOptions[key.substr(2)] = value; } -}); +} var options = { directory: argv.directory || null, @@ -225,7 +222,7 @@ var options = { log.verbose("CON", "options:"); log.verbose("CON", util.inspect(options)); -var command = _.first(argv._) || "build"; +var command = argv._[0] || "build"; log.verbose("CON", "Running command: " + command); diff --git a/lib/appCMakeJSConfig.js b/lib/appCMakeJSConfig.js index b11f0e0d..1de5171d 100644 --- a/lib/appCMakeJSConfig.js +++ b/lib/appCMakeJSConfig.js @@ -1,6 +1,6 @@ "use strict"; let path = require("path"); -let _ = require("lodash"); +let isPlainObject = require("lodash.isplainobject"); function getConfig(lookPath, log) { let pjsonPath = path.join(lookPath, "package.json"); @@ -8,7 +8,7 @@ function getConfig(lookPath, log) { try { let json = require(pjsonPath); log.silly("CFG", "Loaded:\n" + JSON.stringify(json)); - if (_.isPlainObject(json) && _.isPlainObject(json["cmake-js"])) { + if (isPlainObject(json) && isPlainObject(json["cmake-js"])) { log.silly("CFG", "Config found."); return json["cmake-js"]; } diff --git a/lib/buildSystem.js b/lib/buildSystem.js index 0845a84c..1ebc7201 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -5,7 +5,7 @@ let CMLog = require("./cmLog"); let appCMakeJSConfig = require("./appCMakeJSConfig"); let npmConfig = require("./npmConfig"); let path = require("path"); -let _ = require("lodash"); +let isPlainObject = require("lodash.isplainobject"); let Toolset = require("./toolset"); function BuildSystem(options) { @@ -15,11 +15,11 @@ function BuildSystem(options) { let appConfig = appCMakeJSConfig(this.options.directory, this.log); let npmOptions = npmConfig(this.log); - if (_.isPlainObject(npmOptions) && _.keys(npmOptions).length) { + if (isPlainObject(npmOptions) && Object.keys(npmOptions).length) { this.options.runtimeDirectory = npmOptions["nodedir"]; } - if (_.isPlainObject(appConfig)) { - if (_.keys(appConfig).length) { + 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 diff --git a/lib/cMake.js b/lib/cMake.js index 2f81619f..5ab3675a 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -1,13 +1,10 @@ "use strict"; -let splitargs = require("splitargs"); let which = require("which"); let fs = require("fs-extra"); let path = require("path"); -let _ = require("lodash"); let environment = require("./environment"); let Dist = require("./dist"); let CMLog = require("./cmLog"); -let vsDetect = require("./vsDetect"); let TargetOptions = require("./targetOptions"); let processHelpers = require("./processHelpers"); let locateNAN = require("./locateNAN"); @@ -58,7 +55,7 @@ CMake.isAvailable = function (options) { } } catch (e) { - _.noop(e); + // Ignore } return false; }; @@ -91,7 +88,7 @@ CMake.getGenerators = async function (options, log) { 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 (_.endsWith(gen, arch)) { + if (gen.endsWith(arch)) { gen = gen.substr(0, gen.length - arch.length); } gens.push(gen); @@ -181,8 +178,8 @@ CMake.prototype.getConfigureCommand = async function () { } // Custom options - for (let k of _.keys(this.cMakeOptions)) { - D.push({[k]: this.cMakeOptions[k]}); + for (let [key, value] of Object.entries(this.cMakeOptions)) { + D.push({ [key]: value }); } // Toolset: @@ -214,21 +211,17 @@ CMake.prototype.getConfigureCommand = async function () { } // Load NPM config - for (let key of _.keys(npmConfigData)) { - if (_.startsWith(key, "cmake_")) { - let s = {}; + for (const [key, value] of Object.entries(npmConfigData)) { + if (key.startsWith("cmake_")) { let sk = key.substr(6); - if (sk) { - s[sk] = npmConfigData[key]; - if (s[sk]) { - D.push(s); - } + if (sk && value) { + D.push({ [sk]: value }); } } } command = command.concat(D.map(function (p) { - return "-D" + _.keys(p)[0] + "=" + _.values(p)[0]; + return "-D" + Object.keys(p)[0] + "=" + Object.values(p)[0]; })); return command; @@ -252,7 +245,7 @@ CMake.prototype.configure = async function () { await fs.ensureDir(this.workDir); } catch (e) { - _.noop(e); + // Ignore } let cwd = process.cwd(); @@ -270,7 +263,6 @@ CMake.prototype.ensureConfigured = async function () { await fs.lstat(path.join(this.workDir, "CMakeCache.txt")); } catch (e) { - _.noop(e); await this.configure(); } }; @@ -318,7 +310,6 @@ CMake.prototype.compile = async function () { await this.build(); } catch (e) { - _.noop(e); this.log.info("REP", "Build has been failed, trying to do a full rebuild."); await this.rebuild(); } diff --git a/lib/dist.js b/lib/dist.js index a2c0821e..29489091 100644 --- a/lib/dist.js +++ b/lib/dist.js @@ -3,16 +3,15 @@ let environment = require("./environment"); let path = require("path"); let urljoin = require("url-join"); let fs = require("fs-extra"); -let _ = require("lodash"); let CMLog = require("./cmLog"); let TargetOptions = require("./targetOptions"); let runtimePaths = require("./runtimePaths"); let Downloader = require("./downloader"); function testSum(sums, sum, fPath) { - let serverSum = _.first(sums.filter(function (s) { + let serverSum = sums.find(function (s) { return s.getPath === fPath; - })); + }); if (serverSum && serverSum.sum === sum) { return; } @@ -78,8 +77,8 @@ Object.defineProperties(Dist.prototype, { } catch (e) { return { - isFile: _.constant(false), - isDirectory: _.constant(false) + isFile: () => false, + isDirectory: () => false }; } } @@ -118,7 +117,7 @@ Dist.prototype.download = async function () { }; Dist.prototype._downloadShaSums = async function () { - if (this.targetOptions.runtime === "node" || this.targetOptions.runtime === "iojs") { + if (this.targetOptions.runtime === "node") { let sumUrl = urljoin(this.externalPath, "SHASUMS256.txt"); let log = this.log; log.http("DIST", "\t- " + sumUrl); diff --git a/lib/downloader.js b/lib/downloader.js index 429b3fe3..1b864f10 100644 --- a/lib/downloader.js +++ b/lib/downloader.js @@ -1,13 +1,10 @@ "use strict"; -let Promise = require("bluebird"); let crypto = require("crypto"); let axios = require("axios"); let MemoryStream = require("memory-stream"); let zlib = require("zlib"); let tar = require("tar"); let fs = require("fs"); -let _ = require("lodash"); -let unzip = require("unzipper"); let CMLog = require("./cmLog"); function Downloader(options) { @@ -26,7 +23,7 @@ Downloader.prototype.downloadToStream = function(url, stream, hash) { .get(url, { responseType: "stream" }) .then(function (response) { length = parseInt(response.headers["content-length"]); - if (!_.isNumber(length)) { + if (typeof length !== 'number') { length = 0; } @@ -68,7 +65,7 @@ Downloader.prototype.downloadString = async function (url) { }; Downloader.prototype.downloadFile = async function (url, options) { - if (_.isString(options)) { + if (typeof options === 'string') { options.path = options; } let result = fs.createWriteStream(options.path); @@ -78,7 +75,7 @@ Downloader.prototype.downloadFile = async function (url, options) { }; Downloader.prototype.downloadTgz = async function (url, options) { - if (_.isString(options)) { + if (typeof options === 'string') { options.cwd = options; } let gunzip = zlib.createGunzip(); @@ -89,16 +86,6 @@ Downloader.prototype.downloadTgz = async function (url, options) { return sum; }; -Downloader.prototype.downloadZip = async function (url, options) { - if (_.isString(options)) { - options.path = options; - } - let extractor = new unzip.Extract(options); - let sum = await this.downloadToStream(url, extractor, 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!"); diff --git a/lib/environment.js b/lib/environment.js index ee5243e7..d9948f43 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -1,8 +1,6 @@ "use strict"; let os = require("os"); -let isIOJS = require("is-iojs"); let which = require("which"); -let _ = require("lodash"); let environment = module.exports = { moduleVersion: require("../package.json").version, @@ -14,7 +12,7 @@ let environment = module.exports = { isX86: os.arch() === "ia32", isX64: os.arch() === "x64", isArm: os.arch() === "arm", - runtime: isIOJS ? "iojs" : "node", + runtime: "node", runtimeVersion: process.versions.node, home: process.env[(os.platform() === "win32") ? "USERPROFILE" : "HOME"], EOL: os.EOL @@ -40,7 +38,7 @@ Object.defineProperties(environment, { } } catch (e) { - _.noop(e); + // Ignore } } return this._isNinjaAvailable; @@ -60,7 +58,7 @@ Object.defineProperties(environment, { } } catch (e) { - _.noop(e); + // Ignore } } return this._isMakeAvailable; @@ -80,7 +78,7 @@ Object.defineProperties(environment, { } } catch (e) { - _.noop(e); + // Ignore } } return this._isGPPAvailable; @@ -100,7 +98,7 @@ Object.defineProperties(environment, { } } catch (e) { - _.noop(e); + // Ignore } } return this._isClangAvailable; diff --git a/lib/locateNAN.js b/lib/locateNAN.js index a449eee5..0205d9fc 100644 --- a/lib/locateNAN.js +++ b/lib/locateNAN.js @@ -1,7 +1,6 @@ "use strict"; let fs = require("fs-extra"); let path = require("path"); -let _ = require("lodash"); let isNANModule = async function (dir) { let h = path.join(dir, "nan.h"); @@ -10,7 +9,6 @@ let isNANModule = async function (dir) { return stat.isFile(); } catch (e) { - _.noop(e); return false; } }; @@ -29,7 +27,7 @@ let isNodeJSProject = async function (dir) { } } catch (e) { - _.noop(e); + // Ignore } return false; }; diff --git a/lib/processHelpers.js b/lib/processHelpers.js index c90976cd..b51cb2e6 100644 --- a/lib/processHelpers.js +++ b/lib/processHelpers.js @@ -1,13 +1,11 @@ "use strict"; -let Promise = require("bluebird"); -let splitargs = require("splitargs"); -let _ = require("lodash"); let spawn = require("child_process").spawn; let execFile = require("child_process").execFile; let processHelpers = { run: function (command, options) { - options = _.defaults(options, {silent: false}); + if (!options) options = {}; + return new Promise(function (resolve, reject) { const env = Object.assign({}, process.env); if (env.Path && env.PATH && env.Path !== env.PATH) { diff --git a/lib/runtimePaths.js b/lib/runtimePaths.js index 7c88d258..9edf94a2 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -1,10 +1,9 @@ "use strict"; -let _ = require("lodash"); let assert = require("assert"); let semver = require("semver"); +let isPlainObject = require("lodash.isplainobject"); let NODE_MIRROR = process.env.NVM_NODEJS_ORG_MIRROR || "https://nodejs.org/dist"; -let IOJS_MIRROR = process.env.NVM_IOJS_ORG_MIRROR || "https://iojs.org/dist"; let ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || "https://atom.io/download/atom-shell"; let runtimePaths = { @@ -32,17 +31,6 @@ let runtimePaths = { }; } }, - iojs: function (targetOptions) { - return { - externalPath: IOJS_MIRROR + "/v" + targetOptions.runtimeVersion + "/", - winLibs: [{ - dir: targetOptions.isX64 ? "win-x64" : "win-x86", - name: targetOptions.runtime + ".lib" - }], - tarPath: targetOptions.runtime + "-v" + targetOptions.runtimeVersion + ".tar.gz", - headerOnly: false - }; - }, nw: function (targetOptions) { if (semver.gte(targetOptions.runtimeVersion, "0.13.0")) { return { @@ -83,12 +71,12 @@ let runtimePaths = { }; }, get: function (targetOptions) { - assert(_.isObject(targetOptions)); + assert(targetOptions && typeof targetOptions === 'object'); let runtime = targetOptions.runtime; let func = runtimePaths[runtime]; let paths; - if (_.isFunction(func) && _.isPlainObject(paths = func(targetOptions))) { + if (typeof func === 'function' && isPlainObject(paths = func(targetOptions))) { return paths; } throw new Error("Unknown runtime: " + runtime); diff --git a/lib/targetOptions.js b/lib/targetOptions.js index e498eafb..66a14b2b 100644 --- a/lib/targetOptions.js +++ b/lib/targetOptions.js @@ -1,7 +1,6 @@ "use strict"; let environment = require("./environment"); -let _ = require("lodash"); function TargetOptions(options) { this.options = options || {}; diff --git a/lib/toolset.js b/lib/toolset.js index 93b68fc7..12a6fb34 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -1,7 +1,4 @@ "use strict"; -let Promise = require("bluebird"); -let async = Promise.coroutine; -let _ = require("lodash"); let TargetOptions = require("./targetOptions"); let environment = require("./environment"); let assert = require("assert"); @@ -245,7 +242,7 @@ Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () { }; Toolset.prototype._getVersionFromVSWhere = async function () { - let programFilesPath = _.get(process.env, "ProgramFiles(x86)", _.get(process.env, "ProgramFiles")); + let programFilesPath = process.env["ProgramFiles(x86)"] || process.env["ProgramFiles"]; let vswhereCommand = path.resolve(programFilesPath, "Microsoft Visual Studio", "Installer", "vswhere.exe"); let vswhereOutput = null; diff --git a/lib/vsDetect.js b/lib/vsDetect.js index 570c0655..855a7219 100644 --- a/lib/vsDetect.js +++ b/lib/vsDetect.js @@ -1,7 +1,5 @@ "use strict"; let processHelpers = require("./processHelpers"); -let _ = require("lodash"); -let path = require("path"); let vsDetect = { isInstalled: async function (version) { @@ -23,7 +21,7 @@ let vsDetect = { return stdout && stdout.indexOf("installationVersion: " + mainVer) > 0; } catch (e) { - _.noop(e); + // Ignore } */ return false; @@ -47,7 +45,7 @@ let vsDetect = { return stdout && stdout.indexOf(testPhrase) > 0; } catch (e) { - _.noop(e); + // Ignore } return false; }, @@ -68,7 +66,7 @@ let vsDetect = { } } catch (e) { - _.noop(e); + // Ignore } return false; }, @@ -88,7 +86,7 @@ let vsDetect = { } } catch (e) { - _.noop(e); + // Ignore } return false; } diff --git a/package.json b/package.json index 3f2f686b..f3c17c19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cmake-js", - "description": "CMake.js - a Node.js/io.js native addon build tool", + "description": "CMake.js - a Node.js native addon build tool", "license": "MIT", "keywords": [ "native", @@ -27,29 +27,29 @@ "node": ">= 10.0.0" }, "dependencies": { - "axios": "^0.21.1", - "bluebird": "^3", + "axios": "^0.27.2", "debug": "^4", - "fs-extra": "^5.0.0", - "is-iojs": "^1.0.1", - "lodash": "^4", - "memory-stream": "0", - "npmlog": "^1.2.0", + "fs-extra": "^9.1.0", + "lodash.isplainobject": "^4.0.6", + "memory-stream": "^1.0.0", + "npmlog": "^5.0.1", "rc": "^1.2.7", - "semver": "^5.0.3", - "splitargs": "0", - "tar": "^4", - "unzipper": "^0.8.13", - "url-join": "0", - "which": "^1.0.9", - "yargs": "^3.6.0" + "semver": "^7.3.7", + "tar": "^6.1.11", + "url-join": "^4.0.1", + "which": "^2.0.2", + "yargs": "^17.4.1" }, "devDependencies": { "mocha": "*", - "nan": "^2.1.0", - "natives": "^1.1.6" + "nan": "^2.1.0" }, "scripts": { "test": "mocha tests" - } + }, + "files": [ + "lib", + "bin", + "*.md" + ] } diff --git a/tests/es6/buildSystem.js b/tests/es6/buildSystem.js index 6e2c22c0..542bbc0f 100644 --- a/tests/es6/buildSystem.js +++ b/tests/es6/buildSystem.js @@ -4,11 +4,7 @@ let assert = require("assert"); let lib = require("../../"); let CMake = lib.CMake; -let BuildSystem = lib.BuildSystem; -let _ = require("lodash"); let path = require("path"); -let Bluebird = require("bluebird"); -let async = Bluebird.coroutine; let log = require("npmlog"); let testRunner = require("./testRunner"); let testCases = require("./testCases"); @@ -32,24 +28,22 @@ describe("BuildSystem", function () { testRunner.runCase(testCases.buildPrototypeWithDirectoryOption); }); - it("should provide list of generators", function (done) { - async(function*() { - let gens = yield CMake.getGenerators(); - assert(_.isArray(gens)); - assert(gens.length > 0); - assert.equal(gens.filter(function (g) { return g.length; }).length, gens.length); - })().nodeify(done); + it("should provide list of generators", async function () { + let 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", function (done) { - testCases.buildPrototype2WithCWD().nodeify(done); + it("should rebuild prototype if cwd is the source directory", async function () { + await testCases.buildPrototype2WithCWD(); }); - it("should run with old GNU compilers", function (done) { - testCases.shouldConfigurePreC11Properly().nodeify(done); + it("should run with old GNU compilers", async function () { + await testCases.shouldConfigurePreC11Properly(); }); - it("should configure with custom option", function (done) { - testCases.configureWithCustomOptions().nodeify(done); + it("should configure with custom option", async function () { + await testCases.configureWithCustomOptions(); }); }); diff --git a/tests/es6/dist.js b/tests/es6/dist.js index 2c638faf..1e77aee6 100644 --- a/tests/es6/dist.js +++ b/tests/es6/dist.js @@ -1,29 +1,26 @@ "use strict"; /* global describe,it */ -let Bluebird = require("bluebird"); let fs = require("fs-extra"); let Dist = require("../../").Dist; let assert = require("assert"); -let async = Bluebird.coroutine; let testDownload = process.env.TEST_DOWNLOAD === "1"; describe("dist", function () { - it("should download dist files if needed", function (done) { + it("should download dist files if needed", async function () { this.timeout(60000); - async(function*() { - let dist = new Dist(); - if (testDownload) { - yield fs.remove(dist.internalPath); - assert(dist.downloaded === false); - yield dist.ensureDownloaded(); - assert(dist.downloaded); - } - else { - yield dist.ensureDownloaded(); - } - })().nodeify(done); + + let 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/locateNAN.js b/tests/es6/locateNAN.js index 17296464..dc066136 100644 --- a/tests/es6/locateNAN.js +++ b/tests/es6/locateNAN.js @@ -1,8 +1,6 @@ "use strict"; /* global describe,it */ -let Bluebird = require("bluebird"); -let async = Bluebird.coroutine; let locateNAN = require("../../").locateNAN; let path = require("path"); let assert = require("assert"); @@ -22,27 +20,25 @@ 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", function () { + it("should locate NAN from dependency", async function () { let dir = path.join(PROJECT_DIR, "node_modules", "dep-1"); - return async(function*() { - let nan = yield locateNAN(dir); - assert.equal(nan, NAN_DIR); - })(); + + let nan = await locateNAN(dir); + assert.equal(nan, NAN_DIR); }); - it("should locate NAN from nested dependency", function () { + it("should locate NAN from nested dependency", async function () { let dir = path.join(PROJECT_DIR, "node_modules", "dep-1", "node_modules", "dep-3"); - return async(function*() { - let nan = yield locateNAN(dir); - assert.equal(nan, NAN_DIR); - })(); + + let nan = await locateNAN(dir); + assert.equal(nan, NAN_DIR); + }); - it("should locate NAN from scoped dependency", function () { + it("should locate NAN from scoped dependency", async function () { let dir = path.join(PROJECT_DIR, "node_modules", "@scope", "dep-2"); - return async(function*() { - let nan = yield locateNAN(dir); - assert.equal(nan, NAN_DIR); - })(); + + let nan = await locateNAN(dir); + assert.equal(nan, NAN_DIR); }); }); diff --git a/tests/es6/prototype/CMakeLists.txt b/tests/es6/prototype/CMakeLists.txt index 9fe91143..4c29d9bc 100644 --- a/tests/es6/prototype/CMakeLists.txt +++ b/tests/es6/prototype/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.8) project (addon) if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") endif() include_directories(${CMAKE_JS_INC}) diff --git a/tests/es6/prototype/src/addon.cpp b/tests/es6/prototype/src/addon.cpp index 02d13ffb..ad830a69 100644 --- a/tests/es6/prototype/src/addon.cpp +++ b/tests/es6/prototype/src/addon.cpp @@ -3,7 +3,7 @@ #include NAN_METHOD(Add) { - double value = info[0]->NumberValue() + info[1]->NumberValue(); + double value = info[0]->NumberValue(Nan::GetCurrentContext()).FromJust() + info[1]->NumberValue(Nan::GetCurrentContext()).FromJust(); info.GetReturnValue().Set(Nan::New(value)); } diff --git a/tests/es6/prototype2/CMakeLists.txt b/tests/es6/prototype2/CMakeLists.txt index 37ada899..0189ddc4 100644 --- a/tests/es6/prototype2/CMakeLists.txt +++ b/tests/es6/prototype2/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 2.8) project (addon2) if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14") endif() include_directories(${CMAKE_JS_INC}) diff --git a/tests/es6/prototype2/src/addon.cpp b/tests/es6/prototype2/src/addon.cpp index eec97114..71e9d094 100644 --- a/tests/es6/prototype2/src/addon.cpp +++ b/tests/es6/prototype2/src/addon.cpp @@ -3,7 +3,7 @@ #include NAN_METHOD(Mul) { - double value = info[0]->NumberValue() * info[1]->NumberValue(); + double value = info[0]->NumberValue(Nan::GetCurrentContext()).FromJust() * info[1]->NumberValue(Nan::GetCurrentContext()).FromJust(); info.GetReturnValue().Set(Nan::New(value)); } diff --git a/tests/es6/testCases.js b/tests/es6/testCases.js index cd872def..65f2f362 100644 --- a/tests/es6/testCases.js +++ b/tests/es6/testCases.js @@ -2,56 +2,56 @@ let assert = require("assert"); let lib = require("../../"); let BuildSystem = lib.BuildSystem; -let _ = require("lodash"); let path = require("path"); -let Bluebird = require("bluebird"); -let async = Bluebird.coroutine; let fs = require("fs-extra"); let testCases = { - buildPrototypeWithDirectoryOption: async(function*(options) { - options = _.extend({ - directory: path.resolve(path.join(__dirname, "./prototype")) - }, options); + buildPrototypeWithDirectoryOption: async function(options) { + options = { + directory: path.resolve(path.join(__dirname, "./prototype")), + ...options + }; let buildSystem = new BuildSystem(options); - yield buildSystem.rebuild(); - assert.ok((yield fs.stat(path.join(__dirname, "prototype/build/Release/addon.node"))).isFile()); - }), - buildPrototype2WithCWD: async(function*(options) { + await buildSystem.rebuild(); + assert.ok((await fs.stat(path.join(__dirname, "prototype/build/Release/addon.node"))).isFile()); + }, + buildPrototype2WithCWD: async function(options) { let cwd = process.cwd(); process.chdir(path.resolve(path.join(__dirname, "./prototype2"))); let buildSystem = new BuildSystem(options); try { - yield buildSystem.rebuild(); - assert.ok((yield fs.stat(path.join(__dirname, "prototype2/build/Release/addon2.node"))).isFile()); + 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 = _.extend({ + }, + shouldConfigurePreC11Properly: async function(options) { + options = { directory: path.resolve(path.join(__dirname, "./prototype")), - std: "c++98" - }, options); + std: "c++98", + ...options + }; let buildSystem = new BuildSystem(options); if (!/visual studio/i.test(buildSystem.toolset.generator)) { - let command = yield buildSystem.getConfigureCommand(); - assert.equal(command.indexOf("-std=c++11"), -1, "c++11 still forced"); + let command = await buildSystem.getConfigureCommand(); + assert.equal(command.indexOf("-std=c++"), -1, "c++ version still forced"); } - }), - configureWithCustomOptions: async(function*(options) { - options = _.extend({ + }, + configureWithCustomOptions: async function(options) { + options = { directory: path.resolve(path.join(__dirname, "./prototype")), cMakeOptions: { foo: "bar" - } - }, options); + }, + ...options + }; let buildSystem = new BuildSystem(options); - let command = yield buildSystem.getConfigureCommand(); + let command = await buildSystem.getConfigureCommand(); assert.notEqual(command.indexOf("-Dfoo=bar"), -1, "custom options added"); - }) + } }; module.exports = testCases; diff --git a/tests/es6/testRunner.js b/tests/es6/testRunner.js index 4413e3eb..18ecdef2 100644 --- a/tests/es6/testRunner.js +++ b/tests/es6/testRunner.js @@ -2,9 +2,6 @@ /* global it */ let lib = require("../../"); let environment = lib.environment; -let Bluebird = require("bluebird"); -let async = Bluebird.coroutine; -let _ = require("lodash"); let log = require("npmlog"); let util = require("util"); @@ -13,32 +10,30 @@ function* generateRuntimeOptions() { // Old: yield { runtime: "node", - runtimeVersion: "0.10.36", + runtimeVersion: "14.0.0", arch: arch }; // LTS: yield { runtime: "node", - runtimeVersion: "4.4.2", + runtimeVersion: "16.0.0", arch: arch }; // Current: - if (environment.runtimeVersion !== "5.10.0") { - yield { - runtime: "node", - runtimeVersion: "5.10.0", - arch: arch - }; - } + yield { + runtime: "node", + runtimeVersion: "18.0.0", + arch: arch + }; } function* generateForNWJS(arch) { // Latest: yield { runtime: "nw", - runtimeVersion: "0.13.2", + runtimeVersion: "0.64.0", arch: arch }; } @@ -47,7 +42,7 @@ function* generateRuntimeOptions() { // Latest: yield { runtime: "electron", - runtimeVersion: "0.37.3", + runtimeVersion: "18.2.1", arch: arch }; } @@ -78,16 +73,16 @@ function* generateOptions() { } else { // Clang, Make - yield _.extend({}, runtimeOptions, {preferClang: true, referMake: true}); + yield { ...runtimeOptions, preferClang: true, referMake: true }; // Clang, Ninja - yield _.extend({}, runtimeOptions, {preferClang: true}); + yield { ...runtimeOptions, preferClang: true }; // g++, Make - yield _.extend({}, runtimeOptions, {preferGnu: true, referMake: true}); + yield { ...runtimeOptions, preferGnu: true, referMake: true }; // g++, Ninja - yield _.extend({}, runtimeOptions, {preferGnu: true}); + yield { ...runtimeOptions, preferGnu: true }; // Default: yield runtimeOptions; @@ -98,12 +93,10 @@ function* generateOptions() { let testRunner = { runCase: function (testCase, options) { for (let testOptions of generateOptions()) { - let currentOptions = _.extend({}, testOptions, options || {}); - it("should build with: " + util.inspect(currentOptions), function (done) { - async(function*() { - log.info("TEST", "Running case for options of: " + util.inspect(currentOptions)); - yield testCase(currentOptions); - })().nodeify(done); + let 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); }); } } From 29f00b9aab33d1d272293af1e2a3c9f0381d03fa Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 10 May 2022 19:29:21 +0100 Subject: [PATCH 02/84] chore!: require node 14.15 or higher --- README.md | 2 +- package.json | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 11b5d491..832effab 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## 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 10+ since CMake.js v6.0.0 (for older runtimes please use CMake.js 5). Newer version can produce builds targetting older 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 - [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) (formerly known as atom-shell): 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) diff --git a/package.json b/package.json index f3c17c19..b6d93d7a 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "boost" ], "main": "lib", - "version": "6.3.0", + "version": "7.0.0-0", "author": "Gábor Mező aka unbornchikken", "repository": { "type": "git", @@ -24,15 +24,15 @@ }, "bin": "./bin/cmake-js", "engines": { - "node": ">= 10.0.0" + "node": ">= 14.15.0" }, "dependencies": { "axios": "^0.27.2", "debug": "^4", - "fs-extra": "^9.1.0", + "fs-extra": "^10.1.0", "lodash.isplainobject": "^4.0.6", "memory-stream": "^1.0.0", - "npmlog": "^5.0.1", + "npmlog": "^6.0.2", "rc": "^1.2.7", "semver": "^7.3.7", "tar": "^6.1.11", From d19e52986684d5447a7a5421ef245da964405564 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 10 May 2022 19:36:05 +0100 Subject: [PATCH 03/84] chore(docs): Update Electron note, add win_delay_hook #215 --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 832effab..2d8444ab 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,13 @@ Available settings: - **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.* +#### Electron + +On Windows, the [`win_delay_load_hook`](https://www.electronjs.org/docs/tutorial/using-native-node-modules#a-note-about-win_delay_load_hook) is required to be embedded in the module or it will fail to load in the render process. +cmake-js will add the hook if the CMakeLists.txt contains the library `${CMAKE_JS_SRC}`. + +Without the hook, the module can only be called from the render process using the Electron [remote](https://github.com/electron/electron/blob/master/docs/api/remote.md) module. + #### Runtime options in CMakeLists.txt The actual node runtime parameters are detectable in CMakeLists.txt files, the following variables are set: @@ -268,10 +275,6 @@ To make compatible your NW.js application with any CMake.js based modules, write 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). -##### Note -Currently Electron (V1.4.x+) can only call modules built using CMake.js from the main process. To call such a module from a render process use the Electron [remote](https://github.com/electron/electron/blob/master/docs/api/remote.md) module in your require statement: - -```var yourModule = require('electron').remote.require('pathToYourModule/cmakeModuleName.node')``` #### Heroku [Heroku](https://heroku.com) uses the concept of a [buildpack](https://devcenter.heroku.com/articles/buildpacks) to define From e61cb07d9b9523c4ce18e9c00ab25324b6e55f75 Mon Sep 17 00:00:00 2001 From: Nate Brake <33383515+njbrake@users.noreply.github.com> Date: Tue, 10 May 2022 14:39:26 -0400 Subject: [PATCH 04/84] feat: add parallel as option (#262) Co-authored-by: Nathan Brake Co-authored-by: Julian Waller --- README.md | 721 ++++++++++++++++++++++++++------------------------- bin/cmake-js | 9 +- lib/cMake.js | 3 + 3 files changed, 372 insertions(+), 361 deletions(-) diff --git a/README.md b/README.md index 2d8444ab..fc3112f1 100644 --- a/README.md +++ b/README.md @@ -1,360 +1,361 @@ -# CMake.js (MIT) - -## 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 -- [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) (formerly known as atom-shell): 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) - -## Installation - -```bash -npm install cmake-js -``` - -**Help:** - -```bash -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 - -Options: - --version Show version number [boolean] - -h, --help show this screen [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 - located) [string] - -D, --debug build debug configuration [boolean] - -B, --config specify build configuration (Debug, RelWithDebInfo, - Release), will ignore '--debug' if specified [string] - -c, --cmake-path path of CMake executable [string] - -m, --prefer-make use Unix Makefiles even if Ninja is available (Posix) - [boolean] - -x, --prefer-xcode use Xcode instead of Unix Makefiles [boolean] - -g, --prefer-gnu use GNU compiler instead of default CMake compiler, if - available (Posix) [boolean] - -G, --generator use specified generator [string] - -t, --toolset use specified toolset [string] - -A, --platform use specified platform name [string] - -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] - -r, --runtime the runtime to use [string] - -v, --runtime-version the runtime version to use [string] - -a, --arch the architecture to build in [string] - --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 - projectRoot/build [string] -``` - -**Requirements:** - -- [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/) - or a recent version of Visual C++ will do ([the free Community](https://www.visualstudio.com/products/visual-studio-community-vs) version works well) - - **Unix/Posix**: - - Clang or GCC - - Ninja or Make (Ninja will be picked if both present) - -## Usage - -### General - -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 -project (your-addon-name-here) - -include_directories(${CMAKE_JS_INC}) - -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}) -``` - -- Add the following into your package.json scripts section: - -```json -"scripts": { - "install": "cmake-js compile" - } -``` - -#### Commandline - -In your module folder you can access cmake-js commands if you install cmake-js globally: - -``` -npm install -g cmake-js -``` - -Please refer to the `--help` for the lists of available commands (they are like commands in `node-gyp`). - -You can override the project default runtimes via `--runtime` and `--runtime-version`, such as: `--runtime=electron --runtime-version=0.26.0`. See below for more info on runtimes. - -### CMake Specific - -`CMAKE_JS_VERSION` variable will reflect the actual CMake.js version. So CMake.js based builds could be detected, eg.: - -```cmake -if (CMAKE_JS_VERSION) - add_subdirectory(node_addon) -else() - add_subdirectory(other_subproject) -endif() -``` - -### NPM Config Integration - -You can set npm configuration options for CMake.js. - -For all users (global): - -``` -npm config set cmake_ --global -``` - -For current user: - -``` -npm config set cmake_ -``` - -CMake.js will set a variable named `""` to `` (by using `-D=""` option). User settings will **overwrite** globals. - -UPDATE: - -You can set CMake.js command line arguments with npm config using the following pattern: - -``` -npm config set cmake_js_G "Visual Studio 56 Win128" -``` - -Which sets the CMake generator, basically defaults to: - -``` -cmake-js -G "Visual Studio 56 Win128" -``` - -#### Example: - -Enter at command prompt: - -``` -npm config set cmake_BuBu="kittyfck" -``` - -Then write to your CMakeLists.txt the following: - -```cmake -message (STATUS ${BuBu}) -``` - -This will print during configure: - -``` ---- kittyfck -``` - -### Custom CMake options - -You can add custom CMake options by beginning option name with `CD`. - -#### Example - -In command prompt: -``` -cmake-js compile --CDBUBU="kittyfck" -``` - -Then in your CMakeLists.txt: - -```cmake -message (STATUS ${BUBU}) -``` - -This will print during configure: - -``` ---- kittyfck -``` - -### Runtimes - -#### Important - -It is important to understand that this setting is to be configured in the **application's root package.json file**. If you're creating a native module targeting nw.js for example, then **do not specify anything** in your module's package.json. It's the actual application's decision to specify its runtime, your module's just compatible anything that was mentioned in the [About chapter](#about). Actually defining `cmake-js` key in your module's package.json file may lead to an error. Why? If you set it up to use nw.js 0.12.1 for example, then when it gets compiled during development time (to run its unit tests for example) it's gonna be compiled against io.js 1.2 runtime. But if you're having io.js 34.0.1 at the command line then, which is binary incompatible with 1.2, then your unit tests will fail for sure. So it is advised to not use cmake-js target settings in your module's package.json, because that way CMake.js will use that you have, and your tests will pass. - -#### Configuration - -If any of the `runtime`, `runtimeVersion`, or `arch` configuration parameters is not explicitly configured, sensible defaults will be auto-detected based on the JavaScript environment where CMake.js runs within. - -You can configure runtimes for compiling target for all depending CMake.js modules in an application. Define a `cmake-js` key in the application's root `package.json` file, eg.: - -```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" - } -} -``` - -Available settings: - -- **runtime**: application's target runtime, possible values are: - - `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.* - -#### Electron - -On Windows, the [`win_delay_load_hook`](https://www.electronjs.org/docs/tutorial/using-native-node-modules#a-note-about-win_delay_load_hook) is required to be embedded in the module or it will fail to load in the render process. -cmake-js will add the hook if the CMakeLists.txt contains the library `${CMAKE_JS_SRC}`. - -Without the hook, the module can only be called from the render process using the Electron [remote](https://github.com/electron/electron/blob/master/docs/api/remote.md) module. - -#### Runtime options in CMakeLists.txt - -The actual node runtime parameters are detectable in CMakeLists.txt files, the following variables are set: - -- **NODE_RUNTIME**: `"node"`, `"nw"`, `"electron"` -- **NODE_RUNTIMEVERSION**: for example: `"0.12.1"` -- **NODE_ARCH**: `"x64"`, `"ia32"`, `"arm64"`, `"arm"` - -#### NW.js - -To make compatible your NW.js application with any CMake.js based modules, write the following to your application's package.json file: - -```json -{ - "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). - - -#### 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, -[heroku/nodejs](https://github.com/heroku/heroku-buildpack-nodejs), -provides an environment capable of running [node-gyp](https://github.com/TooTallNate/node-gyp), -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) - -- In the root directory of the application, -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 - -- 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. - - -#### Node-API and `node-addon-api` - -[ABI-stable Node.js API -(Node-API)](https://nodejs.org/api/n-api.html#n_api_node_api), -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. - -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. - -To compile a native module that uses the header-only C++ wrapper -classes provided by -[`node-addon-api`](https://github.com/nodejs/node-addon-api), -you need to make your package depend on it with: - - npm install --save node-addon-api - -and add it to the include directories of your *CMake* project file -`CMakeLists.txt`: - -```cmake -# Include node-addon-api wrappers -execute_process(COMMAND node -p "require('node-addon-api').include" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE NODE_ADDON_API_DIR - ) -string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) -string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) -target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) -``` - - -## Tutorials - -- [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) - -## 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 - -Open a PR to add your own project here. - -## Changelog - -View [changelog.md](changelog.md) - -## Credits - -https://github.com/cmake-js/cmake-js/graphs/contributors - -Ty all! \ No newline at end of file +# CMake.js (MIT) + +## 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 +- [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) (formerly known as atom-shell): 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) + +## Installation + +```bash +npm install cmake-js +``` + +**Help:** + +```bash +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 + +Options: + --version Show version number [boolean] + -h, --help show this screen [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 + located) [string] + -D, --debug build debug configuration [boolean] + -B, --config specify build configuration (Debug, RelWithDebInfo, + Release), will ignore '--debug' if specified [string] + -c, --cmake-path path of CMake executable [string] + -m, --prefer-make use Unix Makefiles even if Ninja is available (Posix) + [boolean] + -x, --prefer-xcode use Xcode instead of Unix Makefiles [boolean] + -g, --prefer-gnu use GNU compiler instead of default CMake compiler, if + available (Posix) [boolean] + -G, --generator use specified generator [string] + -t, --toolset use specified toolset [string] + -A, --platform use specified platform name [string] + -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] + -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: + -D [string] + -i, --silent Prevents CMake.js to print to the stdio [boolean] + -O, --out Specify the output directory to compile to, default is + projectRoot/build [string] +``` + +**Requirements:** + +- [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/) + or a recent version of Visual C++ will do ([the free Community](https://www.visualstudio.com/products/visual-studio-community-vs) version works well) + - **Unix/Posix**: + - Clang or GCC + - Ninja or Make (Ninja will be picked if both present) + +## Usage + +### General + +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 +project (your-addon-name-here) + +include_directories(${CMAKE_JS_INC}) + +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}) +``` + +- Add the following into your package.json scripts section: + +```json +"scripts": { + "install": "cmake-js compile" + } +``` + +#### Commandline + +In your module folder you can access cmake-js commands if you install cmake-js globally: + +``` +npm install -g cmake-js +``` + +Please refer to the `--help` for the lists of available commands (they are like commands in `node-gyp`). + +You can override the project default runtimes via `--runtime` and `--runtime-version`, such as: `--runtime=electron --runtime-version=0.26.0`. See below for more info on runtimes. + +### CMake Specific + +`CMAKE_JS_VERSION` variable will reflect the actual CMake.js version. So CMake.js based builds could be detected, eg.: + +```cmake +if (CMAKE_JS_VERSION) + add_subdirectory(node_addon) +else() + add_subdirectory(other_subproject) +endif() +``` + +### NPM Config Integration + +You can set npm configuration options for CMake.js. + +For all users (global): + +``` +npm config set cmake_ --global +``` + +For current user: + +``` +npm config set cmake_ +``` + +CMake.js will set a variable named `""` to `` (by using `-D=""` option). User settings will **overwrite** globals. + +UPDATE: + +You can set CMake.js command line arguments with npm config using the following pattern: + +``` +npm config set cmake_js_G "Visual Studio 56 Win128" +``` + +Which sets the CMake generator, basically defaults to: + +``` +cmake-js -G "Visual Studio 56 Win128" +``` + +#### Example: + +Enter at command prompt: + +``` +npm config set cmake_BuBu="kittyfck" +``` + +Then write to your CMakeLists.txt the following: + +```cmake +message (STATUS ${BuBu}) +``` + +This will print during configure: + +``` +--- kittyfck +``` + +### Custom CMake options + +You can add custom CMake options by beginning option name with `CD`. + +#### Example + +In command prompt: +``` +cmake-js compile --CDBUBU="kittyfck" +``` + +Then in your CMakeLists.txt: + +```cmake +message (STATUS ${BUBU}) +``` + +This will print during configure: + +``` +--- kittyfck +``` + +### Runtimes + +#### Important + +It is important to understand that this setting is to be configured in the **application's root package.json file**. If you're creating a native module targeting nw.js for example, then **do not specify anything** in your module's package.json. It's the actual application's decision to specify its runtime, your module's just compatible anything that was mentioned in the [About chapter](#about). Actually defining `cmake-js` key in your module's package.json file may lead to an error. Why? If you set it up to use nw.js 0.12.1 for example, then when it gets compiled during development time (to run its unit tests for example) it's gonna be compiled against io.js 1.2 runtime. But if you're having io.js 34.0.1 at the command line then, which is binary incompatible with 1.2, then your unit tests will fail for sure. So it is advised to not use cmake-js target settings in your module's package.json, because that way CMake.js will use that you have, and your tests will pass. + +#### Configuration + +If any of the `runtime`, `runtimeVersion`, or `arch` configuration parameters is not explicitly configured, sensible defaults will be auto-detected based on the JavaScript environment where CMake.js runs within. + +You can configure runtimes for compiling target for all depending CMake.js modules in an application. Define a `cmake-js` key in the application's root `package.json` file, eg.: + +```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" + } +} +``` + +Available settings: + +- **runtime**: application's target runtime, possible values are: + - `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.* + +#### Electron + +On Windows, the [`win_delay_load_hook`](https://www.electronjs.org/docs/tutorial/using-native-node-modules#a-note-about-win_delay_load_hook) is required to be embedded in the module or it will fail to load in the render process. +cmake-js will add the hook if the CMakeLists.txt contains the library `${CMAKE_JS_SRC}`. + +Without the hook, the module can only be called from the render process using the Electron [remote](https://github.com/electron/electron/blob/master/docs/api/remote.md) module. + +#### Runtime options in CMakeLists.txt + +The actual node runtime parameters are detectable in CMakeLists.txt files, the following variables are set: + +- **NODE_RUNTIME**: `"node"`, `"nw"`, `"electron"` +- **NODE_RUNTIMEVERSION**: for example: `"0.12.1"` +- **NODE_ARCH**: `"x64"`, `"ia32"`, `"arm64"`, `"arm"` + +#### NW.js + +To make compatible your NW.js application with any CMake.js based modules, write the following to your application's package.json file: + +```json +{ + "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). + + +#### 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, +[heroku/nodejs](https://github.com/heroku/heroku-buildpack-nodejs), +provides an environment capable of running [node-gyp](https://github.com/TooTallNate/node-gyp), +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) + +- In the root directory of the application, +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 + +- 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. + + +#### Node-API and `node-addon-api` + +[ABI-stable Node.js API +(Node-API)](https://nodejs.org/api/n-api.html#n_api_node_api), +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. + +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. + +To compile a native module that uses the header-only C++ wrapper +classes provided by +[`node-addon-api`](https://github.com/nodejs/node-addon-api), +you need to make your package depend on it with: + + npm install --save node-addon-api + +and add it to the include directories of your *CMake* project file +`CMakeLists.txt`: + +```cmake +# Include node-addon-api wrappers +execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR + ) +string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) +string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) +target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) +``` + + +## Tutorials + +- [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) + +## 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 + +Open a PR to add your own project here. + +## Changelog + +View [changelog.md](changelog.md) + +## Credits + +https://github.com/cmake-js/cmake-js/graphs/contributors + +Ty all! diff --git a/bin/cmake-js b/bin/cmake-js index 681ea2c9..aadb4473 100755 --- a/bin/cmake-js +++ b/bin/cmake-js @@ -151,6 +151,12 @@ var yargs = require("yargs") 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", @@ -216,7 +222,8 @@ var options = { cMakeOptions: customOptions, silent: argv.i, out: argv.O, - config: argv.B + config: argv.B, + parallel: argv.p }; log.verbose("CON", "options:"); diff --git a/lib/cMake.js b/lib/cMake.js index 5ab3675a..7c628727 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -272,6 +272,9 @@ CMake.prototype.getBuildCommand = function () { if (this.options.target) { command.push("--target", this.options.target); } + if (this.options.parallel) { + command.push("--parallel", this.options.parallel); + } return Promise.resolve(command); }; From ce775ae202b65057ec09ed21e9a5f474c1ed995a Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 10 May 2022 19:41:43 +0100 Subject: [PATCH 05/84] chore: run eslint --- lib/downloader.js | 6 +++--- lib/npmConfig.js | 12 ++++++------ package.json | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/downloader.js b/lib/downloader.js index 1b864f10..6a1bae9c 100644 --- a/lib/downloader.js +++ b/lib/downloader.js @@ -40,13 +40,13 @@ Downloader.prototype.downloadToStream = function(url, stream, hash) { lastPercent = percent; } } - }) + }); - response.data.pipe(stream) + response.data.pipe(stream); }) .catch(function (err) { reject(err); - }) + }); stream.once("error", function (err) { reject(err); diff --git a/lib/npmConfig.js b/lib/npmConfig.js index 570197cd..b0468c5a 100644 --- a/lib/npmConfig.js +++ b/lib/npmConfig.js @@ -2,17 +2,17 @@ function getNpmConfig() { let npmOptions = {}; - let npmConfigPrefix = 'npm_config_' + let npmConfigPrefix = 'npm_config_'; Object.keys(process.env).forEach(function (name) { if (name.indexOf(npmConfigPrefix) !== 0) { - return + return; } - let value = process.env[name] - name = name.substring(npmConfigPrefix.length) + let value = process.env[name]; + name = name.substring(npmConfigPrefix.length); if (name) { - npmOptions[name] = value + npmOptions[name] = value; } - }, this) + }, this); return npmOptions; } diff --git a/package.json b/package.json index b6d93d7a..1071cba8 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "nan": "^2.1.0" }, "scripts": { - "test": "mocha tests" + "test": "mocha tests", + "lint": "eslint lib bin/cmake-js tests" }, "files": [ "lib", From afdaaf203376303abd4dbd78379c0883e6faa6c2 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 10 May 2022 19:44:34 +0100 Subject: [PATCH 06/84] chore: replace let with const where possible #238 --- .eslintrc.json | 4 ++- bin/cmake-js | 28 ++++++++-------- lib/appCMakeJSConfig.js | 10 +++--- lib/buildSystem.js | 20 ++++++------ lib/cMake.js | 70 ++++++++++++++++++++-------------------- lib/cmLog.js | 2 +- lib/dist.js | 54 +++++++++++++++---------------- lib/downloader.js | 30 ++++++++--------- lib/environment.js | 6 ++-- lib/locateNAN.js | 24 +++++++------- lib/npmConfig.js | 8 ++--- lib/processHelpers.js | 8 ++--- lib/runtimePaths.js | 16 ++++----- lib/targetOptions.js | 2 +- lib/toolset.js | 34 +++++++++---------- lib/vsDetect.js | 32 +++++++++--------- tests/es6/buildSystem.js | 16 ++++----- tests/es6/dist.js | 10 +++--- tests/es6/locateNAN.js | 18 +++++------ tests/es6/testCases.js | 26 +++++++-------- tests/es6/testRunner.js | 16 ++++----- 21 files changed, 218 insertions(+), 216 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index c35a0036..af263b4e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,6 +4,8 @@ "sourceType": "module" }, "rules": { - "semi": 2 + "semi": 2, + "no-var": 2, + "prefer-const": 2 } } \ No newline at end of file diff --git a/bin/cmake-js b/bin/cmake-js index aadb4473..aecf12f3 100755 --- a/bin/cmake-js +++ b/bin/cmake-js @@ -1,16 +1,16 @@ #!/usr/bin/env node "use strict"; -var log = require("npmlog"); -var BuildSystem = require("../").BuildSystem; -var util = require("util"); -var version = require("../package").version; -var 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"]; -var npmConfigData = require("rc")("npm"); +const npmConfigData = require("rc")("npm"); for (const [key, value] of Object.entries(npmConfigData)) { if (key.startsWith("cmake_js_")) { - var option = key.substr(9); + const option = key.substr(9); if (option.length === 1) { process.argv.push("-" + option); } @@ -25,7 +25,7 @@ for (const [key, value] of Object.entries(npmConfigData)) { console.log(process.argv); -var yargs = require("yargs") +const yargs = require("yargs") .usage("CMake.js " + version + "\n\nUsage: $0 [] [options]") .version(version) .command("install", "Install Node.js distribution files if needed") @@ -173,7 +173,7 @@ var yargs = require("yargs") type: "string" } }); -var argv = yargs.argv; +const argv = yargs.argv; // If help, then print and exit: @@ -195,14 +195,14 @@ log.silly("CON", util.inspect(argv)); log.verbose("CON", "Parsing arguments"); // Extract custom cMake options -var customOptions = {}; +const customOptions = {}; for (const [key, value] of Object.entries(argv)) { if (value && key.startsWith("CD")) { customOptions[key.substr(2)] = value; } } -var options = { +const options = { directory: argv.directory || null, debug: argv.debug, cmakePath: argv.c || null, @@ -229,11 +229,11 @@ var options = { log.verbose("CON", "options:"); log.verbose("CON", util.inspect(options)); -var command = argv._[0] || "build"; +const command = argv._[0] || "build"; log.verbose("CON", "Running command: " + command); -var buildSystem = new BuildSystem(options); +const buildSystem = new BuildSystem(options); function ifCommand(c, f) { if (c === command) { @@ -289,7 +289,7 @@ function compile() { exitOnError(buildSystem.compile()); } -var done = ifCommand("install", install); +let done = ifCommand("install", install); done = done || ifCommand("configure", configure); done = done || ifCommand("print-configure", printConfigure); done = done || ifCommand("build", build); diff --git a/lib/appCMakeJSConfig.js b/lib/appCMakeJSConfig.js index 1de5171d..23f602b7 100644 --- a/lib/appCMakeJSConfig.js +++ b/lib/appCMakeJSConfig.js @@ -1,12 +1,12 @@ "use strict"; -let path = require("path"); -let isPlainObject = require("lodash.isplainobject"); +const path = require("path"); +const isPlainObject = require("lodash.isplainobject"); function getConfig(lookPath, log) { - let pjsonPath = path.join(lookPath, "package.json"); + const pjsonPath = path.join(lookPath, "package.json"); log.silly("CFG", "Looking for package.json in: '" + pjsonPath + "'."); try { - let json = require(pjsonPath); + const json = require(pjsonPath); log.silly("CFG", "Loaded:\n" + JSON.stringify(json)); if (isPlainObject(json) && isPlainObject(json["cmake-js"])) { log.silly("CFG", "Config found."); @@ -35,7 +35,7 @@ module.exports = function (projectPath, log) { } try { log.silly("CFG", "Looking for parent path."); - let lastPath = currPath; + const lastPath = currPath; currPath = path.normalize(path.join(currPath, "..")); if (lastPath === currPath) { currPath = null; // root diff --git a/lib/buildSystem.js b/lib/buildSystem.js index 1ebc7201..7c855a51 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -1,19 +1,19 @@ "use strict"; -let CMake = require("./cMake"); -let Dist = require("./dist"); -let CMLog = require("./cmLog"); -let appCMakeJSConfig = require("./appCMakeJSConfig"); -let npmConfig = require("./npmConfig"); -let path = require("path"); -let isPlainObject = require("lodash.isplainobject"); -let Toolset = require("./toolset"); +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"); function BuildSystem(options) { this.options = options || {}; this.options.directory = path.resolve(this.options.directory || process.cwd()); this.log = new CMLog(this.options); - let appConfig = appCMakeJSConfig(this.options.directory, this.log); - let npmOptions = npmConfig(this.log); + 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"]; diff --git a/lib/cMake.js b/lib/cMake.js index 7c628727..615de6c3 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -1,15 +1,15 @@ "use strict"; -let which = require("which"); -let fs = require("fs-extra"); -let path = require("path"); -let environment = require("./environment"); -let Dist = require("./dist"); -let CMLog = require("./cmLog"); -let TargetOptions = require("./targetOptions"); -let processHelpers = require("./processHelpers"); -let locateNAN = require("./locateNAN"); -let npmConfigData = require("rc")("npm"); -let Toolset = require("./toolset"); +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 npmConfigData = require("rc")("npm"); +const Toolset = require("./toolset"); function CMake(options) { this.options = options || {}; @@ -46,7 +46,7 @@ CMake.isAvailable = function (options) { options = options || {}; try { if (options.cmakePath) { - let stat = fs.lstatSync(options.cmakePath); + const stat = fs.lstatSync(options.cmakePath); return !stat.isDirectory(); } else { @@ -61,14 +61,14 @@ CMake.isAvailable = function (options) { }; CMake.getGenerators = async function (options, log) { - let arch = " [arch]"; + const arch = " [arch]"; options = options || {}; - let gens = []; + const gens = []; if (CMake.isAvailable(options)) { // try parsing machine-readable capabilities (available since CMake 3.7) try { - let stdout = await processHelpers.execFile([options.cmakePath || "cmake", "-E", "capabilities"]); - let capabilities = JSON.parse(stdout); + const stdout = await processHelpers.execFile([options.cmakePath || "cmake", "-E", "capabilities"]); + const capabilities = JSON.parse(stdout); return capabilities.generators.map(x => x.name); } catch (error) { @@ -78,13 +78,13 @@ CMake.getGenerators = async function (options, log) { } // fall back to parsing help text - let stdout = await processHelpers.execFile([options.cmakePath || "cmake", "--help"]); - let hasCr = stdout.includes("\r\n"); - let output = hasCr ? stdout.split("\r\n") : stdout.split("\n"); + 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) { - let parts = line.split("="); + 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(); @@ -119,7 +119,7 @@ CMake.prototype.getConfigureCommand = async function () { // Create command: let command = [this.path, this.projectRoot, "--no-warn-unused-cli"]; - let D = []; + const D = []; // CMake.js watermark D.push({"CMAKE_JS_VERSION": environment.moduleVersion}); @@ -139,14 +139,14 @@ CMake.prototype.getConfigureCommand = async function () { incPaths = [path.join(this.dist.internalPath, "/include/node")]; } else { - let nodeH = path.join(this.dist.internalPath, "/src"); - let v8H = path.join(this.dist.internalPath, "/deps/v8/include"); - let uvH = path.join(this.dist.internalPath, "/deps/uv/include"); + 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 - let nanH = await locateNAN(this.projectRoot); + const nanH = await locateNAN(this.projectRoot); if (nanH) { incPaths.push(nanH); } @@ -155,9 +155,9 @@ CMake.prototype.getConfigureCommand = async function () { D.push({"CMAKE_JS_INC": incPaths.join(";")}); // Sources: - let srcPaths = []; + const srcPaths = []; if (environment.isWin) { - let delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')); + const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')); srcPaths.push(delayHook.replace(/\\/gm, '/')); } @@ -171,14 +171,14 @@ CMake.prototype.getConfigureCommand = async function () { if (environment.isWin) { // Win - let libs = this.dist.winLibs; + const libs = this.dist.winLibs; if (libs.length) { D.push({"CMAKE_JS_LIB": libs.join(";")}); } } // Custom options - for (let [key, value] of Object.entries(this.cMakeOptions)) { + for (const [key, value] of Object.entries(this.cMakeOptions)) { D.push({ [key]: value }); } @@ -213,7 +213,7 @@ CMake.prototype.getConfigureCommand = async function () { // Load NPM config for (const [key, value] of Object.entries(npmConfigData)) { if (key.startsWith("cmake_")) { - let sk = key.substr(6); + const sk = key.substr(6); if (sk && value) { D.push({ [sk]: value }); } @@ -231,8 +231,8 @@ CMake.prototype.configure = async function () { this.verifyIfAvailable(); this.log.info("CMD", "CONFIGURE"); - let listPath = path.join(this.projectRoot, "CMakeLists.txt"); - let command = await this.getConfigureCommand(); + const listPath = path.join(this.projectRoot, "CMakeLists.txt"); + const command = await this.getConfigureCommand(); try { await fs.lstat(listPath); @@ -248,7 +248,7 @@ CMake.prototype.configure = async function () { // Ignore } - let cwd = process.cwd(); + const cwd = process.cwd(); process.chdir(this.workDir); try { await this._run(command); @@ -268,7 +268,7 @@ CMake.prototype.ensureConfigured = async function () { }; CMake.prototype.getBuildCommand = function () { - var command = [this.path, "--build", this.workDir, "--config", this.config]; + const command = [this.path, "--build", this.workDir, "--config", this.config]; if (this.options.target) { command.push("--target", this.options.target); } @@ -282,7 +282,7 @@ CMake.prototype.build = async function () { this.verifyIfAvailable(); await this.ensureConfigured(); - let buildCommand = await this.getBuildCommand(); + const buildCommand = await this.getBuildCommand(); this.log.info("CMD", "BUILD"); await this._run(buildCommand); }; diff --git a/lib/cmLog.js b/lib/cmLog.js index de199055..f1e85a60 100644 --- a/lib/cmLog.js +++ b/lib/cmLog.js @@ -1,5 +1,5 @@ "use strict"; -let log = require("npmlog"); +const log = require("npmlog"); function CMLog(options) { this.options = options || {}; diff --git a/lib/dist.js b/lib/dist.js index 29489091..a019e017 100644 --- a/lib/dist.js +++ b/lib/dist.js @@ -1,15 +1,15 @@ "use strict"; -let environment = require("./environment"); -let path = require("path"); -let urljoin = require("url-join"); -let fs = require("fs-extra"); -let CMLog = require("./cmLog"); -let TargetOptions = require("./targetOptions"); -let runtimePaths = require("./runtimePaths"); -let Downloader = require("./downloader"); +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"); function testSum(sums, sum, fPath) { - let serverSum = sums.find(function (s) { + const serverSum = sums.find(function (s) { return s.getPath === fPath; }); if (serverSum && serverSum.sum === sum) { @@ -29,9 +29,9 @@ function Dist(options) { Object.defineProperties(Dist.prototype, { internalPath: { get: function () { - let cacheDirectory = ".cmake-js"; - let runtimeArchDirectory = (this.targetOptions.runtime) + "-" + this.targetOptions.arch; - let runtimeVersionDirectory = "v" + this.targetOptions.runtimeVersion; + 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, @@ -63,7 +63,7 @@ Object.defineProperties(Dist.prototype, { } } if (environment.isWin) { - for (let libPath of this.winLibs) { + for (const libPath of this.winLibs) { stat = getStat(libPath); libs = libs && stat.isFile(); } @@ -86,9 +86,9 @@ Object.defineProperties(Dist.prototype, { }, winLibs: { get: function () { - let libs = runtimePaths.get(this.targetOptions).winLibs; - let result = []; - for (let lib of libs) { + 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; @@ -109,22 +109,22 @@ Dist.prototype.ensureDownloaded = async function () { }; Dist.prototype.download = async function () { - let log = this.log; + const log = this.log; log.info("DIST", "Downloading distribution files to: " + this.internalPath); await fs.ensureDir(this.internalPath); - let sums = await this._downloadShaSums(); + const sums = await this._downloadShaSums(); await Promise.all([this._downloadLibs(sums), this._downloadTar(sums)]); }; Dist.prototype._downloadShaSums = async function () { if (this.targetOptions.runtime === "node") { - let sumUrl = urljoin(this.externalPath, "SHASUMS256.txt"); - let log = this.log; + 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) { - let parts = line.split(/\s+/); + const parts = line.split(/\s+/); return { getPath: parts[1], sum: parts[0] @@ -140,13 +140,13 @@ Dist.prototype._downloadShaSums = async function () { }; Dist.prototype._downloadTar = async function (sums) { - let log = this.log; - let self = this; - let tarLocalPath = runtimePaths.get(self.targetOptions).tarPath; - let tarUrl = urljoin(self.externalPath, tarLocalPath); + 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); - let sum = await this.downloader.downloadTgz(tarUrl, { + const sum = await this.downloader.downloadTgz(tarUrl, { hash: sums ? "sha256" : null, cwd: self.internalPath, strip: 1, @@ -154,7 +154,7 @@ Dist.prototype._downloadTar = async function (sums) { if (entryPath === self.internalPath) { return true; } - let ext = path.extname(entryPath); + const ext = path.extname(entryPath); return ext && ext.toLowerCase() === ".h"; } }); diff --git a/lib/downloader.js b/lib/downloader.js index 6a1bae9c..2c109ac6 100644 --- a/lib/downloader.js +++ b/lib/downloader.js @@ -1,11 +1,11 @@ "use strict"; -let crypto = require("crypto"); -let axios = require("axios"); -let MemoryStream = require("memory-stream"); -let zlib = require("zlib"); -let tar = require("tar"); -let fs = require("fs"); -let CMLog = require("./cmLog"); +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 || {}; @@ -13,8 +13,8 @@ function Downloader(options) { } Downloader.prototype.downloadToStream = function(url, stream, hash) { - let self = this; - let shasum = hash ? crypto.createHash(hash) : null; + const self = this; + const shasum = hash ? crypto.createHash(hash) : null; return new Promise(function (resolve, reject) { let length = 0; let done = 0; @@ -59,7 +59,7 @@ Downloader.prototype.downloadToStream = function(url, stream, hash) { }; Downloader.prototype.downloadString = async function (url) { - let result = new MemoryStream(); + const result = new MemoryStream(); await this.downloadToStream(url, result); return result.toString(); }; @@ -68,8 +68,8 @@ Downloader.prototype.downloadFile = async function (url, options) { if (typeof options === 'string') { options.path = options; } - let result = fs.createWriteStream(options.path); - let sum = await this.downloadToStream(url, result, options.hash); + const result = fs.createWriteStream(options.path); + const sum = await this.downloadToStream(url, result, options.hash); this.testSum(url, sum, options); return sum; }; @@ -78,10 +78,10 @@ Downloader.prototype.downloadTgz = async function (url, options) { if (typeof options === 'string') { options.cwd = options; } - let gunzip = zlib.createGunzip(); - let extractor = tar.extract(options); + const gunzip = zlib.createGunzip(); + const extractor = tar.extract(options); gunzip.pipe(extractor); - let sum = await this.downloadToStream(url, gunzip, options.hash); + const sum = await this.downloadToStream(url, gunzip, options.hash); this.testSum(url, sum, options); return sum; }; diff --git a/lib/environment.js b/lib/environment.js index d9948f43..1820848f 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -1,8 +1,8 @@ "use strict"; -let os = require("os"); -let which = require("which"); +const os = require("os"); +const which = require("which"); -let environment = module.exports = { +const environment = module.exports = { moduleVersion: require("../package.json").version, platform: os.platform(), isWin: os.platform() === "win32", diff --git a/lib/locateNAN.js b/lib/locateNAN.js index 0205d9fc..ccedf7f1 100644 --- a/lib/locateNAN.js +++ b/lib/locateNAN.js @@ -1,11 +1,11 @@ "use strict"; -let fs = require("fs-extra"); -let path = require("path"); +const fs = require("fs-extra"); +const path = require("path"); -let isNANModule = async function (dir) { - let h = path.join(dir, "nan.h"); +const isNANModule = async function (dir) { + const h = path.join(dir, "nan.h"); try { - let stat = await fs.stat(h); + const stat = await fs.stat(h); return stat.isFile(); } catch (e) { @@ -13,9 +13,9 @@ let isNANModule = async function (dir) { } }; -let isNodeJSProject = async function (dir) { - let pjson = path.join(dir, "package.json"); - let node_modules = path.join(dir, "node_modules"); +const isNodeJSProject = async function (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()) { @@ -32,7 +32,7 @@ let isNodeJSProject = async function (dir) { return false; }; -let locateNAN = module.exports = async function (projectRoot) { +const locateNAN = module.exports = async function (projectRoot) { if (locateNAN.__projectRoot) { projectRoot = locateNAN.__projectRoot; } @@ -40,7 +40,7 @@ let locateNAN = module.exports = async function (projectRoot) { if (!result) { return null; } - let nanModulePath = path.join(projectRoot, "node_modules", "nan"); + const nanModulePath = path.join(projectRoot, "node_modules", "nan"); result = await isNANModule(nanModulePath); if (result) { return nanModulePath; @@ -51,8 +51,8 @@ let locateNAN = module.exports = async function (projectRoot) { }; function goUp(dir) { - let items = dir.split(path.sep); - let scopeItem = items[items.length - 2]; + const items = dir.split(path.sep); + const scopeItem = items[items.length - 2]; if (scopeItem && scopeItem[0] === "@") { // skip scope dir = path.join(dir, ".."); diff --git a/lib/npmConfig.js b/lib/npmConfig.js index b0468c5a..6b17badf 100644 --- a/lib/npmConfig.js +++ b/lib/npmConfig.js @@ -1,13 +1,13 @@ "use strict"; function getNpmConfig() { - let npmOptions = {}; - let npmConfigPrefix = 'npm_config_'; + const npmOptions = {}; + const npmConfigPrefix = 'npm_config_'; Object.keys(process.env).forEach(function (name) { if (name.indexOf(npmConfigPrefix) !== 0) { return; } - let value = process.env[name]; + const value = process.env[name]; name = name.substring(npmConfigPrefix.length); if (name) { npmOptions[name] = value; @@ -19,7 +19,7 @@ function getNpmConfig() { module.exports = function (log) { log.verbose("CFG", "Looking for NPM config."); - let options = getNpmConfig(); + const options = getNpmConfig(); if (options) { log.silly("CFG", "NPM options:", options); diff --git a/lib/processHelpers.js b/lib/processHelpers.js index b51cb2e6..eb9ae574 100644 --- a/lib/processHelpers.js +++ b/lib/processHelpers.js @@ -1,8 +1,8 @@ "use strict"; -let spawn = require("child_process").spawn; -let execFile = require("child_process").execFile; +const spawn = require("child_process").spawn; +const execFile = require("child_process").execFile; -let processHelpers = { +const processHelpers = { run: function (command, options) { if (!options) options = {}; @@ -12,7 +12,7 @@ let processHelpers = { env.PATH = env.Path + ';' + env.PATH; delete env.Path; } - let child = spawn(command[0], command.slice(1), { + const child = spawn(command[0], command.slice(1), { stdio: options.silent ? "ignore" : "inherit", env }); diff --git a/lib/runtimePaths.js b/lib/runtimePaths.js index 9edf94a2..18ca9e0e 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -1,12 +1,12 @@ "use strict"; -let assert = require("assert"); -let semver = require("semver"); -let isPlainObject = require("lodash.isplainobject"); +const assert = require("assert"); +const semver = require("semver"); +const isPlainObject = require("lodash.isplainobject"); -let NODE_MIRROR = process.env.NVM_NODEJS_ORG_MIRROR || "https://nodejs.org/dist"; -let ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || "https://atom.io/download/atom-shell"; +const NODE_MIRROR = process.env.NVM_NODEJS_ORG_MIRROR || "https://nodejs.org/dist"; +const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || "https://atom.io/download/atom-shell"; -let runtimePaths = { +const runtimePaths = { node: function (targetOptions) { if (semver.lt(targetOptions.runtimeVersion, "4.0.0")) { return { @@ -73,8 +73,8 @@ let runtimePaths = { get: function (targetOptions) { assert(targetOptions && typeof targetOptions === 'object'); - let runtime = targetOptions.runtime; - let func = runtimePaths[runtime]; + const runtime = targetOptions.runtime; + const func = runtimePaths[runtime]; let paths; if (typeof func === 'function' && isPlainObject(paths = func(targetOptions))) { return paths; diff --git a/lib/targetOptions.js b/lib/targetOptions.js index 66a14b2b..a6b5f2d7 100644 --- a/lib/targetOptions.js +++ b/lib/targetOptions.js @@ -1,6 +1,6 @@ "use strict"; -let environment = require("./environment"); +const environment = require("./environment"); function TargetOptions(options) { this.options = options || {}; diff --git a/lib/toolset.js b/lib/toolset.js index 12a6fb34..3a4d5941 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -1,11 +1,11 @@ "use strict"; -let TargetOptions = require("./targetOptions"); -let environment = require("./environment"); -let assert = require("assert"); -let vsDetect = require("./vsDetect"); -let path = require("path"); -let CMLog = require("./cmLog"); -let processHelpers = require("./processHelpers"); +const TargetOptions = require("./targetOptions"); +const environment = require("./environment"); +const assert = require("assert"); +const vsDetect = require("./vsDetect"); +const path = require("path"); +const CMLog = require("./cmLog"); +const processHelpers = require("./processHelpers"); function Toolset(options) { this.options = options || {}; @@ -153,7 +153,7 @@ Toolset.prototype.initializeWin = async function (install) { } return; } - let topVS = await this._getTopSupportedVisualStudioGenerator(); + const topVS = await this._getTopSupportedVisualStudioGenerator(); if (topVS) { if (install) { this.log.info("TOOL", `Using ${topVS} generator.`); @@ -170,7 +170,7 @@ Toolset.prototype.initializeWin = async function (install) { } let ver = 0; - let found = /^visual studio (\d+)/i.exec(topVS); + const found = /^visual studio (\d+)/i.exec(topVS); if (found) { ver = parseInt(found[1]); } @@ -205,21 +205,21 @@ Toolset.prototype.initializeWin = async function (install) { }; Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () { - let CMake = require("./cMake"); + const CMake = require("./cMake"); assert(environment.isWin); - let vswhereVersion = await this._getVersionFromVSWhere(); + const vswhereVersion = await this._getVersionFromVSWhere(); - let list = await CMake.getGenerators(this.options, this.log); + const list = await CMake.getGenerators(this.options, this.log); let maxVer = 0; let result = null; - for (let gen of list) { - let found = /^visual studio (\d+)/i.exec(gen); + for (const gen of list) { + const found = /^visual studio (\d+)/i.exec(gen); if (!found) { continue; } - let ver = parseInt(found[1]); + const ver = parseInt(found[1]); if (ver <= maxVer) { continue; } @@ -242,8 +242,8 @@ Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () { }; Toolset.prototype._getVersionFromVSWhere = async function () { - let programFilesPath = process.env["ProgramFiles(x86)"] || process.env["ProgramFiles"]; - let vswhereCommand = path.resolve(programFilesPath, "Microsoft Visual Studio", "Installer", "vswhere.exe"); + const programFilesPath = process.env["ProgramFiles(x86)"] || process.env["ProgramFiles"]; + const vswhereCommand = path.resolve(programFilesPath, "Microsoft Visual Studio", "Installer", "vswhere.exe"); let vswhereOutput = null; try { diff --git a/lib/vsDetect.js b/lib/vsDetect.js index 855a7219..77152d36 100644 --- a/lib/vsDetect.js +++ b/lib/vsDetect.js @@ -1,12 +1,12 @@ "use strict"; -let processHelpers = require("./processHelpers"); +const processHelpers = require("./processHelpers"); -let vsDetect = { +const vsDetect = { isInstalled: async function (version) { - let vsInstalled = await this._isVSInstalled(version); - let vsvNextInstalled = await this._isVSvNextInstalled(version); - let buildToolsInstalled = await this._isBuildToolsInstalled(version); - let foundByVSWhere = await this._isFoundByVSWhere(version); + const vsInstalled = await this._isVSInstalled(version); + const vsvNextInstalled = await this._isVSvNextInstalled(version); + const buildToolsInstalled = await this._isBuildToolsInstalled(version); + const foundByVSWhere = await this._isFoundByVSWhere(version); return vsInstalled || vsvNextInstalled || buildToolsInstalled || foundByVSWhere; }, @@ -28,7 +28,7 @@ let vsDetect = { }, _isBuildToolsInstalled: async function (version) { - let mainVer = version.split(".")[0]; + const mainVer = version.split(".")[0]; let key; let testPhrase; if (Number(mainVer) >= 15) { @@ -39,9 +39,9 @@ let vsDetect = { key = "HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VS.VisualCppBuildTools_x86_enu,v" + mainVer; testPhrase = "Visual C++"; } - let command = ["reg", "query", key]; + const command = ["reg", "query", key]; try { - let stdout = await processHelpers.execFile(command); + const stdout = await processHelpers.execFile(command); return stdout && stdout.indexOf(testPhrase) > 0; } catch (e) { @@ -53,11 +53,11 @@ let vsDetect = { _isVSInstalled: async function (version) { // On x64 this will look for x64 keys only, but if VS and compilers installed properly, // it will write it's keys to 64 bit registry as well. - let command = ["reg", "query", "HKLM\\Software\\Microsoft\\VisualStudio\\" + version]; + const command = ["reg", "query", "HKLM\\Software\\Microsoft\\VisualStudio\\" + version]; try { - let stdout = await processHelpers.execFile(command); + const stdout = await processHelpers.execFile(command); if (stdout) { - let lines = stdout.split("\r\n").filter(function (line) { + const lines = stdout.split("\r\n").filter(function (line) { return line.length > 10; }); if (lines.length >= 4) { @@ -72,12 +72,12 @@ let vsDetect = { }, _isVSvNextInstalled: async function (version) { - let mainVer = version.split(".")[0]; - let command = ["reg", "query", "HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VisualStudio.MinShell.Msi,v" + mainVer]; + const mainVer = version.split(".")[0]; + const command = ["reg", "query", "HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VisualStudio.MinShell.Msi,v" + mainVer]; try { - let stdout = await processHelpers.execFile(command); + const stdout = await processHelpers.execFile(command); if (stdout) { - let lines = stdout.split("\r\n").filter(function (line) { + const lines = stdout.split("\r\n").filter(function (line) { return line.length > 10; }); if (lines.length >= 3) { diff --git a/tests/es6/buildSystem.js b/tests/es6/buildSystem.js index 542bbc0f..8bd80c5c 100644 --- a/tests/es6/buildSystem.js +++ b/tests/es6/buildSystem.js @@ -1,13 +1,13 @@ "use strict"; /* global describe,it,before */ -let assert = require("assert"); -let lib = require("../../"); -let CMake = lib.CMake; -let path = require("path"); -let log = require("npmlog"); -let testRunner = require("./testRunner"); -let testCases = require("./testCases"); +const assert = require("assert"); +const lib = require("../../"); +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); @@ -29,7 +29,7 @@ describe("BuildSystem", function () { }); it("should provide list of generators", async function () { - let gens = await CMake.getGenerators(); + 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); diff --git a/tests/es6/dist.js b/tests/es6/dist.js index 1e77aee6..fe3ff610 100644 --- a/tests/es6/dist.js +++ b/tests/es6/dist.js @@ -1,17 +1,17 @@ "use strict"; /* global describe,it */ -let fs = require("fs-extra"); -let Dist = require("../../").Dist; -let assert = require("assert"); +const fs = require("fs-extra"); +const Dist = require("../../").Dist; +const assert = require("assert"); -let 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); - let dist = new Dist(); + const dist = new Dist(); if (testDownload) { await fs.remove(dist.internalPath); assert(dist.downloaded === false); diff --git a/tests/es6/locateNAN.js b/tests/es6/locateNAN.js index dc066136..f23084a3 100644 --- a/tests/es6/locateNAN.js +++ b/tests/es6/locateNAN.js @@ -1,9 +1,9 @@ "use strict"; /* global describe,it */ -let locateNAN = require("../../").locateNAN; -let path = require("path"); -let assert = require("assert"); +const locateNAN = require("../../").locateNAN; +const path = require("path"); +const assert = require("assert"); /* @@ -21,24 +21,24 @@ describe("locateNAN", function () { const NAN_DIR = path.join(PROJECT_DIR, "node_modules", "nan"); it("should locate NAN from dependency", async function () { - let dir = path.join(PROJECT_DIR, "node_modules", "dep-1"); + const dir = path.join(PROJECT_DIR, "node_modules", "dep-1"); - let nan = await locateNAN(dir); + const nan = await locateNAN(dir); assert.equal(nan, NAN_DIR); }); it("should locate NAN from nested dependency", async function () { - let dir = path.join(PROJECT_DIR, "node_modules", "dep-1", "node_modules", "dep-3"); + const dir = path.join(PROJECT_DIR, "node_modules", "dep-1", "node_modules", "dep-3"); - let nan = await locateNAN(dir); + const nan = await locateNAN(dir); assert.equal(nan, NAN_DIR); }); it("should locate NAN from scoped dependency", async function () { - let dir = path.join(PROJECT_DIR, "node_modules", "@scope", "dep-2"); + const dir = path.join(PROJECT_DIR, "node_modules", "@scope", "dep-2"); - let nan = await locateNAN(dir); + const nan = await locateNAN(dir); assert.equal(nan, NAN_DIR); }); }); diff --git a/tests/es6/testCases.js b/tests/es6/testCases.js index 65f2f362..29d77fa5 100644 --- a/tests/es6/testCases.js +++ b/tests/es6/testCases.js @@ -1,24 +1,24 @@ "use strict"; -let assert = require("assert"); -let lib = require("../../"); -let BuildSystem = lib.BuildSystem; -let path = require("path"); -let fs = require("fs-extra"); +const assert = require("assert"); +const lib = require("../../"); +const BuildSystem = lib.BuildSystem; +const path = require("path"); +const fs = require("fs-extra"); -let testCases = { +const testCases = { buildPrototypeWithDirectoryOption: async function(options) { options = { directory: path.resolve(path.join(__dirname, "./prototype")), ...options }; - let buildSystem = new BuildSystem(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) { - let cwd = process.cwd(); + const cwd = process.cwd(); process.chdir(path.resolve(path.join(__dirname, "./prototype2"))); - let buildSystem = new BuildSystem(options); + const buildSystem = new BuildSystem(options); try { await buildSystem.rebuild(); assert.ok((await fs.stat(path.join(__dirname, "prototype2/build/Release/addon2.node"))).isFile()); @@ -33,9 +33,9 @@ let testCases = { std: "c++98", ...options }; - let buildSystem = new BuildSystem(options); + const buildSystem = new BuildSystem(options); if (!/visual studio/i.test(buildSystem.toolset.generator)) { - let command = await buildSystem.getConfigureCommand(); + const command = await buildSystem.getConfigureCommand(); assert.equal(command.indexOf("-std=c++"), -1, "c++ version still forced"); } }, @@ -47,9 +47,9 @@ let testCases = { }, ...options }; - let buildSystem = new BuildSystem(options); + const buildSystem = new BuildSystem(options); - let command = await buildSystem.getConfigureCommand(); + const command = await buildSystem.getConfigureCommand(); assert.notEqual(command.indexOf("-Dfoo=bar"), -1, "custom options added"); } }; diff --git a/tests/es6/testRunner.js b/tests/es6/testRunner.js index 18ecdef2..503112c0 100644 --- a/tests/es6/testRunner.js +++ b/tests/es6/testRunner.js @@ -1,9 +1,9 @@ "use strict"; /* global it */ -let lib = require("../../"); -let environment = lib.environment; -let log = require("npmlog"); -let util = require("util"); +const lib = require("../../"); +const environment = lib.environment; +const log = require("npmlog"); +const util = require("util"); function* generateRuntimeOptions() { function* generateForNode(arch) { @@ -66,7 +66,7 @@ function* generateRuntimeOptions() { } function* generateOptions() { - for (let runtimeOptions of generateRuntimeOptions()) { + for (const runtimeOptions of generateRuntimeOptions()) { if (environment.isWin) { // V C++: yield runtimeOptions; @@ -90,10 +90,10 @@ function* generateOptions() { } } -let testRunner = { +const testRunner = { runCase: function (testCase, options) { - for (let testOptions of generateOptions()) { - let currentOptions = { ...testOptions, ...(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); From ea9d18fbff989b3973d7d06932755b1799b0a16b Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 20 May 2022 21:21:36 +0100 Subject: [PATCH 07/84] chore: reword readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fc3112f1..ce0b3274 100644 --- a/README.md +++ b/README.md @@ -172,19 +172,19 @@ cmake-js -G "Visual Studio 56 Win128" Enter at command prompt: ``` -npm config set cmake_BuBu="kittyfck" +npm config set cmake_Foo="bar" ``` Then write to your CMakeLists.txt the following: ```cmake -message (STATUS ${BuBu}) +message (STATUS ${Foo}) ``` This will print during configure: ``` ---- kittyfck +--- bar ``` ### Custom CMake options @@ -195,19 +195,19 @@ You can add custom CMake options by beginning option name with `CD`. In command prompt: ``` -cmake-js compile --CDBUBU="kittyfck" +cmake-js compile --CDFOO="bar" ``` Then in your CMakeLists.txt: ```cmake -message (STATUS ${BUBU}) +message (STATUS ${FOO}) ``` This will print during configure: ``` ---- kittyfck +--- bar ``` ### Runtimes From 404d166b9a6ce2a56bdd3824fd7f6f3ef2647be2 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 1 Jun 2022 18:44:55 +0100 Subject: [PATCH 08/84] chore: tidying --- lib/environment.js | 2 +- lib/index.js | 1 - lib/locateNAN.js | 5 ++++- tests/es6/buildSystem.js | 5 +++-- tests/es6/locateNAN.js | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/environment.js b/lib/environment.js index 1820848f..088a06fa 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -3,7 +3,7 @@ const os = require("os"); const which = require("which"); const environment = module.exports = { - moduleVersion: require("../package.json").version, + cmakeJsVersion: require("../package.json").version, platform: os.platform(), isWin: os.platform() === "win32", isLinux: os.platform() === "linux", diff --git a/lib/index.js b/lib/index.js index 31b0c863..c62f500b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -10,5 +10,4 @@ module.exports = { downloader: require("./downloader"), Toolset: require("./toolset"), processHelpers: require("./processHelpers"), - locateNAN: require("./locateNAN") }; diff --git a/lib/locateNAN.js b/lib/locateNAN.js index ccedf7f1..0776c7d0 100644 --- a/lib/locateNAN.js +++ b/lib/locateNAN.js @@ -13,7 +13,7 @@ const isNANModule = async function (dir) { } }; -const isNodeJSProject = async function (dir) { +async function isNodeJSProject (dir) { const pjson = path.join(dir, "package.json"); const node_modules = path.join(dir, "node_modules"); try { @@ -34,12 +34,15 @@ const isNodeJSProject = async function (dir) { 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; } + const nanModulePath = path.join(projectRoot, "node_modules", "nan"); result = await isNANModule(nanModulePath); if (result) { diff --git a/tests/es6/buildSystem.js b/tests/es6/buildSystem.js index 8bd80c5c..b6989a8d 100644 --- a/tests/es6/buildSystem.js +++ b/tests/es6/buildSystem.js @@ -3,6 +3,7 @@ const assert = require("assert"); const lib = require("../../"); +const locateNAN = require("../../lib/locateNAN") const CMake = lib.CMake; const path = require("path"); const log = require("npmlog"); @@ -17,11 +18,11 @@ describe("BuildSystem", function () { log.level = process.env.UT_LOG_LEVEL; log.resume(); } - lib.locateNAN.__projectRoot = path.resolve(path.join(__dirname, "../../")); + locateNAN.__projectRoot = path.resolve(path.join(__dirname, "../../")); }); after(function() { - lib.locateNAN.__projectRoot = undefined; + locateNAN.__projectRoot = undefined; }); describe("Build with various options", function() { diff --git a/tests/es6/locateNAN.js b/tests/es6/locateNAN.js index f23084a3..aa633db4 100644 --- a/tests/es6/locateNAN.js +++ b/tests/es6/locateNAN.js @@ -1,7 +1,7 @@ "use strict"; /* global describe,it */ -const locateNAN = require("../../").locateNAN; +const locateNAN = require("../../lib/locateNAN"); const path = require("path"); const assert = require("assert"); From 93061ed7a10a0ae0ed1dda9ae8e968afe7450715 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 2 Jun 2022 20:17:41 +0100 Subject: [PATCH 09/84] fix: cross-compiling on macos --- lib/cMake.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/cMake.js b/lib/cMake.js index 615de6c3..df0d28bc 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -175,6 +175,12 @@ CMake.prototype.getConfigureCommand = async function () { if (libs.length) { D.push({"CMAKE_JS_LIB": libs.join(";")}); } + } else 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 From b27c198664017c611d4214b5fea4e04c232b8fb9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 3 Jun 2022 00:30:19 +0100 Subject: [PATCH 10/84] chore: update github url --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1071cba8..fd01f9b7 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "author": "Gábor Mező aka unbornchikken", "repository": { "type": "git", - "url": "git://github.com/unbornchikken/cmake-js.git" + "url": "git://github.com/cmake-js/cmake-js.git" }, "bin": "./bin/cmake-js", "engines": { From 04d48e35018d25f183d8095ab962f31c8b0a5835 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 3 Jun 2022 00:13:58 +0100 Subject: [PATCH 11/84] chore: add as maintainer --- package.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/package.json b/package.json index fd01f9b7..e4179bac 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,13 @@ "main": "lib", "version": "7.0.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" From 28ccd3ff5b057b749e5005c394b848b6f5173997 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 2 Jun 2022 23:15:29 +0100 Subject: [PATCH 12/84] feat: prototype prebuildify inspired prebuilds solution --- bin/cmake-js-prebuildify.mjs | 103 +++++++++++++++++++++++++++++++++++ bin/cmake-js-verify.mjs | 44 +++++++++++++++ bindings.d.ts | 21 +++++++ bindings.js | 97 +++++++++++++++++++++++++++++++++ lib/prebuild.js | 44 +++++++++++++++ package.json | 9 ++- 6 files changed, 316 insertions(+), 2 deletions(-) create mode 100755 bin/cmake-js-prebuildify.mjs create mode 100755 bin/cmake-js-verify.mjs create mode 100644 bindings.d.ts create mode 100644 bindings.js create mode 100644 lib/prebuild.js diff --git a/bin/cmake-js-prebuildify.mjs b/bin/cmake-js-prebuildify.mjs new file mode 100755 index 00000000..4e1fa759 --- /dev/null +++ b/bin/cmake-js-prebuildify.mjs @@ -0,0 +1,103 @@ +#!/usr/bin/env node + +import fs from 'fs' +import path from 'path' +import os from 'os' +import { createRequire } from 'module' +import { getPrebuildName } from '../lib/prebuild.js' +import cp from 'child_process' + +/** + * Rename a binding file and move to the prebuilds folder, named according to the supplied parameters. + */ + +const require = createRequire(import.meta.url) +const version = require("../package").version; + +const yargs = require("yargs") + .usage("CMake.js " + version + "\n\nUsage: $0 [] [options]") + .version(version) + .options({ + source: { + demand: true, + describe: "path to the built binary", + type: "string" + }, + name: { + demand: true, + describe: 'name of the module', + type: 'string' + }, + strip: { + demand: false, + describe: 'strip file of debug symbols', + type: 'boolean' + }, + libc: { + demand: false, + describe: 'libc environment', + type: 'string' + }, + napi_version: { + demand: false, + describe: 'node-api version', + type: 'string' + }, + runtime: { + demand: false, + describe: 'runtime', + type: 'string' + }, + arch: { + demand: false, + describe: 'override the architecture', + type: 'string' + }, + platform: { + demand: false, + describe: 'override the platform', + type: 'string' + } + }) + +const argv = yargs.argv; + +const targetDir = path.join(process.cwd(), 'prebuilds') +const sourceFile = path.join(process.cwd(), argv.source) + +if (!fs.existsSync(sourceFile)) { + console.error(`Built binary does not exist!`) + process.exit(1) +} + +let libc = argv.libc +if (libc === 'glibc') libc = null + +// Determine the target filename +const prebuildName = getPrebuildName({ + arch: argv.arch || os.arch(), + platform: argv.platform || os.platform(), + name: argv.name, + libc: libc, + napi_version: argv.napi_version, + runtime: argv.runtime || 'node' +}) + +// Make sure the directory exists +if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }) +} + +// Copy the bindings file +const destFile = path.join(targetDir, prebuildName) +fs.copyFileSync(sourceFile, destFile) + +if (argv.strip) { + if (os.platform() === 'linux') { + cp.spawnSync('strip', [destFile, '--strip-all']) + } else if (os.platform() === 'darwin') { + cp.spawnSync('strip', [destFile, '-Sx']) + } +} + +console.log('Done') diff --git a/bin/cmake-js-verify.mjs b/bin/cmake-js-verify.mjs new file mode 100755 index 00000000..f727b488 --- /dev/null +++ b/bin/cmake-js-verify.mjs @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +import path from 'path' +import { createRequire } from 'module' +import loadBinding from '../bindings.js' + +/** + * Equivalent of the prebuild-install command. + * It expects a single parameter of path to a file containing the options passed when using require with the binding. + * This makes sure that there is a binding available for the current architecture and platform, + * or the one specified by the npm_config_* environment variables. + */ + +if (process.env.npm_config_build_from_source) { + // Force a build from source + process.exit(1) +} + +if (process.argv.length < 3) { + console.error(`Missing path to binding options`) + process.exit(1) +} + +const require = createRequire(import.meta.url) + +try { + // Load the options file + const optionsPath = path.join(process.cwd(), process.argv[2]) + const options = require(optionsPath) + + // Find the correct bindings file + const resolvedPath = loadBinding.resolve(process.cwd(), options, true) + + // Report result + if (resolvedPath) { + process.exit(0) + } else { + process.exit(1) + } + +} catch (e) { + console.error(`Failed to check for bindings file!: ${e}`) + process.exit(1) +} diff --git a/bindings.d.ts b/bindings.d.ts new file mode 100644 index 00000000..3ffdfd92 --- /dev/null +++ b/bindings.d.ts @@ -0,0 +1,21 @@ +/** + * Load the native binding for a module + * @param basePath - the base path of the module. Searching is done using this as the root + * @param options - Describe how the prebuilt binary is named. This is the same as what is given to cmake-js-verify. By describing all these properties explicitly, we can make the loading be much simpler and more deterministic, and avoid needing the list the contents of folders and detemrining which is the best match. + */ +export = function loadBinding(basePath: string, options: { + /** + * Unique name of the binding. + * This must match the output file as specified in CMake + * Typically this will be the same as the name in your package.json, but you are free to make it different + */ + name: string, + /** + * The node-api versions that are built. + */ + napi_versions?: number[], + /** + * Whether the bindings are labelled with the arm version (eg armv7, arm64v8) + */ + armv?: boolean, +}): T; diff --git a/bindings.js b/bindings.js new file mode 100644 index 00000000..cd351a00 --- /dev/null +++ b/bindings.js @@ -0,0 +1,97 @@ +const fs = require('fs') +const path = require('path') +const os = require('os') +const { getPrebuildName, isNwjs, isElectron, isAlpine } = require('./lib/prebuild') + +// Workaround to fix webpack's build warnings: 'the request of a dependency is an expression' +const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line + +/** + * Find the best path to the binding file + * @param {string} basePath - Base path of the module, where binaries will be located + * @param {object} options - Describe how the prebuilt binary is named + * @param {boolean} verifyPrebuild - True if we are verifying that a prebuild exists + * @returns + */ +function resolvePath(basePath, options, verifyPrebuild) { + if (typeof basePath !== 'string' || !basePath) throw new Error(`Invalid basePath to cmake-js/bindings`) + + if (typeof options !== 'object' || !options) throw new Error(`Invalid options to cmake-js/bindings`) + if (typeof options.name !== 'string' || !options.name) throw new Error(`Invalid name to cmake-js/bindings`) + + let isNodeApi = false + if (options.napi_versions && Array.isArray(options.napi_versions)) { + isNodeApi = true + } + + const arch = (verifyPrebuild && process.env.npm_config_arch) || os.arch() + const platform = (verifyPrebuild && process.env.npm_config_platform) || os.platform() + + let runtime = 'node' + if (verifyPrebuild && process.env.npm_config_runtime) { + runtime = process.env.npm_config_runtime + } else if (isElectron()) { + runtime = 'electron' + } else if (isNwjs()) { + runtime = 'node-webkit' + } + if (runtime === 'electron' && isNodeApi) runtime = 'node' + + const candidates = [] + + if (!verifyPrebuild) { + // Try for a locally built binding + candidates.push( + path.join(basePath, 'build', 'Debug', `${options.name}.node`), + path.join(basePath, 'build', 'Release', `${options.name}.node`), + ) + } + + let libc = undefined + if (isAlpine(platform)) libc = 'musl' + + // Look for prebuilds + if (isNodeApi) { + // Look for node-api versioned builds + for (const ver of options.napi_versions) { + const prebuildName = getPrebuildName({ + name: options.name, + platform, + arch, + libc, + napi_version: ver, + runtime, + // armv: options.armv ? (arch === 'arm64' ? '8' : vars.arm_version) : null, + }) + candidates.push(path.join(basePath, 'prebuilds', prebuildName)) + } + } else { + throw new Error('Not implemented for NAN!') + } + + + let foundPath = null + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) { + const stat = fs.statSync(candidate) + if (stat.isFile()) { + foundPath = candidate + break + } + } + } + + return foundPath +} + +function loadBinding (basePath, options) { + const foundPath = resolvePath(basePath, options) + + if (!foundPath) throw new Error(`Failed to find binding for ${options.name}`) + + return runtimeRequire(foundPath) +} +loadBinding.resolve = resolvePath + +module.exports = loadBinding \ No newline at end of file diff --git a/lib/prebuild.js b/lib/prebuild.js new file mode 100644 index 00000000..3eaf4a90 --- /dev/null +++ b/lib/prebuild.js @@ -0,0 +1,44 @@ +const fs = require('fs') + + +/** + * Generate the filename of the prebuild file. + * The format of the name is possible to calculate based on some options + * @param {object} options + * @returns + */ +function getPrebuildName(options) { + if (!options.napi_version) throw new Error('NAN not implemented') // TODO + + const tokens = [ + options.name, + `v${options.napi_version}`, + options.platform, + options.arch, + // options.armv ? (options.arch === 'arm64' ? '8' : vars.arm_version) : null, + options.libc && options.platform === 'linux' ? options.libc : null, + options.runtime, + ] + return `${tokens.filter(t => !!t).join('-')}.node` +} + +function isNwjs () { + return !!(process.versions && process.versions.nw) +} + +function isElectron () { + if (process.versions && process.versions.electron) return true + if (process.env.ELECTRON_RUN_AS_NODE) return true + return typeof window !== 'undefined' && window.process && window.process.type === 'renderer' +} + +function isAlpine (platform) { + return platform === 'linux' && fs.existsSync('/etc/alpine-release') +} + +module.exports = { + getPrebuildName, + isNwjs, + isElectron, + isAlpine +} \ No newline at end of file diff --git a/package.json b/package.json index e4179bac..0b46d0ea 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,11 @@ "type": "git", "url": "git://github.com/cmake-js/cmake-js.git" }, - "bin": "./bin/cmake-js", + "bin": { + "cmake-js": "./bin/cmake-js", + "cmake-js-prebuildify": "./bin/cmake-js-prebuildify.mjs", + "cmake-js-verify": "./bin/cmake-js-verify.mjs" + }, "engines": { "node": ">= 14.15.0" }, @@ -49,7 +53,8 @@ }, "devDependencies": { "mocha": "*", - "nan": "^2.1.0" + "nan": "^2.1.0", + "node-addon-api": "^5.0.0" }, "scripts": { "test": "mocha tests", From cd7e82c6e95dbdf2e1977ddcc24f5e7ecabd127c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Fri, 3 Jun 2022 18:09:39 +0100 Subject: [PATCH 13/84] v7.0.0-1 --- changelog.md | 8 ++++++++ package.json | 6 ++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index cb1bd606..6bc9acef 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +v7.0.0 - unreleased +========== + +- update dependencies +- replace some dependencies with modern language features +- prototype bindings loader and prebuildify-like system +- fix support for cross-compiling on macos + v6.3.0 - 26/11/21 ========== diff --git a/package.json b/package.json index 0b46d0ea..b78cce56 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "boost" ], "main": "lib", - "version": "7.0.0-0", + "version": "7.0.0-1", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { @@ -63,6 +63,8 @@ "files": [ "lib", "bin", - "*.md" + "*.md", + "bindings.js", + "bindings.d.ts" ] } From 2eedfc52ab94d0774850938df4e11761f518d9b4 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 4 Jun 2022 16:13:50 +0100 Subject: [PATCH 14/84] chore: remove prebuildify prototype --- bin/cmake-js-prebuildify.mjs | 103 ----------------------------------- bin/cmake-js-verify.mjs | 44 --------------- bindings.d.ts | 21 ------- bindings.js | 97 --------------------------------- changelog.md | 1 - lib/prebuild.js | 44 --------------- package.json | 4 +- 7 files changed, 1 insertion(+), 313 deletions(-) delete mode 100755 bin/cmake-js-prebuildify.mjs delete mode 100755 bin/cmake-js-verify.mjs delete mode 100644 bindings.d.ts delete mode 100644 bindings.js delete mode 100644 lib/prebuild.js diff --git a/bin/cmake-js-prebuildify.mjs b/bin/cmake-js-prebuildify.mjs deleted file mode 100755 index 4e1fa759..00000000 --- a/bin/cmake-js-prebuildify.mjs +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env node - -import fs from 'fs' -import path from 'path' -import os from 'os' -import { createRequire } from 'module' -import { getPrebuildName } from '../lib/prebuild.js' -import cp from 'child_process' - -/** - * Rename a binding file and move to the prebuilds folder, named according to the supplied parameters. - */ - -const require = createRequire(import.meta.url) -const version = require("../package").version; - -const yargs = require("yargs") - .usage("CMake.js " + version + "\n\nUsage: $0 [] [options]") - .version(version) - .options({ - source: { - demand: true, - describe: "path to the built binary", - type: "string" - }, - name: { - demand: true, - describe: 'name of the module', - type: 'string' - }, - strip: { - demand: false, - describe: 'strip file of debug symbols', - type: 'boolean' - }, - libc: { - demand: false, - describe: 'libc environment', - type: 'string' - }, - napi_version: { - demand: false, - describe: 'node-api version', - type: 'string' - }, - runtime: { - demand: false, - describe: 'runtime', - type: 'string' - }, - arch: { - demand: false, - describe: 'override the architecture', - type: 'string' - }, - platform: { - demand: false, - describe: 'override the platform', - type: 'string' - } - }) - -const argv = yargs.argv; - -const targetDir = path.join(process.cwd(), 'prebuilds') -const sourceFile = path.join(process.cwd(), argv.source) - -if (!fs.existsSync(sourceFile)) { - console.error(`Built binary does not exist!`) - process.exit(1) -} - -let libc = argv.libc -if (libc === 'glibc') libc = null - -// Determine the target filename -const prebuildName = getPrebuildName({ - arch: argv.arch || os.arch(), - platform: argv.platform || os.platform(), - name: argv.name, - libc: libc, - napi_version: argv.napi_version, - runtime: argv.runtime || 'node' -}) - -// Make sure the directory exists -if (!fs.existsSync(targetDir)) { - fs.mkdirSync(targetDir, { recursive: true }) -} - -// Copy the bindings file -const destFile = path.join(targetDir, prebuildName) -fs.copyFileSync(sourceFile, destFile) - -if (argv.strip) { - if (os.platform() === 'linux') { - cp.spawnSync('strip', [destFile, '--strip-all']) - } else if (os.platform() === 'darwin') { - cp.spawnSync('strip', [destFile, '-Sx']) - } -} - -console.log('Done') diff --git a/bin/cmake-js-verify.mjs b/bin/cmake-js-verify.mjs deleted file mode 100755 index f727b488..00000000 --- a/bin/cmake-js-verify.mjs +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node - -import path from 'path' -import { createRequire } from 'module' -import loadBinding from '../bindings.js' - -/** - * Equivalent of the prebuild-install command. - * It expects a single parameter of path to a file containing the options passed when using require with the binding. - * This makes sure that there is a binding available for the current architecture and platform, - * or the one specified by the npm_config_* environment variables. - */ - -if (process.env.npm_config_build_from_source) { - // Force a build from source - process.exit(1) -} - -if (process.argv.length < 3) { - console.error(`Missing path to binding options`) - process.exit(1) -} - -const require = createRequire(import.meta.url) - -try { - // Load the options file - const optionsPath = path.join(process.cwd(), process.argv[2]) - const options = require(optionsPath) - - // Find the correct bindings file - const resolvedPath = loadBinding.resolve(process.cwd(), options, true) - - // Report result - if (resolvedPath) { - process.exit(0) - } else { - process.exit(1) - } - -} catch (e) { - console.error(`Failed to check for bindings file!: ${e}`) - process.exit(1) -} diff --git a/bindings.d.ts b/bindings.d.ts deleted file mode 100644 index 3ffdfd92..00000000 --- a/bindings.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Load the native binding for a module - * @param basePath - the base path of the module. Searching is done using this as the root - * @param options - Describe how the prebuilt binary is named. This is the same as what is given to cmake-js-verify. By describing all these properties explicitly, we can make the loading be much simpler and more deterministic, and avoid needing the list the contents of folders and detemrining which is the best match. - */ -export = function loadBinding(basePath: string, options: { - /** - * Unique name of the binding. - * This must match the output file as specified in CMake - * Typically this will be the same as the name in your package.json, but you are free to make it different - */ - name: string, - /** - * The node-api versions that are built. - */ - napi_versions?: number[], - /** - * Whether the bindings are labelled with the arm version (eg armv7, arm64v8) - */ - armv?: boolean, -}): T; diff --git a/bindings.js b/bindings.js deleted file mode 100644 index cd351a00..00000000 --- a/bindings.js +++ /dev/null @@ -1,97 +0,0 @@ -const fs = require('fs') -const path = require('path') -const os = require('os') -const { getPrebuildName, isNwjs, isElectron, isAlpine } = require('./lib/prebuild') - -// Workaround to fix webpack's build warnings: 'the request of a dependency is an expression' -const runtimeRequire = typeof __webpack_require__ === 'function' ? __non_webpack_require__ : require // eslint-disable-line - -/** - * Find the best path to the binding file - * @param {string} basePath - Base path of the module, where binaries will be located - * @param {object} options - Describe how the prebuilt binary is named - * @param {boolean} verifyPrebuild - True if we are verifying that a prebuild exists - * @returns - */ -function resolvePath(basePath, options, verifyPrebuild) { - if (typeof basePath !== 'string' || !basePath) throw new Error(`Invalid basePath to cmake-js/bindings`) - - if (typeof options !== 'object' || !options) throw new Error(`Invalid options to cmake-js/bindings`) - if (typeof options.name !== 'string' || !options.name) throw new Error(`Invalid name to cmake-js/bindings`) - - let isNodeApi = false - if (options.napi_versions && Array.isArray(options.napi_versions)) { - isNodeApi = true - } - - const arch = (verifyPrebuild && process.env.npm_config_arch) || os.arch() - const platform = (verifyPrebuild && process.env.npm_config_platform) || os.platform() - - let runtime = 'node' - if (verifyPrebuild && process.env.npm_config_runtime) { - runtime = process.env.npm_config_runtime - } else if (isElectron()) { - runtime = 'electron' - } else if (isNwjs()) { - runtime = 'node-webkit' - } - if (runtime === 'electron' && isNodeApi) runtime = 'node' - - const candidates = [] - - if (!verifyPrebuild) { - // Try for a locally built binding - candidates.push( - path.join(basePath, 'build', 'Debug', `${options.name}.node`), - path.join(basePath, 'build', 'Release', `${options.name}.node`), - ) - } - - let libc = undefined - if (isAlpine(platform)) libc = 'musl' - - // Look for prebuilds - if (isNodeApi) { - // Look for node-api versioned builds - for (const ver of options.napi_versions) { - const prebuildName = getPrebuildName({ - name: options.name, - platform, - arch, - libc, - napi_version: ver, - runtime, - // armv: options.armv ? (arch === 'arm64' ? '8' : vars.arm_version) : null, - }) - candidates.push(path.join(basePath, 'prebuilds', prebuildName)) - } - } else { - throw new Error('Not implemented for NAN!') - } - - - let foundPath = null - - for (const candidate of candidates) { - if (fs.existsSync(candidate)) { - const stat = fs.statSync(candidate) - if (stat.isFile()) { - foundPath = candidate - break - } - } - } - - return foundPath -} - -function loadBinding (basePath, options) { - const foundPath = resolvePath(basePath, options) - - if (!foundPath) throw new Error(`Failed to find binding for ${options.name}`) - - return runtimeRequire(foundPath) -} -loadBinding.resolve = resolvePath - -module.exports = loadBinding \ No newline at end of file diff --git a/changelog.md b/changelog.md index 6bc9acef..b5eb42d1 100644 --- a/changelog.md +++ b/changelog.md @@ -3,7 +3,6 @@ v7.0.0 - unreleased - update dependencies - replace some dependencies with modern language features -- prototype bindings loader and prebuildify-like system - fix support for cross-compiling on macos v6.3.0 - 26/11/21 diff --git a/lib/prebuild.js b/lib/prebuild.js deleted file mode 100644 index 3eaf4a90..00000000 --- a/lib/prebuild.js +++ /dev/null @@ -1,44 +0,0 @@ -const fs = require('fs') - - -/** - * Generate the filename of the prebuild file. - * The format of the name is possible to calculate based on some options - * @param {object} options - * @returns - */ -function getPrebuildName(options) { - if (!options.napi_version) throw new Error('NAN not implemented') // TODO - - const tokens = [ - options.name, - `v${options.napi_version}`, - options.platform, - options.arch, - // options.armv ? (options.arch === 'arm64' ? '8' : vars.arm_version) : null, - options.libc && options.platform === 'linux' ? options.libc : null, - options.runtime, - ] - return `${tokens.filter(t => !!t).join('-')}.node` -} - -function isNwjs () { - return !!(process.versions && process.versions.nw) -} - -function isElectron () { - if (process.versions && process.versions.electron) return true - if (process.env.ELECTRON_RUN_AS_NODE) return true - return typeof window !== 'undefined' && window.process && window.process.type === 'renderer' -} - -function isAlpine (platform) { - return platform === 'linux' && fs.existsSync('/etc/alpine-release') -} - -module.exports = { - getPrebuildName, - isNwjs, - isElectron, - isAlpine -} \ No newline at end of file diff --git a/package.json b/package.json index b78cce56..26973c16 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,7 @@ "url": "git://github.com/cmake-js/cmake-js.git" }, "bin": { - "cmake-js": "./bin/cmake-js", - "cmake-js-prebuildify": "./bin/cmake-js-prebuildify.mjs", - "cmake-js-verify": "./bin/cmake-js-verify.mjs" + "cmake-js": "./bin/cmake-js" }, "engines": { "node": ">= 14.15.0" From 5daadfbf42512cfd882f2b6963dbf7c280497f02 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 4 Jun 2022 23:50:35 +0100 Subject: [PATCH 15/84] chore: github actions --- .github/workflows/node.yaml | 94 +++++++++++++++++++++++++++++++++++++ README.md | 5 ++ tests/es6/testRunner.js | 2 +- 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/node.yaml diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml new file mode 100644 index 00000000..52917baf --- /dev/null +++ b/.github/workflows/node.yaml @@ -0,0 +1,94 @@ +name: Test + +on: + push: + +jobs: + test: + name: Build ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.libc }} + runs-on: ${{ matrix.os }} + + strategy: + fail-fast: false + matrix: + include: + # windows + - os: windows-2019 + arch: x64 + - os: windows-2022 + arch: x64 + - os: windows-2019 + arch: ia32 + # macos + - os: macos-12 + arch: x64 + # - os: macos-11 + # arch: arm64 + - os: macos-10.15 + arch: x64 + # linux + - os: ubuntu-22.04 + arch: x64 + - os: ubuntu-18.04 + arch: x64 + # linux-libc + - os: ubuntu-latest + arch: arm64 + docker-arch: linux/arm64 + docker-image: node:14-buster + - os: ubuntu-latest + arch: arm + docker-arch: linux/arm/v7 + docker-image: node:14-buster + # linux-musl + - os: ubuntu-latest + arch: x64 + docker-arch: linux/amd64 + docker-image: node:14-alpine + libc: musl + + steps: + - uses: actions/checkout@v2 + + - name: Use Node.js 14.x + if: ${{ !matrix.docker-arch }} + uses: actions/setup-node@v1 + with: + architecture: ${{ matrix.arch }} + node-version: 14.x + + - name: run tests + if: ${{ !matrix.docker-arch }} + shell: bash + run: | + npm install + npm test + env: + CI: true + npm_config_build_from_source: true + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + if: matrix.docker-arch + - name: run tests (in docker) + uses: addnab/docker-run-action@v3 + if: matrix.docker-arch + with: + image: ${{ matrix.docker-image }} + # shell: bash + options: --platform=${{ matrix.docker-arch }} -v ${{ github.workspace }}:/work -e CI=1 + run: | + if command -v apt-get &> /dev/null + then + apt-get update + apt-get install -y cmake + elif command -v apk &> /dev/null + then + apk update + apk add cmake make g++ gcc + fi + + cd /work + + npm install + npm test diff --git a/README.md b/README.md index ce0b3274..c5653262 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # CMake.js (MIT) +[![Node CI](https://github.com/cmake-js/cmake-js/actions/workflows/node.yaml/badge.svg)](https://github.com/cmake-js/cmake-js/actions/workflows/node.yaml) +[![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: @@ -84,6 +87,8 @@ Options: ### General +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` diff --git a/tests/es6/testRunner.js b/tests/es6/testRunner.js index 503112c0..74280306 100644 --- a/tests/es6/testRunner.js +++ b/tests/es6/testRunner.js @@ -24,7 +24,7 @@ function* generateRuntimeOptions() { // Current: yield { runtime: "node", - runtimeVersion: "18.0.0", + runtimeVersion: "18.3.0", arch: arch }; } From 1650e56bebce1f45116df4c5922119fcf929025c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 00:12:24 +0100 Subject: [PATCH 16/84] chore: update github actions workflow --- .github/workflows/node.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index 52917baf..107da3ef 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -5,7 +5,7 @@ on: jobs: test: - name: Build ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.libc }} + name: Run tests for ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.libc }} runs-on: ${{ matrix.os }} strategy: @@ -52,7 +52,7 @@ jobs: - name: Use Node.js 14.x if: ${{ !matrix.docker-arch }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: architecture: ${{ matrix.arch }} node-version: 14.x From eee0d93677ec13e37bc1dc6ac034b3d22e86e3f8 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 01:06:27 +0100 Subject: [PATCH 17/84] chore: fix gha --- .github/workflows/node.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index 107da3ef..7e6d77b3 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -18,7 +18,7 @@ jobs: - os: windows-2022 arch: x64 - os: windows-2019 - arch: ia32 + arch: x86 # macos - os: macos-12 arch: x64 From 2e3b7a02d254033f9de00710fb0b950fc4bef251 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 17:05:35 -0700 Subject: [PATCH 18/84] feat: copy visual studio detection/selection logic from node-gyp --- lib/buildSystem.js | 1 + lib/import/Find-VisualStudio.cs | 250 +++++++++++++++++++ lib/import/LICENSE | 24 ++ lib/import/README | 6 + lib/import/find-visualstudio.js | 425 ++++++++++++++++++++++++++++++++ lib/import/util.js | 64 +++++ lib/index.js | 1 - lib/toolset.js | 157 +++++------- lib/vsDetect.js | 95 ------- 9 files changed, 827 insertions(+), 196 deletions(-) create mode 100644 lib/import/Find-VisualStudio.cs create mode 100644 lib/import/LICENSE create mode 100644 lib/import/README create mode 100644 lib/import/find-visualstudio.js create mode 100644 lib/import/util.js delete mode 100644 lib/vsDetect.js diff --git a/lib/buildSystem.js b/lib/buildSystem.js index 7c855a51..866b409b 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -17,6 +17,7 @@ function BuildSystem(options) { 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) { diff --git a/lib/import/Find-VisualStudio.cs b/lib/import/Find-VisualStudio.cs new file mode 100644 index 00000000..a58c8075 --- /dev/null +++ b/lib/import/Find-VisualStudio.cs @@ -0,0 +1,250 @@ +// Copyright 2017 - Refael Ackermann +// Distributed under MIT style license +// See accompanying file LICENSE at https://github.com/node4good/windows-autoconf + +// Usage: +// powershell -ExecutionPolicy Unrestricted -Command "Add-Type -Path Find-VisualStudio.cs; [VisualStudioConfiguration.Main]::PrintJson()" +// This script needs to be compatible with PowerShell v2 to run on Windows 2008R2 and Windows 7. + +using System; +using System.Text; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +namespace VisualStudioConfiguration +{ + [Flags] + public enum InstanceState : uint + { + None = 0, + Local = 1, + Registered = 2, + NoRebootRequired = 4, + NoErrors = 8, + Complete = 4294967295, + } + + [Guid("6380BCFF-41D3-4B2E-8B2E-BF8A6810C848")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface IEnumSetupInstances + { + + void Next([MarshalAs(UnmanagedType.U4), In] int celt, + [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Interface), Out] ISetupInstance[] rgelt, + [MarshalAs(UnmanagedType.U4)] out int pceltFetched); + + void Skip([MarshalAs(UnmanagedType.U4), In] int celt); + + void Reset(); + + [return: MarshalAs(UnmanagedType.Interface)] + IEnumSetupInstances Clone(); + } + + [Guid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupConfiguration + { + } + + [Guid("26AAB78C-4A60-49D6-AF3B-3C35BC93365D")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupConfiguration2 : ISetupConfiguration + { + + [return: MarshalAs(UnmanagedType.Interface)] + IEnumSetupInstances EnumInstances(); + + [return: MarshalAs(UnmanagedType.Interface)] + ISetupInstance GetInstanceForCurrentProcess(); + + [return: MarshalAs(UnmanagedType.Interface)] + ISetupInstance GetInstanceForPath([MarshalAs(UnmanagedType.LPWStr), In] string path); + + [return: MarshalAs(UnmanagedType.Interface)] + IEnumSetupInstances EnumAllInstances(); + } + + [Guid("B41463C3-8866-43B5-BC33-2B0676F7F42E")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupInstance + { + } + + [Guid("89143C9A-05AF-49B0-B717-72E218A2185C")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupInstance2 : ISetupInstance + { + [return: MarshalAs(UnmanagedType.BStr)] + string GetInstanceId(); + + [return: MarshalAs(UnmanagedType.Struct)] + System.Runtime.InteropServices.ComTypes.FILETIME GetInstallDate(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetInstallationName(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetInstallationPath(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetInstallationVersion(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetDisplayName([MarshalAs(UnmanagedType.U4), In] int lcid); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetDescription([MarshalAs(UnmanagedType.U4), In] int lcid); + + [return: MarshalAs(UnmanagedType.BStr)] + string ResolvePath([MarshalAs(UnmanagedType.LPWStr), In] string pwszRelativePath); + + [return: MarshalAs(UnmanagedType.U4)] + InstanceState GetState(); + + [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UNKNOWN)] + ISetupPackageReference[] GetPackages(); + + ISetupPackageReference GetProduct(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetProductPath(); + + [return: MarshalAs(UnmanagedType.VariantBool)] + bool IsLaunchable(); + + [return: MarshalAs(UnmanagedType.VariantBool)] + bool IsComplete(); + + [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UNKNOWN)] + ISetupPropertyStore GetProperties(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetEnginePath(); + } + + [Guid("DA8D8A16-B2B6-4487-A2F1-594CCCCD6BF5")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupPackageReference + { + + [return: MarshalAs(UnmanagedType.BStr)] + string GetId(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetVersion(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetChip(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetLanguage(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetBranch(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetType(); + + [return: MarshalAs(UnmanagedType.BStr)] + string GetUniqueId(); + + [return: MarshalAs(UnmanagedType.VariantBool)] + bool GetIsExtension(); + } + + [Guid("c601c175-a3be-44bc-91f6-4568d230fc83")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [ComImport] + public interface ISetupPropertyStore + { + + [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)] + string[] GetNames(); + + object GetValue([MarshalAs(UnmanagedType.LPWStr), In] string pwszName); + } + + [Guid("42843719-DB4C-46C2-8E7C-64F1816EFD5B")] + [CoClass(typeof(SetupConfigurationClass))] + [ComImport] + public interface SetupConfiguration : ISetupConfiguration2, ISetupConfiguration + { + } + + [Guid("177F0C4A-1CD3-4DE7-A32C-71DBBB9FA36D")] + [ClassInterface(ClassInterfaceType.None)] + [ComImport] + public class SetupConfigurationClass + { + } + + public static class Main + { + public static void PrintJson() + { + ISetupConfiguration query = new SetupConfiguration(); + ISetupConfiguration2 query2 = (ISetupConfiguration2)query; + IEnumSetupInstances e = query2.EnumAllInstances(); + + int pceltFetched; + ISetupInstance2[] rgelt = new ISetupInstance2[1]; + List instances = new List(); + while (true) + { + e.Next(1, rgelt, out pceltFetched); + if (pceltFetched <= 0) + { + Console.WriteLine(String.Format("[{0}]", string.Join(",", instances.ToArray()))); + return; + } + + try + { + instances.Add(InstanceJson(rgelt[0])); + } + catch (COMException) + { + // Ignore instances that can't be queried. + } + } + } + + private static string JsonString(string s) + { + return "\"" + s.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; + } + + private static string InstanceJson(ISetupInstance2 setupInstance2) + { + // Visual Studio component directory: + // https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids + + StringBuilder json = new StringBuilder(); + json.Append("{"); + + string path = JsonString(setupInstance2.GetInstallationPath()); + json.Append(String.Format("\"path\":{0},", path)); + + string version = JsonString(setupInstance2.GetInstallationVersion()); + json.Append(String.Format("\"version\":{0},", version)); + + List packages = new List(); + foreach (ISetupPackageReference package in setupInstance2.GetPackages()) + { + string id = JsonString(package.GetId()); + packages.Add(id); + } + json.Append(String.Format("\"packages\":[{0}]", string.Join(",", packages.ToArray()))); + + json.Append("}"); + return json.ToString(); + } + } +} \ No newline at end of file diff --git a/lib/import/LICENSE b/lib/import/LICENSE new file mode 100644 index 00000000..924e1e3b --- /dev/null +++ b/lib/import/LICENSE @@ -0,0 +1,24 @@ +(The MIT License) + +Copyright (c) 2012 Nathan Rajlich + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lib/import/README b/lib/import/README new file mode 100644 index 00000000..9c246cdd --- /dev/null +++ b/lib/import/README @@ -0,0 +1,6 @@ +This is a copy of some files from node-gyp, with some minor modifications.a + +Currently based on v9.0.0 with support for vs2013 removed + +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 diff --git a/lib/import/find-visualstudio.js b/lib/import/find-visualstudio.js new file mode 100644 index 00000000..4d1dfa5d --- /dev/null +++ b/lib/import/find-visualstudio.js @@ -0,0 +1,425 @@ +'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() +} + +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 diff --git a/lib/import/util.js b/lib/import/util.js new file mode 100644 index 00000000..9e78a819 --- /dev/null +++ b/lib/import/util.js @@ -0,0 +1,64 @@ +'use strict' + +const log = require('npmlog') +const execFile = require('child_process').execFile +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) + } +} + +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) + + 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) + } + + 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() +} + +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() +} + +module.exports = { + logWithPrefix: logWithPrefix, + regGetValue: regGetValue, + regSearchKeys: regSearchKeys +} \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index c62f500b..945db7c4 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9,5 +9,4 @@ module.exports = { CMake: require("./cMake"), downloader: require("./downloader"), Toolset: require("./toolset"), - processHelpers: require("./processHelpers"), }; diff --git a/lib/toolset.js b/lib/toolset.js index 2305d8a8..ca9fd699 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -2,10 +2,11 @@ const TargetOptions = require("./targetOptions"); const environment = require("./environment"); const assert = require("assert"); -const vsDetect = require("./vsDetect"); -const path = require("path"); const CMLog = require("./cmLog"); -const processHelpers = require("./processHelpers"); +const util = require('util') +const findVisualStudioRaw = require('./import/find-visualstudio') + +const findVisualStudio = util.promisify(findVisualStudioRaw) function Toolset(options) { this.options = options || {}; @@ -65,7 +66,7 @@ Toolset.prototype.initializePosix = function (install) { // if it's already set because of options... if (this.generator) { if (install) { - this.log.info("TOOL", "Using " + this.options.generator + " generator, as specified from commandline."); + this.log.info("TOOL", "Using " + this.generator + " generator, as specified from commandline."); } } // 2: Generator @@ -136,72 +137,56 @@ Toolset.prototype.initializePosix = function (install) { }; Toolset.prototype.initializeWin = async function (install) { - // Visual Studio: - // if it's already set because of options... - if (this.generator) { - 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 (!this.generator) { + const foundVsInfo = await this._getTopSupportedVisualStudioGenerator(); + if (foundVsInfo) { if (install) { - this.log.verbose("TOOL", "Setting SAFESEH:NO linker flag."); + this.log.info("TOOL", `Using ${foundVsInfo.generator} generator.`); } - this.linkerFlags.push("/SAFESEH:NO"); + 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."); } - return; - } - const topVS = await this._getTopSupportedVisualStudioGenerator(); - if (topVS) { + } else { + // if it's already set because of options... if (install) { - this.log.info("TOOL", `Using ${topVS} generator.`); - } - this.generator = topVS; - - 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"); + this.log.info("TOOL", "Using " + this.options.generator + " generator, as specified from commandline."); } + } - let ver = 0; - const found = /^visual studio (\d+)/i.exec(topVS); - if (found) { - ver = parseInt(found[1]); - } - const isAboveVS16 = ver >= 16; + this.linkerFlags.push("/DELAYLOAD:NODE.EXE"); - // 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; - } + if (this.targetOptions.isX86) { + if (install) { + this.log.verbose("TOOL", "Setting SAFESEH:NO linker flag."); } - } - else { - throw new Error("There is no Visual C++ compiler installed. Install Visual C++ Build Toolset or Visual Studio."); + this.linkerFlags.push("/SAFESEH:NO"); } }; @@ -209,24 +194,18 @@ Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () { const CMake = require("./cMake"); assert(environment.isWin); - const vswhereVersion = await this._getVersionFromVSWhere(); + const selectedVs = await findVisualStudio(this.options.msvsVersion) + if (!selectedVs) return null; const list = await CMake.getGenerators(this.options, this.log); - let maxVer = 0; - let result = null; for (const gen of list) { - const found = /^visual studio (\d+)/i.exec(gen); + const found = gen.startsWith(`Visual Studio ${selectedVs.versionMajor}`) if (!found) { continue; } - const ver = parseInt(found[1]); - if (ver <= maxVer) { - continue; - } - - // unlike previous versions "Visual Studio 16 2019" doesn't end with arch name - const isAboveVS16 = ver >= 16; + // 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)) { @@ -234,36 +213,14 @@ Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () { } } - if (ver === vswhereVersion || (await vsDetect.isInstalled(ver + ".0"))) { - result = gen; - maxVer = ver; + return { + ...selectedVs, + generator: gen } } - return result; -}; - -Toolset.prototype._getVersionFromVSWhere = async function () { - const programFilesPath = process.env["ProgramFiles(x86)"] || process.env["ProgramFiles"]; - const vswhereCommand = path.resolve(programFilesPath, "Microsoft Visual Studio", "Installer", "vswhere.exe"); - let vswhereOutput = null; - - try { - this.log.verbose("TOOL", `Looking for vswhere.exe at '${vswhereCommand}'.`); - vswhereOutput = await processHelpers.execFile([vswhereCommand, "-latest", "-products", "*", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationVersion"]); - } - catch (e) { - this.log.verbose("TOOL", "Could not find vswhere.exe (VS installation is probably older than 15.2)."); - return null; - } - - if (!vswhereOutput) { - return null; - } - - let version = vswhereOutput.trim(); - version = version.substring(0, version.indexOf(".")); - return Number(version); + // Nothing matched + return null; }; module.exports = Toolset; diff --git a/lib/vsDetect.js b/lib/vsDetect.js deleted file mode 100644 index 77152d36..00000000 --- a/lib/vsDetect.js +++ /dev/null @@ -1,95 +0,0 @@ -"use strict"; -const processHelpers = require("./processHelpers"); - -const vsDetect = { - isInstalled: async function (version) { - const vsInstalled = await this._isVSInstalled(version); - const vsvNextInstalled = await this._isVSvNextInstalled(version); - const buildToolsInstalled = await this._isBuildToolsInstalled(version); - const foundByVSWhere = await this._isFoundByVSWhere(version); - - return vsInstalled || vsvNextInstalled || buildToolsInstalled || foundByVSWhere; - }, - - _isFoundByVSWhere: async function (version) { - // TODO: with auto download - /* - let mainVer = version.split(".")[0]; - let command = path.resolve("vswhere.exe"); - try { - let stdout = yield processHelpers.execFile([command, "-version", version]); - return stdout && stdout.indexOf("installationVersion: " + mainVer) > 0; - } - catch (e) { - // Ignore - } - */ - return false; - }, - - _isBuildToolsInstalled: async function (version) { - const mainVer = version.split(".")[0]; - let key; - let testPhrase; - if (Number(mainVer) >= 15) { - key = "HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VS.windows_toolscore,v" + mainVer; - testPhrase = "Version"; - } - else { - key = "HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VS.VisualCppBuildTools_x86_enu,v" + mainVer; - testPhrase = "Visual C++"; - } - const command = ["reg", "query", key]; - try { - const stdout = await processHelpers.execFile(command); - return stdout && stdout.indexOf(testPhrase) > 0; - } - catch (e) { - // Ignore - } - return false; - }, - - _isVSInstalled: async function (version) { - // On x64 this will look for x64 keys only, but if VS and compilers installed properly, - // it will write it's keys to 64 bit registry as well. - const command = ["reg", "query", "HKLM\\Software\\Microsoft\\VisualStudio\\" + version]; - try { - const stdout = await processHelpers.execFile(command); - if (stdout) { - const lines = stdout.split("\r\n").filter(function (line) { - return line.length > 10; - }); - if (lines.length >= 4) { - return true; - } - } - } - catch (e) { - // Ignore - } - return false; - }, - - _isVSvNextInstalled: async function (version) { - const mainVer = version.split(".")[0]; - const command = ["reg", "query", "HKLM\\SOFTWARE\\Classes\\Installer\\Dependencies\\Microsoft.VisualStudio.MinShell.Msi,v" + mainVer]; - try { - const stdout = await processHelpers.execFile(command); - if (stdout) { - const lines = stdout.split("\r\n").filter(function (line) { - return line.length > 10; - }); - if (lines.length >= 3) { - return true; - } - } - } - catch (e) { - // Ignore - } - return false; - } -}; - -module.exports = vsDetect; From ce9e883a83fae3565346824f405aae5b17254ba9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 17:21:46 -0700 Subject: [PATCH 19/84] chore: update readme for vs selection changes --- README.md | 4 ++-- changelog.md | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c5653262..0806d3af 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,8 @@ 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/) - or a recent version of Visual C++ will do ([the free Community](https://www.visualstudio.com/products/visual-studio-community-vs) version works well) + - [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. + - 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) diff --git a/changelog.md b/changelog.md index ee70ee15..5113698a 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ v7.0.0 - unreleased - update dependencies - replace some dependencies with modern language features +- follow node-gyp behaviour for visual-studio version detection and selection v6.3.1 - 05/06/22 ========== From 024761814f427dfff6790be299e14091503d9357 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 18:21:16 -0700 Subject: [PATCH 20/84] feat: automatically locate and setup node-addon-api --- README.md | 15 +-------------- changelog.md | 1 + lib/cMake.js | 7 +++++++ lib/locateNodeApi.js | 18 ++++++++++++++++++ 4 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 lib/locateNodeApi.js diff --git a/README.md b/README.md index 0806d3af..59c33074 100644 --- a/README.md +++ b/README.md @@ -327,20 +327,7 @@ you need to make your package depend on it with: npm install --save node-addon-api -and add it to the include directories of your *CMake* project file -`CMakeLists.txt`: - -```cmake -# Include node-addon-api wrappers -execute_process(COMMAND node -p "require('node-addon-api').include" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE NODE_ADDON_API_DIR - ) -string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) -string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) -target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) -``` - +cmake-js will then add it to the include search path automatically ## Tutorials diff --git a/changelog.md b/changelog.md index 5113698a..52f2fba2 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,7 @@ v7.0.0 - unreleased - 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 v6.3.1 - 05/06/22 ========== diff --git a/lib/cMake.js b/lib/cMake.js index df0d28bc..1207e0a1 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -8,6 +8,7 @@ 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"); @@ -151,6 +152,12 @@ CMake.prototype.getConfigureCommand = async function () { incPaths.push(nanH); } + // Node-api + const napiH = await locateNodeApi(this.projectRoot) + if (napiH) { + incPaths.push(napiH) + } + // Includes: D.push({"CMAKE_JS_INC": incPaths.join(";")}); diff --git a/lib/locateNodeApi.js b/lib/locateNodeApi.js new file mode 100644 index 00000000..a05301fd --- /dev/null +++ b/lib/locateNodeApi.js @@ -0,0 +1,18 @@ +"use strict"; +const path = require("path"); + +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.replaceAll('"', '') + } catch (e) { + // It most likely wasn't found + return null; + } +}; From 2a28e7009d230942f752e11c367c2c4c8dfaabbd Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 17:41:59 +0100 Subject: [PATCH 21/84] chore: add choco instructions to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 59c33074..28f11ec7 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ Options: - 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 From 565aa232904fedf29a1bbae58510388fb53566bf Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 20:22:24 +0100 Subject: [PATCH 22/84] fix: node-api broken in node 14 --- lib/locateNodeApi.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/locateNodeApi.js b/lib/locateNodeApi.js index a05301fd..6ee9ee5e 100644 --- a/lib/locateNodeApi.js +++ b/lib/locateNodeApi.js @@ -10,7 +10,7 @@ const locateNodeApi = module.exports = async function (projectRoot) { try { const tmpRequire = require('module').createRequire(path.join(projectRoot, 'package.json')) const inc = tmpRequire('node-addon-api') - return inc.include.replaceAll('"', '') + return inc.include.replace(/"/g, '') } catch (e) { // It most likely wasn't found return null; diff --git a/package.json b/package.json index 26973c16..3b735cde 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "boost" ], "main": "lib", - "version": "7.0.0-1", + "version": "7.0.0-2", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From 1c78b2a235dac33034471ce1a69f8f5aabcd80de Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 15:52:51 -0700 Subject: [PATCH 23/84] feat: skip downloading node headers when targetting node-api (#278) --- README.md | 8 +++++ changelog.md | 1 + lib/buildSystem.js | 46 ++++++++++++++++++++++++++- lib/cMake.js | 76 +++++++++++++++++++++++++++----------------- lib/locateNodeApi.js | 2 +- lib/toolset.js | 2 +- package.json | 7 ++-- 7 files changed, 107 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 28f11ec7..b01ce300 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,14 @@ 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 + execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) +endif() +``` + To compile a native module that uses the header-only C++ wrapper classes provided by [`node-addon-api`](https://github.com/nodejs/node-addon-api), diff --git a/changelog.md b/changelog.md index 52f2fba2..1611209b 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,7 @@ v7.0.0 - unreleased - 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 v6.3.1 - 05/06/22 ========== diff --git a/lib/buildSystem.js b/lib/buildSystem.js index 866b409b..f2e6c7f1 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -4,14 +4,29 @@ const Dist = require("./dist"); const CMLog = require("./cmLog"); const appCMakeJSConfig = require("./appCMakeJSConfig"); const npmConfig = require("./npmConfig"); +const fs = require('fs/promises') const path = require("path"); const isPlainObject = require("lodash.isplainobject"); const Toolset = require("./toolset"); +const headers = require('node-api-headers'); + +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 + } +} 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); @@ -39,7 +54,11 @@ function BuildSystem(options) { BuildSystem.prototype._ensureInstalled = async function () { try { await this.toolset.initialize(true); - await this.dist.ensureDownloaded(); + if (this.options.isNodeApi) { + this.options.nodeLibDefPath = await this._generateNodeLibDef(path.join(this.options.out, 'node-lib.def')); + } else { + await this.dist.ensureDownloaded(); + } } catch (e) { this._showError(e); @@ -47,6 +66,31 @@ BuildSystem.prototype._ensureInstalled = async function () { } }; +BuildSystem.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) + } + } + + // Ensure the build dir exists + const parentDir = path.dirname(targetFile) + await fs.mkdir(parentDir, { recursive: true }) + + // 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`) + } +} + BuildSystem.prototype._showError = function (e) { if (this.log === undefined) { // handle internal errors (init failed) diff --git a/lib/cMake.js b/lib/cMake.js index 1207e0a1..ed2be269 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -133,29 +133,35 @@ CMake.prototype.getConfigureCommand = async function () { else { D.push({"CMAKE_LIBRARY_OUTPUT_DIRECTORY": this.buildDir}); } + + 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]; + } - // Include and lib: - let incPaths; - 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); - } - - // Node-api - const napiH = await locateNodeApi(this.projectRoot) - if (napiH) { - incPaths.push(napiH) + // 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: @@ -176,13 +182,7 @@ CMake.prototype.getConfigureCommand = async function () { D.push({"NODE_RUNTIMEVERSION": this.targetOptions.runtimeVersion}); D.push({"NODE_ARCH": this.targetOptions.arch}); - if (environment.isWin) { - // Win - const libs = this.dist.winLibs; - if (libs.length) { - D.push({"CMAKE_JS_LIB": libs.join(";")}); - } - } else if (environment.isOSX) { + if (environment.isOSX) { if (this.targetOptions.arch) { let xcodeArch = this.targetOptions.arch if (xcodeArch === 'x64') xcodeArch = 'x86_64' @@ -198,6 +198,24 @@ CMake.prototype.getConfigureCommand = async function () { // Toolset: await this.toolset.initialize(false); + if (environment.isWin) { + // Win + const libs = [] + if (this.options.isNodeApi) { + if (this.options.nodeLibDefPath) { + const nodeLibPath = path.join(this.workDir, 'node.lib') + D.push({ CMAKE_JS_NODELIB_DEF: this.options.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); } diff --git a/lib/locateNodeApi.js b/lib/locateNodeApi.js index 6ee9ee5e..ee1fbf30 100644 --- a/lib/locateNodeApi.js +++ b/lib/locateNodeApi.js @@ -15,4 +15,4 @@ const locateNodeApi = module.exports = async function (projectRoot) { // It most likely wasn't found return null; } -}; +}; \ No newline at end of file diff --git a/lib/toolset.js b/lib/toolset.js index ca9fd699..935d137b 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -4,7 +4,7 @@ const environment = require("./environment"); const assert = require("assert"); const CMLog = require("./cmLog"); const util = require('util') -const findVisualStudioRaw = require('./import/find-visualstudio') +const findVisualStudioRaw = require('./import/find-visualstudio'); const findVisualStudio = util.promisify(findVisualStudioRaw) diff --git a/package.json b/package.json index 3b735cde..ef05a3aa 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ "author": "Gábor Mező aka unbornchikken", "maintainers": [ { - "name" : "Julian Waller", - "email" : "git@julusian.co.uk", - "url" : "https://github.com/julusian/" + "name": "Julian Waller", + "email": "git@julusian.co.uk", + "url": "https://github.com/julusian/" } ], "repository": { @@ -41,6 +41,7 @@ "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.7", From 272ec1883bc1207205abae948022eecdea02e225 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 6 Jun 2022 00:10:52 +0100 Subject: [PATCH 24/84] fix: define BUILDING_NODE_EXTENSION on all platforms #37 --- lib/toolset.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/toolset.js b/lib/toolset.js index 935d137b..9156feb5 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -126,9 +126,10 @@ Toolset.prototype.initializePosix = function (install) { 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.compilerFlags.push("-DBUILDING_NODE_EXTENSION"); this.linkerFlags.push("-undefined dynamic_lookup"); } + + this.compilerFlags.push("-DBUILDING_NODE_EXTENSION"); // 4: Build target if (this.options.target) { From 0e2c948ea53de521fa3c224b7353316e856576fc Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 6 Jun 2022 10:55:49 -0700 Subject: [PATCH 25/84] fix(msvc): node.lib should be generated as part of the configure step --- lib/buildSystem.js | 31 +------------------------------ lib/cMake.js | 44 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 35 insertions(+), 40 deletions(-) diff --git a/lib/buildSystem.js b/lib/buildSystem.js index f2e6c7f1..e1690df7 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -4,11 +4,9 @@ const Dist = require("./dist"); const CMLog = require("./cmLog"); const appCMakeJSConfig = require("./appCMakeJSConfig"); const npmConfig = require("./npmConfig"); -const fs = require('fs/promises') const path = require("path"); const isPlainObject = require("lodash.isplainobject"); const Toolset = require("./toolset"); -const headers = require('node-api-headers'); function isNodeApi(log, projectRoot) { try { @@ -54,9 +52,7 @@ function BuildSystem(options) { BuildSystem.prototype._ensureInstalled = async function () { try { await this.toolset.initialize(true); - if (this.options.isNodeApi) { - this.options.nodeLibDefPath = await this._generateNodeLibDef(path.join(this.options.out, 'node-lib.def')); - } else { + if (!this.options.isNodeApi) { await this.dist.ensureDownloaded(); } } @@ -66,31 +62,6 @@ BuildSystem.prototype._ensureInstalled = async function () { } }; -BuildSystem.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) - } - } - - // Ensure the build dir exists - const parentDir = path.dirname(targetFile) - await fs.mkdir(parentDir, { recursive: true }) - - // 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`) - } -} - BuildSystem.prototype._showError = function (e) { if (this.log === undefined) { // handle internal errors (init failed) diff --git a/lib/cMake.js b/lib/cMake.js index ed2be269..03e29f9b 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -11,6 +11,7 @@ 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 || {}; @@ -116,7 +117,7 @@ CMake.prototype.verifyIfAvailable = function () { } }; -CMake.prototype.getConfigureCommand = async function () { +CMake.prototype.getConfigureCommand = async function (nodeLibDefPath) { // Create command: let command = [this.path, this.projectRoot, "--no-warn-unused-cli"]; @@ -201,14 +202,13 @@ CMake.prototype.getConfigureCommand = async function () { if (environment.isWin) { // Win const libs = [] - if (this.options.isNodeApi) { - if (this.options.nodeLibDefPath) { - const nodeLibPath = path.join(this.workDir, 'node.lib') - D.push({ CMAKE_JS_NODELIB_DEF: this.options.nodeLibDefPath }) - D.push({ CMAKE_JS_NODELIB_TARGET: nodeLibPath }) - libs.push(nodeLibPath) - } - } else { + 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) + } + if (!this.options.isNodeApi) { libs.push(...this.dist.winLibs) } if (libs.length) { @@ -258,12 +258,35 @@ CMake.prototype.getConfigureCommand = async function () { 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 ? 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(); + const command = await this.getConfigureCommand(nodeLibDefPath); try { await fs.lstat(listPath); @@ -282,6 +305,7 @@ CMake.prototype.configure = async function () { const cwd = process.cwd(); process.chdir(this.workDir); try { + await this._generateNodeLibDef(nodeLibDefPath) await this._run(command); } finally { From 3e4558f970374a3c5e7112314dd18561c63052dd Mon Sep 17 00:00:00 2001 From: ci7lus <7887955+ci7lus@users.noreply.github.com> Date: Tue, 7 Jun 2022 03:23:27 +0900 Subject: [PATCH 26/84] chore: update baseurl for electron mirror (#279) --- lib/runtimePaths.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtimePaths.js b/lib/runtimePaths.js index 18ca9e0e..ccde788f 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -4,7 +4,7 @@ const semver = require("semver"); const isPlainObject = require("lodash.isplainobject"); const NODE_MIRROR = process.env.NVM_NODEJS_ORG_MIRROR || "https://nodejs.org/dist"; -const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || "https://atom.io/download/atom-shell"; +const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || "https://artifacts.electronjs.org/headers/dist"; const runtimePaths = { node: function (targetOptions) { From a38fd3a30e1570904f4657b98f85c714c01fbfed Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 6 Jun 2022 19:06:35 +0100 Subject: [PATCH 27/84] chore: add some more keywords --- package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index ef05a3aa..86b10e5f 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,15 @@ "c++", "bindings", "build", + "buildtools", "cmake", "nw.js", "electron", - "boost" + "boost", + "nan", + "napi", + "node-api", + "node-addon-api" ], "main": "lib", "version": "7.0.0-2", From b205e13855ae5fb49b444ca1c48aae497358b2e3 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 6 Jun 2022 19:28:43 +0100 Subject: [PATCH 28/84] fix: typo --- lib/cMake.js | 10 ++++++---- tests/es6/testRunner.js | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/cMake.js b/lib/cMake.js index 03e29f9b..1e060a60 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -207,8 +207,7 @@ CMake.prototype.getConfigureCommand = async function (nodeLibDefPath) { D.push({ CMAKE_JS_NODELIB_DEF: nodeLibDefPath }) D.push({ CMAKE_JS_NODELIB_TARGET: nodeLibPath }) libs.push(nodeLibPath) - } - if (!this.options.isNodeApi) { + } else { libs.push(...this.dist.winLibs) } if (libs.length) { @@ -282,7 +281,7 @@ CMake.prototype._generateNodeLibDef = async function (targetFile) { CMake.prototype.configure = async function () { this.verifyIfAvailable(); - const nodeLibDefPath = environment.isWin ? path.join(this.options.out, 'node-lib.def') : undefined + 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"); @@ -305,7 +304,10 @@ CMake.prototype.configure = async function () { const cwd = process.cwd(); process.chdir(this.workDir); try { - await this._generateNodeLibDef(nodeLibDefPath) + if (environment.isWin) { + await this._generateNodeLibDef(nodeLibDefPath) + } + await this._run(command); } finally { diff --git a/tests/es6/testRunner.js b/tests/es6/testRunner.js index 74280306..c9616bc0 100644 --- a/tests/es6/testRunner.js +++ b/tests/es6/testRunner.js @@ -73,13 +73,13 @@ function* generateOptions() { } else { // Clang, Make - yield { ...runtimeOptions, preferClang: true, referMake: true }; + yield { ...runtimeOptions, preferClang: true, preferMake: true }; // Clang, Ninja yield { ...runtimeOptions, preferClang: true }; // g++, Make - yield { ...runtimeOptions, preferGnu: true, referMake: true }; + yield { ...runtimeOptions, preferGnu: true, preferMake: true }; // g++, Ninja yield { ...runtimeOptions, preferGnu: true }; From 261e462897b7ea2b820614c4e69b9fa20dea9e31 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 6 Jun 2022 20:13:26 +0100 Subject: [PATCH 29/84] fix: typo --- lib/cMake.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cMake.js b/lib/cMake.js index 1e060a60..5ed37a8a 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -304,7 +304,7 @@ CMake.prototype.configure = async function () { const cwd = process.cwd(); process.chdir(this.workDir); try { - if (environment.isWin) { + if (environment.isWin && nodeLibDefPath) { await this._generateNodeLibDef(nodeLibDefPath) } From 2be9d8f732c21f538786fc64c443a7359d777ccf Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 8 Jun 2022 18:35:58 +0100 Subject: [PATCH 30/84] v7.0.0-3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 86b10e5f..29b00b6a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "node-addon-api" ], "main": "lib", - "version": "7.0.0-2", + "version": "7.0.0-3", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From c99e10eb08a519119e2fff30212857e073b81f61 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 8 Oct 2022 12:41:58 +0100 Subject: [PATCH 31/84] fix: Update `win_delay_load_hook.cc` based on nw.js --- lib/cpp/win_delay_load_hook.cc | 40 +++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/cpp/win_delay_load_hook.cc b/lib/cpp/win_delay_load_hook.cc index f19ad95e..72dca35d 100644 --- a/lib/cpp/win_delay_load_hook.cc +++ b/lib/cpp/win_delay_load_hook.cc @@ -1,3 +1,12 @@ +/* + * When this file is linked to a DLL, it sets up a delay-load hook that + * intervenes when the DLL is trying to load 'node.exe' or 'iojs.exe' + * dynamically. Instead of trying to locate the .exe file it'll just return + * a handle to the process image. + * + * This allows compiled addons to work when node.exe or iojs.exe is renamed. + */ + #ifdef _MSC_VER #ifndef WIN32_LEAN_AND_MEAN @@ -5,19 +14,34 @@ #endif #include + #include #include -static FARPROC WINAPI load_exe_hook(unsigned int event, DelayLoadInfo *info) { - HMODULE m; - if (event != dliNotePreLoadLibrary) - return NULL; +static HMODULE node_dll = NULL; +static HMODULE nw_dll = NULL; + +static FARPROC WINAPI load_exe_hook(unsigned int event, DelayLoadInfo* info) { + if (event == dliNotePreGetProcAddress) { + FARPROC ret = NULL; + ret = GetProcAddress(node_dll, info->dlp.szProcName); + if (ret) + return ret; + ret = GetProcAddress(nw_dll, info->dlp.szProcName); + return ret; + } + if (event == dliStartProcessing) { + node_dll = GetModuleHandleA("node.dll"); + nw_dll = GetModuleHandleA("nw.dll"); + return NULL; + } + if (event != dliNotePreLoadLibrary) + return NULL; - if (_stricmp(info->szDll, "NODE.EXE") != 0) - return NULL; + if (_stricmp(info->szDll, "node.exe") != 0) + return NULL; - m = GetModuleHandle(NULL); - return (FARPROC)m; + return (FARPROC) node_dll; } decltype(__pfnDliNotifyHook2) __pfnDliNotifyHook2 = load_exe_hook; From 6712a0fcbce24c55e9473df1b64e3be175ec8d54 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 8 Oct 2022 13:55:07 +0100 Subject: [PATCH 32/84] feat: Set `CMAKE_MSVC_RUNTIME_LIBRARY` to encourage use of MT builds --- README.md | 83 ++++++++++++++++++++++++++++++---------------------- lib/cMake.js | 4 +++ 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index b01ce300..bdbebab7 100644 --- a/README.md +++ b/README.md @@ -96,17 +96,26 @@ In a nutshell. *(For more complete documentation please see [the first tutorial] - 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) +add_definitions(-DNAPI_VERSION=4) + include_directories(${CMAKE_JS_INC}) 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}) + +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() ``` - Add the following into your package.json scripts section: @@ -119,10 +128,12 @@ target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB}) #### Commandline -In your module folder you can access cmake-js commands if you install cmake-js globally: +With cmake-js installed as a depdendency or devDependency of your module, you can access run commands directly with: ``` -npm install -g cmake-js +npx cmake-js --help +# OR +yarn cmake-js --help ``` Please refer to the `--help` for the lists of available commands (they are like commands in `node-gyp`). @@ -251,6 +262,38 @@ Available settings: - **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.* + +#### Node-API and `node-addon-api` + +[ABI-stable Node.js API +(Node-API)](https://nodejs.org/api/n-api.html#n_api_node_api), +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. + +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 + execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) +endif() +``` + +To compile a native module that uses the header-only C++ wrapper +classes provided by +[`node-addon-api`](https://github.com/nodejs/node-addon-api), +you need to make your package depend on it with: + + npm install --save node-addon-api + +cmake-js will then add it to the include search path automatically + + #### Electron On Windows, the [`win_delay_load_hook`](https://www.electronjs.org/docs/tutorial/using-native-node-modules#a-note-about-win_delay_load_hook) is required to be embedded in the module or it will fail to load in the render process. @@ -268,7 +311,7 @@ The actual node runtime parameters are detectable in CMakeLists.txt files, the f #### NW.js -To make compatible your NW.js application with any CMake.js based modules, write the following to your application's package.json file: +To make compatible your NW.js application with any NAN CMake.js based modules, write the following to your application's package.json file (this is not neccessary for node-api modules): ```json { @@ -308,36 +351,6 @@ The `heroku-buildpack-multi` will run each buildpack in order allowing the node build environment. -#### Node-API and `node-addon-api` - -[ABI-stable Node.js API -(Node-API)](https://nodejs.org/api/n-api.html#n_api_node_api), -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. - -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 - execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) -endif() -``` - -To compile a native module that uses the header-only C++ wrapper -classes provided by -[`node-addon-api`](https://github.com/nodejs/node-addon-api), -you need to make your package depend on it with: - - npm install --save node-addon-api - -cmake-js will then add it to the include search path automatically - ## Tutorials - [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) diff --git a/lib/cMake.js b/lib/cMake.js index 5ed37a8a..c73f4bce 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -135,6 +135,10 @@ CMake.prototype.getConfigureCommand = async function (nodeLibDefPath) { 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>"}) + let incPaths = []; if (!this.options.isNodeApi) { // Include and lib: From 46fba95d4bda7554c681eea7b8afdf08176c1df9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 8 Oct 2022 14:35:34 +0100 Subject: [PATCH 33/84] chore: update dependencies --- changelog.md | 1 + package.json | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 1611209b..006d7db8 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ v7.0.0 - unreleased - 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 ========== diff --git a/package.json b/package.json index 29b00b6a..44c188b4 100644 --- a/package.json +++ b/package.json @@ -49,15 +49,15 @@ "node-api-headers": "^0.0.1", "npmlog": "^6.0.2", "rc": "^1.2.7", - "semver": "^7.3.7", + "semver": "^7.3.8", "tar": "^6.1.11", "url-join": "^4.0.1", "which": "^2.0.2", - "yargs": "^17.4.1" + "yargs": "^17.6.0" }, "devDependencies": { "mocha": "*", - "nan": "^2.1.0", + "nan": "^2.16.0", "node-addon-api": "^5.0.0" }, "scripts": { From 110c5637119bac684aa70fbcde561f7d2ce1850d Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sat, 8 Oct 2022 15:02:19 +0100 Subject: [PATCH 34/84] v7.0.0 --- README.md | 4 +++- changelog.md | 2 +- package.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index bdbebab7..4d7b9eba 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ CMake.js is a Node.js native addon build tool which works (almost) *exactly* lik - 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 - [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) (formerly known as atom-shell): 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) +- [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) + +If you use `node-api` for your module instead of `nan` it should be able to run on all the runtimes above without needing to be built separately for each. ## Installation diff --git a/changelog.md b/changelog.md index 006d7db8..a8c1a41c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -v7.0.0 - unreleased +v7.0.0 - 08/10/22 ========== - update dependencies diff --git a/package.json b/package.json index 44c188b4..eb6849f7 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "node-addon-api" ], "main": "lib", - "version": "7.0.0-3", + "version": "7.0.0", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From 1a6bce79d13a1eccfc3b4804ce85b0b63d4420e4 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 30 Oct 2022 00:04:49 +0100 Subject: [PATCH 35/84] fix: missing js_native_api_symbols --- lib/cMake.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/cMake.js b/lib/cMake.js index c73f4bce..39d73a22 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -269,6 +269,9 @@ CMake.prototype._generateNodeLibDef = async function (targetFile) { for (const sym of ver.node_api_symbols) { allSymbols.add(sym) } + for (const sym of ver.js_native_api_symbols) { + allSymbols.add(sym) + } } // Write a 'def' file for NODE.EXE From 3be712f122251c9947f68bf3877bcc0d27d41947 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 30 Oct 2022 00:24:22 +0100 Subject: [PATCH 36/84] chore: add node-api unit test --- tests/es6/buildSystem.js | 5 +++++ tests/es6/prototype-napi/CMakeLists.txt | 20 ++++++++++++++++++++ tests/es6/prototype-napi/package.json | 5 +++++ tests/es6/prototype-napi/src/addon.cpp | 14 ++++++++++++++ tests/es6/testCases.js | 12 ++++++++++++ 5 files changed, 56 insertions(+) create mode 100644 tests/es6/prototype-napi/CMakeLists.txt create mode 100644 tests/es6/prototype-napi/package.json create mode 100644 tests/es6/prototype-napi/src/addon.cpp diff --git a/tests/es6/buildSystem.js b/tests/es6/buildSystem.js index b6989a8d..87ea8979 100644 --- a/tests/es6/buildSystem.js +++ b/tests/es6/buildSystem.js @@ -39,6 +39,11 @@ describe("BuildSystem", function () { 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(); diff --git a/tests/es6/prototype-napi/CMakeLists.txt b/tests/es6/prototype-napi/CMakeLists.txt new file mode 100644 index 00000000..9a39c3fa --- /dev/null +++ b/tests/es6/prototype-napi/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 2.8) + +project (addon_napi) + +if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "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() diff --git a/tests/es6/prototype-napi/package.json b/tests/es6/prototype-napi/package.json new file mode 100644 index 00000000..2a750b1a --- /dev/null +++ b/tests/es6/prototype-napi/package.json @@ -0,0 +1,5 @@ +{ + "binary": { + "napi_versions": 7 + } +} \ No newline at end of file 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/testCases.js b/tests/es6/testCases.js index 29d77fa5..c0622531 100644 --- a/tests/es6/testCases.js +++ b/tests/es6/testCases.js @@ -27,6 +27,18 @@ const testCases = { 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")), From 98e4184ebd2bc35fde66aec40218b50047435d8e Mon Sep 17 00:00:00 2001 From: Craig Mason Date: Wed, 14 Dec 2022 22:46:44 +0000 Subject: [PATCH 37/84] fix: win_delay_load_hook.cc will fall back to the host process (#289) --- lib/cpp/win_delay_load_hook.cc | 3 +++ 1 file changed, 3 insertions(+) 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; } From b06d80a19a39986e6a451c7ef72ad18ff62c4b9b Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 14 Dec 2022 23:35:21 +0000 Subject: [PATCH 38/84] feat: add print-cmakejs-* commands for ide integration #290 --- README.md | 37 +++++++------ bin/cmake-js | 26 ++++++++- lib/buildSystem.js | 12 +++++ lib/cMake.js | 128 +++++++++++++++++++++++++-------------------- 4 files changed, 128 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 4d7b9eba..6411eccc 100644 --- a/README.md +++ b/README.md @@ -28,20 +28,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 +68,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 diff --git a/bin/cmake-js b/bin/cmake-js index aecf12f3..d747d740 100755 --- a/bin/cmake-js +++ b/bin/cmake-js @@ -23,14 +23,15 @@ for (const [key, value] of Object.entries(npmConfigData)) { } } -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("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") @@ -261,6 +262,24 @@ function printConfigure() { 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()); } @@ -292,6 +311,9 @@ function 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); diff --git a/lib/buildSystem.js b/lib/buildSystem.js index e1690df7..d60d0fd1 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -95,6 +95,18 @@ BuildSystem.prototype.getConfigureCommand = function () { return this._invokeCMake("getConfigureCommand"); }; +BuildSystem.prototype.getCmakeJsLibString = function () { + return this._invokeCMake("getCmakeJsLibString"); +}; + +BuildSystem.prototype.getCmakeJsIncludeString = function () { + return this._invokeCMake("getCmakeJsIncludeString"); +}; + +BuildSystem.prototype.getCmakeJsSrcString = function () { + return this._invokeCMake("getCmakeJsSrcString"); +}; + BuildSystem.prototype.configure = function () { return this._invokeCMake("configure"); }; diff --git a/lib/cMake.js b/lib/cMake.js index 39d73a22..1d033c2c 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -117,7 +117,9 @@ CMake.prototype.verifyIfAvailable = function () { } }; -CMake.prototype.getConfigureCommand = async function (nodeLibDefPath) { +CMake.prototype.getConfigureCommand = async function () { + const nodeLibDefPath = environment.isWin && this.options.isNodeApi ? path.join(this.options.out, 'node-lib.def') : undefined + // Create command: let command = [this.path, this.projectRoot, "--no-warn-unused-cli"]; @@ -139,48 +141,13 @@ CMake.prototype.getConfigureCommand = async function (nodeLibDefPath) { // 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(";")}); + const includesString = await this.getCmakeJsIncludeString(); + D.push({ "CMAKE_JS_INC": includesString }); // 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(";")}); + const srcsString = this.getCmakeJsSrcString(); + D.push({ "CMAKE_JS_SRC": srcsString }); // Runtime: D.push({"NODE_RUNTIME": this.targetOptions.runtime}); @@ -203,21 +170,8 @@ CMake.prototype.getConfigureCommand = async function (nodeLibDefPath) { // 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(";")}); - } - } + const libsString = this.getCmakeJsLibString() + D.push({ "CMAKE_JS_LIB": libsString }); if (this.toolset.generator) { command.push("-G", this.toolset.generator); @@ -261,6 +215,66 @@ CMake.prototype.getConfigureCommand = async function (nodeLibDefPath) { return command; }; +CMake.prototype.getCmakeJsLibString = function () { + const libs = [] + if (environment.isWin) { + 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) + } + } + return libs.join(";"); +}; + +CMake.prototype.getCmakeJsIncludeString = async function () { + 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(";"); +}; + +CMake.prototype.getCmakeJsSrcString = function () { + 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(";"); +}; + CMake.prototype._generateNodeLibDef = async function (targetFile) { try { // Compile a Set of all the symbols that could be exported @@ -288,11 +302,9 @@ CMake.prototype._generateNodeLibDef = async function (targetFile) { 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); + const command = await this.getConfigureCommand(); try { await fs.lstat(listPath); From 42c5f7f8ad9be0b24f365cf72bdd0f5b37f0d325 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 14 Dec 2022 23:50:25 +0000 Subject: [PATCH 39/84] v7.1.0 --- changelog.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index a8c1a41c..d9767b2c 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,10 @@ +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 ========== diff --git a/package.json b/package.json index eb6849f7..5117a2f5 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "node-addon-api" ], "main": "lib", - "version": "7.0.0", + "version": "7.1.0", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From d2e1c42687b9ed7f9ac1e0d440430bfa203ea8c9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 15 Dec 2022 00:20:17 +0000 Subject: [PATCH 40/84] fix: typo --- lib/cMake.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/cMake.js b/lib/cMake.js index 1d033c2c..79988ba0 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -118,7 +118,6 @@ CMake.prototype.verifyIfAvailable = function () { }; CMake.prototype.getConfigureCommand = async function () { - const nodeLibDefPath = environment.isWin && this.options.isNodeApi ? path.join(this.options.out, 'node-lib.def') : undefined // Create command: let command = [this.path, this.projectRoot, "--no-warn-unused-cli"]; @@ -216,8 +215,10 @@ CMake.prototype.getConfigureCommand = async function () { }; CMake.prototype.getCmakeJsLibString = function () { + const libs = [] - if (environment.isWin) { + if (environment.isWin) { + const nodeLibDefPath = this.getNodeLibDefPath() if (nodeLibDefPath) { const nodeLibPath = path.join(this.workDir, 'node.lib') D.push({ CMAKE_JS_NODELIB_DEF: nodeLibDefPath }) @@ -275,6 +276,10 @@ CMake.prototype.getCmakeJsSrcString = function () { return srcPaths.join(";"); }; +CMake.prototype.getNodeLibDefPath = function () { + return environment.isWin && this.options.isNodeApi ? path.join(this.options.out, 'node-lib.def') : undefined +} + CMake.prototype._generateNodeLibDef = async function (targetFile) { try { // Compile a Set of all the symbols that could be exported @@ -323,6 +328,8 @@ CMake.prototype.configure = async function () { const cwd = process.cwd(); process.chdir(this.workDir); try { + const nodeLibDefPath = this.getNodeLibDefPath() + if (environment.isWin && nodeLibDefPath) { await this._generateNodeLibDef(nodeLibDefPath) } From a50552c8fcb8d038a77ceedf38c8622fbe852a21 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 15 Dec 2022 00:40:12 +0000 Subject: [PATCH 41/84] fix: typo --- lib/cMake.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/cMake.js b/lib/cMake.js index 79988ba0..ab152ce9 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -172,6 +172,15 @@ CMake.prototype.getConfigureCommand = async function () { 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); } @@ -220,10 +229,7 @@ CMake.prototype.getCmakeJsLibString = function () { 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 }) - libs.push(nodeLibPath) + libs.push(path.join(this.workDir, 'node.lib')) } else { libs.push(...this.dist.winLibs) } From 9ccfd0dfb99ead7edeeb967ab5ed34722c58ad24 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 15 Dec 2022 00:56:02 +0000 Subject: [PATCH 42/84] chore: update github action --- .github/workflows/node.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index 7e6d77b3..b59c82f9 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -48,7 +48,7 @@ jobs: libc: musl steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Use Node.js 14.x if: ${{ !matrix.docker-arch }} From 3a06b71cd27332b29e042bad972843d827586f88 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 15 Dec 2022 00:57:27 +0000 Subject: [PATCH 43/84] v7.1.1 --- changelog.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index d9767b2c..a1b87a6f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +v7.1.1 - 15/12/22 +========== + +- fix build errors on windows + v7.1.0 - 14/12/22 ========== diff --git a/package.json b/package.json index 5117a2f5..40da7ac9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "node-addon-api" ], "main": "lib", - "version": "7.1.0", + "version": "7.1.1", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From e36dd99d9fb2fb3eb744b2bc4edbf6df1cb690dd Mon Sep 17 00:00:00 2001 From: Paul Taylor Date: Sun, 12 Feb 2023 04:01:19 -0800 Subject: [PATCH 44/84] fix: `-DCMAKE_JS_VERSION=undefined` (#298) Fixes https://github.com/cmake-js/cmake-js/issues/295 --- lib/cMake.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cMake.js b/lib/cMake.js index ab152ce9..5be18a2b 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -125,7 +125,7 @@ CMake.prototype.getConfigureCommand = async function () { const D = []; // CMake.js watermark - D.push({"CMAKE_JS_VERSION": environment.moduleVersion}); + D.push({"CMAKE_JS_VERSION": environment.cmakeJsVersion}); // Build configuration: D.push({"CMAKE_BUILD_TYPE": this.config}); From e40d2f31624ea62ca9e978aec738a69944d7ab61 Mon Sep 17 00:00:00 2001 From: Paul Taylor Date: Sun, 12 Feb 2023 04:03:24 -0800 Subject: [PATCH 45/84] feat: Forward extra arguments to CMake commands (#297) --- bin/cmake-js | 3 ++- lib/cMake.js | 18 +++++++++++------- tests/es6/buildSystem.js | 8 ++++++-- tests/es6/testCases.js | 20 +++++++++++++++++++- 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/bin/cmake-js b/bin/cmake-js index d747d740..55df9fa4 100755 --- a/bin/cmake-js +++ b/bin/cmake-js @@ -224,7 +224,8 @@ const options = { silent: argv.i, out: argv.O, config: argv.B, - parallel: argv.p + parallel: argv.p, + extraCMakeArgs: argv._.slice(1), }; log.verbose("CON", "options:"); diff --git a/lib/cMake.js b/lib/cMake.js index 5be18a2b..9fea02b9 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -25,6 +25,7 @@ function CMake(options) { 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; } @@ -135,11 +136,11 @@ CMake.prototype.getConfigureCommand = async function () { 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. + // 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 }); @@ -220,11 +221,11 @@ CMake.prototype.getConfigureCommand = async function () { return "-D" + Object.keys(p)[0] + "=" + Object.values(p)[0]; })); - return command; + return command.concat(this.extraCMakeArgs); }; CMake.prototype.getCmakeJsLibString = function () { - + const libs = [] if (environment.isWin) { const nodeLibDefPath = this.getNodeLibDefPath() @@ -364,7 +365,7 @@ CMake.prototype.getBuildCommand = function () { if (this.options.parallel) { command.push("--parallel", this.options.parallel); } - return Promise.resolve(command); + return Promise.resolve(command.concat(this.extraCMakeArgs)); }; CMake.prototype.build = async function () { @@ -377,7 +378,7 @@ CMake.prototype.build = async function () { }; CMake.prototype.getCleanCommand = function () { - return [this.path, "-E", "remove_directory", this.workDir]; + return [this.path, "-E", "remove_directory", this.workDir].concat(this.extraCMakeArgs); }; CMake.prototype.clean = function () { @@ -388,16 +389,19 @@ CMake.prototype.clean = function () { }; CMake.prototype.reconfigure = async function () { + this.extraCMakeArgs = []; await this.clean(); await this.configure(); }; CMake.prototype.rebuild = async function () { + this.extraCMakeArgs = []; await this.clean(); await this.build(); }; CMake.prototype.compile = async function () { + this.extraCMakeArgs = []; try { await this.build(); } diff --git a/tests/es6/buildSystem.js b/tests/es6/buildSystem.js index 87ea8979..a8b28310 100644 --- a/tests/es6/buildSystem.js +++ b/tests/es6/buildSystem.js @@ -3,7 +3,7 @@ const assert = require("assert"); const lib = require("../../"); -const locateNAN = require("../../lib/locateNAN") +const locateNAN = require("../../lib/locateNAN"); const CMake = lib.CMake; const path = require("path"); const log = require("npmlog"); @@ -39,7 +39,7 @@ describe("BuildSystem", function () { 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(); }); @@ -52,4 +52,8 @@ describe("BuildSystem", function () { 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/testCases.js b/tests/es6/testCases.js index c0622531..6f8af6b1 100644 --- a/tests/es6/testCases.js +++ b/tests/es6/testCases.js @@ -63,7 +63,25 @@ const testCases = { 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 + }; + + 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; From e2452ee226490bc666d286ab0860aeb35fbbb035 Mon Sep 17 00:00:00 2001 From: Paul Taylor Date: Sun, 12 Feb 2023 04:14:43 -0800 Subject: [PATCH 46/84] fix: Only add build type to `CMAKE_LIBRARY_OUTPUT_DIRECTORY` if needed (#299) Co-authored-by: Julian Waller --- lib/cMake.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/cMake.js b/lib/cMake.js index 9fea02b9..03ac5ccf 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -131,11 +131,14 @@ CMake.prototype.getConfigureCommand = async function () { // Build configuration: D.push({"CMAKE_BUILD_TYPE": this.config}); if (environment.isWin) { - D.push({"CMAKE_RUNTIME_OUTPUT_DIRECTORY": this.workDir}); - } - else { - D.push({"CMAKE_LIBRARY_OUTPUT_DIRECTORY": this.buildDir}); - } + 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. From cea310355598d6612894b9bbbba30b3afbd3c564 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 12 Feb 2023 12:26:03 +0000 Subject: [PATCH 47/84] chore: update cmake version for tests --- tests/es6/prototype-napi/CMakeLists.txt | 4 +++- tests/es6/prototype/CMakeLists.txt | 4 +++- tests/es6/prototype2/CMakeLists.txt | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/tests/es6/prototype-napi/CMakeLists.txt b/tests/es6/prototype-napi/CMakeLists.txt index 9a39c3fa..5e090b60 100644 --- a/tests/es6/prototype-napi/CMakeLists.txt +++ b/tests/es6/prototype-napi/CMakeLists.txt @@ -1,4 +1,6 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.15) +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0042 NEW) project (addon_napi) diff --git a/tests/es6/prototype/CMakeLists.txt b/tests/es6/prototype/CMakeLists.txt index 4c29d9bc..6893b869 100644 --- a/tests/es6/prototype/CMakeLists.txt +++ b/tests/es6/prototype/CMakeLists.txt @@ -1,4 +1,6 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.15) +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0042 NEW) project (addon) diff --git a/tests/es6/prototype2/CMakeLists.txt b/tests/es6/prototype2/CMakeLists.txt index 0189ddc4..0d4bde80 100644 --- a/tests/es6/prototype2/CMakeLists.txt +++ b/tests/es6/prototype2/CMakeLists.txt @@ -1,4 +1,6 @@ -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.15) +cmake_policy(SET CMP0091 NEW) +cmake_policy(SET CMP0042 NEW) project (addon2) From b7aeab6fba9998b54563283b818233d0e82c46cc Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 12 Feb 2023 13:17:24 +0000 Subject: [PATCH 48/84] chore: fix test failures --- tests/es6/prototype-napi/CMakeLists.txt | 2 +- tests/es6/prototype/CMakeLists.txt | 2 +- tests/es6/prototype2/CMakeLists.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/es6/prototype-napi/CMakeLists.txt b/tests/es6/prototype-napi/CMakeLists.txt index 5e090b60..53320e33 100644 --- a/tests/es6/prototype-napi/CMakeLists.txt +++ b/tests/es6/prototype-napi/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_policy(SET CMP0042 NEW) project (addon_napi) -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() diff --git a/tests/es6/prototype/CMakeLists.txt b/tests/es6/prototype/CMakeLists.txt index 6893b869..0a0e9ab3 100644 --- a/tests/es6/prototype/CMakeLists.txt +++ b/tests/es6/prototype/CMakeLists.txt @@ -4,7 +4,7 @@ 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() diff --git a/tests/es6/prototype2/CMakeLists.txt b/tests/es6/prototype2/CMakeLists.txt index 0d4bde80..aab7498a 100644 --- a/tests/es6/prototype2/CMakeLists.txt +++ b/tests/es6/prototype2/CMakeLists.txt @@ -4,7 +4,7 @@ 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() From 862108474134f8eaf15bac7a36ec5a3111e9275d Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 12 Feb 2023 13:38:27 +0000 Subject: [PATCH 49/84] chore: update debian version --- .github/workflows/node.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index b59c82f9..7faf9887 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -35,11 +35,11 @@ jobs: - os: ubuntu-latest arch: arm64 docker-arch: linux/arm64 - docker-image: node:14-buster + docker-image: node:14-bullseye - 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 From c63c9f76119a9f85602700909c181a061a094220 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 12 Feb 2023 13:35:28 +0000 Subject: [PATCH 50/84] chore: fix windows x86 tests --- package.json | 2 +- tests/es6/testRunner.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 40da7ac9..08ae4e39 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "devDependencies": { "mocha": "*", "nan": "^2.16.0", - "node-addon-api": "^5.0.0" + "node-addon-api": "^6.0.0" }, "scripts": { "test": "mocha tests", diff --git a/tests/es6/testRunner.js b/tests/es6/testRunner.js index c9616bc0..d54cec8f 100644 --- a/tests/es6/testRunner.js +++ b/tests/es6/testRunner.js @@ -24,7 +24,7 @@ function* generateRuntimeOptions() { // Current: yield { runtime: "node", - runtimeVersion: "18.3.0", + runtimeVersion: "18.13.0", arch: arch }; } From 46a0f84bcd6b736ebd7af9b60eeea04fe1826862 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 12 Feb 2023 14:20:38 +0000 Subject: [PATCH 51/84] chore: update dependencies --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 08ae4e39..eec8ed95 100644 --- a/package.json +++ b/package.json @@ -41,12 +41,12 @@ "node": ">= 14.15.0" }, "dependencies": { - "axios": "^0.27.2", + "axios": "^1.3.2", "debug": "^4", "fs-extra": "^10.1.0", "lodash.isplainobject": "^4.0.6", "memory-stream": "^1.0.0", - "node-api-headers": "^0.0.1", + "node-api-headers": "^0.0.2", "npmlog": "^6.0.2", "rc": "^1.2.7", "semver": "^7.3.8", From a1c10147773b55dd4ac1b6244e267bd25d97ccfd Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 12 Feb 2023 15:07:48 +0000 Subject: [PATCH 52/84] v7.2.0 --- changelog.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index a1b87a6f..96f68a7f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,10 @@ +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 ========== diff --git a/package.json b/package.json index eec8ed95..9f3f4c48 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "node-addon-api" ], "main": "lib", - "version": "7.1.1", + "version": "7.2.0", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From 6eb7ea3816ed948a09459b104996c7419dbfdca6 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 13 Feb 2023 22:41:06 +0000 Subject: [PATCH 53/84] fix: update find-visualstudio to support Windows11SDK feat: Update function getSDK() to support Windows 11 SDK (#2565) https://github.com/nodejs/node-gyp/commit/ea8520e3855374bd15b6d001fe112d58a8d7d737 --- lib/import/find-visualstudio.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/import/find-visualstudio.js b/lib/import/find-visualstudio.js index 4d1dfa5d..2522d03a 100644 --- a/lib/import/find-visualstudio.js +++ b/lib/import/find-visualstudio.js @@ -309,29 +309,30 @@ VisualStudioFinder.prototype = { getSDK: function getSDK (info) { const win8SDK = 'Microsoft.VisualStudio.Component.Windows81SDK' const win10SDKPrefix = 'Microsoft.VisualStudio.Component.Windows10SDK.' + const win11SDKPrefix = 'Microsoft.VisualStudio.Component.Windows11SDK.' - var Win10SDKVer = 0 + var Win10or11SDKVer = 0 info.packages.forEach((pkg) => { - if (!pkg.startsWith(win10SDKPrefix)) { + 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 Win10SDK:', pkg) + 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 Win10SDK number:', pkg) + this.log.silly('- failed to parse Win10/11SDK number:', pkg) return } - this.log.silly('- found Win10SDK:', foundSdkVer) - Win10SDKVer = Math.max(Win10SDKVer, foundSdkVer) + this.log.silly('- found Win10/11SDK:', foundSdkVer) + Win10or11SDKVer = Math.max(Win10or11SDKVer, foundSdkVer) }) - if (Win10SDKVer !== 0) { - return `10.0.${Win10SDKVer}.0` + if (Win10or11SDKVer !== 0) { + return `10.0.${Win10or11SDKVer}.0` } else if (info.packages.indexOf(win8SDK) !== -1) { this.log.silly('- found Win8SDK') return '8.1' From 14525bf4750824b2f87ef515a5d19b5b3249b267 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 13 Feb 2023 22:41:57 +0000 Subject: [PATCH 54/84] fix: update find-visualstudio to remove support for VS2015 feat: remove support for VS2015 in Node.js >=19 (#2746) https://github.com/nodejs/node-gyp/commit/131d1a463baf034a04154bcda753a8295f112a34 --- lib/import/find-visualstudio.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/import/find-visualstudio.js b/lib/import/find-visualstudio.js index 2522d03a..c040f0b3 100644 --- a/lib/import/find-visualstudio.js +++ b/lib/import/find-visualstudio.js @@ -341,7 +341,12 @@ VisualStudioFinder.prototype = { }, // Find an installation of Visual Studio 2015 to use - findVisualStudio2015: function findVisualStudio2015 (cb) { + findVisualStudio2015: function findVisualStudio2015(cb) { + if (this.nodeSemver.major >= 19) { + this.addLog( + 'not looking for VS2015 as it is only supported up to Node.js 18') + return cb(null) + } return this.findOldVS({ version: '14.0', versionMajor: 14, From 6a2a50ba3d2e82a0ea80a8bb77cd2d3a03fb838c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 14 Feb 2023 20:41:35 +0000 Subject: [PATCH 55/84] v7.2.1 --- changelog.md | 5 +++++ package.json | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 96f68a7f..838ab8e2 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,8 @@ +v7.2.1 - 14/02/23 +========== + +- fix: support Windows11SDK + v7.2.0 - 12/02/23 ========== diff --git a/package.json b/package.json index 9f3f4c48..246ee9ae 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "build", "buildtools", "cmake", - "nw.js", + "nw.js", "electron", "boost", "nan", @@ -21,7 +21,7 @@ "node-addon-api" ], "main": "lib", - "version": "7.2.0", + "version": "7.2.1", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From e112728f769ec3da1582b9ab919c9b0ac16af779 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 29 Oct 2023 17:29:05 +0000 Subject: [PATCH 56/84] chore: clarify how to declare project as node-api to readme --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 6411eccc..c759983e 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,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: @@ -302,6 +310,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 From eb05966c8bda0a7514c545ba1309eafc98bfef49 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 30 Oct 2023 19:41:41 +0000 Subject: [PATCH 57/84] fix: update `node-api-headers` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 246ee9ae..1b67eb08 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "fs-extra": "^10.1.0", "lodash.isplainobject": "^4.0.6", "memory-stream": "^1.0.0", - "node-api-headers": "^0.0.2", + "node-api-headers": "^1.1.0", "npmlog": "^6.0.2", "rc": "^1.2.7", "semver": "^7.3.8", From d9c190bf2a4f0d60e39222bb1ddb7a4f88c28a49 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 30 Oct 2023 19:50:39 +0000 Subject: [PATCH 58/84] fix(windows): replace custom `libnode.def` with version provided by `node-api-headers` --- lib/cMake.js | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/lib/cMake.js b/lib/cMake.js index 03ac5ccf..b9feba55 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -287,31 +287,7 @@ CMake.prototype.getCmakeJsSrcString = function () { }; CMake.prototype.getNodeLibDefPath = function () { - return environment.isWin && this.options.isNodeApi ? path.join(this.options.out, 'node-lib.def') : undefined -} - -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) - } - for (const sym of ver.js_native_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`) - } + return environment.isWin && this.options.isNodeApi ? headers.def_paths.node_api_def : undefined } CMake.prototype.configure = async function () { @@ -338,12 +314,6 @@ CMake.prototype.configure = async function () { const cwd = process.cwd(); process.chdir(this.workDir); try { - const nodeLibDefPath = this.getNodeLibDefPath() - - if (environment.isWin && nodeLibDefPath) { - await this._generateNodeLibDef(nodeLibDefPath) - } - await this._run(command); } finally { From 789bfa7a82fa9f45ee969e22580bc78d6b8c5e07 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 30 Oct 2023 20:17:17 +0000 Subject: [PATCH 59/84] chore: update ci platforms --- .github/workflows/node.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index 7faf9887..c6348df5 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -24,12 +24,12 @@ jobs: arch: x64 # - os: macos-11 # arch: arm64 - - os: macos-10.15 + - os: macos-11 arch: x64 # linux - os: ubuntu-22.04 arch: x64 - - os: ubuntu-18.04 + - os: ubuntu-20.04 arch: x64 # linux-libc - os: ubuntu-latest From 62f98e00f19d8293451babb623d07fb52ffa0cf6 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 15 Jan 2024 20:07:41 +0000 Subject: [PATCH 60/84] fix: update find-visualstudio #317 --- lib/import/README | 6 +- lib/import/find-visualstudio.js | 237 +++++++++++++++++--------------- lib/import/util.js | 67 +++++---- lib/toolset.js | 5 +- 4 files changed, 167 insertions(+), 148 deletions(-) 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 c040f0b3..f48a5ac6 100644 --- a/lib/import/find-visualstudio.js +++ b/lib/import/find-visualstudio.js @@ -1,38 +1,33 @@ '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"); -function VisualStudioFinder (configMsvsVersion, callback) { - this.configMsvsVersion = configMsvsVersion - this.callback = callback - this.errorLog = [] - this.validVersions = [] -} +class VisualStudioFinder { + static findVisualStudio = (...args) => new VisualStudioFinder(...args).findVisualStudio() -VisualStudioFinder.prototype = { - log: logWithPrefix(log, 'find VS'), + log = logWithPrefix(log, 'find VS') - regSearchKeys: regSearchKeys, + 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: function addLog (message) { + addLog (message) { this.log.verbose(message) this.errorLog.push(message) - }, + } - findVisualStudio: function findVisualStudio () { + async findVisualStudio () { this.configVersionYear = null this.configPath = null if (this.configMsvsVersion) { @@ -59,28 +54,30 @@ VisualStudioFinder.prototype = { this.addLog('VCINSTALLDIR not set, not running in VS Command Prompt') } - this.findVisualStudio2017OrNewer((info) => { + const checks = [ + () => this.findVisualStudio2017OrNewer(), + () => this.findVisualStudio2015(), + () => this.findVisualStudio2013() + ] + + for (const check of checks) { + const info = await check() if (info) { return this.succeed(info) } - this.findVisualStudio2015((info) => { - if (info) { - return this.succeed(info) - } - - this.fail() - }) - }) - }, + } + + return this.fail() + } - succeed: function succeed (info) { + 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)) - }, + return info + } - fail: function fail () { + fail () { if (this.configMsvsVersion && this.envVcInstallDir) { this.errorLog.push( 'msvs_version does not match this VS Command Prompt or the', @@ -114,17 +111,16 @@ VisualStudioFinder.prototype = { ].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'))) - }, + throw 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', + async findVisualStudio2017OrNewer () { + const ps = path.join(process.env.SystemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe') - var csFile = path.join(__dirname, 'Find-VisualStudio.cs') - var psArgs = [ + const csFile = path.join(__dirname, 'Find-VisualStudio.cs') + const psArgs = [ '-ExecutionPolicy', 'Unrestricted', '-NoProfile', @@ -133,22 +129,19 @@ VisualStudioFinder.prototype = { ] 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() - }, + const [err, stdout, stderr] = await execFile(ps, psArgs, { encoding: 'utf8' }) + return this.parseData(err, stdout, stderr) + } // 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) { + parseData (err, stdout, stderr) { 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) + return null } if (err) { @@ -156,9 +149,9 @@ VisualStudioFinder.prototype = { return failPowershell() } - var vsInfo + let vsInfo try { - vsInfo = JSON.parse(stdout) + vsInfo = JSON.parse(stdout) } catch (e) { this.log.silly('PS stdout = %j', stdout) this.log.silly(e) @@ -173,7 +166,7 @@ VisualStudioFinder.prototype = { vsInfo = vsInfo.map((info) => { this.log.silly(`processing installation: "${info.path}"`) info.path = path.resolve(info.path) - var ret = this.getVersionInfo(info) + const ret = this.getVersionInfo(info) ret.path = info.path ret.msBuild = this.getMSBuild(info, ret.versionYear) ret.toolset = this.getToolset(info, ret.versionYear) @@ -194,7 +187,7 @@ VisualStudioFinder.prototype = { // Sort to place newer versions first vsInfo.sort((a, b) => b.versionYear - a.versionYear) - for (var i = 0; i < vsInfo.length; ++i) { + 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}"`) @@ -224,23 +217,23 @@ VisualStudioFinder.prototype = { continue } - return cb(info) + return info } this.addLog( 'could not find a version of Visual Studio 2017 or newer to use') - cb(null) - }, + return null + } // Helper - process version information - getVersionInfo: function getVersionInfo (info) { + 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 = { + const ret = { version: info.version, versionMajor: parseInt(match[1], 10), versionMinor: parseInt(match[2], 10) @@ -259,12 +252,17 @@ VisualStudioFinder.prototype = { } this.log.silly('- unsupported version:', ret.versionMajor) return {} - }, + } + + msBuildPathExists (path) { + return existsSync(path) + } // Helper - process MSBuild information - getMSBuild: function getMSBuild (info, versionYear) { + 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) { @@ -274,15 +272,21 @@ VisualStudioFinder.prototype = { return msbuildPath } } - // visual studio 2022 don't has msbuild pkg - if (fs.existsSync(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: function getToolset (info, versionYear) { + getToolset (info, versionYear) { const pkg = 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64' const express = 'Microsoft.VisualStudio.WDExpress' @@ -303,15 +307,15 @@ VisualStudioFinder.prototype = { } this.log.silly('- invalid versionYear:', versionYear) return null - }, + } // Helper - process Windows SDK information - getSDK: function getSDK (info) { + getSDK (info) { const win8SDK = 'Microsoft.VisualStudio.Component.Windows81SDK' const win10SDKPrefix = 'Microsoft.VisualStudio.Component.Windows10SDK.' const win11SDKPrefix = 'Microsoft.VisualStudio.Component.Windows11SDK.' - var Win10or11SDKVer = 0 + let Win10or11SDKVer = 0 info.packages.forEach((pkg) => { if (!pkg.startsWith(win10SDKPrefix) && !pkg.startsWith(win11SDKPrefix)) { return @@ -338,14 +342,14 @@ VisualStudioFinder.prototype = { return '8.1' } return null - }, + } // Find an installation of Visual Studio 2015 to use - findVisualStudio2015: function findVisualStudio2015(cb) { - if (this.nodeSemver.major >= 19) { + 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 cb(null) + return null } return this.findOldVS({ version: '14.0', @@ -353,55 +357,68 @@ VisualStudioFinder.prototype = { versionMinor: 0, versionYear: 2015, toolset: 'v140' - }, cb) - }, + }) + } + + // 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 - findOldVS: function findOldVS (info, cb) { + 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}`) - this.regSearchKeys(regVC7, info.version, [], (err, res) => { - if (err) { - this.addLog('- not found') - return cb(null) - } - + 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'] - 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) - }) - }) - }, + + 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: function checkConfigVersion (versionYear, vsPath) { + checkConfigVersion (versionYear, vsPath) { this.validVersions.push(versionYear) this.validVersions.push(vsPath) @@ -424,8 +441,4 @@ VisualStudioFinder.prototype = { } } -module.exports = findVisualStudio -module.exports.test = { - VisualStudioFinder: VisualStudioFinder, - findVisualStudio: findVisualStudio -} \ No newline at end of file +module.exports = VisualStudioFinder \ No newline at end of file diff --git a/lib/import/util.js b/lib/import/util.js index 9e78a819..cf7cb9fe 100644 --- a/lib/import/util.js +++ b/lib/import/util.js @@ -1,9 +1,16 @@ 'use strict' const log = require('npmlog') -const execFile = require('child_process').execFile +const cp = require('child_process') const path = require('path') + +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 @@ -17,48 +24,48 @@ function logWithPrefix (log, prefix) { } } -function regGetValue (key, value, addOpts, cb) { +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 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) - } + const [err, stdout, stderr] = await execFile(reg, regArgs, { encoding: 'utf8' }) - 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() + 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') + 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) - }) +async function regSearchKeys (keys, value, addOpts) { + for (const key of keys) { + try { + return await regGetValue(key, value, addOpts) + } catch { + continue + } } - search() } module.exports = { logWithPrefix: logWithPrefix, regGetValue: regGetValue, - regSearchKeys: regSearchKeys + regSearchKeys: regSearchKeys, + execFile: execFile } \ No newline at end of file diff --git a/lib/toolset.js b/lib/toolset.js index 9156feb5..c6cbad42 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -4,9 +4,8 @@ const environment = require("./environment"); const assert = require("assert"); const CMLog = require("./cmLog"); const util = require('util') -const findVisualStudioRaw = require('./import/find-visualstudio'); +const { findVisualStudio } = environment.isWin ? require('./import/find-visualstudio') : {} -const findVisualStudio = util.promisify(findVisualStudioRaw) function Toolset(options) { this.options = options || {}; @@ -195,7 +194,7 @@ Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () { const CMake = require("./cMake"); assert(environment.isWin); - const selectedVs = await findVisualStudio(this.options.msvsVersion) + const selectedVs = await findVisualStudio(environment.runtimeVersion, this.options.msvsVersion) if (!selectedVs) return null; const list = await CMake.getGenerators(this.options, this.log); From 8e3905003d6e84af1218246cc8af8bc9cf815132 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 15 Jan 2024 20:08:04 +0000 Subject: [PATCH 61/84] chore: enable tests for pull_requests --- .github/workflows/node.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index c6348df5..5e19bc6f 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -2,6 +2,7 @@ name: Test on: push: + pull_request: jobs: test: From eee2b4d6b7e78d3d32afc985e34f6207346bc49a Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 15 Jan 2024 20:16:33 +0000 Subject: [PATCH 62/84] chore: add some more examples to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index c759983e..f972e36e 100644 --- a/README.md +++ b/README.md @@ -385,6 +385,8 @@ build environment. ## 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 +* [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. From f3169e835c0f6839b8d00feff0bd7adef2a548ed Mon Sep 17 00:00:00 2001 From: Andreas Franek Date: Mon, 15 Jan 2024 21:30:18 +0100 Subject: [PATCH 63/84] fix(windows): always remove `Path` if `PATH` is also defined (#319) Co-authored-by: Andreas Franek --- lib/processHelpers.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/processHelpers.js b/lib/processHelpers.js index eb9ae574..d039418a 100644 --- a/lib/processHelpers.js +++ b/lib/processHelpers.js @@ -8,8 +8,10 @@ const processHelpers = { 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; + 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), { From 30e9ae5d96a51e4dbd01fb01be3602b1639fdcec Mon Sep 17 00:00:00 2001 From: Matthew Bramer <515152+iOnline247@users.noreply.github.com> Date: Mon, 15 Jan 2024 15:31:32 -0500 Subject: [PATCH 64/84] chore: fix typo in README.md (#316) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f972e36e..c92070ac 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## 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 +- 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) From b3d4cc6eb198745e8e45a6ad03d99383f62564df Mon Sep 17 00:00:00 2001 From: Rasmus Eneman Date: Mon, 15 Jan 2024 21:48:18 +0100 Subject: [PATCH 65/84] fix: Cmake arguments got converted to numbers (#314) --- bin/cmake-js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bin/cmake-js b/bin/cmake-js index 55df9fa4..0b2afdac 100755 --- a/bin/cmake-js +++ b/bin/cmake-js @@ -197,9 +197,14 @@ 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; +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; } } From eee8351ca320393ba030bb49b1e4ba4300bc0872 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 15 Jan 2024 21:03:54 +0000 Subject: [PATCH 66/84] chore: update dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 1b67eb08..2046c947 100644 --- a/package.json +++ b/package.json @@ -41,24 +41,24 @@ "node": ">= 14.15.0" }, "dependencies": { - "axios": "^1.3.2", + "axios": "^1.6.5", "debug": "^4", - "fs-extra": "^10.1.0", + "fs-extra": "^11.2.0", "lodash.isplainobject": "^4.0.6", "memory-stream": "^1.0.0", "node-api-headers": "^1.1.0", "npmlog": "^6.0.2", "rc": "^1.2.7", - "semver": "^7.3.8", - "tar": "^6.1.11", + "semver": "^7.5.4", + "tar": "^6.2.0", "url-join": "^4.0.1", "which": "^2.0.2", - "yargs": "^17.6.0" + "yargs": "^17.7.2" }, "devDependencies": { "mocha": "*", - "nan": "^2.16.0", - "node-addon-api": "^6.0.0" + "nan": "^2.18.0", + "node-addon-api": "^6.1.0" }, "scripts": { "test": "mocha tests", From b4b5b160ee6cd7b0b3756709c0590df490d2815d Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 15 Jan 2024 21:09:47 +0000 Subject: [PATCH 67/84] v7.3.0 --- changelog.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 838ab8e2..fedf7d8a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,13 @@ +v7.3.0 - 15/01/23 +========== + +- 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 ========== diff --git a/package.json b/package.json index 2046c947..6f69f0de 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "node-addon-api" ], "main": "lib", - "version": "7.2.1", + "version": "7.3.0", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From 66fc7b575f65254bbb50f4baa6437ea246ed1e2c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 16 Jan 2024 22:17:09 +0000 Subject: [PATCH 68/84] chore: reformat with prettier and fix eslint --- .eslintrc.json | 30 +- .prettierrc.json | 9 + README.md | 77 +-- bin/cmake-js | 552 ++++++++-------- changelog.md | 497 +++++++-------- lib/appCMakeJSConfig.js | 112 ++-- lib/buildSystem.js | 203 +++--- lib/cMake.js | 678 ++++++++++---------- lib/cmLog.js | 121 ++-- lib/dist.js | 346 +++++------ lib/downloader.js | 158 ++--- lib/environment.js | 204 +++--- lib/import/find-visualstudio.js | 863 +++++++++++++------------- lib/import/util.js | 105 ++-- lib/index.js | 20 +- lib/locateNAN.js | 108 ++-- lib/locateNodeApi.js | 32 +- lib/npmConfig.js | 46 +- lib/processHelpers.js | 100 ++- lib/runtimePaths.js | 169 ++--- lib/targetOptions.js | 70 +-- lib/toolset.js | 427 +++++++------ package.json | 151 ++--- tests/es6/buildSystem.js | 121 ++-- tests/es6/dist.js | 41 +- tests/es6/index.js | 8 +- tests/es6/locateNAN.js | 50 +- tests/es6/prototype-napi/package.json | 8 +- tests/es6/testCases.js | 158 +++-- tests/es6/testRunner.js | 192 +++--- tests/fixtures/project/package.json | 1 + tests/index.js | 2 +- 32 files changed, 2800 insertions(+), 2859 deletions(-) create mode 100644 .prettierrc.json 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/.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 c92070ac..01c0d48e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ [![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: + +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 @@ -85,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 @@ -99,7 +100,7 @@ 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).)* +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: @@ -228,6 +229,7 @@ You can add custom CMake options by beginning option name with `CD`. #### Example In command prompt: + ``` cmake-js compile --CDFOO="bar" ``` @@ -258,27 +260,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` @@ -287,13 +288,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 @@ -339,18 +341,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). - #### 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, @@ -361,20 +363,19 @@ 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. - ## Tutorials - [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) @@ -384,9 +385,9 @@ build environment. ## 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 -* [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 +- [@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 0b2afdac..fdea4c5c 100755 --- a/bin/cmake-js +++ b/bin/cmake-js @@ -1,339 +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) + } + } } -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; +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 = {}; +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; - } + 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, - extraCMakeArgs: argv._.slice(1), -}; + 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); - })); + exitOnError( + buildSystem.getCmakeJsLibString().then(function (command) { + console.info(command) + }), + ) } function printCmakeJsInclude() { - exitOnError(buildSystem.getCmakeJsIncludeString() - .then(function (command) { - console.info(command); - })); + exitOnError( + buildSystem.getCmakeJsIncludeString().then(function (command) { + console.info(command) + }), + ) } function printCmakeJsSrc() { - exitOnError(buildSystem.getCmakeJsSrcString() - .then(function (command) { - console.info(command); - })); + 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("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); +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 fedf7d8a..04b55332 100644 --- a/changelog.md +++ b/changelog.md @@ -1,272 +1,225 @@ -v7.3.0 - 15/01/23 -========== - -- 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+ +# v7.3.0 - 15/01/23 + +- 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..ed8621dd 100644 --- a/lib/appCMakeJSConfig.js +++ b/lib/appCMakeJSConfig.js @@ -1,63 +1,59 @@ -"use strict"; -const path = require("path"); -const isPlainObject = require("lodash.isplainobject"); +'use strict' +const path = require('path') +const isPlainObject = require('lodash.isplainobject') 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 (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 + } } 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 d60d0fd1..b94d8893 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -1,142 +1,139 @@ -"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 isPlainObject = require('lodash.isplainobject') +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); + 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) } 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; - } -}; + 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); - } -}; + 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(); -}; + 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; - } -}; + try { + await this._ensureInstalled() + return await this.cmake[method]() + } catch (e) { + this._showError(e) + throw e + } +} BuildSystem.prototype.getConfigureCommand = function () { - return this._invokeCMake("getConfigureCommand"); -}; + return this._invokeCMake('getConfigureCommand') +} BuildSystem.prototype.getCmakeJsLibString = function () { - return this._invokeCMake("getCmakeJsLibString"); -}; + return this._invokeCMake('getCmakeJsLibString') +} BuildSystem.prototype.getCmakeJsIncludeString = function () { - return this._invokeCMake("getCmakeJsIncludeString"); -}; + return this._invokeCMake('getCmakeJsIncludeString') +} BuildSystem.prototype.getCmakeJsSrcString = function () { - return this._invokeCMake("getCmakeJsSrcString"); -}; + return this._invokeCMake('getCmakeJsSrcString') +} BuildSystem.prototype.configure = function () { - return this._invokeCMake("configure"); -}; + return this._invokeCMake('configure') +} BuildSystem.prototype.getBuildCommand = function () { - return this._invokeCMake("getBuildCommand"); -}; + return this._invokeCMake('getBuildCommand') +} BuildSystem.prototype.build = function () { - return this._invokeCMake("build"); -}; + return this._invokeCMake('build') +} BuildSystem.prototype.getCleanCommand = function () { - return this._invokeCMake("getCleanCommand"); -}; + return this._invokeCMake('getCleanCommand') +} BuildSystem.prototype.clean = function () { - return this._invokeCMake("clean"); -}; + return this._invokeCMake('clean') +} BuildSystem.prototype.reconfigure = function () { - return this._invokeCMake("reconfigure"); -}; + return this._invokeCMake('reconfigure') +} BuildSystem.prototype.rebuild = function () { - return this._invokeCMake("rebuild"); -}; + return this._invokeCMake('rebuild') +} BuildSystem.prototype.compile = function () { - return this._invokeCMake("compile"); -}; + return this._invokeCMake('compile') +} -module.exports = BuildSystem; +module.exports = BuildSystem diff --git a/lib/cMake.js b/lib/cMake.js index b9feba55..6042787f 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -1,392 +1,384 @@ -"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'); +'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.extraCMakeArgs = this.options.extraCMakeArgs || []; - this.silent = !!options.silent; + 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 } 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; - } - } -}); + 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; -}; + 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; -}; + 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); -}; + 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."); - } -}; + 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 () { - - // 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); -}; + // 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) +} CMake.prototype.getCmakeJsLibString = function () { - - 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(";"); -}; + 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(';') +} CMake.prototype.getCmakeJsIncludeString = async function () { - 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(";"); -}; + 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(';') +} CMake.prototype.getCmakeJsSrcString = function () { - const srcPaths = []; - if (environment.isWin) { - const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')); + const srcPaths = [] + if (environment.isWin) { + const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')) - srcPaths.push(delayHook.replace(/\\/gm, '/')); - } + srcPaths.push(delayHook.replace(/\\/gm, '/')) + } - return srcPaths.join(";"); -}; + return srcPaths.join(';') +} CMake.prototype.getNodeLibDefPath = function () { - return environment.isWin && this.options.isNodeApi ? headers.def_paths.node_api_def : undefined + return environment.isWin && this.options.isNodeApi ? headers.def_paths.node_api_def : undefined } CMake.prototype.configure = async function () { - 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); - } -}; + 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) + } +} CMake.prototype.ensureConfigured = async function () { - try { - await fs.lstat(path.join(this.workDir, "CMakeCache.txt")); - } - catch (e) { - await this.configure(); - } -}; + 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.concat(this.extraCMakeArgs)); -}; + 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)) +} CMake.prototype.build = async function () { - this.verifyIfAvailable(); + this.verifyIfAvailable() - await this.ensureConfigured(); - const buildCommand = await this.getBuildCommand(); - this.log.info("CMD", "BUILD"); - await this._run(buildCommand); -}; + 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].concat(this.extraCMakeArgs); -}; + return [this.path, '-E', 'remove_directory', this.workDir].concat(this.extraCMakeArgs) +} CMake.prototype.clean = function () { - this.verifyIfAvailable(); + this.verifyIfAvailable() - this.log.info("CMD", "CLEAN"); - return this._run(this.getCleanCommand()); -}; + this.log.info('CMD', 'CLEAN') + return this._run(this.getCleanCommand()) +} CMake.prototype.reconfigure = async function () { - this.extraCMakeArgs = []; - await this.clean(); - await this.configure(); -}; + this.extraCMakeArgs = [] + await this.clean() + await this.configure() +} CMake.prototype.rebuild = async function () { - this.extraCMakeArgs = []; - await this.clean(); - await this.build(); -}; + this.extraCMakeArgs = [] + await this.clean() + await this.build() +} CMake.prototype.compile = async function () { - 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(); - } -}; + 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() + } +} CMake.prototype._run = function (command) { - this.log.info("RUN", command); - return processHelpers.run(command, {silent: this.silent}); -}; + 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..eba5c9b2 100644 --- a/lib/cmLog.js +++ b/lib/cmLog.js @@ -1,76 +1,69 @@ -"use strict"; -const log = require("npmlog"); +'use strict' +const log = require('npmlog') function CMLog(options) { - this.options = options || {}; - this.debug = require("debug")(this.options.logName || "cmake-js"); + this.options = options || {} + this.debug = require('debug')(this.options.logName || 'cmake-js') } Object.defineProperties(CMLog.prototype, { - level: { - get: function() { - if (this.options.noLog) { - return "silly"; - } - else { - return log.level; - } - } - } -}); + 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.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.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.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.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.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); - } -}; +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/dist.js b/lib/dist.js index a019e017..bf5ae1ac 100644 --- a/lib/dist.js +++ b/lib/dist.js @@ -1,195 +1,191 @@ -"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') 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); + this.options = options || {} + this.log = new CMLog(this.options) + this.targetOptions = new TargetOptions(this.options) + this.downloader = new Downloader(this.options) } // 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; - } - } -}); + 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(); - } -}; + 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)]); -}; + 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; - } -}; + 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); - } -}; + 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; + 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 diff --git a/lib/downloader.js b/lib/downloader.js index 2c109ac6..46d48093 100644 --- a/lib/downloader.js +++ b/lib/downloader.js @@ -1,95 +1,95 @@ -"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); + 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; - } +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.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); - }); + response.data.pipe(stream) + }) + .catch(function (err) { + reject(err) + }) - stream.once("error", function (err) { - reject(err); - }); + stream.once('error', function (err) { + reject(err) + }) - stream.once("finish", function () { - resolve(shasum ? shasum.digest("hex") : undefined); - }); - }); -}; + stream.once('finish', function () { + resolve(shasum ? shasum.digest('hex') : undefined) + }) + }) +} Downloader.prototype.downloadString = async function (url) { - const result = new MemoryStream(); - await this.downloadToStream(url, result); - return result.toString(); -}; + const result = new MemoryStream() + await this.downloadToStream(url, result) + return result.toString() +} 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; -}; + 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; -}; + 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!"); - } -}; +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!") + } +} -module.exports = Downloader; +module.exports = Downloader diff --git a/lib/environment.js b/lib/environment.js index d8e32d5c..efb96572 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -1,107 +1,103 @@ -"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', + runtime: 'node', + runtimeVersion: process.versions.node, + home: process.env[os.platform() === 'win32' ? 'USERPROFILE' : 'HOME'], + EOL: os.EOL, +}) 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 + 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 + }, + }, +}) diff --git a/lib/import/find-visualstudio.js b/lib/import/find-visualstudio.js index f48a5ac6..4244c7e4 100644 --- a/lib/import/find-visualstudio.js +++ b/lib/import/find-visualstudio.js @@ -4,441 +4,436 @@ const log = require('npmlog') const { existsSync } = require('fs') const { win32: path } = require('path') const { regSearchKeys, execFile, logWithPrefix } = require('./util') -const semver = require("semver"); +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.findVisualStudio2017OrNewer(), - () => 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') - } - - // Invoke the PowerShell script to get information about Visual Studio 2017 - // or newer installations - async findVisualStudio2017OrNewer () { - 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 execFile(ps, psArgs, { encoding: 'utf8' }) - return this.parseData(err, stdout, stderr) - } - - // Parse the output of the PowerShell script and look for an installation - // of Visual Studio 2017 or newer to use - parseData (err, stdout, stderr) { - 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') - return null - } - - if (err) { - this.log.silly('PS err = %j', err && (err.stack || err)) - return failPowershell() - } - - let 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) - 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 - 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 (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) { - 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 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 (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 - } + 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.findVisualStudio2017OrNewer(), + () => 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') + } + + // Invoke the PowerShell script to get information about Visual Studio 2017 + // or newer installations + async findVisualStudio2017OrNewer() { + 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 execFile(ps, psArgs, { encoding: 'utf8' }) + return this.parseData(err, stdout, stderr) + } + + // Parse the output of the PowerShell script and look for an installation + // of Visual Studio 2017 or newer to use + parseData(err, stdout, stderr) { + 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", + ) + return null + } + + if (err) { + this.log.silly('PS err = %j', err && (err.stack || err)) + return failPowershell() + } + + let 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) + 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 + 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 (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) { + 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 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(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 + } } -module.exports = VisualStudioFinder \ No newline at end of file +module.exports = VisualStudioFinder diff --git a/lib/import/util.js b/lib/import/util.js index cf7cb9fe..4afc10a0 100644 --- a/lib/import/util.js +++ b/lib/import/util.js @@ -4,68 +4,67 @@ const log = require('npmlog') const cp = require('child_process') const path = require('path') +const execFile = async (...args) => + new Promise((resolve) => { + const child = cp.execFile(...args, (...a) => resolve(a)) + child.stdin.end() + }) -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 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), + } } -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) +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 [err, stdout, stderr] = await execFile(reg, regArgs, { encoding: 'utf8' }) - 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) - } + 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') - throw new Error('Could not parse output of reg.exe') - } + 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] + log.silly('reg', 'found: %j', result[1]) + return result[1] } -async function regSearchKeys (keys, value, addOpts) { - for (const key of keys) { - try { - return await regGetValue(key, value, addOpts) - } catch { - continue - } - } +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, - execFile: execFile -} \ 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 d039418a..1f237ad5 100644 --- a/lib/processHelpers.js +++ b/lib/processHelpers.js @@ -1,55 +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) { - 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); - } - }); - }); - } -}; + 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..24d030ef 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -1,86 +1,93 @@ -"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 isPlainObject = require('lodash.isplainobject') -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.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') - 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' && isPlainObject((paths = func(targetOptions)))) { + 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..69307f69 100644 --- a/lib/targetOptions.js +++ b/lib/targetOptions.js @@ -1,42 +1,42 @@ -"use strict"; +'use strict' -const environment = require("./environment"); +const environment = require('./environment') function TargetOptions(options) { - this.options = options || {}; + this.options = options || {} } 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; - } - } -}); + 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 + }, + }, +}) -module.exports = TargetOptions; \ No newline at end of file +module.exports = TargetOptions diff --git a/lib/toolset.js b/lib/toolset.js index c6cbad42..981327e0 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -1,226 +1,225 @@ -"use strict"; -const TargetOptions = require("./targetOptions"); -const environment = require("./environment"); -const assert = require("assert"); -const CMLog = require("./cmLog"); -const util = require('util') +'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') : {} - 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; + 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 } Toolset.prototype.initialize = async function (install) { - if (!this._initialized) { - if (environment.isWin) { - await this.initializeWin(install); - } - else { - this.initializePosix(install); - } - this._initialized = true; - } -}; + 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."); - } -}; + 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"); - } -}; + 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(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; -}; - -module.exports = Toolset; + 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 +} + +module.exports = Toolset diff --git a/package.json b/package.json index 6f69f0de..c1ab3230 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.3.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": "^1.6.5", - "debug": "^4", - "fs-extra": "^11.2.0", - "lodash.isplainobject": "^4.0.6", - "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": { - "mocha": "*", - "nan": "^2.18.0", - "node-addon-api": "^6.1.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.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": "^1.6.5", + "debug": "^4", + "fs-extra": "^11.2.0", + "lodash.isplainobject": "^4.0.6", + "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.18.0", + "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" + ] +} diff --git a/tests/es6/buildSystem.js b/tests/es6/buildSystem.js index a8b28310..1fe4df42 100644 --- a/tests/es6/buildSystem.js +++ b/tests/es6/buildSystem.js @@ -1,59 +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 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(); - }); -}); +'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/package.json b/tests/es6/prototype-napi/package.json index 2a750b1a..ace6167f 100644 --- a/tests/es6/prototype-napi/package.json +++ b/tests/es6/prototype-napi/package.json @@ -1,5 +1,5 @@ { - "binary": { - "napi_versions": 7 - } -} \ No newline at end of file + "binary": { + "napi_versions": 7 + } +} diff --git a/tests/es6/testCases.js b/tests/es6/testCases.js index 6f8af6b1..c359cec0 100644 --- a/tests/es6/testCases.js +++ b/tests/es6/testCases.js @@ -1,87 +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); - } - }, - 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); + 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"); - }, - shouldForwardExtraCMakeArgs: async function(options) { - options = { - directory: path.resolve(path.join(__dirname, "./prototype")), - ...options - }; + 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, + } - 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 = ['--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 = ['--', '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"); - } -}; + 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; +module.exports = testCases diff --git a/tests/es6/testRunner.js b/tests/es6/testRunner.js index d54cec8f..34edc6f1 100644 --- a/tests/es6/testRunner.js +++ b/tests/es6/testRunner.js @@ -1,105 +1,103 @@ -"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.13.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) { + // 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.13.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* 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') From 859c4757c653f036ccb7e589e6a58f7a94928b4d Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 18 Jan 2024 20:08:27 +0000 Subject: [PATCH 69/84] chore: convert some classes to es6 --- lib/buildSystem.js | 202 +++++++-------- lib/cMake.js | 598 +++++++++++++++++++++---------------------- lib/cmLog.js | 104 ++++---- lib/dist.js | 278 ++++++++++---------- lib/downloader.js | 141 +++++----- lib/targetOptions.js | 58 ++--- lib/toolset.js | 357 +++++++++++++------------- 7 files changed, 831 insertions(+), 907 deletions(-) diff --git a/lib/buildSystem.js b/lib/buildSystem.js index b94d8893..2f7c14af 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -19,121 +19,107 @@ function isNodeApi(log, projectRoot) { } } -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 +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 (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) } - 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) -} - -BuildSystem.prototype._ensureInstalled = async function () { - try { - await this.toolset.initialize(true) - if (!this.options.isNodeApi) { - await this.dist.ensureDownloaded() + async _ensureInstalled() { + try { + await this.toolset.initialize(true) + if (!this.options.isNodeApi) { + await this.dist.ensureDownloaded() + } + } catch (e) { + this._showError(e) + throw e } - } 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 + _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) + } } - 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() } -} - -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 + 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.getConfigureCommand = function () { - return this._invokeCMake('getConfigureCommand') -} - -BuildSystem.prototype.getCmakeJsLibString = function () { - return this._invokeCMake('getCmakeJsLibString') -} - -BuildSystem.prototype.getCmakeJsIncludeString = function () { - return this._invokeCMake('getCmakeJsIncludeString') -} - -BuildSystem.prototype.getCmakeJsSrcString = function () { - return this._invokeCMake('getCmakeJsSrcString') -} - -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 diff --git a/lib/cMake.js b/lib/cMake.js index 6042787f..6ec0a79a 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -13,372 +13,350 @@ 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.extraCMakeArgs = this.options.extraCMakeArgs || [] - this.silent = !!options.silent -} - -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 +class CMake { + get path() { + return this.options.cmakePath || 'cmake' + } + get isAvailable() { + if (this._isAvailable === null) { + this._isAvailable = CMake.isAvailable(this.options) } - } catch (e) { - // Ignore + return this._isAvailable } - 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) + 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 { - 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)') + 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) + // 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) } - gens.push(gen) } - } - if (line.trim() === 'Generators') { - on = true - } - }) - } else { - throw new Error('CMake is not installed. Install CMake.') + if (line.trim() === 'Generators') { + on = true + } + }) + } else { + throw new Error('CMake is not installed. Install CMake.') + } + return gens } - 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.", - ) + 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.", + ) + } } -} - -CMake.prototype.getConfigureCommand = async function () { - // Create command: - let command = [this.path, this.projectRoot, '--no-warn-unused-cli'] + async getConfigureCommand() { + // Create command: + let command = [this.path, this.projectRoot, '--no-warn-unused-cli'] - const D = [] + const D = [] - // CMake.js watermark - D.push({ CMAKE_JS_VERSION: environment.cmakeJsVersion }) + // 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 }) - } + // 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>' }) + // 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 }) + // Includes: + const includesString = await this.getCmakeJsIncludeString() + D.push({ CMAKE_JS_INC: includesString }) - // Sources: - const srcsString = this.getCmakeJsSrcString() - D.push({ CMAKE_JS_SRC: srcsString }) + // 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 }) + // 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 }) + 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 }) - } + // Custom options + for (const [key, value] of Object.entries(this.cMakeOptions)) { + D.push({ [key]: value }) + } - // Toolset: - await this.toolset.initialize(false) + // Toolset: + await this.toolset.initialize(false) - const libsString = this.getCmakeJsLibString() - D.push({ CMAKE_JS_LIB: libsString }) + 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 (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 }) - } + 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 }) + // 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) -} + command = command.concat( + D.map(function (p) { + return '-D' + Object.keys(p)[0] + '=' + Object.values(p)[0] + }), + ) -CMake.prototype.getCmakeJsLibString = function () { - 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 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(';') } - 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] + } -CMake.prototype.getCmakeJsIncludeString = async function () { - let incPaths = [] - if (!this.options.isNodeApi) { - // Include and lib: - if (this.dist.headerOnly) { - incPaths = [path.join(this.dist.internalPath, '/include/node')] + // NAN + const nanH = await locateNAN(this.projectRoot) + if (nanH) { + incPaths.push(nanH) + } } 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] + // 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) + } } - // 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')) - return incPaths.join(';') -} - -CMake.prototype.getCmakeJsSrcString = function () { - const srcPaths = [] - if (environment.isWin) { - const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')) + srcPaths.push(delayHook.replace(/\\/gm, '/')) + } - 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() - return srcPaths.join(';') -} - -CMake.prototype.getNodeLibDefPath = function () { - return environment.isWin && this.options.isNodeApi ? headers.def_paths.node_api_def : undefined -} + this.log.info('CMD', 'CONFIGURE') + const listPath = path.join(this.projectRoot, 'CMakeLists.txt') + const command = await this.getConfigureCommand() -CMake.prototype.configure = async function () { - this.verifyIfAvailable() + try { + await fs.lstat(listPath) + } catch (e) { + throw new Error("'" + listPath + "' not found.") + } - this.log.info('CMD', 'CONFIGURE') - const listPath = path.join(this.projectRoot, 'CMakeLists.txt') - const command = await this.getConfigureCommand() + try { + await fs.ensureDir(this.workDir) + } catch (e) { + // Ignore + } - try { - await fs.lstat(listPath) - } catch (e) { - throw new Error("'" + listPath + "' not found.") + const cwd = process.cwd() + process.chdir(this.workDir) + try { + await this._run(command) + } finally { + process.chdir(cwd) + } } - - try { - await fs.ensureDir(this.workDir) - } catch (e) { - // Ignore + async ensureConfigured() { + try { + await fs.lstat(path.join(this.workDir, 'CMakeCache.txt')) + } catch (e) { + await this.configure() + } } - - const cwd = process.cwd() - process.chdir(this.workDir) - try { - await this._run(command) - } finally { - process.chdir(cwd) + 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() -CMake.prototype.ensureConfigured = async function () { - try { - await fs.lstat(path.join(this.workDir, 'CMakeCache.txt')) - } catch (e) { - await this.configure() + 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) + } + clean() { + this.verifyIfAvailable() -CMake.prototype.getBuildCommand = function () { - const command = [this.path, '--build', this.workDir, '--config', this.config] - if (this.options.target) { - command.push('--target', this.options.target) + this.log.info('CMD', 'CLEAN') + return this._run(this.getCleanCommand()) } - if (this.options.parallel) { - command.push('--parallel', this.options.parallel) + async reconfigure() { + this.extraCMakeArgs = [] + await this.clean() + await this.configure() } - return Promise.resolve(command.concat(this.extraCMakeArgs)) -} - -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].concat(this.extraCMakeArgs) -} - -CMake.prototype.clean = function () { - this.verifyIfAvailable() - - this.log.info('CMD', 'CLEAN') - return this._run(this.getCleanCommand()) -} - -CMake.prototype.reconfigure = async function () { - this.extraCMakeArgs = [] - await this.clean() - await this.configure() -} - -CMake.prototype.rebuild = async function () { - this.extraCMakeArgs = [] - await this.clean() - await this.build() -} - -CMake.prototype.compile = async function () { - this.extraCMakeArgs = [] - try { + async rebuild() { + this.extraCMakeArgs = [] + await this.clean() await this.build() - } catch (e) { - this.log.info('REP', 'Build has been failed, trying to do a full rebuild.') - await this.rebuild() } -} + 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 }) + } -CMake.prototype._run = function (command) { - this.log.info('RUN', command) - return processHelpers.run(command, { silent: this.silent }) + async getGenerators() { + return CMake.getGenerators(this.options, this.log) + } } module.exports = CMake diff --git a/lib/cmLog.js b/lib/cmLog.js index eba5c9b2..58fbdfa6 100644 --- a/lib/cmLog.js +++ b/lib/cmLog.js @@ -1,68 +1,60 @@ 'use strict' const log = require('npmlog') -function CMLog(options) { - this.options = options || {} - this.debug = require('debug')(this.options.logName || 'cmake-js') -} - -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) +class CMLog { + get level() { + if (this.options.noLog) { + return 'silly' + } else { + return log.level + } } -} -CMLog.prototype.verbose = function (cat, msg) { - if (this.options.noLog) { - this.debug(cat + ': ' + msg) - } else { - log.verbose(cat, msg) + constructor(options) { + this.options = options || {} + this.debug = require('debug')(this.options.logName || 'cmake-js') } -} - -CMLog.prototype.info = function (cat, msg) { - if (this.options.noLog) { - this.debug(cat + ': ' + msg) - } else { - log.info(cat, msg) + silly(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.silly(cat, msg) + } } -} - -CMLog.prototype.warn = function (cat, msg) { - if (this.options.noLog) { - this.debug(cat + ': ' + msg) - } else { - log.warn(cat, msg) + verbose(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.verbose(cat, msg) + } } -} - -CMLog.prototype.http = function (cat, msg) { - if (this.options.noLog) { - this.debug(cat + ': ' + msg) - } else { - log.http(cat, msg) + info(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.info(cat, msg) + } } -} - -CMLog.prototype.error = function (cat, msg) { - if (this.options.noLog) { - this.debug(cat + ': ' + msg) - } else { - log.error(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) + } } } diff --git a/lib/dist.js b/lib/dist.js index bf5ae1ac..8fc067d2 100644 --- a/lib/dist.js +++ b/lib/dist.js @@ -18,172 +18,156 @@ function testSum(sums, sum, fPath) { 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) -} - -// 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')) +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(environment.home, 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() - } 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() - } + } + if (environment.isWin) { + for (const libPath of this.winLibs) { + stat = getStat(libPath) + libs = libs && stat.isFile() } } - return headers && libs + } + return headers && libs - function getStat(path) { - try { - return fs.statSync(path) - } catch (e) { - return { - isFile: () => false, - isDirectory: () => false, - } + 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() + } + } + 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 } -} -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)]) -} + constructor(options) { + this.options = options || {} + this.log = new CMLog(this.options) + this.targetOptions = new TargetOptions(this.options) + this.downloader = new Downloader(this.options) + } -Dist.prototype._downloadShaSums = async function () { - if (this.targetOptions.runtime === 'node') { - const sumUrl = urljoin(this.externalPath, 'SHASUMS256.txt') + async ensureDownloaded() { + if (!this.downloaded) { + await this.download() + } + } + async download() { 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 + 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._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) + 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) -Dist.prototype._downloadLibs = async function (sums) { - const log = this.log - const self = this - if (!environment.isWin) { - return + 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) + 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)) + 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, - }) + const sum = await this.downloader.downloadFile(libUrl, { + path: path.join(self.internalPath, fPath), + hash: sums ? 'sha256' : null, + }) - if (sums) { - testSum(sums, sum, fPath) + if (sums) { + testSum(sums, sum, fPath) + } } } } diff --git a/lib/downloader.js b/lib/downloader.js index 46d48093..182789ec 100644 --- a/lib/downloader.js +++ b/lib/downloader.js @@ -7,88 +7,85 @@ 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) +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 } - 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.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) }) - response.data.pipe(stream) - }) - .catch(function (err) { + stream.once('error', function (err) { reject(err) }) - stream.once('error', function (err) { - reject(err) - }) - - stream.once('finish', function () { - resolve(shasum ? shasum.digest('hex') : undefined) + stream.once('finish', function () { + resolve(shasum ? shasum.digest('hex') : undefined) + }) }) - }) -} - -Downloader.prototype.downloadString = async function (url) { - const result = new MemoryStream() - await this.downloadToStream(url, result) - return result.toString() -} - -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 + async downloadString(url) { + const result = new MemoryStream() + await this.downloadToStream(url, result) + return result.toString() } - 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!") + 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!") + } } } diff --git a/lib/targetOptions.js b/lib/targetOptions.js index 69307f69..c61bf6fe 100644 --- a/lib/targetOptions.js +++ b/lib/targetOptions.js @@ -2,41 +2,29 @@ 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 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 diff --git a/lib/toolset.js b/lib/toolset.js index 981327e0..3d9c034d 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -5,221 +5,220 @@ const assert = require('assert') const CMLog = require('./cmLog') const { findVisualStudio } = environment.isWin ? require('./import/find-visualstudio') : {} -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 -} - -Toolset.prototype.initialize = async function (install) { - if (!this._initialized) { - if (environment.isWin) { - await this.initializeWin(install) - } else { - this.initializePosix(install) - } - this._initialized = true +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 } -} - -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'.", - ) + async initialize(install) { + if (!this._initialized) { + if (environment.isWin) { + await this.initializeWin(install) } else { - throw new Error( - "C++ Compiler toolset is not available. Install proper compiler toolset with your package manager, eg. 'sudo apt-get install g++'.", - ) + this.initializePosix(install) } + this._initialized = true } - - if (this.options.preferClang && environment.isClangAvailable) { - if (install) { - this.log.info('TOOL', 'Using clang++ compiler, because preferClang option is set, and clang++ is available.') + } + 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++'.", + ) + } } - 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.') + + 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' } - 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 it's already set because of options... + if (this.generator) { if (install) { - this.log.info( - 'TOOL', - 'Using Unix Makefiles generator, because preferMake option is set, and make is available.', - ) + this.log.info('TOOL', 'Using ' + this.generator + ' generator, as specified from commandline.') } - this.generator = 'Unix Makefiles' - } else if (environment.isNinjaAvailable) { - if (install) { - this.log.info('TOOL', 'Using Ninja generator, because ninja is available.') + } + + // 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' } - this.generator = 'Ninja' } else { - if (install) { - this.log.info('TOOL', 'Using Unix Makefiles generator.') + 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' } - 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 { + + // 3: Flags + if (environment.isOSX) { if (install) { - this.log.info('TOOL', 'Using Unix Makefiles generator.') + this.log.verbose('TOOL', 'Setting default OSX compiler flags.') } - 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('-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') - 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.') + // 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 + 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 { - 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.') + // 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') + this.linkerFlags.push('/DELAYLOAD:NODE.EXE') - if (this.targetOptions.isX86) { - if (install) { - this.log.verbose('TOOL', 'Setting SAFESEH:NO linker flag.') + if (this.targetOptions.isX86) { + if (install) { + this.log.verbose('TOOL', 'Setting SAFESEH:NO linker flag.') + } + this.linkerFlags.push('/SAFESEH:NO') } - this.linkerFlags.push('/SAFESEH:NO') } -} + async _getTopSupportedVisualStudioGenerator() { + const CMake = require('./cMake') + assert(environment.isWin) -Toolset.prototype._getTopSupportedVisualStudioGenerator = async function () { - const CMake = require('./cMake') - assert(environment.isWin) + const selectedVs = await findVisualStudio(environment.runtimeVersion, this.options.msvsVersion) + if (!selectedVs) return null - 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 + } - 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 + } + } - // 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, } } - return { - ...selectedVs, - generator: gen, - } + // Nothing matched + return null } - - // Nothing matched - return null } module.exports = Toolset From 7d24eabd915c3cfa10fcd53a8a208f5011875078 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 18 Jan 2024 20:21:42 +0000 Subject: [PATCH 70/84] chore: tidy --- lib/dist.js | 3 ++- lib/environment.js | 7 ------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/dist.js b/lib/dist.js index 8fc067d2..6bfbd320 100644 --- a/lib/dist.js +++ b/lib/dist.js @@ -7,6 +7,7 @@ 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) { @@ -26,7 +27,7 @@ class Dist { return ( this.options.runtimeDirectory || - path.join(environment.home, cacheDirectory, runtimeArchDirectory, runtimeVersionDirectory) + path.join(os.homedir(), cacheDirectory, runtimeArchDirectory, runtimeVersionDirectory) ) } get externalPath() { diff --git a/lib/environment.js b/lib/environment.js index efb96572..0cc7402b 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -14,16 +14,9 @@ const environment = (module.exports = { isArm: os.arch() === 'arm', runtime: 'node', runtimeVersion: process.versions.node, - home: process.env[os.platform() === 'win32' ? 'USERPROFILE' : 'HOME'], - EOL: os.EOL, }) Object.defineProperties(environment, { - isPosix: { - get: function () { - return !this.isWin - }, - }, _isNinjaAvailable: { value: null, writable: true, From aa657314ac012de7e6e2613d828617c6c334f46a Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 01:02:58 +0000 Subject: [PATCH 71/84] fix: remove dependency on `lodash.isplainobject` --- lib/appCMakeJSConfig.js | 3 +-- lib/buildSystem.js | 20 +++++++++----------- lib/runtimePaths.js | 8 +++++--- package.json | 1 - 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/appCMakeJSConfig.js b/lib/appCMakeJSConfig.js index ed8621dd..bee96119 100644 --- a/lib/appCMakeJSConfig.js +++ b/lib/appCMakeJSConfig.js @@ -1,6 +1,5 @@ 'use strict' const path = require('path') -const isPlainObject = require('lodash.isplainobject') function getConfig(lookPath, log) { const pjsonPath = path.join(lookPath, 'package.json') @@ -8,7 +7,7 @@ function getConfig(lookPath, log) { try { const json = require(pjsonPath) log.silly('CFG', 'Loaded:\n' + JSON.stringify(json)) - if (isPlainObject(json) && isPlainObject(json['cmake-js'])) { + if (json && json['cmake-js'] && typeof json['cmake-js'] === 'object') { log.silly('CFG', 'Config found.') return json['cmake-js'] } else { diff --git a/lib/buildSystem.js b/lib/buildSystem.js index 2f7c14af..63c93842 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -5,7 +5,6 @@ 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') function isNodeApi(log, projectRoot) { @@ -29,20 +28,19 @@ class BuildSystem { const appConfig = appCMakeJSConfig(this.options.directory, this.log) const npmOptions = npmConfig(this.log) - if (isPlainObject(npmOptions) && Object.keys(npmOptions).length) { + if (npmOptions && typeof npmOptions === 'object' && 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 - } + 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) diff --git a/lib/runtimePaths.js b/lib/runtimePaths.js index 24d030ef..c513025c 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -1,7 +1,6 @@ 'use strict' const assert = require('assert') const semver = require('semver') -const isPlainObject = require('lodash.isplainobject') 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' @@ -83,8 +82,11 @@ const runtimePaths = { const runtime = targetOptions.runtime const func = runtimePaths[runtime] let paths - if (typeof func === 'function' && isPlainObject((paths = func(targetOptions)))) { - return paths + if (typeof func === 'function') { + paths = func(targetOptions) + if (paths && typeof 'paths' === 'object') { + return paths + } } throw new Error('Unknown runtime: ' + runtime) }, diff --git a/package.json b/package.json index c1ab3230..6b9aae0e 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "axios": "^1.6.5", "debug": "^4", "fs-extra": "^11.2.0", - "lodash.isplainobject": "^4.0.6", "memory-stream": "^1.0.0", "node-api-headers": "^1.1.0", "npmlog": "^6.0.2", From 3922fb87993685ab49992343ae689f2f740d0b85 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 21:47:50 +0000 Subject: [PATCH 72/84] chore: fix typo --- lib/runtimePaths.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/runtimePaths.js b/lib/runtimePaths.js index c513025c..c9680da8 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -84,7 +84,7 @@ const runtimePaths = { let paths if (typeof func === 'function') { paths = func(targetOptions) - if (paths && typeof 'paths' === 'object') { + if (paths && typeof paths === 'object') { return paths } } From 91ed447bfa4a7f97fefd04840d8046f71c61f40d Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 16 Apr 2025 20:48:47 +0100 Subject: [PATCH 73/84] chore: ci update --- .github/dependabot.yml | 13 +++++++++++++ .github/workflows/node.yaml | 32 +++++++++++++++++--------------- 2 files changed, 30 insertions(+), 15 deletions(-) create mode 100644 .github/dependabot.yml 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 5e19bc6f..81fd69a7 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -14,29 +14,31 @@ 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-11 + - os: macos-13 arch: x64 + - os: macos-14 + arch: arm64 + node-version: 16.x # linux - os: ubuntu-22.04 arch: x64 - - os: ubuntu-20.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-bullseye + - os: ubuntu-24.04-arm + arch: arm64 + # linux-libc - os: ubuntu-latest arch: arm docker-arch: linux/arm/v7 @@ -51,12 +53,12 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Use Node.js 14.x + - name: Use Node.js if: ${{ !matrix.docker-arch }} uses: actions/setup-node@v3 with: architecture: ${{ matrix.arch }} - node-version: 14.x + node-version: ${{ matrix.node-version || '14.x' }} - name: run tests if: ${{ !matrix.docker-arch }} From 2130796688439993760cd8cb23223af0a558b002 Mon Sep 17 00:00:00 2001 From: jaycex Date: Mon, 22 Jan 2024 18:10:50 +0800 Subject: [PATCH 74/84] fix(windows): download correct node.lib for arm64 arch on windows --- lib/environment.js | 1 + lib/runtimePaths.js | 4 ++-- lib/targetOptions.js | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/environment.js b/lib/environment.js index 0cc7402b..401b0bac 100644 --- a/lib/environment.js +++ b/lib/environment.js @@ -12,6 +12,7 @@ const environment = (module.exports = { 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, }) diff --git a/lib/runtimePaths.js b/lib/runtimePaths.js index c9680da8..73c92f26 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -24,7 +24,7 @@ const runtimePaths = { externalPath: NODE_MIRROR + '/v' + targetOptions.runtimeVersion + '/', winLibs: [ { - dir: targetOptions.isX64 ? 'win-x64' : 'win-x86', + dir: targetOptions.isArm64 ? 'arm64' : targetOptions.isX64 ? 'win-x64' : 'win-x86', name: targetOptions.runtime + '.lib', }, ], @@ -68,7 +68,7 @@ const runtimePaths = { externalPath: ELECTRON_MIRROR + '/v' + targetOptions.runtimeVersion + '/', winLibs: [ { - dir: targetOptions.isX64 ? 'x64' : '', + dir: targetOptions.isArm64 ? 'arm64' : targetOptions.isX64 ? 'x64' : '', name: 'node.lib', }, ], diff --git a/lib/targetOptions.js b/lib/targetOptions.js index c61bf6fe..70c3d135 100644 --- a/lib/targetOptions.js +++ b/lib/targetOptions.js @@ -15,6 +15,9 @@ class TargetOptions { get isArm() { return this.arch === 'arm' } + get isArm64() { + return this.arch === 'arm64' + } get runtime() { return this.options.runtime || environment.runtime } From c7dac5a02861c157b7415d4d2d9fee0f651ca429 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Wed, 16 Apr 2025 21:03:49 +0100 Subject: [PATCH 75/84] fix: update find-visualstudio script --- lib/import/find-visualstudio.js | 193 ++++++++++++++++++++++++++++---- 1 file changed, 173 insertions(+), 20 deletions(-) diff --git a/lib/import/find-visualstudio.js b/lib/import/find-visualstudio.js index 4244c7e4..d162c2f0 100644 --- a/lib/import/find-visualstudio.js +++ b/lib/import/find-visualstudio.js @@ -54,7 +54,12 @@ class VisualStudioFinder { } const checks = [ - () => this.findVisualStudio2017OrNewer(), + () => this.findVisualStudio2019OrNewerFromSpecifiedLocation(), + () => this.findVisualStudio2019OrNewerUsingSetupModule(), + () => this.findVisualStudio2019OrNewer(), + () => this.findVisualStudio2017FromSpecifiedLocation(), + () => this.findVisualStudio2017UsingSetupModule(), + () => this.findVisualStudio2017(), () => this.findVisualStudio2015(), () => this.findVisualStudio2013(), ] @@ -113,9 +118,121 @@ class VisualStudioFinder { 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 findVisualStudio2017OrNewer() { + 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 = [ @@ -127,25 +244,36 @@ class VisualStudioFinder { ] this.log.silly('Running', ps, psArgs) - const [err, stdout, stderr] = await execFile(ps, psArgs, { encoding: 'utf8' }) - return this.parseData(err, stdout, stderr) + 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 and look for an installation - // of Visual Studio 2017 or newer to use - parseData(err, stdout, stderr) { + // 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 = () => { + 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", + `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() + return failPowershell(`${err}`.substring(0, 40)) } let vsInfo @@ -157,11 +285,16 @@ class VisualStudioFinder { return failPowershell() } - if (!Array.isArray(vsInfo)) { + if (sanityOptions.checkIsArray && !Array.isArray(vsInfo)) { this.log.silly('PS stdout = %j', stdout) - return failPowershell() + 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) @@ -175,11 +308,12 @@ class VisualStudioFinder { 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) { + if (info.versionYear && supportedYears.indexOf(info.versionYear) !== -1) { return true } - this.addLog(`unknown version "${info.version}" found at "${info.path}"`) + this.addLog(`${info.versionYear ? 'unsupported' : 'unknown'} version "${info.version}" found at "${info.path}"`) return false }) @@ -224,7 +358,7 @@ class VisualStudioFinder { // Helper - process version information getVersionInfo(info) { - const match = /^(\d+)\.(\d+)\..*/.exec(info.version) + const match = /^(\d+)\.(\d+)(?:\..*)?/.exec(info.version) if (!match) { this.log.silly('- failed to parse version:', info.version) return {} @@ -266,7 +400,11 @@ class VisualStudioFinder { return path.join(info.path, 'MSBuild', '15.0', 'Bin', 'MSBuild.exe') } if (versionYear === 2019) { - return msbuildPath + if (process.arch === 'arm64' && this.msBuildPathExists(msbuildPathArm64)) { + return msbuildPathArm64 + } else { + return msbuildPath + } } } /** @@ -284,12 +422,23 @@ class VisualStudioFinder { // Helper - process toolset information getToolset(info, versionYear) { - const pkg = 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64' + 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 (info.packages.indexOf(pkg) !== -1) { - this.log.silly('- found VC.Tools.x86.x64') - } else if (info.packages.indexOf(express) !== -1) { + 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 @@ -434,6 +583,10 @@ class VisualStudioFinder { return true } + + async execFile(exec, args) { + return await execFile(exec, args, { encoding: 'utf8' }) + } } module.exports = VisualStudioFinder From 7d3182e7000dd45fef495749be66e2dc614984e5 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 17 Apr 2025 20:24:01 +0100 Subject: [PATCH 76/84] fix: win-arm64 bug add tests for more runtime versions, and include win-arm64 --- lib/runtimePaths.js | 2 +- package.json | 2 +- tests/es6/prototype-napi/CMakeLists.txt | 8 +++++ tests/es6/prototype/CMakeLists.txt | 8 +++++ tests/es6/prototype2/CMakeLists.txt | 10 ++++++- tests/es6/testRunner.js | 40 ++++++++++++++++++------- 6 files changed, 57 insertions(+), 13 deletions(-) diff --git a/lib/runtimePaths.js b/lib/runtimePaths.js index 73c92f26..4fc9f177 100644 --- a/lib/runtimePaths.js +++ b/lib/runtimePaths.js @@ -24,7 +24,7 @@ const runtimePaths = { externalPath: NODE_MIRROR + '/v' + targetOptions.runtimeVersion + '/', winLibs: [ { - dir: targetOptions.isArm64 ? 'arm64' : targetOptions.isX64 ? 'win-x64' : 'win-x86', + dir: targetOptions.isArm64 ? 'win-arm64' : targetOptions.isX64 ? 'win-x64' : 'win-x86', name: targetOptions.runtime + '.lib', }, ], diff --git a/package.json b/package.json index 6b9aae0e..bed2c5a5 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "mocha": "*", - "nan": "^2.18.0", + "nan": "^2.22.2", "node-addon-api": "^6.1.0", "prettier": "^3.2.2" }, diff --git a/tests/es6/prototype-napi/CMakeLists.txt b/tests/es6/prototype-napi/CMakeLists.txt index 53320e33..4579529f 100644 --- a/tests/es6/prototype-napi/CMakeLists.txt +++ b/tests/es6/prototype-napi/CMakeLists.txt @@ -20,3 +20,11 @@ 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/CMakeLists.txt b/tests/es6/prototype/CMakeLists.txt index 0a0e9ab3..21c78254 100644 --- a/tests/es6/prototype/CMakeLists.txt +++ b/tests/es6/prototype/CMakeLists.txt @@ -15,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 aab7498a..0978135d 100644 --- a/tests/es6/prototype2/CMakeLists.txt +++ b/tests/es6/prototype2/CMakeLists.txt @@ -14,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/testRunner.js b/tests/es6/testRunner.js index 34edc6f1..16e8788c 100644 --- a/tests/es6/testRunner.js +++ b/tests/es6/testRunner.js @@ -7,24 +7,36 @@ const util = require('util') function* generateRuntimeOptions() { function* generateForNode(arch) { - // Old: - yield { - runtime: 'node', - runtimeVersion: '14.0.0', - arch: 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, + } } - // LTS: yield { runtime: 'node', - runtimeVersion: '16.0.0', + runtimeVersion: '20.19.0', arch: arch, } // Current: yield { runtime: 'node', - runtimeVersion: '18.13.0', + runtimeVersion: '22.9.0', arch: arch, } } @@ -39,23 +51,31 @@ function* generateRuntimeOptions() { } function* generateForElectron(arch) { - // Latest: yield { runtime: 'electron', runtimeVersion: '18.2.1', arch: arch, } + + yield { + runtime: 'electron', + runtimeVersion: '31.7.7', + arch: arch, + } } function* generateForArch(arch) { yield* generateForNode(arch) - yield* generateForNWJS(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() } From 20d9958fdd0c157553784c7ae0bb58f9e8bb20bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 21:06:22 +0100 Subject: [PATCH 77/84] chore(deps): bump actions/setup-node from 3 to 4 (#346) Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/node.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index 81fd69a7..537a76c7 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -55,7 +55,7 @@ jobs: - name: Use Node.js if: ${{ !matrix.docker-arch }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: architecture: ${{ matrix.arch }} node-version: ${{ matrix.node-version || '14.x' }} From cb0bff8e099f1b3e8f773901489d3328b0f27542 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 21:06:32 +0100 Subject: [PATCH 78/84] chore(deps): bump actions/checkout from 3 to 4 (#344) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/node.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index 537a76c7..b06a2af3 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -51,7 +51,7 @@ jobs: libc: musl steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js if: ${{ !matrix.docker-arch }} From c579a7181cac4b4af96871a9dd6f195b8d85a75d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 21:21:39 +0100 Subject: [PATCH 79/84] chore(deps): bump docker/setup-qemu-action from 2 to 3 (#345) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/node.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/node.yaml b/.github/workflows/node.yaml index b06a2af3..97055c2a 100644 --- a/.github/workflows/node.yaml +++ b/.github/workflows/node.yaml @@ -71,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 From eda3427601a647828ee79a48597b9b8c6c8a7df3 Mon Sep 17 00:00:00 2001 From: Segev Finer Date: Thu, 17 Apr 2025 23:53:00 +0300 Subject: [PATCH 80/84] chore: Modernize CMakeLists.txt example (#342) --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 01c0d48e..f4e4609f 100644 --- a/README.md +++ b/README.md @@ -106,21 +106,18 @@ In a nutshell. _(For more complete documentation please see [the first tutorial] - 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) +cmake_minimum_required(VERSION 3.15...3.31) +project(your-addon-name-here) -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 From 0200582a5aad5d5b4d50e9f4caa6a4dd48fb0556 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 17 Apr 2025 21:54:43 +0100 Subject: [PATCH 81/84] chore: update package.json --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bed2c5a5..958efe6f 100644 --- a/package.json +++ b/package.json @@ -72,5 +72,6 @@ "*.md", "bindings.js", "bindings.d.ts" - ] + ], + "packageManager": "yarn@1.22.22+sha256.c17d3797fb9a9115bf375e31bfd30058cac6bc9c3b8807a3d8cb2094794b51ca" } From 2f9f437d970882adb812f1aa54442082e45e537f Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Thu, 17 Apr 2025 21:56:26 +0100 Subject: [PATCH 82/84] v7.3.1 --- changelog.md | 7 ++++++- package.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 04b55332..0675981d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,9 @@ -# v7.3.0 - 15/01/23 +# 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) diff --git a/package.json b/package.json index 958efe6f..aa131376 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "node-addon-api" ], "main": "lib", - "version": "7.3.0", + "version": "7.3.1", "author": "Gábor Mező aka unbornchikken", "maintainers": [ { From 55ec5cde3e8d7e6c4bde61426ae05f4f95b5f763 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 21 Apr 2025 12:07:25 +0100 Subject: [PATCH 83/84] chore: remove outdated tutorials from readme --- README.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/README.md b/README.md index f4e4609f..a4d42720 100644 --- a/README.md +++ b/README.md @@ -100,8 +100,6 @@ 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: @@ -346,7 +344,7 @@ To make compatible your NW.js application with any NAN CMake.js based modules, w } ``` -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 @@ -373,13 +371,6 @@ The least "painful" way of addressing this is to use heroku's multipack facility The `heroku-buildpack-multi` will run each buildpack in order allowing the node application to reference CMake in the Heroku build environment. -## Tutorials - -- [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) - ## 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 From f257177211ab685ef98092f6b4ba3c01e8e9da64 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 21 Apr 2025 12:23:31 +0100 Subject: [PATCH 84/84] chore: add readme section about using libraries --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index a4d42720..b7d49524 100644 --- a/README.md +++ b/README.md @@ -371,6 +371,19 @@ The least "painful" way of addressing this is to use heroku's multipack facility 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 + +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 + +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