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",