Skip to content

Commit d1b2c9c

Browse files
committed
feat: build with config.gypi from node headers
1 parent f2ad87f commit d1b2c9c

File tree

7 files changed

+112
-22
lines changed

7 files changed

+112
-22
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,22 @@ Python executable, it will be used instead of any of the other configured or
8585
builtin Python search paths. If it's not a compatible version, no further
8686
searching will be done.
8787

88+
### Build for Third Party Node.js Runtimes
89+
90+
When building modules for thid party Node.js runtimes like Electron, which have
91+
different build configurations from the official Node.js distribution, you
92+
should use `--dist-url` or `--nodedir` flags to specify the headers of the
93+
runtime to build for.
94+
95+
Also when `--dist-url` or `--nodedir` flags are passed, node-gyp will use the
96+
`config.gypi` shipped in the headers distribution to generate build
97+
configurations, which is different from the default mode that would use the
98+
`process.config` object of the running Node.js instance.
99+
100+
Some old versions of Electron shipped malformed `config.gypi` in their headers
101+
distributions, and you might need to pass `--force-process-config` to node-gyp
102+
to work around configuration errors.
103+
88104
## How to Use
89105

90106
To compile your native addon, first go to its root directory:
@@ -198,6 +214,7 @@ Some additional resources for Node.js native addons and writing `gyp` configurat
198214
| `--python=$path` | Set path to the Python binary
199215
| `--msvs_version=$version` | Set Visual Studio version (Windows only)
200216
| `--solution=$solution` | Set Visual Studio Solution version (Windows only)
217+
| `--force-process-config` | Force using runtime's `process.config` object to generate `config.gypi` file
201218

202219
## Configuration
203220

lib/configure.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,11 @@ function configure (gyp, argv, callback) {
9797
process.env.GYP_MSVS_VERSION = Math.min(vsInfo.versionYear, 2015)
9898
process.env.GYP_MSVS_OVERRIDE_PATH = vsInfo.path
9999
}
100-
createConfigGypi({ gyp, buildDir, nodeDir, vsInfo }, (err, configPath) => {
100+
createConfigGypi({ gyp, buildDir, nodeDir, vsInfo }).then(configPath => {
101101
configs.push(configPath)
102-
findConfigs(err)
102+
findConfigs()
103+
}).catch(err => {
104+
callback(err)
103105
})
104106
}
105107

lib/create-config-gypi.js

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,46 @@ const fs = require('graceful-fs')
44
const log = require('npmlog')
55
const path = require('path')
66

7-
function getBaseConfigGypi () {
8-
const config = JSON.parse(JSON.stringify(process.config))
7+
function parseConfigGypi (config) {
8+
// translated from tools/js2c.py of Node.js
9+
// 1. string comments
10+
config = config.replace(/#.*/g, '')
11+
// 2. join multiline strings
12+
config = config.replace(/'$\s+'/mg, '')
13+
// 3. normalize string literals from ' into "
14+
config = config.replace(/'/g, '"')
15+
return JSON.parse(config)
16+
}
17+
18+
async function getBaseConfigGypi ({ gyp, nodeDir }) {
19+
// try reading $nodeDir/include/node/config.gypi first when:
20+
// 1. --dist-url or --nodedir is specified
21+
// 2. and --force-process-config is not specified
22+
const shouldReadConfigGypi = (gyp.opts.nodedir || gyp.opts['dist-url']) && !gyp.opts['force-process-config']
23+
if (shouldReadConfigGypi && nodeDir) {
24+
try {
25+
const baseConfigGypiPath = path.resolve(nodeDir, 'include/node/config.gypi')
26+
const baseConfigGypi = await fs.promises.readFile(baseConfigGypiPath)
27+
return parseConfigGypi(baseConfigGypi.toString())
28+
} catch (err) {
29+
console.log(err)
30+
log.verbose('read config.gypi', err.message)
31+
}
32+
}
33+
34+
// fallback to process.config if it is invalid
35+
return JSON.parse(JSON.stringify(process.config))
36+
}
37+
38+
async function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
39+
const config = await getBaseConfigGypi({ gyp, nodeDir })
940
if (!config.target_defaults) {
1041
config.target_defaults = {}
1142
}
1243
if (!config.variables) {
1344
config.variables = {}
1445
}
15-
return config
16-
}
1746

18-
function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
19-
const config = getBaseConfigGypi()
2047
const defaults = config.target_defaults
2148
const variables = config.variables
2249

@@ -85,13 +112,13 @@ function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
85112
return config
86113
}
87114

88-
function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo }, callback) {
115+
async function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo }) {
89116
const configFilename = 'config.gypi'
90117
const configPath = path.resolve(buildDir, configFilename)
91118

92119
log.verbose('build/' + configFilename, 'creating config file')
93120

94-
const config = getCurrentConfigGypi({ gyp, nodeDir, vsInfo })
121+
const config = await getCurrentConfigGypi({ gyp, nodeDir, vsInfo })
95122

96123
// ensures that any boolean values in config.gypi get stringified
97124
function boolsToString (k, v) {
@@ -108,12 +135,13 @@ function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo }, callback) {
108135

109136
const json = JSON.stringify(config, boolsToString, 2)
110137
log.verbose('build/' + configFilename, 'writing out config file: %s', configPath)
111-
fs.writeFile(configPath, [prefix, json, ''].join('\n'), (err) => {
112-
callback(err, configPath)
113-
})
138+
await fs.promises.writeFile(configPath, [prefix, json, ''].join('\n'))
139+
140+
return configPath
114141
}
115142

