From 89c055e57c8d3540b7b56af6e70c29a89ad0164c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 20:44:28 +0100 Subject: [PATCH 1/8] feat: skip downloading node headers when targetting node-api --- lib/buildSystem.js | 16 ++++++++++++++- lib/cMake.js | 50 ++++++++++++++++++++++++++-------------------- package.json | 7 ++++--- 3 files changed, 47 insertions(+), 26 deletions(-) diff --git a/lib/buildSystem.js b/lib/buildSystem.js index 866b409b..8ef0633a 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -8,9 +8,21 @@ const path = require("path"); const isPlainObject = require("lodash.isplainobject"); const Toolset = require("./toolset"); +function isNodeApi(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.isNodeApi = isNodeApi(this.options.directory) this.log = new CMLog(this.options); const appConfig = appCMakeJSConfig(this.options.directory, this.log); const npmOptions = npmConfig(this.log); @@ -39,7 +51,9 @@ function BuildSystem(options) { BuildSystem.prototype._ensureInstalled = async function () { try { await this.toolset.initialize(true); - await this.dist.ensureDownloaded(); + if (!this.options.isNodeApi) { + await this.dist.ensureDownloaded(); + } } catch (e) { this._showError(e); diff --git a/lib/cMake.js b/lib/cMake.js index 1207e0a1..29ad440d 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: 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 7defaf589a8db5783f6c1bac35afc086d79501e1 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 14:39:06 -0700 Subject: [PATCH 2/8] fix: typo --- lib/buildSystem.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/buildSystem.js b/lib/buildSystem.js index 8ef0633a..da4005d0 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -8,7 +8,7 @@ const path = require("path"); const isPlainObject = require("lodash.isplainobject"); const Toolset = require("./toolset"); -function isNodeApi(projectRoot) { +function isNodeApi(log, projectRoot) { try { const projectPkgJson = require(path.join(projectRoot, 'package.json')) // Make sure the property exists @@ -22,8 +22,8 @@ function isNodeApi(projectRoot) { function BuildSystem(options) { this.options = options || {}; this.options.directory = path.resolve(this.options.directory || process.cwd()); - this.options.isNodeApi = isNodeApi(this.options.directory) 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); From ec5dd68ff34b4d870644ea3a88bef648c54cce9b Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 14:40:57 -0700 Subject: [PATCH 3/8] feat: generate minimal node.lib on windows for node-api --- lib/buildSystem.js | 1 + lib/cMake.js | 25 ++++++++++----- lib/locateNodeApi.js | 18 ----------- lib/nodeApi.js | 74 ++++++++++++++++++++++++++++++++++++++++++++ lib/toolset.js | 9 +++++- 5 files changed, 100 insertions(+), 27 deletions(-) delete mode 100644 lib/locateNodeApi.js create mode 100644 lib/nodeApi.js diff --git a/lib/buildSystem.js b/lib/buildSystem.js index da4005d0..e1690df7 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -22,6 +22,7 @@ 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); diff --git a/lib/cMake.js b/lib/cMake.js index 29ad440d..4a4b5225 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -8,7 +8,7 @@ const CMLog = require("./cmLog"); const TargetOptions = require("./targetOptions"); const processHelpers = require("./processHelpers"); const locateNAN = require("./locateNAN"); -const locateNodeApi = require("./locateNodeApi"); +const {locateNodeApi} = require("./nodeApi"); const npmConfigData = require("rc")("npm"); const Toolset = require("./toolset"); @@ -182,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' @@ -204,6 +198,21 @@ CMake.prototype.getConfigureCommand = async function () { // Toolset: await this.toolset.initialize(false); + if (environment.isWin) { + // Win + const libs = [] + if (this.options.isNodeApi) { + if (this.toolset.winNodeLibPath) { + libs.push(this.toolset.winNodeLibPath) + } + } 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 deleted file mode 100644 index 6ee9ee5e..00000000 --- a/lib/locateNodeApi.js +++ /dev/null @@ -1,18 +0,0 @@ -"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.replace(/"/g, '') - } catch (e) { - // It most likely wasn't found - return null; - } -}; diff --git a/lib/nodeApi.js b/lib/nodeApi.js new file mode 100644 index 00000000..2f785d60 --- /dev/null +++ b/lib/nodeApi.js @@ -0,0 +1,74 @@ +"use strict"; +const path = require("path"); +const fs = require('fs/promises') +const headers = require('node-api-headers'); +const processHelpers = require("./processHelpers"); + +function sanitiseArch(arch) { + if (arch === 'ia32') return 'x86' + return arch +} + +async function generateNodeLib (options, vsInfo, targetFile) { + try { + const vsToolsVersion = (await fs.readFile(path.join(vsInfo.path, 'VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt'))).toString().trim() + + // This will probably not work for non x86/x64 builds, but that is a rarity for windows + const runtimeArch = sanitiseArch(options.arch) + const libExePath = path.join(vsInfo.path, 'VC\\Tools\\MSVC', vsToolsVersion, `bin\\Host${sanitiseArch(process.arch)}`, runtimeArch, 'lib.exe') + const stat = await fs.stat(libExePath) + if (!stat.isFile()) return null + + // 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) + } + } + + const parentDir = path.dirname(targetFile) + await fs.mkdir(parentDir, { recursive: true }) + + const allSymbolsArr = Array.from(allSymbols) + const targetFileDef = `${targetFile}.def` + await fs.writeFile(targetFileDef, 'NAME NODE.EXE\nEXPORTS\n' + allSymbolsArr.join('\n')) + + await processHelpers.run([ + libExePath, + `/def:${targetFileDef}`, + `/out:${targetFile}`, + `/machine:${runtimeArch}` + ], { silent: true }) + + // Cleanup + await fs.unlink(targetFileDef) + + return targetFile + } catch(e) { + console.error(e) + // It most likely wasn't found + throw new Error(`Failed to generate node.lib`) + } +} + +async function locateNodeApi (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; + } +}; + +module.exports = { + generateNodeLib, + locateNodeApi +} diff --git a/lib/toolset.js b/lib/toolset.js index ca9fd699..a6c172d4 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -4,7 +4,9 @@ const environment = require("./environment"); const assert = require("assert"); const CMLog = require("./cmLog"); const util = require('util') -const findVisualStudioRaw = require('./import/find-visualstudio') +const path = require('path') +const findVisualStudioRaw = require('./import/find-visualstudio'); +const { generateNodeLib } = require("./nodeApi"); const findVisualStudio = util.promisify(findVisualStudioRaw) @@ -170,6 +172,11 @@ Toolset.prototype.initializeWin = async function (install) { break; } } + + if (this.options.isNodeApi) { + // Generate the node.lib file + this.winNodeLibPath = await generateNodeLib(this.targetOptions, foundVsInfo, path.join(this.options.out, 'node.lib')) + } } else { throw new Error("There is no Visual C++ compiler installed. Install Visual C++ Build Toolset or Visual Studio."); } From 5b74c7ce6e4c7132c942d5461ef0b6eb5be3252c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 15:26:52 -0700 Subject: [PATCH 4/8] feat: generate node.lib via cmake --- README.md | 8 ++++++++ lib/buildSystem.js | 30 +++++++++++++++++++++++++++- lib/cMake.js | 7 +++++-- lib/nodeApi.js | 49 ---------------------------------------------- lib/toolset.js | 7 ------- 5 files changed, 42 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 28f11ec7..68e8d763 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) + # 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/lib/buildSystem.js b/lib/buildSystem.js index e1690df7..44d04a10 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -4,9 +4,11 @@ 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 { @@ -52,7 +54,9 @@ function BuildSystem(options) { BuildSystem.prototype._ensureInstalled = async function () { try { await this.toolset.initialize(true); - if (!this.options.isNodeApi) { + if (this.options.isNodeApi) { + this.options.nodeLibDefPath = await this._generateNodeLibDef(path.join(this.options.out, 'node-lib.def')); + } else { await this.dist.ensureDownloaded(); } } @@ -62,6 +66,30 @@ 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) + } + } + + const parentDir = path.dirname(targetFile) + await fs.mkdir(parentDir, { recursive: true }) + + const allSymbolsArr = Array.from(allSymbols) + await fs.writeFile(targetFile, 'NAME NODE.EXE\nEXPORTS\n' + allSymbolsArr.join('\n')) + + return targetFile + } catch(e) { + console.error(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 4a4b5225..ea654485 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -202,8 +202,11 @@ CMake.prototype.getConfigureCommand = async function () { // Win const libs = [] if (this.options.isNodeApi) { - if (this.toolset.winNodeLibPath) { - libs.push(this.toolset.winNodeLibPath) + 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) diff --git a/lib/nodeApi.js b/lib/nodeApi.js index 2f785d60..106a0c0a 100644 --- a/lib/nodeApi.js +++ b/lib/nodeApi.js @@ -4,54 +4,6 @@ const fs = require('fs/promises') const headers = require('node-api-headers'); const processHelpers = require("./processHelpers"); -function sanitiseArch(arch) { - if (arch === 'ia32') return 'x86' - return arch -} - -async function generateNodeLib (options, vsInfo, targetFile) { - try { - const vsToolsVersion = (await fs.readFile(path.join(vsInfo.path, 'VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt'))).toString().trim() - - // This will probably not work for non x86/x64 builds, but that is a rarity for windows - const runtimeArch = sanitiseArch(options.arch) - const libExePath = path.join(vsInfo.path, 'VC\\Tools\\MSVC', vsToolsVersion, `bin\\Host${sanitiseArch(process.arch)}`, runtimeArch, 'lib.exe') - const stat = await fs.stat(libExePath) - if (!stat.isFile()) return null - - // 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) - } - } - - const parentDir = path.dirname(targetFile) - await fs.mkdir(parentDir, { recursive: true }) - - const allSymbolsArr = Array.from(allSymbols) - const targetFileDef = `${targetFile}.def` - await fs.writeFile(targetFileDef, 'NAME NODE.EXE\nEXPORTS\n' + allSymbolsArr.join('\n')) - - await processHelpers.run([ - libExePath, - `/def:${targetFileDef}`, - `/out:${targetFile}`, - `/machine:${runtimeArch}` - ], { silent: true }) - - // Cleanup - await fs.unlink(targetFileDef) - - return targetFile - } catch(e) { - console.error(e) - // It most likely wasn't found - throw new Error(`Failed to generate node.lib`) - } -} - async function locateNodeApi (projectRoot) { if (locateNodeApi.__projectRoot) { // Override for unit tests @@ -69,6 +21,5 @@ async function locateNodeApi (projectRoot) { }; module.exports = { - generateNodeLib, locateNodeApi } diff --git a/lib/toolset.js b/lib/toolset.js index a6c172d4..935d137b 100644 --- a/lib/toolset.js +++ b/lib/toolset.js @@ -4,9 +4,7 @@ const environment = require("./environment"); const assert = require("assert"); const CMLog = require("./cmLog"); const util = require('util') -const path = require('path') const findVisualStudioRaw = require('./import/find-visualstudio'); -const { generateNodeLib } = require("./nodeApi"); const findVisualStudio = util.promisify(findVisualStudioRaw) @@ -172,11 +170,6 @@ Toolset.prototype.initializeWin = async function (install) { break; } } - - if (this.options.isNodeApi) { - // Generate the node.lib file - this.winNodeLibPath = await generateNodeLib(this.targetOptions, foundVsInfo, path.join(this.options.out, 'node.lib')) - } } else { throw new Error("There is no Visual C++ compiler installed. Install Visual C++ Build Toolset or Visual Studio."); } From cf86fc94c15d27994b86220b61b61539ad44b07f Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 15:29:17 -0700 Subject: [PATCH 5/8] chore: tidying --- lib/buildSystem.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/buildSystem.js b/lib/buildSystem.js index 44d04a10..f2e6c7f1 100644 --- a/lib/buildSystem.js +++ b/lib/buildSystem.js @@ -76,15 +76,16 @@ BuildSystem.prototype._generateNodeLibDef = async function (targetFile) { } } + // 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) { - console.error(e) // It most likely wasn't found throw new Error(`Failed to generate def for node.lib`) } From d4b1767d26ad8d7d71e4663bcda011180bdc079d Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 15:30:57 -0700 Subject: [PATCH 6/8] chore: tidying --- lib/cMake.js | 2 +- lib/{nodeApi.js => locateNodeApi.js} | 11 ++--------- 2 files changed, 3 insertions(+), 10 deletions(-) rename lib/{nodeApi.js => locateNodeApi.js} (67%) diff --git a/lib/cMake.js b/lib/cMake.js index ea654485..ed2be269 100644 --- a/lib/cMake.js +++ b/lib/cMake.js @@ -8,7 +8,7 @@ const CMLog = require("./cmLog"); const TargetOptions = require("./targetOptions"); const processHelpers = require("./processHelpers"); const locateNAN = require("./locateNAN"); -const {locateNodeApi} = require("./nodeApi"); +const locateNodeApi = require("./locateNodeApi"); const npmConfigData = require("rc")("npm"); const Toolset = require("./toolset"); diff --git a/lib/nodeApi.js b/lib/locateNodeApi.js similarity index 67% rename from lib/nodeApi.js rename to lib/locateNodeApi.js index 106a0c0a..ee1fbf30 100644 --- a/lib/nodeApi.js +++ b/lib/locateNodeApi.js @@ -1,10 +1,7 @@ "use strict"; const path = require("path"); -const fs = require('fs/promises') -const headers = require('node-api-headers'); -const processHelpers = require("./processHelpers"); -async function locateNodeApi (projectRoot) { +const locateNodeApi = module.exports = async function (projectRoot) { if (locateNodeApi.__projectRoot) { // Override for unit tests projectRoot = locateNodeApi.__projectRoot; @@ -18,8 +15,4 @@ async function locateNodeApi (projectRoot) { // It most likely wasn't found return null; } -}; - -module.exports = { - locateNodeApi -} +}; \ No newline at end of file From d843b44f662a39346fa3afe08a5938858424711c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 15:30:57 -0700 Subject: [PATCH 7/8] feat: skip downloading node headers when targetting node-api --- README.md | 8 +++++ lib/buildSystem.js | 46 ++++++++++++++++++++++++++- lib/cMake.js | 76 +++++++++++++++++++++++++++----------------- lib/locateNodeApi.js | 2 +- lib/toolset.js | 2 +- package.json | 7 ++-- 6 files changed, 106 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 28f11ec7..68e8d763 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) + # 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/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 f0a813deacd60b274e719990a81e4a7a9b15ade9 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Sun, 5 Jun 2022 15:36:39 -0700 Subject: [PATCH 8/8] chore: update readme --- README.md | 2 +- changelog.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 68e8d763..b01ce300 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ 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) +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/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 ==========