Skip to content

Commit a27dc08

Browse files
zcbenzrvagg
authored andcommitted
feat: build with config.gypi from node headers
1 parent 5a00387 commit a27dc08

File tree

7 files changed

+112
-27
lines changed

7 files changed

+112
-27
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: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,15 @@ 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

106-
function findConfigs (err) {
107-
if (err) {
108-
return callback(err)
109-
}
110-
108+
function findConfigs () {
111109
var name = configNames.shift()
112110
if (!name) {
113111
return runGyp()

lib/create-config-gypi.js

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,45 @@ 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+
log.warn('read config.gypi', err.message)
30+
}
31+
}
32+
33+
// fallback to process.config if it is invalid
34+
return JSON.parse(JSON.stringify(process.config))
35+
}
36+
37+
async function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
38+
const config = await getBaseConfigGypi({ gyp, nodeDir })
939
if (!config.target_defaults) {
1040
config.target_defaults = {}
1141
}
1242
if (!config.variables) {
1343
config.variables = {}
1444
}
15-
return config
16-
}
1745

18-
function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
19-
const config = getBaseConfigGypi()
2046
const defaults = config.target_defaults
2147
const variables = config.variables
2248

@@ -85,13 +111,13 @@ function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
85111
return config
86112
}
87113

88-
function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo }, callback) {
114+
async function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo }) {
89115
const configFilename = 'config.gypi'
90116
const configPath = path.resolve(buildDir, configFilename)
91117

92118
log.verbose('build/' + configFilename, 'creating config file')
93119

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

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

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

116142
module.exports = createConfigGypi
117143
module.exports.test = {
144+
parseConfigGypi: parseConfigGypi,
118145
getCurrentConfigGypi: getCurrentConfigGypi
119146
}

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)