116143
module.exports = createConfigGypi
117144
module.exports.test = {
145+
parseConfigGypi: parseConfigGypi,
118146
getCurrentConfigGypi: getCurrentConfigGypi
119147
}

lib/node-gyp.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ proto.configDefs = {
7575
'dist-url': String, // 'install'
7676
tarball: String, // 'install'
7777
jobs: String, // 'build'
78-
thin: String // 'configure'
78+
thin: String, // 'configure'
79+
'force-process-config': Boolean // 'configure'
7980
}
8081

8182
/**
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Test configuration
2+
{
3+
'variables': {
4+
'build_with_electron': true
5+
}
6+
}

test/test-configure-python.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ const configure = requireInject('../lib/configure', {
1111
closeSync: function () { },
1212
writeFile: function (file, data, cb) { cb() },
1313
stat: function (file, cb) { cb(null, {}) },
14-
mkdir: function (dir, options, cb) { cb() }
14+
mkdir: function (dir, options, cb) { cb() },
15+
promises: {
16+
writeFile: function (file, data) { return Promise.resolve(null) }
17+
}
1518
}
1619
})
1720

test/test-create-config-gypi.js

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,70 @@
11
'use strict'
22

3+
const path = require('path')
34
const { test } = require('tap')
45
const gyp = require('../lib/node-gyp')
56
const createConfigGypi = require('../lib/create-config-gypi')
6-
const { getCurrentConfigGypi } = createConfigGypi.test
7+
const { parseConfigGypi, getCurrentConfigGypi } = createConfigGypi.test
78

8-
test('config.gypi with no options', function (t) {
9+
test('config.gypi with no options', async function (t) {
910
t.plan(2)
1011

1112
const prog = gyp()
1213
prog.parseArgv([])
1314

14-
const config = getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
15+
const config = await getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
1516
t.equal(config.target_defaults.default_configuration, 'Release')
1617
t.equal(config.variables.target_arch, process.arch)
1718
})
1819

19-
test('config.gypi with --debug', function (t) {
20+
test('config.gypi with --debug', async function (t) {
2021
t.plan(1)
2122

2223
const prog = gyp()
2324
prog.parseArgv(['_', '_', '--debug'])
2425

25-
const config = getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
26+
const config = await getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
2627
t.equal(config.target_defaults.default_configuration, 'Debug')
2728
})
2829

29-
test('config.gypi with custom options', function (t) {
30+
test('config.gypi with custom options', async function (t) {
3031
t.plan(1)
3132

3233
const prog = gyp()
3334
prog.parseArgv(['_', '_', '--shared-libxml2'])
3435

35-
const config = getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
36+
const config = await getCurrentConfigGypi({ gyp: prog, vsInfo: {} })
3637
t.equal(config.variables.shared_libxml2, true)
3738
})
39+
40+
test('config.gypi with nodedir', async function (t) {
41+
t.plan(1)
42+
43+
const nodeDir = path.join(__dirname, 'fixtures', 'nodedir')
44+
45+
const prog = gyp()
46+
prog.parseArgv(['_', '_', `--nodedir=${nodeDir}`])
47+
48+
const config = await getCurrentConfigGypi({ gyp: prog, nodeDir, vsInfo: {} })
49+
t.equal(config.variables.build_with_electron, true)
50+
})
51+
52+
test('config.gypi with --force-process-config', async function (t) {
53+
t.plan(1)
54+
55+
const nodeDir = path.join(__dirname, 'fixtures', 'nodedir')
56+
57+
const prog = gyp()
58+
prog.parseArgv(['_', '_', '--force-process-config', `--nodedir=${nodeDir}`])
59+
60+
const config = await getCurrentConfigGypi({ gyp: prog, nodeDir, vsInfo: {} })
61+
t.equal(config.variables.build_with_electron, undefined)
62+
})
63+
64+
test('config.gypi parsing', function (t) {
65+
t.plan(1)
66+
67+
const str = "# Some comments\n{'variables': {'multiline': 'A'\n'B'}}"
68+
const config = parseConfigGypi(str)
69+
t.deepEqual(config, { variables: { multiline: 'AB' } })
70+
})

0 commit comments

Comments
 (0)