@dry-software/cmake-js 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,123 @@
1
+ 'use strict'
2
+ const CMake = require('./cMake')
3
+ const Dist = require('./dist')
4
+ const CMLog = require('./cmLog')
5
+ const appCMakeJSConfig = require('./appCMakeJSConfig')
6
+ const npmConfig = require('./npmConfig')
7
+ const path = require('path')
8
+ const Toolset = require('./toolset')
9
+
10
+ function isNodeApi(log, projectRoot) {
11
+ try {
12
+ const projectPkgJson = require(path.join(projectRoot, 'package.json'))
13
+ // Make sure the property exists
14
+ return !!projectPkgJson?.binary?.napi_versions
15
+ } catch (e) {
16
+ log.silly('CFG', "'package.json' not found.")
17
+ return false
18
+ }
19
+ }
20
+
21
+ class BuildSystem {
22
+ constructor(options) {
23
+ this.options = options || {}
24
+ this.options.directory = path.resolve(this.options.directory || process.cwd())
25
+ this.options.out = path.resolve(this.options.out || path.join(this.options.directory, 'build'))
26
+ this.log = new CMLog(this.options)
27
+ this.options.isNodeApi = isNodeApi(this.log, this.options.directory)
28
+ const appConfig = appCMakeJSConfig(this.options.directory, this.log)
29
+ const npmOptions = npmConfig(this.log)
30
+
31
+ if (npmOptions && typeof npmOptions === 'object' && Object.keys(npmOptions).length) {
32
+ this.options.runtimeDirectory = npmOptions['nodedir']
33
+ this.options.msvsVersion = npmOptions['msvs_version']
34
+ }
35
+ if (appConfig && typeof appConfig === 'object' && Object.keys(appConfig).length) {
36
+ this.log.verbose('CFG', 'Applying CMake.js config from root package.json:')
37
+ this.log.verbose('CFG', JSON.stringify(appConfig))
38
+ // Applying applications's config, if there is no explicit runtime related options specified
39
+ this.options.runtime = this.options.runtime || appConfig.runtime
40
+ this.options.runtimeVersion = this.options.runtimeVersion || appConfig.runtimeVersion
41
+ this.options.arch = this.options.arch || appConfig.arch
42
+ }
43
+
44
+ this.log.verbose('CFG', 'Build system options:')
45
+ this.log.verbose('CFG', JSON.stringify(this.options))
46
+ this.cmake = new CMake(this.options)
47
+ this.dist = new Dist(this.options)
48
+ this.toolset = new Toolset(this.options)
49
+ }
50
+ async _ensureInstalled() {
51
+ try {
52
+ await this.toolset.initialize(true)
53
+ if (!this.options.isNodeApi) {
54
+ await this.dist.ensureDownloaded()
55
+ }
56
+ } catch (e) {
57
+ this._showError(e)
58
+ throw e
59
+ }
60
+ }
61
+ _showError(e) {
62
+ if (this.log === undefined) {
63
+ // handle internal errors (init failed)
64
+ console.error('OMG', e.stack)
65
+ return
66
+ }
67
+ if (this.log.level === 'verbose' || this.log.level === 'silly') {
68
+ this.log.error('OMG', e.stack)
69
+ } else {
70
+ this.log.error('OMG', e.message)
71
+ }
72
+ }
73
+ install() {
74
+ return this._ensureInstalled()
75
+ }
76
+ async _invokeCMake(method) {
77
+ try {
78
+ await this._ensureInstalled()
79
+ return await this.cmake[method]()
80
+ } catch (e) {
81
+ this._showError(e)
82
+ throw e
83
+ }
84
+ }
85
+ getConfigureCommand() {
86
+ return this._invokeCMake('getConfigureCommand')
87
+ }
88
+ getCmakeJsLibString() {
89
+ return this._invokeCMake('getCmakeJsLibString')
90
+ }
91
+ getCmakeJsIncludeString() {
92
+ return this._invokeCMake('getCmakeJsIncludeString')
93
+ }
94
+ getCmakeJsSrcString() {
95
+ return this._invokeCMake('getCmakeJsSrcString')
96
+ }
97
+ configure() {
98
+ return this._invokeCMake('configure')
99
+ }
100
+ getBuildCommand() {
101
+ return this._invokeCMake('getBuildCommand')
102
+ }
103
+ build() {
104
+ return this._invokeCMake('build')
105
+ }
106
+ getCleanCommand() {
107
+ return this._invokeCMake('getCleanCommand')
108
+ }
109
+ clean() {
110
+ return this._invokeCMake('clean')
111
+ }
112
+ reconfigure() {
113
+ return this._invokeCMake('reconfigure')
114
+ }
115
+ rebuild() {
116
+ return this._invokeCMake('rebuild')
117
+ }
118
+ compile() {
119
+ return this._invokeCMake('compile')
120
+ }
121
+ }
122
+
123
+ module.exports = BuildSystem
package/lib/cMake.js ADDED
@@ -0,0 +1,362 @@
1
+ 'use strict'
2
+ const which = require('which')
3
+ const fs = require('fs-extra')
4
+ const path = require('path')
5
+ const environment = require('./environment')
6
+ const Dist = require('./dist')
7
+ const CMLog = require('./cmLog')
8
+ const TargetOptions = require('./targetOptions')
9
+ const processHelpers = require('./processHelpers')
10
+ const locateNAN = require('./locateNAN')
11
+ const locateNodeApi = require('./locateNodeApi')
12
+ const npmConfigData = require('rc')('npm')
13
+ const Toolset = require('./toolset')
14
+ const headers = require('node-api-headers')
15
+
16
+ class CMake {
17
+ get path() {
18
+ return this.options.cmakePath || 'cmake'
19
+ }
20
+ get isAvailable() {
21
+ if (this._isAvailable === null) {
22
+ this._isAvailable = CMake.isAvailable(this.options)
23
+ }
24
+ return this._isAvailable
25
+ }
26
+
27
+ constructor(options) {
28
+ this.options = options || {}
29
+ this.log = new CMLog(this.options)
30
+ this.dist = new Dist(this.options)
31
+ this.projectRoot = path.resolve(this.options.directory || process.cwd())
32
+ this.workDir = path.resolve(this.options.out || path.join(this.projectRoot, 'build'))
33
+ this.config = this.options.config || (this.options.debug ? 'Debug' : 'Release')
34
+ this.buildDir = path.join(this.workDir, this.config)
35
+ this._isAvailable = null
36
+ this.targetOptions = new TargetOptions(this.options)
37
+ this.toolset = new Toolset(this.options)
38
+ this.cMakeOptions = this.options.cMakeOptions || {}
39
+ this.extraCMakeArgs = this.options.extraCMakeArgs || []
40
+ this.silent = !!options.silent
41
+ }
42
+ static isAvailable(options) {
43
+ options = options || {}
44
+ try {
45
+ if (options.cmakePath) {
46
+ const stat = fs.lstatSync(options.cmakePath)
47
+ return !stat.isDirectory()
48
+ } else {
49
+ which.sync('cmake')
50
+ return true
51
+ }
52
+ } catch (e) {
53
+ // Ignore
54
+ }
55
+ return false
56
+ }
57
+ static async getGenerators(options, log) {
58
+ const arch = ' [arch]'
59
+ options = options || {}
60
+ const gens = []
61
+ if (CMake.isAvailable(options)) {
62
+ // try parsing machine-readable capabilities (available since CMake 3.7)
63
+ try {
64
+ const stdout = await processHelpers.execFile([options.cmakePath || 'cmake', '-E', 'capabilities'])
65
+ const capabilities = JSON.parse(stdout)
66
+ return capabilities.generators.map((x) => x.name)
67
+ } catch (error) {
68
+ if (log) {
69
+ log.verbose('TOOL', 'Failed to query CMake capabilities (CMake is probably older than 3.7)')
70
+ }
71
+ }
72
+
73
+ // fall back to parsing help text
74
+ const stdout = await processHelpers.execFile([options.cmakePath || 'cmake', '--help'])
75
+ const hasCr = stdout.includes('\r\n')
76
+ const output = hasCr ? stdout.split('\r\n') : stdout.split('\n')
77
+ let on = false
78
+ output.forEach(function (line, i) {
79
+ if (on) {
80
+ const parts = line.split('=')
81
+ if (
82
+ (parts.length === 2 && parts[0].trim()) ||
83
+ (parts.length === 1 && i !== output.length - 1 && output[i + 1].trim()[0] === '=')
84
+ ) {
85
+ let gen = parts[0].trim()
86
+ if (gen.endsWith(arch)) {
87
+ gen = gen.substr(0, gen.length - arch.length)
88
+ }
89
+ gens.push(gen)
90
+ }
91
+ }
92
+ if (line.trim() === 'Generators') {
93
+ on = true
94
+ }
95
+ })
96
+ } else {
97
+ throw new Error('CMake is not installed. Install CMake.')
98
+ }
99
+ return gens
100
+ }
101
+ verifyIfAvailable() {
102
+ if (!this.isAvailable) {
103
+ throw new Error(
104
+ "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.",
105
+ )
106
+ }
107
+ }
108
+ async getConfigureCommand() {
109
+ // Create command:
110
+ let command = [this.path, this.projectRoot, '--no-warn-unused-cli']
111
+
112
+ const D = []
113
+
114
+ // CMake.js watermark
115
+ D.push({ CMAKE_JS_VERSION: environment.cmakeJsVersion })
116
+
117
+ // Build configuration:
118
+ D.push({ CMAKE_BUILD_TYPE: this.config })
119
+ if (environment.isWin) {
120
+ D.push({ CMAKE_RUNTIME_OUTPUT_DIRECTORY: this.workDir })
121
+ } else if (this.workDir.endsWith(this.config)) {
122
+ D.push({ CMAKE_LIBRARY_OUTPUT_DIRECTORY: this.workDir })
123
+ } else {
124
+ D.push({ CMAKE_LIBRARY_OUTPUT_DIRECTORY: this.buildDir })
125
+ }
126
+
127
+ // In some configurations MD builds will crash upon attempting to free memory.
128
+ // This tries to encourage MT builds which are larger but less likely to have this crash.
129
+ D.push({ CMAKE_MSVC_RUNTIME_LIBRARY: 'MultiThreaded$<$<CONFIG:Debug>:Debug>' })
130
+
131
+ // Includes:
132
+ const includesString = await this.getCmakeJsIncludeString()
133
+ D.push({ CMAKE_JS_INC: includesString })
134
+
135
+ // Sources:
136
+ const srcsString = this.getCmakeJsSrcString()
137
+ D.push({ CMAKE_JS_SRC: srcsString })
138
+
139
+ // Runtime:
140
+ D.push({ NODE_RUNTIME: this.targetOptions.runtime })
141
+ D.push({ NODE_RUNTIMEVERSION: this.targetOptions.runtimeVersion })
142
+ D.push({ NODE_ARCH: this.targetOptions.arch })
143
+
144
+ if (environment.isOSX) {
145
+ if (this.targetOptions.arch) {
146
+ let xcodeArch = this.targetOptions.arch
147
+ if (xcodeArch === 'x64') xcodeArch = 'x86_64'
148
+ D.push({ CMAKE_OSX_ARCHITECTURES: xcodeArch })
149
+ }
150
+ }
151
+
152
+ // Custom options
153
+ for (const [key, value] of Object.entries(this.cMakeOptions)) {
154
+ D.push({ [key]: value })
155
+ }
156
+
157
+ // Toolset:
158
+ await this.toolset.initialize(false)
159
+
160
+ const libsString = this.getCmakeJsLibString()
161
+ D.push({ CMAKE_JS_LIB: libsString })
162
+
163
+ if (environment.isWin) {
164
+ const nodeLibDefPath = this.getNodeLibDefPath()
165
+ if (nodeLibDefPath) {
166
+ const nodeLibPath = path.join(this.workDir, 'node.lib')
167
+ D.push({ CMAKE_JS_NODELIB_DEF: nodeLibDefPath })
168
+ D.push({ CMAKE_JS_NODELIB_TARGET: nodeLibPath })
169
+ }
170
+ }
171
+
172
+ if (this.toolset.generator) {
173
+ command.push('-G', this.toolset.generator)
174
+ }
175
+ if (this.toolset.platform) {
176
+ command.push('-A', this.toolset.platform)
177
+ }
178
+ if (this.toolset.toolset) {
179
+ command.push('-T', this.toolset.toolset)
180
+ }
181
+ if (this.toolset.cppCompilerPath) {
182
+ D.push({ CMAKE_CXX_COMPILER: this.toolset.cppCompilerPath })
183
+ }
184
+ if (this.toolset.cCompilerPath) {
185
+ D.push({ CMAKE_C_COMPILER: this.toolset.cCompilerPath })
186
+ }
187
+ if (this.toolset.compilerFlags.length) {
188
+ D.push({ CMAKE_CXX_FLAGS: this.toolset.compilerFlags.join(' ') })
189
+ }
190
+ if (this.toolset.linkerFlags.length) {
191
+ D.push({ CMAKE_SHARED_LINKER_FLAGS: this.toolset.linkerFlags.join(' ') })
192
+ }
193
+ if (this.toolset.makePath) {
194
+ D.push({ CMAKE_MAKE_PROGRAM: this.toolset.makePath })
195
+ }
196
+
197
+ // Load NPM config
198
+ for (const [key, value] of Object.entries(npmConfigData)) {
199
+ if (key.startsWith('cmake_')) {
200
+ const sk = key.substr(6)
201
+ if (sk && value) {
202
+ D.push({ [sk]: value })
203
+ }
204
+ }
205
+ }
206
+
207
+ command = command.concat(
208
+ D.map(function (p) {
209
+ return '-D' + Object.keys(p)[0] + '=' + Object.values(p)[0]
210
+ }),
211
+ )
212
+
213
+ return command.concat(this.extraCMakeArgs)
214
+ }
215
+ getCmakeJsLibString() {
216
+ const libs = []
217
+ if (environment.isWin) {
218
+ const nodeLibDefPath = this.getNodeLibDefPath()
219
+ if (nodeLibDefPath) {
220
+ libs.push(path.join(this.workDir, 'node.lib'))
221
+ } else {
222
+ libs.push(...this.dist.winLibs)
223
+ }
224
+ }
225
+ return libs.join(';')
226
+ }
227
+ async getCmakeJsIncludeString() {
228
+ let incPaths = []
229
+ if (!this.options.isNodeApi) {
230
+ // Include and lib:
231
+ if (this.dist.headerOnly) {
232
+ incPaths = [path.join(this.dist.internalPath, '/include/node')]
233
+ } else {
234
+ const nodeH = path.join(this.dist.internalPath, '/src')
235
+ const v8H = path.join(this.dist.internalPath, '/deps/v8/include')
236
+ const uvH = path.join(this.dist.internalPath, '/deps/uv/include')
237
+ incPaths = [nodeH, v8H, uvH]
238
+ }
239
+
240
+ // NAN
241
+ const nanH = await locateNAN(this.projectRoot)
242
+ if (nanH) {
243
+ incPaths.push(nanH)
244
+ }
245
+ } else {
246
+ // Base headers
247
+ const apiHeaders = require('node-api-headers')
248
+ incPaths.push(apiHeaders.include_dir)
249
+
250
+ // Node-api
251
+ const napiH = await locateNodeApi(this.projectRoot)
252
+ if (napiH) {
253
+ incPaths.push(napiH)
254
+ }
255
+ }
256
+
257
+ return incPaths.join(';')
258
+ }
259
+ getCmakeJsSrcString() {
260
+ const srcPaths = []
261
+ if (environment.isWin) {
262
+ const delayHook = path.normalize(path.join(__dirname, 'cpp', 'win_delay_load_hook.cc'))
263
+
264
+ srcPaths.push(delayHook.replace(/\\/gm, '/'))
265
+ }
266
+
267
+ return srcPaths.join(';')
268
+ }
269
+ getNodeLibDefPath() {
270
+ return environment.isWin && this.options.isNodeApi ? headers.def_paths.node_api_def : undefined
271
+ }
272
+ async configure() {
273
+ this.verifyIfAvailable()
274
+
275
+ this.log.info('CMD', 'CONFIGURE')
276
+ const listPath = path.join(this.projectRoot, 'CMakeLists.txt')
277
+ const command = await this.getConfigureCommand()
278
+
279
+ try {
280
+ await fs.lstat(listPath)
281
+ } catch (e) {
282
+ throw new Error("'" + listPath + "' not found.")
283
+ }
284
+
285
+ try {
286
+ await fs.ensureDir(this.workDir)
287
+ } catch (e) {
288
+ // Ignore
289
+ }
290
+
291
+ const cwd = process.cwd()
292
+ process.chdir(this.workDir)
293
+ try {
294
+ await this._run(command)
295
+ } finally {
296
+ process.chdir(cwd)
297
+ }
298
+ }
299
+ async ensureConfigured() {
300
+ try {
301
+ await fs.lstat(path.join(this.workDir, 'CMakeCache.txt'))
302
+ } catch (e) {
303
+ await this.configure()
304
+ }
305
+ }
306
+ getBuildCommand() {
307
+ const command = [this.path, '--build', this.workDir, '--config', this.config]
308
+ if (this.options.target) {
309
+ command.push('--target', this.options.target)
310
+ }
311
+ if (this.options.parallel) {
312
+ command.push('--parallel', this.options.parallel)
313
+ }
314
+ return Promise.resolve(command.concat(this.extraCMakeArgs))
315
+ }
316
+ async build() {
317
+ this.verifyIfAvailable()
318
+
319
+ await this.ensureConfigured()
320
+ const buildCommand = await this.getBuildCommand()
321
+ this.log.info('CMD', 'BUILD')
322
+ await this._run(buildCommand)
323
+ }
324
+ getCleanCommand() {
325
+ return [this.path, '-E', 'remove_directory', this.workDir].concat(this.extraCMakeArgs)
326
+ }
327
+ clean() {
328
+ this.verifyIfAvailable()
329
+
330
+ this.log.info('CMD', 'CLEAN')
331
+ return this._run(this.getCleanCommand())
332
+ }
333
+ async reconfigure() {
334
+ this.extraCMakeArgs = []
335
+ await this.clean()
336
+ await this.configure()
337
+ }
338
+ async rebuild() {
339
+ this.extraCMakeArgs = []
340
+ await this.clean()
341
+ await this.build()
342
+ }
343
+ async compile() {
344
+ this.extraCMakeArgs = []
345
+ try {
346
+ await this.build()
347
+ } catch (e) {
348
+ this.log.info('REP', 'Build has been failed, trying to do a full rebuild.')
349
+ await this.rebuild()
350
+ }
351
+ }
352
+ _run(command) {
353
+ this.log.info('RUN', command)
354
+ return processHelpers.run(command, { silent: this.silent })
355
+ }
356
+
357
+ async getGenerators() {
358
+ return CMake.getGenerators(this.options, this.log)
359
+ }
360
+ }
361
+
362
+ module.exports = CMake
package/lib/cmLog.js ADDED
@@ -0,0 +1,61 @@
1
+ 'use strict'
2
+ const log = require('npmlog')
3
+
4
+ class CMLog {
5
+ get level() {
6
+ if (this.options.noLog) {
7
+ return 'silly'
8
+ } else {
9
+ return log.level
10
+ }
11
+ }
12
+
13
+ constructor(options) {
14
+ this.options = options || {}
15
+ this.debug = require('debug')(this.options.logName || 'cmake-js')
16
+ }
17
+ silly(cat, msg) {
18
+ if (this.options.noLog) {
19
+ this.debug(cat + ': ' + msg)
20
+ } else {
21
+ log.silly(cat, msg)
22
+ }
23
+ }
24
+ verbose(cat, msg) {
25
+ if (this.options.noLog) {
26
+ this.debug(cat + ': ' + msg)
27
+ } else {
28
+ log.verbose(cat, msg)
29
+ }
30
+ }
31
+ info(cat, msg) {
32
+ if (this.options.noLog) {
33
+ this.debug(cat + ': ' + msg)
34
+ } else {
35
+ log.info(cat, msg)
36
+ }
37
+ }
38
+ warn(cat, msg) {
39
+ if (this.options.noLog) {
40
+ this.debug(cat + ': ' + msg)
41
+ } else {
42
+ log.warn(cat, msg)
43
+ }
44
+ }
45
+ http(cat, msg) {
46
+ if (this.options.noLog) {
47
+ this.debug(cat + ': ' + msg)
48
+ } else {
49
+ log.http(cat, msg)
50
+ }
51
+ }
52
+ error(cat, msg) {
53
+ if (this.options.noLog) {
54
+ this.debug(cat + ': ' + msg)
55
+ } else {
56
+ log.error(cat, msg)
57
+ }
58
+ }
59
+ }
60
+
61
+ module.exports = CMLog
@@ -0,0 +1,52 @@
1
+ /*
2
+ * When this file is linked to a DLL, it sets up a delay-load hook that
3
+ * intervenes when the DLL is trying to load 'node.exe' or 'iojs.exe'
4
+ * dynamically. Instead of trying to locate the .exe file it'll just return
5
+ * a handle to the process image.
6
+ *
7
+ * This allows compiled addons to work when node.exe or iojs.exe is renamed.
8
+ */
9
+
10
+ #ifdef _MSC_VER
11
+
12
+ #ifndef WIN32_LEAN_AND_MEAN
13
+ #define WIN32_LEAN_AND_MEAN
14
+ #endif
15
+
16
+ #include <windows.h>
17
+
18
+ #include <delayimp.h>
19
+ #include <string.h>
20
+
21
+ static HMODULE node_dll = NULL;
22
+ static HMODULE nw_dll = NULL;
23
+
24
+ static FARPROC WINAPI load_exe_hook(unsigned int event, DelayLoadInfo* info) {
25
+ if (event == dliNotePreGetProcAddress) {
26
+ FARPROC ret = NULL;
27
+ ret = GetProcAddress(node_dll, info->dlp.szProcName);
28
+ if (ret)
29
+ return ret;
30
+ ret = GetProcAddress(nw_dll, info->dlp.szProcName);
31
+ return ret;
32
+ }
33
+ if (event == dliStartProcessing) {
34
+ node_dll = GetModuleHandleA("node.dll");
35
+ nw_dll = GetModuleHandleA("nw.dll");
36
+ return NULL;
37
+ }
38
+ if (event != dliNotePreLoadLibrary)
39
+ return NULL;
40
+
41
+ if (_stricmp(info->szDll, "node.exe") != 0)
42
+ return NULL;
43
+
44
+ // Fall back to the current process
45
+ if(!node_dll) node_dll = GetModuleHandleA(NULL);
46
+
47
+ return (FARPROC) node_dll;
48
+ }
49
+
50
+ decltype(__pfnDliNotifyHook2) __pfnDliNotifyHook2 = load_exe_hook;
51
+
52
+ #endif