From 1214f4288c394c034c809418ba6a6b7ddc39f8ad Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 01:29:06 +0000 Subject: [PATCH 1/8] wip: add cmake helper module --- Lib.cmake | 106 ++++++++++++++++++++++++++++++++++++++++++ bin/cmake-js-versions | 9 ++++ 2 files changed, 115 insertions(+) create mode 100644 Lib.cmake create mode 100755 bin/cmake-js-versions diff --git a/Lib.cmake b/Lib.cmake new file mode 100644 index 00000000..4a44863a --- /dev/null +++ b/Lib.cmake @@ -0,0 +1,106 @@ +cmake_minimum_required(VERSION 3.15) + +# setup some cmake required config +set(CMAKE_POSITION_INDEPENDENT_CODE ON) +add_definitions("-DBUILDING_NODE_EXTENSION") + +# 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. +set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") + +# Setup some variables +if(NOT CMAKE_BUILD_TYPE) + SET (CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) +endif() +if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}") +endif() + +# Find versions info +execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/bin/cmake-js-versions" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE CMAKE_JS_VERSIONS +) +if (NOT DEFINED CMAKE_JS_VERSIONS OR "${CMAKE_JS_VERSIONS}" STREQUAL "") + message(FATAL_ERROR "Failed to find cmake-js and nodejs versions!") +endif() +string(REGEX MATCH "CMAKEJS_VERSION ([0-9a-zA-Z\.]+)" _ ${CMAKE_JS_VERSIONS}) +set(CMAKEJS_VERSION ${CMAKE_MATCH_1}) +string(REGEX MATCH "NODE_RUNTIME ([0-9a-zA-Z\.]+)" _ ${CMAKE_JS_VERSIONS}) +set(NODE_RUNTIME ${CMAKE_MATCH_1}) +string(REGEX MATCH "NODE_RUNTIMEVERSION ([0-9a-zA-Z\.]+)" _ ${CMAKE_JS_VERSIONS}) +set(NODE_RUNTIMEVERSION ${CMAKE_MATCH_1}) +string(REGEX MATCH "NODE_ARCH ([0-9a-zA-Z\.]+)" _ ${CMAKE_JS_VERSIONS}) +set(NODE_ARCH ${CMAKE_MATCH_1}) + +# cmake-js version of CMake `add_library` specifically for node addons +FUNCTION (cmake_js_add_node_addon PROJECT_NAME) + cmake_parse_arguments( + PARSED_ARGS # prefix of output variables + "OLD_ADDON_API" # list of names of the boolean arguments (only defined ones will be true) + "" # list of names of mono-valued arguments + "SOURCES" # list of names of multi-valued arguments (output variables are lists) + ${ARGN} # arguments of the function to parse, here we take the all original ones + ) + + # windows delay hook + set(CMAKE_JS_SRC "") + if (MSVC) + list (append CMAKE_JS_SRC "${CMAKE_CURRENT_LIST_DIR}/cpp/win_delay_load_hook.cc") + endif() + + # Setup the library and some default config + add_library(${PROJECT_NAME} SHARED ${PARSED_ARGS_SOURCES} ${CMAKE_JS_SRC}) + set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node") + + if (OLD_ADDON_API) + # # Try finding nan + # execute_process(COMMAND node -p "require('nan').include" + # WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + # OUTPUT_VARIABLE NODE_NAN_DIR + # OUTPUT_STRIP_TRAILING_WHITESPACE + # ) + # if (DEFINED NODE_ADDON_API_DIR AND NOT "${NODE_NAN_DIR}" STREQUAL "") + # string(REGEX REPLACE "[\"]" "" NODE_NAN_DIR ${NODE_NAN_DIR}) + # message("Found nan: ${NODE_NAN_DIR}") + # target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_NAN_DIR}) + # endif() + + # TODO nan and headers + else() + # Find node-addon-api + execute_process(COMMAND node -p "require('node-api-headers').include_dir" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_API_HEADERS_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (DEFINED NODE_API_HEADERS_DIR AND NOT "${NODE_API_HEADERS_DIR}" STREQUAL "") + message("Found node-api-headers: ${NODE_API_HEADERS_DIR}") + target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_API_HEADERS_DIR}) + else() + message(FATAL_ERROR "Failed to find node-api-headers!") + endif() + + # Try finding node-addon-api + execute_process(COMMAND node -p "require('node-addon-api').include" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_ADDON_API_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (DEFINED NODE_ADDON_API_DIR AND NOT "${NODE_ADDON_API_DIR}" STREQUAL "") + string(REGEX REPLACE "[\"]" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR}) + message("Found node-addon-api: ${NODE_ADDON_API_DIR}") + target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR}) + endif() + + # Generate node.lib if needed + set(CMAKE_JS_NODELIB_DEF "") # TODO + if(MSVC AND CMAKE_JS_NODELIB_DEF) + # Generate node.lib + set(CMAKE_JS_NODELIB_TARGET "${CMAKE_BINARY_DIR}/node.lib") + execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) + target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_NODELIB_TARGET}) + endif() + endif() + +ENDFUNCTION () \ No newline at end of file diff --git a/bin/cmake-js-versions b/bin/cmake-js-versions new file mode 100755 index 00000000..04e2cc22 --- /dev/null +++ b/bin/cmake-js-versions @@ -0,0 +1,9 @@ +#!/usr/bin/env node +'use strict' + +const version = require('../package').version + +console.log(`CMAKEJS_VERSION ${version}`) +console.log(`NODE_RUNTIME node`) +console.log(`NODE_RUNTIMEVERSION ${process.version.slice(1)}`) +console.log(`NODE_ARCH ${process.arch}`) From 5f3935983baf6de045b332fb5e091cd4d13c7ea2 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 22:55:31 +0000 Subject: [PATCH 2/8] wip: add `cmake-js-cmake` helper to invoke the located cmake --- .gitignore | 1 + .prettierignore | 1 + bin/cmake-js-cmake.mjs | 15 +++++++++++ package.json | 9 +++++-- rewrite/src/processHelpers.ts | 49 +++++++++++++++++++++++++++++++++++ rewrite/src/toolchain.ts | 33 +++++++++++++++++++++++ tsconfig.json | 15 +++++++++++ 7 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 .prettierignore create mode 100755 bin/cmake-js-cmake.mjs create mode 100644 rewrite/src/processHelpers.ts create mode 100644 rewrite/src/toolchain.ts create mode 100644 tsconfig.json diff --git a/.gitignore b/.gitignore index d841f14b..5135cda3 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ node_modules build package-lock.json yarn.lock +rewrite/dist # Users Environment Variables .lock-wscript diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..ec6d3cdd --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +package.json diff --git a/bin/cmake-js-cmake.mjs b/bin/cmake-js-cmake.mjs new file mode 100755 index 00000000..b365d37b --- /dev/null +++ b/bin/cmake-js-cmake.mjs @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +/** + * This script is intended to be a minimal helper script which makes for easy invocation of `cmake`, + * utilising the same logic to locate cmake and potentially fill in some parameters + */ + +// TODO - a helper to replace #GENERATOR# with autoselected generator, to simplify windows + +import { runCommand } from '../rewrite/dist/processHelpers.js' +import { findCmake } from '../rewrite/dist/toolchain.js' + +const cmakePath = await findCmake() + +await runCommand([cmakePath, ...process.argv.slice(2)]) diff --git a/package.json b/package.json index 6b9aae0e..f0a6fac8 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "url": "git://github.com/cmake-js/cmake-js.git" }, "bin": { - "cmake-js": "./bin/cmake-js" + "cmake-js": "./bin/cmake-js", + "cmake-js-cmake": "./bin/cmake-js-cmake.mjs" }, "engines": { "node": ">= 14.15.0" @@ -55,12 +56,16 @@ "yargs": "^17.7.2" }, "devDependencies": { + "@tsconfig/node14": "^14.1.0", + "@types/node": "^14.18.63", + "@types/which": "^2.0.2", "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" + "prettier": "^3.2.2", + "typescript": "^5.3.3" }, "scripts": { "test": "mocha tests", diff --git a/rewrite/src/processHelpers.ts b/rewrite/src/processHelpers.ts new file mode 100644 index 00000000..699ff7af --- /dev/null +++ b/rewrite/src/processHelpers.ts @@ -0,0 +1,49 @@ +import { spawn, execFile as execFileRaw } from 'child_process' + +export async function runCommand(command: string[], options?: { silent?: boolean }): Promise { + return new Promise(function (resolve, reject) { + if (!options) options = {} + + 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 + } + }) + }) +} + +export async function execFile(command: string[]): Promise { + return new Promise(function (resolve, reject) { + execFileRaw(command[0], command.slice(1), function (err, stdout, stderr) { + if (err) { + reject(new Error(err.message + '\n' + (stdout || stderr))) + } else { + resolve(stdout) + } + }) + }) +} diff --git a/rewrite/src/toolchain.ts b/rewrite/src/toolchain.ts new file mode 100644 index 00000000..725cd25d --- /dev/null +++ b/rewrite/src/toolchain.ts @@ -0,0 +1,33 @@ +import fs from 'fs/promises' +import which from 'which' + +// export interface SelectedToolchain { +// cmakePath: string +// generatorName: string | null +// archName: string | null +// } + +// export async function findBestToolchain(): Promise { +// // TODO +// } + +export async function findCmake(): Promise { + const overridePath = process.env['CMAKE_JS_CMAKE_PATH'] + if (overridePath) { + try { + const stat = await fs.lstat(overridePath) + if (!stat.isDirectory()) { + return overridePath + } + } catch (e) { + // Ignore + } + throw new Error(`Invalid cmake path: ${overridePath}`) + } else { + const res = which('cmake', { first: true }) + if (!res) { + throw new Error(`cmake not found in PATH`) + } + return res + } +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..b9ecef54 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/node14/tsconfig.json", + "include": ["rewrite/src/**/*.ts"], + "exclude": ["node_modules/**", "src/**/*spec.ts", "src/**/__tests__/*", "src/**/__mocks__/*", "scratch/**"], + "compilerOptions": { + "outDir": "./rewrite/dist", + "baseUrl": "./", + "paths": { + "*": ["./node_modules/*"] + }, + "types": ["node"], + "module": "Node16", + "typeRoots": ["./typings", "./node_modules/@types"] + } +} From 745c3e8af100bbfdafed2189bd49fd829cc4ab29 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 23:05:16 +0000 Subject: [PATCH 3/8] wip: copy old code to allow for gradual deletion upon reimplementation --- rewrite/old/appCMakeJSConfig.js | 58 +++++ rewrite/old/buildSystem.js | 123 +++++++++++ rewrite/old/cMake.js | 362 ++++++++++++++++++++++++++++++++ rewrite/old/cmLog.js | 61 ++++++ rewrite/old/dist.js | 176 ++++++++++++++++ rewrite/old/downloader.js | 92 ++++++++ rewrite/old/environment.js | 96 +++++++++ rewrite/old/index.js | 12 ++ rewrite/old/locateNAN.js | 63 ++++++ rewrite/old/locateNodeApi.js | 18 ++ rewrite/old/npmConfig.js | 31 +++ rewrite/old/processHelpers.js | 53 +++++ rewrite/old/runtimePaths.js | 95 +++++++++ rewrite/old/targetOptions.js | 30 +++ rewrite/old/toolset.js | 224 ++++++++++++++++++++ 15 files changed, 1494 insertions(+) create mode 100644 rewrite/old/appCMakeJSConfig.js create mode 100644 rewrite/old/buildSystem.js create mode 100644 rewrite/old/cMake.js create mode 100644 rewrite/old/cmLog.js create mode 100644 rewrite/old/dist.js create mode 100644 rewrite/old/downloader.js create mode 100644 rewrite/old/environment.js create mode 100644 rewrite/old/index.js create mode 100644 rewrite/old/locateNAN.js create mode 100644 rewrite/old/locateNodeApi.js create mode 100644 rewrite/old/npmConfig.js create mode 100644 rewrite/old/processHelpers.js create mode 100644 rewrite/old/runtimePaths.js create mode 100644 rewrite/old/targetOptions.js create mode 100644 rewrite/old/toolset.js diff --git a/rewrite/old/appCMakeJSConfig.js b/rewrite/old/appCMakeJSConfig.js new file mode 100644 index 00000000..bee96119 --- /dev/null +++ b/rewrite/old/appCMakeJSConfig.js @@ -0,0 +1,58 @@ +'use strict' +const path = require('path') + +function getConfig(lookPath, log) { + const pjsonPath = path.join(lookPath, 'package.json') + log.silly('CFG', "Looking for package.json in: '" + pjsonPath + "'.") + try { + const json = require(pjsonPath) + log.silly('CFG', 'Loaded:\n' + JSON.stringify(json)) + if (json && json['cmake-js'] && typeof json['cmake-js'] === 'object') { + log.silly('CFG', 'Config found.') + return json['cmake-js'] + } else { + log.silly('CFG', 'Config not found.') + return null + } + } catch (e) { + log.silly('CFG', "'package.json' not found.") + return null + } +} + +module.exports = function (projectPath, log) { + log.verbose('CFG', "Looking for application level CMake.js config in '" + projectPath + '.') + let currPath = projectPath + let lastConfig = null + let currConfig + for (;;) { + currConfig = getConfig(currPath, log) + if (currConfig) { + lastConfig = currConfig + } + try { + log.silly('CFG', 'Looking for parent path.') + const lastPath = currPath + currPath = path.normalize(path.join(currPath, '..')) + if (lastPath === currPath) { + currPath = null // root + } + if (currPath) { + log.silly('CFG', "Parent path: '" + currPath + "'.") + } + } catch (e) { + log.silly('CFG', 'Exception:\n' + e.stack) + break + } + if (currPath === null) { + log.silly('CFG', "Parent path with package.json file doesn't exists. Done.") + break + } + } + if (lastConfig) { + log.verbose('CFG', 'Application level CMake.js config found:\n' + JSON.stringify(lastConfig)) + } else { + log.verbose('CFG', "Application level CMake.js config doesn't exists.") + } + return lastConfig +} diff --git a/rewrite/old/buildSystem.js b/rewrite/old/buildSystem.js new file mode 100644 index 00000000..63c93842 --- /dev/null +++ b/rewrite/old/buildSystem.js @@ -0,0 +1,123 @@ +'use strict' +const CMake = require('./cMake') +const Dist = require('./dist') +const CMLog = require('./cmLog') +const appCMakeJSConfig = require('./appCMakeJSConfig') +const npmConfig = require('./npmConfig') +const path = require('path') +const 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 + } +} + +class BuildSystem { + constructor(options) { + this.options = options || {} + this.options.directory = path.resolve(this.options.directory || process.cwd()) + this.options.out = path.resolve(this.options.out || path.join(this.options.directory, 'build')) + this.log = new CMLog(this.options) + this.options.isNodeApi = isNodeApi(this.log, this.options.directory) + const appConfig = appCMakeJSConfig(this.options.directory, this.log) + const npmOptions = npmConfig(this.log) + + if (npmOptions && typeof npmOptions === 'object' && Object.keys(npmOptions).length) { + this.options.runtimeDirectory = npmOptions['nodedir'] + this.options.msvsVersion = npmOptions['msvs_version'] + } + if (appConfig && typeof appConfig === 'object' && Object.keys(appConfig).length) { + this.log.verbose('CFG', 'Applying CMake.js config from root package.json:') + this.log.verbose('CFG', JSON.stringify(appConfig)) + // Applying applications's config, if there is no explicit runtime related options specified + this.options.runtime = this.options.runtime || appConfig.runtime + this.options.runtimeVersion = this.options.runtimeVersion || appConfig.runtimeVersion + this.options.arch = this.options.arch || appConfig.arch + } + + this.log.verbose('CFG', 'Build system options:') + this.log.verbose('CFG', JSON.stringify(this.options)) + this.cmake = new CMake(this.options) + this.dist = new Dist(this.options) + this.toolset = new Toolset(this.options) + } + async _ensureInstalled() { + try { + await this.toolset.initialize(true) + if (!this.options.isNodeApi) { + await this.dist.ensureDownloaded() + } + } catch (e) { + this._showError(e) + throw e + } + } + _showError(e) { + if (this.log === undefined) { + // handle internal errors (init failed) + console.error('OMG', e.stack) + return + } + if (this.log.level === 'verbose' || this.log.level === 'silly') { + this.log.error('OMG', e.stack) + } else { + this.log.error('OMG', e.message) + } + } + install() { + return this._ensureInstalled() + } + async _invokeCMake(method) { + try { + await this._ensureInstalled() + return await this.cmake[method]() + } catch (e) { + this._showError(e) + throw e + } + } + getConfigureCommand() { + return this._invokeCMake('getConfigureCommand') + } + getCmakeJsLibString() { + return this._invokeCMake('getCmakeJsLibString') + } + getCmakeJsIncludeString() { + return this._invokeCMake('getCmakeJsIncludeString') + } + getCmakeJsSrcString() { + return this._invokeCMake('getCmakeJsSrcString') + } + configure() { + return this._invokeCMake('configure') + } + getBuildCommand() { + return this._invokeCMake('getBuildCommand') + } + build() { + return this._invokeCMake('build') + } + getCleanCommand() { + return this._invokeCMake('getCleanCommand') + } + clean() { + return this._invokeCMake('clean') + } + reconfigure() { + return this._invokeCMake('reconfigure') + } + rebuild() { + return this._invokeCMake('rebuild') + } + compile() { + return this._invokeCMake('compile') + } +} + +module.exports = BuildSystem diff --git a/rewrite/old/cMake.js b/rewrite/old/cMake.js new file mode 100644 index 00000000..6ec0a79a --- /dev/null +++ b/rewrite/old/cMake.js @@ -0,0 +1,362 @@ +'use strict' +const which = require('which') +const fs = require('fs-extra') +const path = require('path') +const environment = require('./environment') +const Dist = require('./dist') +const CMLog = require('./cmLog') +const TargetOptions = require('./targetOptions') +const processHelpers = require('./processHelpers') +const locateNAN = require('./locateNAN') +const locateNodeApi = require('./locateNodeApi') +const npmConfigData = require('rc')('npm') +const Toolset = require('./toolset') +const headers = require('node-api-headers') + +class CMake { + get path() { + return this.options.cmakePath || 'cmake' + } + get isAvailable() { + if (this._isAvailable === null) { + this._isAvailable = CMake.isAvailable(this.options) + } + return this._isAvailable + } + + constructor(options) { + this.options = options || {} + this.log = new CMLog(this.options) + this.dist = new Dist(this.options) + this.projectRoot = path.resolve(this.options.directory || process.cwd()) + this.workDir = path.resolve(this.options.out || path.join(this.projectRoot, 'build')) + this.config = this.options.config || (this.options.debug ? 'Debug' : 'Release') + this.buildDir = path.join(this.workDir, this.config) + this._isAvailable = null + this.targetOptions = new TargetOptions(this.options) + this.toolset = new Toolset(this.options) + this.cMakeOptions = this.options.cMakeOptions || {} + this.extraCMakeArgs = this.options.extraCMakeArgs || [] + this.silent = !!options.silent + } + static isAvailable(options) { + options = options || {} + try { + if (options.cmakePath) { + const stat = fs.lstatSync(options.cmakePath) + return !stat.isDirectory() + } else { + which.sync('cmake') + return true + } + } catch (e) { + // Ignore + } + return false + } + static async getGenerators(options, log) { + const arch = ' [arch]' + options = options || {} + const gens = [] + if (CMake.isAvailable(options)) { + // try parsing machine-readable capabilities (available since CMake 3.7) + try { + const stdout = await processHelpers.execFile([options.cmakePath || 'cmake', '-E', 'capabilities']) + const capabilities = JSON.parse(stdout) + return capabilities.generators.map((x) => x.name) + } catch (error) { + if (log) { + log.verbose('TOOL', 'Failed to query CMake capabilities (CMake is probably older than 3.7)') + } + } + + // fall back to parsing help text + const stdout = await processHelpers.execFile([options.cmakePath || 'cmake', '--help']) + const hasCr = stdout.includes('\r\n') + const output = hasCr ? stdout.split('\r\n') : stdout.split('\n') + let on = false + output.forEach(function (line, i) { + if (on) { + const parts = line.split('=') + if ( + (parts.length === 2 && parts[0].trim()) || + (parts.length === 1 && i !== output.length - 1 && output[i + 1].trim()[0] === '=') + ) { + let gen = parts[0].trim() + if (gen.endsWith(arch)) { + gen = gen.substr(0, gen.length - arch.length) + } + gens.push(gen) + } + } + if (line.trim() === 'Generators') { + on = true + } + }) + } else { + throw new Error('CMake is not installed. Install CMake.') + } + return gens + } + verifyIfAvailable() { + if (!this.isAvailable) { + throw new Error( + "CMake executable is not found. Please use your system's package manager to install it, or you can get installers from there: http://cmake.org.", + ) + } + } + async getConfigureCommand() { + // Create command: + let command = [this.path, this.projectRoot, '--no-warn-unused-cli'] + + const D = [] + + // CMake.js watermark + D.push({ CMAKE_JS_VERSION: environment.cmakeJsVersion }) + + // Build configuration: + D.push({ CMAKE_BUILD_TYPE: this.config }) + if (environment.isWin) { + D.push({ CMAKE_RUNTIME_OUTPUT_DIRECTORY: this.workDir }) + } else if (this.workDir.endsWith(this.config)) { + D.push({ CMAKE_LIBRARY_OUTPUT_DIRECTORY: this.workDir }) + } else { + D.push({ CMAKE_LIBRARY_OUTPUT_DIRECTORY: this.buildDir }) + } + + // In some configurations MD builds will crash upon attempting to free memory. + // This tries to encourage MT builds which are larger but less likely to have this crash. + D.push({ CMAKE_MSVC_RUNTIME_LIBRARY: 'MultiThreaded$<$:Debug>' }) + + // Includes: + const includesString = await this.getCmakeJsIncludeString() + D.push({ CMAKE_JS_INC: includesString }) + + // Sources: + const srcsString = this.getCmakeJsSrcString() + D.push({ CMAKE_JS_SRC: srcsString }) + + // Runtime: + D.push({ NODE_RUNTIME: this.targetOptions.runtime }) + D.push({ NODE_RUNTIMEVERSION: this.targetOptions.runtimeVersion }) + D.push({ NODE_ARCH: this.targetOptions.arch }) + + if (environment.isOSX) { + if (this.targetOptions.arch) { + let xcodeArch = this.targetOptions.arch + if (xcodeArch === 'x64') xcodeArch = 'x86_64' + D.push({ CMAKE_OSX_ARCHITECTURES: xcodeArch }) + } + } + + // Custom options + for (const [key, value] of Object.entries(this.cMakeOptions)) { + D.push({ [key]: value }) + } + + // Toolset: + await this.toolset.initialize(false) + + const libsString = this.getCmakeJsLibString() + D.push({ CMAKE_JS_LIB: libsString }) + + if (environment.isWin) { + const nodeLibDefPath = this.getNodeLibDefPath() + if (nodeLibDefPath) { + const nodeLibPath = path.join(this.workDir, 'node.lib') + D.push({ CMAKE_JS_NODELIB_DEF: nodeLibDefPath }) + D.push({ CMAKE_JS_NODELIB_TARGET: nodeLibPath }) + } + } + + if (this.toolset.generator) { + command.push('-G', this.toolset.generator) + } + if (this.toolset.platform) { + command.push('-A', this.toolset.platform) + } + if (this.toolset.toolset) { + command.push('-T', this.toolset.toolset) + } + if (this.toolset.cppCompilerPath) { + D.push({ CMAKE_CXX_COMPILER: this.toolset.cppCompilerPath }) + } + if (this.toolset.cCompilerPath) { + D.push({ CMAKE_C_COMPILER: this.toolset.cCompilerPath }) + } + if (this.toolset.compilerFlags.length) { + D.push({ CMAKE_CXX_FLAGS: this.toolset.compilerFlags.join(' ') }) + } + if (this.toolset.linkerFlags.length) { + D.push({ CMAKE_SHARED_LINKER_FLAGS: this.toolset.linkerFlags.join(' ') }) + } + if (this.toolset.makePath) { + D.push({ CMAKE_MAKE_PROGRAM: this.toolset.makePath }) + } + + // Load NPM config + for (const [key, value] of Object.entries(npmConfigData)) { + if (key.startsWith('cmake_')) { + const sk = key.substr(6) + if (sk && value) { + D.push({ [sk]: value }) + } + } + } + + command = command.concat( + D.map(function (p) { + return '-D' + Object.keys(p)[0] + '=' + Object.values(p)[0] + }), + ) + + return command.concat(this.extraCMakeArgs) + } + getCmakeJsLibString() { + const libs = [] + if (environment.isWin) { + const nodeLibDefPath = this.getNodeLibDefPath() + if (nodeLibDefPath) { + libs.push(path.join(this.workDir, 'node.lib')) + } else { + libs.push(...this.dist.winLibs) + } + } + return libs.join(';') + } + async getCmakeJsIncludeString() { + let incPaths = [] + if (!this.options.isNodeApi) { + // Include and lib: + if (this.dist.headerOnly) { + incPaths = [path.join(this.dist.internalPath, '/include/node')] + } else { + const nodeH = path.join(this.dist.internalPath, '/src') + const v8H = path.join(this.dist.internalPath, '/deps/v8/include') + const uvH = path.join(this.dist.internalPath, '/deps/uv/include') + incPaths = [nodeH, v8H, uvH] + } + + // NAN + const nanH = await locateNAN(this.projectRoot) + if (nanH) { + incPaths.push(nanH) + } + } else { + // Base headers + const apiHeaders = require('node-api-headers') + incPaths.push(apiHeaders.include_dir) + + // Node-api + const napiH = await locateNodeApi(this.projectRoot) + if (napiH) { + incPaths.push(napiH) + } + } + + return incPaths.join(';') + } + getCmakeJsSrcString() { + const srcPaths = [] + if (environment.isWin) { + const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')) + + srcPaths.push(delayHook.replace(/\\/gm, '/')) + } + + return srcPaths.join(';') + } + getNodeLibDefPath() { + return environment.isWin && this.options.isNodeApi ? headers.def_paths.node_api_def : undefined + } + async configure() { + this.verifyIfAvailable() + + this.log.info('CMD', 'CONFIGURE') + const listPath = path.join(this.projectRoot, 'CMakeLists.txt') + const command = await this.getConfigureCommand() + + try { + await fs.lstat(listPath) + } catch (e) { + throw new Error("'" + listPath + "' not found.") + } + + try { + await fs.ensureDir(this.workDir) + } catch (e) { + // Ignore + } + + const cwd = process.cwd() + process.chdir(this.workDir) + try { + await this._run(command) + } finally { + process.chdir(cwd) + } + } + async ensureConfigured() { + try { + await fs.lstat(path.join(this.workDir, 'CMakeCache.txt')) + } catch (e) { + await this.configure() + } + } + getBuildCommand() { + const command = [this.path, '--build', this.workDir, '--config', this.config] + if (this.options.target) { + command.push('--target', this.options.target) + } + if (this.options.parallel) { + command.push('--parallel', this.options.parallel) + } + return Promise.resolve(command.concat(this.extraCMakeArgs)) + } + async build() { + this.verifyIfAvailable() + + 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() + + this.log.info('CMD', 'CLEAN') + return this._run(this.getCleanCommand()) + } + async reconfigure() { + this.extraCMakeArgs = [] + await this.clean() + await this.configure() + } + async rebuild() { + this.extraCMakeArgs = [] + await this.clean() + await this.build() + } + async compile() { + this.extraCMakeArgs = [] + try { + await this.build() + } catch (e) { + this.log.info('REP', 'Build has been failed, trying to do a full rebuild.') + await this.rebuild() + } + } + _run(command) { + this.log.info('RUN', command) + return processHelpers.run(command, { silent: this.silent }) + } + + async getGenerators() { + return CMake.getGenerators(this.options, this.log) + } +} + +module.exports = CMake diff --git a/rewrite/old/cmLog.js b/rewrite/old/cmLog.js new file mode 100644 index 00000000..58fbdfa6 --- /dev/null +++ b/rewrite/old/cmLog.js @@ -0,0 +1,61 @@ +'use strict' +const log = require('npmlog') + +class CMLog { + get level() { + if (this.options.noLog) { + return 'silly' + } else { + return log.level + } + } + + constructor(options) { + this.options = options || {} + this.debug = require('debug')(this.options.logName || 'cmake-js') + } + silly(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.silly(cat, msg) + } + } + verbose(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.verbose(cat, msg) + } + } + info(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.info(cat, msg) + } + } + warn(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.warn(cat, msg) + } + } + http(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.http(cat, msg) + } + } + error(cat, msg) { + if (this.options.noLog) { + this.debug(cat + ': ' + msg) + } else { + log.error(cat, msg) + } + } +} + +module.exports = CMLog diff --git a/rewrite/old/dist.js b/rewrite/old/dist.js new file mode 100644 index 00000000..6bfbd320 --- /dev/null +++ b/rewrite/old/dist.js @@ -0,0 +1,176 @@ +'use strict' +const environment = require('./environment') +const path = require('path') +const urljoin = require('url-join') +const fs = require('fs-extra') +const CMLog = require('./cmLog') +const TargetOptions = require('./targetOptions') +const runtimePaths = require('./runtimePaths') +const Downloader = require('./downloader') +const os = require('os') + +function testSum(sums, sum, fPath) { + const serverSum = sums.find(function (s) { + return s.getPath === fPath + }) + if (serverSum && serverSum.sum === sum) { + return + } + throw new Error("SHA sum of file '" + fPath + "' mismatch!") +} + +class Dist { + get internalPath() { + const cacheDirectory = '.cmake-js' + const runtimeArchDirectory = this.targetOptions.runtime + '-' + this.targetOptions.arch + const runtimeVersionDirectory = 'v' + this.targetOptions.runtimeVersion + + return ( + this.options.runtimeDirectory || + path.join(os.homedir(), cacheDirectory, runtimeArchDirectory, runtimeVersionDirectory) + ) + } + get externalPath() { + return runtimePaths.get(this.targetOptions).externalPath + } + get downloaded() { + let headers = false + let libs = true + let stat = getStat(this.internalPath) + if (stat.isDirectory()) { + if (this.headerOnly) { + stat = getStat(path.join(this.internalPath, 'include/node/node.h')) + headers = stat.isFile() + } else { + stat = getStat(path.join(this.internalPath, 'src/node.h')) + if (stat.isFile()) { + stat = getStat(path.join(this.internalPath, 'deps/v8/include/v8.h')) + headers = stat.isFile() + } + } + if (environment.isWin) { + for (const libPath of this.winLibs) { + stat = getStat(libPath) + libs = libs && stat.isFile() + } + } + } + return headers && libs + + function getStat(path) { + try { + return fs.statSync(path) + } catch (e) { + return { + isFile: () => false, + isDirectory: () => false, + } + } + } + } + get winLibs() { + const libs = runtimePaths.get(this.targetOptions).winLibs + const result = [] + for (const lib of libs) { + result.push(path.join(this.internalPath, lib.dir, lib.name)) + } + return result + } + get headerOnly() { + return runtimePaths.get(this.targetOptions).headerOnly + } + + constructor(options) { + this.options = options || {} + this.log = new CMLog(this.options) + this.targetOptions = new TargetOptions(this.options) + this.downloader = new Downloader(this.options) + } + + async ensureDownloaded() { + if (!this.downloaded) { + await this.download() + } + } + async download() { + const log = this.log + log.info('DIST', 'Downloading distribution files to: ' + this.internalPath) + await fs.ensureDir(this.internalPath) + const sums = await this._downloadShaSums() + await Promise.all([this._downloadLibs(sums), this._downloadTar(sums)]) + } + async _downloadShaSums() { + if (this.targetOptions.runtime === 'node') { + const sumUrl = urljoin(this.externalPath, 'SHASUMS256.txt') + const log = this.log + log.http('DIST', '\t- ' + sumUrl) + return (await this.downloader.downloadString(sumUrl)) + .split('\n') + .map(function (line) { + const parts = line.split(/\s+/) + return { + getPath: parts[1], + sum: parts[0], + } + }) + .filter(function (i) { + return i.getPath && i.sum + }) + } else { + return null + } + } + async _downloadTar(sums) { + const log = this.log + const self = this + const tarLocalPath = runtimePaths.get(self.targetOptions).tarPath + const tarUrl = urljoin(self.externalPath, tarLocalPath) + log.http('DIST', '\t- ' + tarUrl) + + const sum = await this.downloader.downloadTgz(tarUrl, { + hash: sums ? 'sha256' : null, + cwd: self.internalPath, + strip: 1, + filter: function (entryPath) { + if (entryPath === self.internalPath) { + return true + } + const ext = path.extname(entryPath) + return ext && ext.toLowerCase() === '.h' + }, + }) + + if (sums) { + testSum(sums, sum, tarLocalPath) + } + } + async _downloadLibs(sums) { + const log = this.log + const self = this + if (!environment.isWin) { + return + } + + const paths = runtimePaths.get(self.targetOptions) + for (const dirs of paths.winLibs) { + const subDir = dirs.dir + const fn = dirs.name + const fPath = subDir ? urljoin(subDir, fn) : fn + const libUrl = urljoin(self.externalPath, fPath) + log.http('DIST', '\t- ' + libUrl) + + await fs.ensureDir(path.join(self.internalPath, subDir)) + + const sum = await this.downloader.downloadFile(libUrl, { + path: path.join(self.internalPath, fPath), + hash: sums ? 'sha256' : null, + }) + + if (sums) { + testSum(sums, sum, fPath) + } + } + } +} + +module.exports = Dist diff --git a/rewrite/old/downloader.js b/rewrite/old/downloader.js new file mode 100644 index 00000000..182789ec --- /dev/null +++ b/rewrite/old/downloader.js @@ -0,0 +1,92 @@ +'use strict' +const crypto = require('crypto') +const axios = require('axios') +const MemoryStream = require('memory-stream') +const zlib = require('zlib') +const tar = require('tar') +const fs = require('fs') +const CMLog = require('./cmLog') + +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 + } + + 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) + }) + + stream.once('error', function (err) { + reject(err) + }) + + stream.once('finish', function () { + resolve(shasum ? shasum.digest('hex') : undefined) + }) + }) + } + async downloadString(url) { + const result = new MemoryStream() + await this.downloadToStream(url, result) + return result.toString() + } + async downloadFile(url, options) { + if (typeof options === 'string') { + options.path = options + } + const result = fs.createWriteStream(options.path) + const sum = await this.downloadToStream(url, result, options.hash) + this.testSum(url, sum, options) + return sum + } + async downloadTgz(url, options) { + if (typeof options === 'string') { + options.cwd = options + } + const gunzip = zlib.createGunzip() + const extractor = tar.extract(options) + gunzip.pipe(extractor) + const sum = await this.downloadToStream(url, gunzip, options.hash) + this.testSum(url, sum, options) + return sum + } + testSum(url, sum, options) { + if (options.hash && sum && options.sum && options.sum !== sum) { + throw new Error(options.hash.toUpperCase() + " sum of download '" + url + "' mismatch!") + } + } +} + +module.exports = Downloader diff --git a/rewrite/old/environment.js b/rewrite/old/environment.js new file mode 100644 index 00000000..0cc7402b --- /dev/null +++ b/rewrite/old/environment.js @@ -0,0 +1,96 @@ +'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, +}) + +Object.defineProperties(environment, { + _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/rewrite/old/index.js b/rewrite/old/index.js new file mode 100644 index 00000000..3773db00 --- /dev/null +++ b/rewrite/old/index.js @@ -0,0 +1,12 @@ +'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'), +} diff --git a/rewrite/old/locateNAN.js b/rewrite/old/locateNAN.js new file mode 100644 index 00000000..c8d0b0bf --- /dev/null +++ b/rewrite/old/locateNAN.js @@ -0,0 +1,63 @@ +'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 + } +} + +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 + } + + 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 + } + + // 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) +} diff --git a/rewrite/old/locateNodeApi.js b/rewrite/old/locateNodeApi.js new file mode 100644 index 00000000..04c6f084 --- /dev/null +++ b/rewrite/old/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.replace(/"/g, '') + } catch (e) { + // It most likely wasn't found + return null + } +}) diff --git a/rewrite/old/npmConfig.js b/rewrite/old/npmConfig.js new file mode 100644 index 00000000..0fb6cf2d --- /dev/null +++ b/rewrite/old/npmConfig.js @@ -0,0 +1,31 @@ +'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) + + return npmOptions +} + +module.exports = function (log) { + 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.') + } + + return options +} diff --git a/rewrite/old/processHelpers.js b/rewrite/old/processHelpers.js new file mode 100644 index 00000000..1f237ad5 --- /dev/null +++ b/rewrite/old/processHelpers.js @@ -0,0 +1,53 @@ +'use strict' +const spawn = require('child_process').spawn +const execFile = require('child_process').execFile + +const processHelpers = { + 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) + } + }) + }) + }, +} + +module.exports = processHelpers diff --git a/rewrite/old/runtimePaths.js b/rewrite/old/runtimePaths.js new file mode 100644 index 00000000..c9680da8 --- /dev/null +++ b/rewrite/old/runtimePaths.js @@ -0,0 +1,95 @@ +'use strict' +const assert = require('assert') +const semver = require('semver') + +const NODE_MIRROR = process.env.NVM_NODEJS_ORG_MIRROR || 'https://nodejs.org/dist' +const ELECTRON_MIRROR = process.env.ELECTRON_MIRROR || 'https://artifacts.electronjs.org/headers/dist' + +const 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') + + const runtime = targetOptions.runtime + const func = runtimePaths[runtime] + let paths + if (typeof func === 'function') { + paths = func(targetOptions) + if (paths && typeof paths === 'object') { + return paths + } + } + throw new Error('Unknown runtime: ' + runtime) + }, +} + +module.exports = runtimePaths diff --git a/rewrite/old/targetOptions.js b/rewrite/old/targetOptions.js new file mode 100644 index 00000000..c61bf6fe --- /dev/null +++ b/rewrite/old/targetOptions.js @@ -0,0 +1,30 @@ +'use strict' + +const environment = require('./environment') + +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 + } + + constructor(options) { + this.options = options || {} + } +} + +module.exports = TargetOptions diff --git a/rewrite/old/toolset.js b/rewrite/old/toolset.js new file mode 100644 index 00000000..3d9c034d --- /dev/null +++ b/rewrite/old/toolset.js @@ -0,0 +1,224 @@ +'use strict' +const TargetOptions = require('./targetOptions') +const environment = require('./environment') +const assert = require('assert') +const CMLog = require('./cmLog') +const { findVisualStudio } = environment.isWin ? require('./import/find-visualstudio') : {} + +class Toolset { + constructor(options) { + this.options = options || {} + this.targetOptions = new TargetOptions(this.options) + this.generator = options.generator + this.toolset = options.toolset + this.platform = options.platform + this.target = options.target + this.cCompilerPath = options.cCompilerPath + this.cppCompilerPath = options.cppCompilerPath + this.compilerFlags = [] + this.linkerFlags = [] + this.makePath = null + this.log = new CMLog(this.options) + this._initialized = false + } + async initialize(install) { + if (!this._initialized) { + if (environment.isWin) { + await this.initializeWin(install) + } else { + this.initializePosix(install) + } + this._initialized = true + } + } + initializePosix(install) { + if (!this.cCompilerPath || !this.cppCompilerPath) { + // 1: Compiler + if (!environment.isGPPAvailable && !environment.isClangAvailable) { + if (environment.isOSX) { + throw new Error( + "C++ Compiler toolset is not available. Install Xcode Commandline Tools from Apple Dev Center, or install Clang with homebrew by invoking: 'brew install llvm --with-clang --with-asan'.", + ) + } else { + throw new Error( + "C++ Compiler toolset is not available. Install proper compiler toolset with your package manager, eg. 'sudo apt-get install g++'.", + ) + } + } + + if (this.options.preferClang && environment.isClangAvailable) { + if (install) { + this.log.info('TOOL', 'Using clang++ compiler, because preferClang option is set, and clang++ is available.') + } + this.cppCompilerPath = this.cppCompilerPath || 'clang++' + this.cCompilerPath = this.cCompilerPath || 'clang' + } else if (this.options.preferGnu && environment.isGPPAvailable) { + if (install) { + this.log.info('TOOL', 'Using g++ compiler, because preferGnu option is set, and g++ is available.') + } + this.cppCompilerPath = this.cppCompilerPath || 'g++' + this.cCompilerPath = this.cCompilerPath || 'gcc' + } + } + // if it's already set because of options... + if (this.generator) { + if (install) { + this.log.info('TOOL', 'Using ' + this.generator + ' generator, as specified from commandline.') + } + } + + // 2: Generator + else if (environment.isOSX) { + if (this.options.preferXcode) { + if (install) { + this.log.info('TOOL', 'Using Xcode generator, because preferXcode option is set.') + } + this.generator = 'Xcode' + } else if (this.options.preferMake && environment.isMakeAvailable) { + if (install) { + this.log.info( + 'TOOL', + 'Using Unix Makefiles generator, because preferMake option is set, and make is available.', + ) + } + this.generator = 'Unix Makefiles' + } else if (environment.isNinjaAvailable) { + if (install) { + this.log.info('TOOL', 'Using Ninja generator, because ninja is available.') + } + this.generator = 'Ninja' + } else { + if (install) { + this.log.info('TOOL', 'Using Unix Makefiles generator.') + } + this.generator = 'Unix Makefiles' + } + } else { + if (this.options.preferMake && environment.isMakeAvailable) { + if (install) { + this.log.info( + 'TOOL', + 'Using Unix Makefiles generator, because preferMake option is set, and make is available.', + ) + } + this.generator = 'Unix Makefiles' + } else if (environment.isNinjaAvailable) { + if (install) { + this.log.info('TOOL', 'Using Ninja generator, because ninja is available.') + } + this.generator = 'Ninja' + } else { + if (install) { + this.log.info('TOOL', 'Using Unix Makefiles generator.') + } + this.generator = 'Unix Makefiles' + } + } + + // 3: Flags + if (environment.isOSX) { + if (install) { + this.log.verbose('TOOL', 'Setting default OSX compiler flags.') + } + + this.compilerFlags.push('-D_DARWIN_USE_64_BIT_INODE=1') + this.compilerFlags.push('-D_LARGEFILE_SOURCE') + this.compilerFlags.push('-D_FILE_OFFSET_BITS=64') + this.linkerFlags.push('-undefined dynamic_lookup') + } + + this.compilerFlags.push('-DBUILDING_NODE_EXTENSION') + + // 4: Build target + if (this.options.target) { + this.log.info('TOOL', 'Building only the ' + this.options.target + ' target, as specified from the command line.') + } + } + async initializeWin(install) { + if (!this.generator) { + const foundVsInfo = await this._getTopSupportedVisualStudioGenerator() + if (foundVsInfo) { + if (install) { + this.log.info('TOOL', `Using ${foundVsInfo.generator} generator.`) + } + this.generator = foundVsInfo.generator + + const isAboveVS16 = foundVsInfo.versionMajor >= 16 + + // The CMake Visual Studio Generator does not support the Win64 or ARM suffix on + // the generator name. Instead the generator platform must be set explicitly via + // the platform parameter + if (!this.platform && isAboveVS16) { + switch (this.targetOptions.arch) { + case 'ia32': + case 'x86': + this.platform = 'Win32' + break + case 'x64': + this.platform = 'x64' + break + case 'arm': + this.platform = 'ARM' + break + case 'arm64': + this.platform = 'ARM64' + break + default: + this.log.warn('TOOL', 'Unknown NodeJS architecture: ' + this.targetOptions.arch) + break + } + } + } else { + throw new Error('There is no Visual C++ compiler installed. Install Visual C++ Build Toolset or Visual Studio.') + } + } else { + // if it's already set because of options... + if (install) { + this.log.info('TOOL', 'Using ' + this.options.generator + ' generator, as specified from commandline.') + } + } + + this.linkerFlags.push('/DELAYLOAD:NODE.EXE') + + if (this.targetOptions.isX86) { + if (install) { + this.log.verbose('TOOL', 'Setting SAFESEH:NO linker flag.') + } + this.linkerFlags.push('/SAFESEH:NO') + } + } + async _getTopSupportedVisualStudioGenerator() { + const CMake = require('./cMake') + assert(environment.isWin) + + const selectedVs = await findVisualStudio(environment.runtimeVersion, this.options.msvsVersion) + if (!selectedVs) return null + + const list = await CMake.getGenerators(this.options, this.log) + for (const gen of list) { + const found = gen.startsWith(`Visual Studio ${selectedVs.versionMajor}`) + if (!found) { + continue + } + + // unlike previous versions "Visual Studio 16 2019" and onwards don't end with arch name + const isAboveVS16 = selectedVs.versionMajor >= 16 + if (!isAboveVS16) { + const is64Bit = gen.endsWith('Win64') + if ((this.targetOptions.isX86 && is64Bit) || (this.targetOptions.isX64 && !is64Bit)) { + continue + } + } + + return { + ...selectedVs, + generator: gen, + } + } + + // Nothing matched + return null + } +} + +module.exports = Toolset From 889fe7643403d5c4c4cda2a8cbfba0d428fbfeac Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 23:05:25 +0000 Subject: [PATCH 4/8] wip: add typings for findVisualStudio helper --- lib/import/find-visualstudio.d.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 lib/import/find-visualstudio.d.ts diff --git a/lib/import/find-visualstudio.d.ts b/lib/import/find-visualstudio.d.ts new file mode 100644 index 00000000..48c95cba --- /dev/null +++ b/lib/import/find-visualstudio.d.ts @@ -0,0 +1,15 @@ +export interface FoundVisualStudio { + version: string + versionMajor: number + versionMinor: number + versionYear: number + + path: string + msBuild: string + toolset: string + sdk: string +} + +export class FindVisualStudio { + static findVisualStudio(nodeSemver: string, configMsvsVersion?: string): Promise +} From 2753026e84886fa31132eed802d3e2906c17d499 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 23:06:44 +0000 Subject: [PATCH 5/8] wip: tidy --- rewrite/old/appCMakeJSConfig.js | 58 --------------------- rewrite/old/buildSystem.js | 9 ---- rewrite/old/downloader.js | 92 --------------------------------- rewrite/old/index.js | 12 ----- rewrite/old/locateNAN.js | 63 ---------------------- rewrite/old/locateNodeApi.js | 18 ------- rewrite/old/processHelpers.js | 53 ------------------- rewrite/src/toolchain.ts | 10 ---- 8 files changed, 315 deletions(-) delete mode 100644 rewrite/old/appCMakeJSConfig.js delete mode 100644 rewrite/old/downloader.js delete mode 100644 rewrite/old/index.js delete mode 100644 rewrite/old/locateNAN.js delete mode 100644 rewrite/old/locateNodeApi.js delete mode 100644 rewrite/old/processHelpers.js diff --git a/rewrite/old/appCMakeJSConfig.js b/rewrite/old/appCMakeJSConfig.js deleted file mode 100644 index bee96119..00000000 --- a/rewrite/old/appCMakeJSConfig.js +++ /dev/null @@ -1,58 +0,0 @@ -'use strict' -const path = require('path') - -function getConfig(lookPath, log) { - const pjsonPath = path.join(lookPath, 'package.json') - log.silly('CFG', "Looking for package.json in: '" + pjsonPath + "'.") - try { - const json = require(pjsonPath) - log.silly('CFG', 'Loaded:\n' + JSON.stringify(json)) - if (json && json['cmake-js'] && typeof json['cmake-js'] === 'object') { - log.silly('CFG', 'Config found.') - return json['cmake-js'] - } else { - log.silly('CFG', 'Config not found.') - return null - } - } catch (e) { - log.silly('CFG', "'package.json' not found.") - return null - } -} - -module.exports = function (projectPath, log) { - log.verbose('CFG', "Looking for application level CMake.js config in '" + projectPath + '.') - let currPath = projectPath - let lastConfig = null - let currConfig - for (;;) { - currConfig = getConfig(currPath, log) - if (currConfig) { - lastConfig = currConfig - } - try { - log.silly('CFG', 'Looking for parent path.') - const lastPath = currPath - currPath = path.normalize(path.join(currPath, '..')) - if (lastPath === currPath) { - currPath = null // root - } - if (currPath) { - log.silly('CFG', "Parent path: '" + currPath + "'.") - } - } catch (e) { - log.silly('CFG', 'Exception:\n' + e.stack) - break - } - if (currPath === null) { - log.silly('CFG', "Parent path with package.json file doesn't exists. Done.") - break - } - } - if (lastConfig) { - log.verbose('CFG', 'Application level CMake.js config found:\n' + JSON.stringify(lastConfig)) - } else { - log.verbose('CFG', "Application level CMake.js config doesn't exists.") - } - return lastConfig -} diff --git a/rewrite/old/buildSystem.js b/rewrite/old/buildSystem.js index 63c93842..847dfd40 100644 --- a/rewrite/old/buildSystem.js +++ b/rewrite/old/buildSystem.js @@ -25,21 +25,12 @@ class BuildSystem { this.options.out = path.resolve(this.options.out || path.join(this.options.directory, 'build')) this.log = new CMLog(this.options) this.options.isNodeApi = isNodeApi(this.log, this.options.directory) - const appConfig = appCMakeJSConfig(this.options.directory, this.log) const npmOptions = npmConfig(this.log) if (npmOptions && typeof npmOptions === 'object' && Object.keys(npmOptions).length) { this.options.runtimeDirectory = npmOptions['nodedir'] this.options.msvsVersion = npmOptions['msvs_version'] } - if (appConfig && typeof appConfig === 'object' && Object.keys(appConfig).length) { - this.log.verbose('CFG', 'Applying CMake.js config from root package.json:') - this.log.verbose('CFG', JSON.stringify(appConfig)) - // Applying applications's config, if there is no explicit runtime related options specified - this.options.runtime = this.options.runtime || appConfig.runtime - this.options.runtimeVersion = this.options.runtimeVersion || appConfig.runtimeVersion - this.options.arch = this.options.arch || appConfig.arch - } this.log.verbose('CFG', 'Build system options:') this.log.verbose('CFG', JSON.stringify(this.options)) diff --git a/rewrite/old/downloader.js b/rewrite/old/downloader.js deleted file mode 100644 index 182789ec..00000000 --- a/rewrite/old/downloader.js +++ /dev/null @@ -1,92 +0,0 @@ -'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') - -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 - } - - 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) - }) - - stream.once('error', function (err) { - reject(err) - }) - - stream.once('finish', function () { - resolve(shasum ? shasum.digest('hex') : undefined) - }) - }) - } - async downloadString(url) { - const result = new MemoryStream() - await this.downloadToStream(url, result) - return result.toString() - } - async downloadFile(url, options) { - if (typeof options === 'string') { - options.path = options - } - const result = fs.createWriteStream(options.path) - const sum = await this.downloadToStream(url, result, options.hash) - this.testSum(url, sum, options) - return sum - } - async downloadTgz(url, options) { - if (typeof options === 'string') { - options.cwd = options - } - const gunzip = zlib.createGunzip() - const extractor = tar.extract(options) - gunzip.pipe(extractor) - const sum = await this.downloadToStream(url, gunzip, options.hash) - this.testSum(url, sum, options) - return sum - } - testSum(url, sum, options) { - if (options.hash && sum && options.sum && options.sum !== sum) { - throw new Error(options.hash.toUpperCase() + " sum of download '" + url + "' mismatch!") - } - } -} - -module.exports = Downloader diff --git a/rewrite/old/index.js b/rewrite/old/index.js deleted file mode 100644 index 3773db00..00000000 --- a/rewrite/old/index.js +++ /dev/null @@ -1,12 +0,0 @@ -'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'), -} diff --git a/rewrite/old/locateNAN.js b/rewrite/old/locateNAN.js deleted file mode 100644 index c8d0b0bf..00000000 --- a/rewrite/old/locateNAN.js +++ /dev/null @@ -1,63 +0,0 @@ -'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 - } -} - -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 - } - - 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 - } - - // 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) -} diff --git a/rewrite/old/locateNodeApi.js b/rewrite/old/locateNodeApi.js deleted file mode 100644 index 04c6f084..00000000 --- a/rewrite/old/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/rewrite/old/processHelpers.js b/rewrite/old/processHelpers.js deleted file mode 100644 index 1f237ad5..00000000 --- a/rewrite/old/processHelpers.js +++ /dev/null @@ -1,53 +0,0 @@ -'use strict' -const spawn = require('child_process').spawn -const execFile = require('child_process').execFile - -const processHelpers = { - 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) - } - }) - }) - }, -} - -module.exports = processHelpers diff --git a/rewrite/src/toolchain.ts b/rewrite/src/toolchain.ts index 725cd25d..ab424e7c 100644 --- a/rewrite/src/toolchain.ts +++ b/rewrite/src/toolchain.ts @@ -1,16 +1,6 @@ import fs from 'fs/promises' import which from 'which' -// export interface SelectedToolchain { -// cmakePath: string -// generatorName: string | null -// archName: string | null -// } - -// export async function findBestToolchain(): Promise { -// // TODO -// } - export async function findCmake(): Promise { const overridePath = process.env['CMAKE_JS_CMAKE_PATH'] if (overridePath) { From 55ea434d9aed16373eaeb113acf5ca5293fbe87c Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 22 Jan 2024 23:56:45 +0000 Subject: [PATCH 6/8] wip: fixes --- Lib.cmake | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Lib.cmake b/Lib.cmake index 4a44863a..44172f55 100644 --- a/Lib.cmake +++ b/Lib.cmake @@ -16,8 +16,10 @@ if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${CMAKE_BUILD_TYPE}") endif() +set(NODE_PATH "node") # TODO - allow setting externally + # Find versions info -execute_process(COMMAND "${CMAKE_CURRENT_LIST_DIR}/bin/cmake-js-versions" +execute_process(COMMAND ${NODE_PATH} "${CMAKE_CURRENT_LIST_DIR}/bin/cmake-js-versions" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE CMAKE_JS_VERSIONS ) @@ -33,6 +35,8 @@ set(NODE_RUNTIMEVERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "NODE_ARCH ([0-9a-zA-Z\.]+)" _ ${CMAKE_JS_VERSIONS}) set(NODE_ARCH ${CMAKE_MATCH_1}) +set(CMAKE_JS_PATH ${CMAKE_CURRENT_LIST_DIR}) # cache value of CMAKE_CURRENT_LIST_DIR, as it changes inside function calls + # cmake-js version of CMake `add_library` specifically for node addons FUNCTION (cmake_js_add_node_addon PROJECT_NAME) cmake_parse_arguments( @@ -46,7 +50,7 @@ FUNCTION (cmake_js_add_node_addon PROJECT_NAME) # windows delay hook set(CMAKE_JS_SRC "") if (MSVC) - list (append CMAKE_JS_SRC "${CMAKE_CURRENT_LIST_DIR}/cpp/win_delay_load_hook.cc") + list (APPEND CMAKE_JS_SRC "${CMAKE_JS_PATH}/lib/cpp/win_delay_load_hook.cc") endif() # Setup the library and some default config @@ -55,7 +59,7 @@ FUNCTION (cmake_js_add_node_addon PROJECT_NAME) if (OLD_ADDON_API) # # Try finding nan - # execute_process(COMMAND node -p "require('nan').include" + # execute_process(COMMAND ${NODE_PATH} -p "require('nan').include" # WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} # OUTPUT_VARIABLE NODE_NAN_DIR # OUTPUT_STRIP_TRAILING_WHITESPACE @@ -69,7 +73,7 @@ FUNCTION (cmake_js_add_node_addon PROJECT_NAME) # TODO nan and headers else() # Find node-addon-api - execute_process(COMMAND node -p "require('node-api-headers').include_dir" + execute_process(COMMAND ${NODE_PATH} -p "require('node-api-headers').include_dir" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE NODE_API_HEADERS_DIR OUTPUT_STRIP_TRAILING_WHITESPACE @@ -82,7 +86,7 @@ FUNCTION (cmake_js_add_node_addon PROJECT_NAME) endif() # Try finding node-addon-api - execute_process(COMMAND node -p "require('node-addon-api').include" + execute_process(COMMAND ${NODE_PATH} -p "require('node-addon-api').include" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE NODE_ADDON_API_DIR OUTPUT_STRIP_TRAILING_WHITESPACE From 38ba6c356d53317c91191d793cec83a0c0c277c3 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 23 Jan 2024 00:26:32 +0000 Subject: [PATCH 7/8] wip: windows --- Lib.cmake | 34 +++++++++++++++++++++++------- rewrite/old/buildSystem.js | 6 ------ rewrite/old/cMake.js | 42 ++------------------------------------ rewrite/old/toolset.js | 10 --------- 4 files changed, 29 insertions(+), 63 deletions(-) diff --git a/Lib.cmake b/Lib.cmake index 44172f55..a8087fcf 100644 --- a/Lib.cmake +++ b/Lib.cmake @@ -27,7 +27,7 @@ if (NOT DEFINED CMAKE_JS_VERSIONS OR "${CMAKE_JS_VERSIONS}" STREQUAL "") message(FATAL_ERROR "Failed to find cmake-js and nodejs versions!") endif() string(REGEX MATCH "CMAKEJS_VERSION ([0-9a-zA-Z\.]+)" _ ${CMAKE_JS_VERSIONS}) -set(CMAKEJS_VERSION ${CMAKE_MATCH_1}) +set(CMAKE_JS_VERSION ${CMAKE_MATCH_1}) string(REGEX MATCH "NODE_RUNTIME ([0-9a-zA-Z\.]+)" _ ${CMAKE_JS_VERSIONS}) set(NODE_RUNTIME ${CMAKE_MATCH_1}) string(REGEX MATCH "NODE_RUNTIMEVERSION ([0-9a-zA-Z\.]+)" _ ${CMAKE_JS_VERSIONS}) @@ -98,12 +98,32 @@ FUNCTION (cmake_js_add_node_addon PROJECT_NAME) endif() # Generate node.lib if needed - set(CMAKE_JS_NODELIB_DEF "") # TODO - if(MSVC AND CMAKE_JS_NODELIB_DEF) - # Generate node.lib - set(CMAKE_JS_NODELIB_TARGET "${CMAKE_BINARY_DIR}/node.lib") - execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) - target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_NODELIB_TARGET}) + if(MSVC) + # Find node-addon-api + execute_process(COMMAND ${NODE_PATH} -p "require('node-api-headers').def_paths.node_api_def" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE NODE_API_DEF_PATH + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if (DEFINED NODE_API_DEF_PATH AND NOT "${NODE_API_DEF_PATH}" STREQUAL "") + # Generate node.lib + set(CMAKE_JS_NODELIB_TARGET "${CMAKE_BINARY_DIR}/node.lib") + execute_process(COMMAND ${CMAKE_AR} /def:${NODE_API_DEF_PATH} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_JS_NODELIB_TARGET}) + else() + message(FATAL_ERROR "Failed to find node-api-headers node_api_def!") + endif() + + endif() + endif() + + if (MSVC) + # setup delayload + target_link_options(${PROJECT_NAME} PRIVATE "/DELAYLOAD:NODE.EXE") + target_link_libraries(${PROJECT_NAME} PRIVATE delayimp) + + if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)") + target_link_options(${PROJECT_NAME} PUBLIC "/SAFESEH:NO") endif() endif() diff --git a/rewrite/old/buildSystem.js b/rewrite/old/buildSystem.js index 847dfd40..1e0e61cc 100644 --- a/rewrite/old/buildSystem.js +++ b/rewrite/old/buildSystem.js @@ -79,12 +79,6 @@ class BuildSystem { getCmakeJsLibString() { return this._invokeCMake('getCmakeJsLibString') } - getCmakeJsIncludeString() { - return this._invokeCMake('getCmakeJsIncludeString') - } - getCmakeJsSrcString() { - return this._invokeCMake('getCmakeJsSrcString') - } configure() { return this._invokeCMake('configure') } diff --git a/rewrite/old/cMake.js b/rewrite/old/cMake.js index 6ec0a79a..5d6851e9 100644 --- a/rewrite/old/cMake.js +++ b/rewrite/old/cMake.js @@ -8,10 +8,8 @@ const CMLog = require('./cmLog') const TargetOptions = require('./targetOptions') const processHelpers = require('./processHelpers') const locateNAN = require('./locateNAN') -const locateNodeApi = require('./locateNodeApi') const npmConfigData = require('rc')('npm') const Toolset = require('./toolset') -const headers = require('node-api-headers') class CMake { get path() { @@ -111,8 +109,6 @@ class CMake { const D = [] - // CMake.js watermark - D.push({ CMAKE_JS_VERSION: environment.cmakeJsVersion }) // Build configuration: D.push({ CMAKE_BUILD_TYPE: this.config }) @@ -132,10 +128,6 @@ class CMake { 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 }) @@ -160,15 +152,6 @@ class CMake { 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) } @@ -217,7 +200,6 @@ class CMake { if (environment.isWin) { const nodeLibDefPath = this.getNodeLibDefPath() if (nodeLibDefPath) { - libs.push(path.join(this.workDir, 'node.lib')) } else { libs.push(...this.dist.winLibs) } @@ -243,32 +225,12 @@ class CMake { incPaths.push(nanH) } } else { - // Base headers - const apiHeaders = require('node-api-headers') - incPaths.push(apiHeaders.include_dir) - - // Node-api - const napiH = await locateNodeApi(this.projectRoot) - if (napiH) { - incPaths.push(napiH) - } + } return incPaths.join(';') } - getCmakeJsSrcString() { - const srcPaths = [] - if (environment.isWin) { - const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc')) - - srcPaths.push(delayHook.replace(/\\/gm, '/')) - } - - return srcPaths.join(';') - } - getNodeLibDefPath() { - return environment.isWin && this.options.isNodeApi ? headers.def_paths.node_api_def : undefined - } + async configure() { this.verifyIfAvailable() diff --git a/rewrite/old/toolset.js b/rewrite/old/toolset.js index 3d9c034d..636da60e 100644 --- a/rewrite/old/toolset.js +++ b/rewrite/old/toolset.js @@ -127,8 +127,6 @@ class Toolset { 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.') @@ -178,14 +176,6 @@ class Toolset { } } - this.linkerFlags.push('/DELAYLOAD:NODE.EXE') - - if (this.targetOptions.isX86) { - if (install) { - this.log.verbose('TOOL', 'Setting SAFESEH:NO linker flag.') - } - this.linkerFlags.push('/SAFESEH:NO') - } } async _getTopSupportedVisualStudioGenerator() { const CMake = require('./cMake') From 1f8ada33cc41d4bd1d43f958d8cd2197752a8661 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 23 Jan 2024 00:45:25 +0000 Subject: [PATCH 8/8] wip --- Lib.cmake | 14 +++++++++++++- rewrite/old/buildSystem.js | 12 ------------ rewrite/old/cMake.js | 32 -------------------------------- rewrite/old/runtimePaths.js | 12 ++++++------ 4 files changed, 19 insertions(+), 51 deletions(-) diff --git a/Lib.cmake b/Lib.cmake index a8087fcf..7965d4e6 100644 --- a/Lib.cmake +++ b/Lib.cmake @@ -69,8 +69,20 @@ FUNCTION (cmake_js_add_node_addon PROJECT_NAME) # message("Found nan: ${NODE_NAN_DIR}") # target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_NAN_DIR}) # endif() - + # TODO nan and headers + + # include headers + set (NODE_HEADERS_DIR "") # TODO + target_include_directories(${PROJECT_NAME} PRIVATE + # some runtimes provide tidy headers + "${NODE_HEADERS_DIR}/include/node" + # some runtimes provide a lot more stuff + "${NODE_HEADERS_DIR}/src" + "${NODE_HEADERS_DIR}/deps/v8/include" + "${NODE_HEADERS_DIR}/deps/uv/include" + ) + else() # Find node-addon-api execute_process(COMMAND ${NODE_PATH} -p "require('node-api-headers').include_dir" diff --git a/rewrite/old/buildSystem.js b/rewrite/old/buildSystem.js index 1e0e61cc..eaf19156 100644 --- a/rewrite/old/buildSystem.js +++ b/rewrite/old/buildSystem.js @@ -7,24 +7,12 @@ const npmConfig = require('./npmConfig') const path = require('path') const Toolset = require('./toolset') -function isNodeApi(log, projectRoot) { - try { - const projectPkgJson = require(path.join(projectRoot, 'package.json')) - // Make sure the property exists - return !!projectPkgJson?.binary?.napi_versions - } catch (e) { - log.silly('CFG', "'package.json' not found.") - return false - } -} - 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 npmOptions = npmConfig(this.log) if (npmOptions && typeof npmOptions === 'object' && Object.keys(npmOptions).length) { diff --git a/rewrite/old/cMake.js b/rewrite/old/cMake.js index 5d6851e9..c289d1b3 100644 --- a/rewrite/old/cMake.js +++ b/rewrite/old/cMake.js @@ -120,14 +120,6 @@ class CMake { 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 }) - // Runtime: D.push({ NODE_RUNTIME: this.targetOptions.runtime }) D.push({ NODE_RUNTIMEVERSION: this.targetOptions.runtimeVersion }) @@ -206,30 +198,6 @@ class CMake { } return libs.join(';') } - async getCmakeJsIncludeString() { - let incPaths = [] - if (!this.options.isNodeApi) { - // Include and lib: - if (this.dist.headerOnly) { - incPaths = [path.join(this.dist.internalPath, '/include/node')] - } else { - const nodeH = path.join(this.dist.internalPath, '/src') - const v8H = path.join(this.dist.internalPath, '/deps/v8/include') - const uvH = path.join(this.dist.internalPath, '/deps/uv/include') - incPaths = [nodeH, v8H, uvH] - } - - // NAN - const nanH = await locateNAN(this.projectRoot) - if (nanH) { - incPaths.push(nanH) - } - } else { - - } - - return incPaths.join(';') - } async configure() { this.verifyIfAvailable() diff --git a/rewrite/old/runtimePaths.js b/rewrite/old/runtimePaths.js index c9680da8..35fb2a17 100644 --- a/rewrite/old/runtimePaths.js +++ b/rewrite/old/runtimePaths.js @@ -13,10 +13,10 @@ const runtimePaths = { winLibs: [ { dir: targetOptions.isX64 ? 'x64' : '', - name: targetOptions.runtime + '.lib', + name: 'node.lib', }, ], - tarPath: targetOptions.runtime + '-v' + targetOptions.runtimeVersion + '.tar.gz', + tarPath: 'node-v' + targetOptions.runtimeVersion + '.tar.gz', headerOnly: false, } } else { @@ -25,10 +25,10 @@ const runtimePaths = { winLibs: [ { dir: targetOptions.isX64 ? 'win-x64' : 'win-x86', - name: targetOptions.runtime + '.lib', + name: 'node.lib', }, ], - tarPath: targetOptions.runtime + '-v' + targetOptions.runtimeVersion + '-headers.tar.gz', + tarPath: 'node-v' + targetOptions.runtimeVersion + '-headers.tar.gz', headerOnly: true, } } @@ -40,7 +40,7 @@ const runtimePaths = { winLibs: [ { dir: targetOptions.isX64 ? 'x64' : '', - name: targetOptions.runtime + '.lib', + name: 'nw.lib', }, { dir: targetOptions.isX64 ? 'x64' : '', @@ -56,7 +56,7 @@ const runtimePaths = { winLibs: [ { dir: targetOptions.isX64 ? 'x64' : '', - name: targetOptions.runtime + '.lib', + name: 'nw.lib', }, ], tarPath: 'nw-headers-v' + targetOptions.runtimeVersion + '.tar.gz',