@flun/nodejs-mobile-gyp 1.0.1 → 1.1.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.
- package/CHANGELOG.md +53 -2
- package/README.md +131 -21
- package/addon.gypi +13 -1
- package/bin/node-gyp.js +66 -109
- package/gyp/pylib/gyp/MSVSSettings.py +13 -0
- package/gyp/pylib/gyp/common.py +5 -2
- package/gyp/pylib/gyp/generator/make.py +18 -3
- package/lib/build.js +202 -198
- package/lib/clean.js +13 -16
- package/lib/configure.js +184 -262
- package/lib/create-config-gypi.js +102 -156
- package/lib/download.js +29 -42
- package/lib/find-node-directory.js +38 -55
- package/lib/find-python.js +227 -241
- package/lib/find-visualstudio.js +196 -295
- package/lib/install.js +269 -378
- package/lib/list.js +19 -25
- package/lib/log.js +69 -133
- package/lib/node-gyp.js +62 -107
- package/lib/process-release.js +100 -139
- package/lib/rebuild.js +3 -8
- package/lib/remove.js +34 -44
- package/lib/util.js +62 -73
- package/package.json +5 -3
- package/gyp/LICENSE +0 -28
package/lib/build.js
CHANGED
|
@@ -1,221 +1,225 @@
|
|
|
1
|
-
import gracefulFs from 'graceful-fs'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import { glob } from 'glob'
|
|
4
|
-
import * as log from './log.js'
|
|
5
|
-
import which from 'which'
|
|
6
|
-
import os from 'os'
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (process.platform === 'aix') {
|
|
17
|
-
platformMake = 'gmake'
|
|
18
|
-
} else if (process.platform === 'os400') {
|
|
19
|
-
platformMake = 'gmake'
|
|
20
|
-
} else if (process.platform.indexOf('bsd') !== -1) {
|
|
21
|
-
platformMake = 'gmake'
|
|
22
|
-
} else if (win && argv.length > 0) {
|
|
23
|
-
argv = argv.map(function (target) {
|
|
24
|
-
return '/t:' + target
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const makeCommand = gyp.opts.make || process.env.MAKE || platformMake
|
|
29
|
-
let command = win ? 'msbuild' : makeCommand
|
|
30
|
-
const jobs = gyp.opts.jobs || process.env.JOBS
|
|
31
|
-
let buildType
|
|
32
|
-
let config
|
|
33
|
-
let arch
|
|
34
|
-
let nodeDir
|
|
35
|
-
let guessedSolution
|
|
36
|
-
let python
|
|
37
|
-
let buildBinsDir
|
|
38
|
-
|
|
39
|
-
await loadConfigGypi()
|
|
40
|
-
|
|
1
|
+
import gracefulFs from 'graceful-fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
import * as log from './log.js';
|
|
5
|
+
import which from 'which';
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { spawnSync } from 'child_process';
|
|
8
|
+
import fsSync from 'fs';
|
|
9
|
+
|
|
10
|
+
const fs = gracefulFs.promises,
|
|
11
|
+
_hostIsWindows = process.platform === 'win32',
|
|
12
|
+
// Android 交叉编译即使宿主是 Windows,也必须走 make(make-android 生成器)
|
|
13
|
+
_isAndroidCrossBuild = process.env.npm_config_platform === 'android' || process.env.npm_config_format === 'make-android',
|
|
14
|
+
// win 在此文件中语义为"使用 MSBuild":仅 Windows 宿主且非 Android 交叉编译
|
|
15
|
+
win = _hostIsWindows && !_isAndroidCrossBuild,
|
|
41
16
|
/**
|
|
42
|
-
*
|
|
17
|
+
* Windows 上 GNU make 需要 sh.exe 来执行 Makefile 里的 Unix 命令
|
|
18
|
+
* (printf / xargs / rm 等)。优先复用 Git for Windows 自带的 usr/bin,
|
|
19
|
+
* 避免要求用户额外安装 MSYS2。通过 "where git" 反推,不硬编码路径。
|
|
43
20
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
21
|
+
findWindowsShellDir = () => {
|
|
22
|
+
if (process.platform !== 'win32') return null;
|
|
23
|
+
const tryShDir = (dir) => {
|
|
24
|
+
try { return fsSync.existsSync(path.join(dir, 'sh.exe')) ? dir : null } catch { return null }
|
|
25
|
+
};
|
|
26
|
+
const toShDir = (gitRoot) => tryShDir(path.join(gitRoot, 'usr', 'bin'));
|
|
27
|
+
// 1) 注册表:Git for Windows 安装时必写,与 PATH 无关(最可靠)
|
|
28
|
+
const reg = process.env.SystemRoot ? path.join(process.env.SystemRoot, 'System32', 'reg.exe') : 'reg.exe';
|
|
29
|
+
for (const hive of ['HKLM', 'HKCU']) {
|
|
30
|
+
for (const key of ['SOFTWARE\\GitForWindows', 'SOFTWARE\\WOW6432Node\\GitForWindows']) {
|
|
31
|
+
try {
|
|
32
|
+
const r = spawnSync(reg, ['query', hive + '\\' + key, '/v', 'InstallPath'], { encoding: 'utf8' });
|
|
33
|
+
if (r.status === 0) {
|
|
34
|
+
const m = (r.stdout || '').match(/REG_SZ\s+(.+)/);
|
|
35
|
+
if (m) {
|
|
36
|
+
const dir = toShDir(m[1].trim());
|
|
37
|
+
if (dir) return dir;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} catch { }
|
|
54
41
|
}
|
|
55
42
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
log.verbose('python', python)
|
|
76
|
-
|
|
77
|
-
if (win) {
|
|
78
|
-
await findSolutionFile()
|
|
79
|
-
} else {
|
|
80
|
-
await doWhich()
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Windows 下查找第一个 build/*.sln 文件
|
|
86
|
-
*/
|
|
87
|
-
async function findSolutionFile () {
|
|
88
|
-
const files = await glob('build/*.sln')
|
|
89
|
-
if (files.length === 0) {
|
|
90
|
-
throw new Error('Could not find *.sln file. Did you run "configure"?')
|
|
43
|
+
// 2) which.sync(PATH 中能找到 git 时可用)
|
|
44
|
+
try {
|
|
45
|
+
const gitPath = which.sync('git');
|
|
46
|
+
if (gitPath) {
|
|
47
|
+
const dir = path.resolve(path.dirname(gitPath), '..', 'usr', 'bin');
|
|
48
|
+
const r = tryShDir(dir);
|
|
49
|
+
if (r) return r;
|
|
50
|
+
}
|
|
51
|
+
} catch { }
|
|
52
|
+
// 3) PATH 中找 git.exe,再反推 ../usr/bin
|
|
53
|
+
for (const pd of (process.env.PATH || '').split(';')) {
|
|
54
|
+
if (!pd) continue;
|
|
55
|
+
try {
|
|
56
|
+
if (fsSync.existsSync(path.join(pd, 'git.exe'))) {
|
|
57
|
+
const dir = path.resolve(pd, '..', 'usr', 'bin');
|
|
58
|
+
const r = tryShDir(dir);
|
|
59
|
+
if (r) return r;
|
|
60
|
+
}
|
|
61
|
+
} catch { }
|
|
91
62
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
async function doWhich () {
|
|
101
|
-
// Windows 使用 node-gyp configure 提供的 msbuild
|
|
102
|
-
if (win) {
|
|
103
|
-
if (!config.variables.msbuild_path) {
|
|
104
|
-
throw new Error('MSBuild is not set, please run `node-gyp configure`.')
|
|
63
|
+
// 4) 全盘符常见位置兜底
|
|
64
|
+
const suffixes = [
|
|
65
|
+
'Program Files\\Git', 'Program Files (x86)\\Git', 'Git', 'msys64',
|
|
66
|
+
];
|
|
67
|
+
for (const drv of ['C:', 'D:', 'E:', 'F:']) {
|
|
68
|
+
for (const sfx of suffixes) {
|
|
69
|
+
const dir = tryShDir(drv + '\\' + sfx);
|
|
70
|
+
if (dir) return dir;
|
|
105
71
|
}
|
|
106
|
-
command = config.variables.msbuild_path
|
|
107
|
-
log.verbose('using MSBuild:', command)
|
|
108
|
-
await doBuild()
|
|
109
|
-
return
|
|
110
72
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const execPath = await which(command)
|
|
114
|
-
log.verbose('`which` succeeded for `' + command + '`', execPath)
|
|
115
|
-
await doBuild()
|
|
116
|
-
}
|
|
73
|
+
return null;
|
|
74
|
+
},
|
|
117
75
|
|
|
118
76
|
/**
|
|
119
|
-
*
|
|
77
|
+
* 调用 make/msbuild 构建原生模块
|
|
120
78
|
*/
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
: (archLower === 'arm'
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
argv.push('/m:' + j)
|
|
155
|
-
} else if (jobs.toUpperCase() === 'MAX') {
|
|
156
|
-
argv.push('/m:' + os.cpus().length)
|
|
79
|
+
build = async (gyp, argv) => {
|
|
80
|
+
let platformMake = 'make';
|
|
81
|
+
|
|
82
|
+
if (['aix', 'os400'].includes(process.platform) || process.platform.includes('bsd')) platformMake = 'gmake';
|
|
83
|
+
else if (win && argv.length > 0) argv = argv.map(target => '/t:' + target);
|
|
84
|
+
|
|
85
|
+
const makeCommand = gyp.opts.make || process.env.MAKE || platformMake, jobs = gyp.opts.jobs || process.env.JOBS;
|
|
86
|
+
let command = win ? 'msbuild' : makeCommand, buildType, config, arch, nodeDir, guessedSolution, python, buildBinsDir;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 真正启动子进程并编译模块
|
|
90
|
+
*/
|
|
91
|
+
const doBuild = async () => {
|
|
92
|
+
// 启用 verbose 构建
|
|
93
|
+
const verbose = log.logger.isVisible('verbose');
|
|
94
|
+
let j;
|
|
95
|
+
|
|
96
|
+
if (!win && verbose) argv.push('V=1');
|
|
97
|
+
if (win && !verbose) argv.push('/clp:Verbosity=minimal');
|
|
98
|
+
if (win) argv.push('/nologo'); // Windows 上关闭 Microsoft logo
|
|
99
|
+
|
|
100
|
+
// 指定构建类型,默认 Release
|
|
101
|
+
if (win) {
|
|
102
|
+
// 把 .gypi 配置中的 target_arch 转换为 MSBuild 的 /Platform
|
|
103
|
+
// 因为表示"32 位 Intel"的方式很多,默认按此处理
|
|
104
|
+
// 注意:msbuild 的 Condition 字符串相等比较不区分大小写
|
|
105
|
+
const archLower = arch.toLowerCase(), p = archLower === 'x64' ? 'x64' : (archLower === 'arm' ? 'ARM'
|
|
106
|
+
: (archLower === 'arm64' ? 'ARM64' : 'Win32'));
|
|
107
|
+
argv.push('/p:Configuration=' + buildType + ';Platform=' + p)
|
|
108
|
+
if (jobs) {
|
|
109
|
+
j = parseInt(jobs, 10)
|
|
110
|
+
if (!isNaN(j) && j > 0) argv.push('/m:' + j);
|
|
111
|
+
else if (jobs.toUpperCase() === 'MAX') argv.push('/m:' + os.cpus().length);
|
|
157
112
|
}
|
|
158
113
|
}
|
|
159
|
-
} else {
|
|
160
|
-
argv.push('BUILDTYPE=' + buildType)
|
|
161
114
|
// 调用 build 目录下的 Makefile
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
argv.push('--jobs')
|
|
168
|
-
argv.push(j)
|
|
169
|
-
} else if (jobs.toUpperCase() === 'MAX') {
|
|
170
|
-
argv.push('--jobs')
|
|
171
|
-
argv.push(os.cpus().length)
|
|
115
|
+
else {
|
|
116
|
+
argv.push('BUILDTYPE=' + buildType), argv.push('-C'), argv.push('build');
|
|
117
|
+
if (jobs) {
|
|
118
|
+
j = parseInt(jobs, 10);
|
|
119
|
+
if (!isNaN(j) && j > 0) argv.push('--jobs'), argv.push(j);
|
|
120
|
+
else if (jobs.toUpperCase() === 'MAX') argv.push('--jobs'), argv.push(os.cpus().length);
|
|
172
121
|
}
|
|
173
122
|
}
|
|
174
|
-
}
|
|
175
123
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
})
|
|
181
|
-
if (!hasSln) {
|
|
182
|
-
argv.unshift(gyp.opts.solution || guessedSolution)
|
|
124
|
+
if (win) {
|
|
125
|
+
// 用户是否指定了自己的 .sln 文件?
|
|
126
|
+
const hasSln = argv.some(arg => path.extname(arg) === '.sln');
|
|
127
|
+
if (!hasSln) argv.unshift(gyp.opts.solution || guessedSolution);
|
|
183
128
|
}
|
|
184
|
-
}
|
|
185
129
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
130
|
+
if (!win) {
|
|
131
|
+
// 把构建期依赖的符号链接(如 Python)加入 PATH
|
|
132
|
+
buildBinsDir = path.resolve('build', 'node_gyp_bins');
|
|
133
|
+
if (_hostIsWindows) {
|
|
134
|
+
const shellDir = findWindowsShellDir();
|
|
135
|
+
if (shellDir) {
|
|
136
|
+
process.env.PATH = `${shellDir};${process.env.PATH}`;
|
|
137
|
+
log.verbose('bin shell', `prepended ${shellDir} to PATH for make`);
|
|
138
|
+
} else {
|
|
139
|
+
log.warn('bin shell', 'no sh.exe found; GNU make 的 Unix 命令可能失败。建议安装 Git for Windows');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
process.env.PATH = _hostIsWindows
|
|
143
|
+
? `${buildBinsDir};${process.env.PATH}`
|
|
144
|
+
: `${buildBinsDir}:${process.env.PATH}`;
|
|
145
|
+
await fs.mkdir(buildBinsDir, { recursive: true });
|
|
146
|
+
|
|
147
|
+
if (_hostIsWindows) {
|
|
148
|
+
// Windows 无 python3 名称,且创建符号链接需要管理员权限;改用 .cmd 桥接
|
|
149
|
+
const shimPath = path.join(buildBinsDir, 'python3.cmd');
|
|
150
|
+
await fs.writeFile(shimPath, `@echo off\r\n"${python}" %*\r\n`);
|
|
151
|
+
log.verbose('bin shims', `created python3.cmd shim to "${python}" and added to PATH`);
|
|
152
|
+
} else {
|
|
153
|
+
const symlinkDestination = path.join(buildBinsDir, 'python3');
|
|
154
|
+
try {
|
|
155
|
+
await fs.unlink(symlinkDestination);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err.code !== 'ENOENT') throw err;
|
|
158
|
+
}
|
|
159
|
+
await fs.symlink(python, symlinkDestination);
|
|
160
|
+
log.verbose('bin symlinks', `created symlink to "${python}" in "${buildBinsDir}" and added to PATH`);
|
|
161
|
+
}
|
|
196
162
|
}
|
|
197
|
-
await fs.symlink(python, symlinkDestination)
|
|
198
|
-
log.verbose('bin symlinks', `created symlink to "${python}" in "${buildBinsDir}" and added to PATH`)
|
|
199
|
-
}
|
|
200
163
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
164
|
+
const proc = gyp.spawn(command, argv);
|
|
165
|
+
await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => {
|
|
166
|
+
if (buildBinsDir) await fs.rm(buildBinsDir, { recursive: true }); // 清理构建期依赖的符号链接
|
|
167
|
+
|
|
168
|
+
if (code !== 0) return reject(new Error('`' + command + '` failed with exit code: ' + code));
|
|
169
|
+
if (signal) return reject(new Error('`' + command + '` got signal: ' + signal));
|
|
170
|
+
resolve();
|
|
171
|
+
}))
|
|
172
|
+
},
|
|
173
|
+
/**
|
|
174
|
+
* 用 node-which 定位 msbuild / make 可执行文件
|
|
175
|
+
*/
|
|
176
|
+
doWhich = async () => {
|
|
177
|
+
// Windows 使用 node-gyp configure 提供的 msbuild
|
|
178
|
+
if (win) {
|
|
179
|
+
if (!config.variables.msbuild_path) throw new Error('MSBuild 未设置,请先运行 `node-gyp configure`');
|
|
180
|
+
command = config.variables.msbuild_path, log.verbose('using MSBuild:', command), await doBuild();
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
207
183
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
184
|
+
// 先确认构建命令在 PATH 中
|
|
185
|
+
const execPath = await which(command);
|
|
186
|
+
log.verbose('`which` succeeded for `' + command + '`', execPath), await doBuild();
|
|
187
|
+
},
|
|
188
|
+
/**
|
|
189
|
+
* Windows 下查找第一个 build/*.sln 文件
|
|
190
|
+
*/
|
|
191
|
+
findSolutionFile = async () => {
|
|
192
|
+
const files = await glob('build/*.sln')
|
|
193
|
+
if (files.length === 0) throw new Error('未找到 *.sln 文件,是否运行过 configure?');
|
|
194
|
+
guessedSolution = files[0], log.verbose('found first Solution file', guessedSolution), await doWhich();
|
|
195
|
+
},
|
|
196
|
+
/**
|
|
197
|
+
* 加载 configure 阶段生成的 config.gypi
|
|
198
|
+
*/
|
|
199
|
+
loadConfigGypi = async () => {
|
|
200
|
+
let data;
|
|
201
|
+
try {
|
|
202
|
+
const configPath = path.resolve('build', 'config.gypi');
|
|
203
|
+
data = await fs.readFile(configPath, 'utf8');
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err.code === 'ENOENT') throw new Error('请先运行 `node-gyp configure`');
|
|
206
|
+
else throw err;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// 从 config 中取出 arch、buildType、nodeDir
|
|
210
|
+
config = JSON.parse(data.replace(/#.+\n/, '')), buildType = config.target_defaults.default_configuration;
|
|
211
|
+
arch = config.variables.target_arch, nodeDir = config.variables.nodedir, python = config.variables.python;
|
|
212
|
+
|
|
213
|
+
if ('debug' in gyp.opts) buildType = gyp.opts.debug ? 'Debug' : 'Release';
|
|
214
|
+
if (!buildType) buildType = 'Release';
|
|
215
|
+
|
|
216
|
+
log.verbose('build type', buildType), log.verbose('architecture', arch), log.verbose('node dev dir', nodeDir);
|
|
217
|
+
log.verbose('python', python);
|
|
218
218
|
|
|
219
|
-
|
|
219
|
+
win ? await findSolutionFile() : await doWhich();
|
|
220
|
+
};
|
|
221
|
+
await loadConfigGypi();
|
|
222
|
+
};
|
|
220
223
|
|
|
221
|
-
|
|
224
|
+
build.usage = 'Invokes `' + (win ? 'msbuild' : 'make') + '` and builds the module';
|
|
225
|
+
export { build };
|
package/lib/clean.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import gracefulFs from 'graceful-fs'
|
|
2
|
-
import * as log from './log.js'
|
|
1
|
+
import gracefulFs from 'graceful-fs';
|
|
2
|
+
import * as log from './log.js';
|
|
3
3
|
|
|
4
|
-
const fs = gracefulFs.promises
|
|
4
|
+
const fs = gracefulFs.promises,
|
|
5
|
+
/**
|
|
6
|
+
* 清理:删除 build 目录
|
|
7
|
+
*/
|
|
8
|
+
clean = async (gyp, argv) => {
|
|
9
|
+
const buildDir = 'build';
|
|
5
10
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const clean = async (gyp, argv) => {
|
|
10
|
-
// 删除 build 目录
|
|
11
|
-
const buildDir = 'build'
|
|
11
|
+
log.verbose('clean', 'removing "%s" directory', buildDir);
|
|
12
|
+
await fs.rm(buildDir, { recursive: true, force: true });
|
|
13
|
+
}
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
clean.usage = 'Removes any generated build files and the "out" dir'
|
|
18
|
-
|
|
19
|
-
export { clean }
|
|
15
|
+
clean.usage = 'Removes any generated build files and the "out" dir';
|
|
16
|
+
export { clean };
|