@flun/nodejs-mobile-gyp 1.2.0 → 1.2.1
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 +26 -5
- package/LICENSE +33 -33
- package/README.md +62 -63
- package/bin/node-gyp.js +22 -24
- package/lib/Find-VisualStudio.cs +2 -1
- package/lib/build.js +25 -35
- package/lib/clean.js +2 -2
- package/lib/configure.js +25 -25
- package/lib/create-config-gypi.js +3 -3
- package/lib/find-node-directory.js +4 -4
- package/lib/find-python.js +44 -44
- package/lib/find-visualstudio.js +52 -52
- package/lib/install.js +41 -41
- package/lib/list.js +2 -2
- package/lib/node-gyp.js +22 -22
- package/lib/process-release.js +1 -1
- package/lib/rebuild.js +1 -1
- package/lib/remove.js +5 -5
- package/lib/util.js +5 -5
- package/package.json +7 -8
package/lib/build.js
CHANGED
|
@@ -7,8 +7,7 @@ import os from 'os';
|
|
|
7
7
|
import { spawnSync } from 'child_process';
|
|
8
8
|
import fsSync from 'fs';
|
|
9
9
|
|
|
10
|
-
const fs = gracefulFs.promises,
|
|
11
|
-
_hostIsWindows = process.platform === 'win32',
|
|
10
|
+
const fs = gracefulFs.promises, _hostIsWindows = process.platform === 'win32',
|
|
12
11
|
// Android 交叉编译即使宿主是 Windows,也必须走 make(make-android 生成器)
|
|
13
12
|
_isAndroidCrossBuild = process.env.npm_config_platform === 'android' || process.env.npm_config_format === 'make-android',
|
|
14
13
|
// win 在此文件中语义为"使用 MSBuild":仅 Windows 宿主且非 Android 交叉编译
|
|
@@ -20,12 +19,11 @@ const fs = gracefulFs.promises,
|
|
|
20
19
|
*/
|
|
21
20
|
findWindowsShellDir = () => {
|
|
22
21
|
if (process.platform !== 'win32') return null;
|
|
23
|
-
const tryShDir =
|
|
22
|
+
const tryShDir = dir => {
|
|
24
23
|
try { return fsSync.existsSync(path.join(dir, 'sh.exe')) ? dir : null } catch { return null }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const reg = process.env.SystemRoot ? path.join(process.env.SystemRoot, 'System32', 'reg.exe') : 'reg.exe';
|
|
24
|
+
}, toShDir = (gitRoot) => tryShDir(path.join(gitRoot, 'usr', 'bin')),
|
|
25
|
+
// 1) 注册表:Git for Windows 安装时必写,与 PATH 无关(最可靠)
|
|
26
|
+
reg = process.env.SystemRoot ? path.join(process.env.SystemRoot, 'System32', 'reg.exe') : 'reg.exe';
|
|
29
27
|
for (const hive of ['HKLM', 'HKCU']) {
|
|
30
28
|
for (const key of ['SOFTWARE\\GitForWindows', 'SOFTWARE\\WOW6432Node\\GitForWindows']) {
|
|
31
29
|
try {
|
|
@@ -44,8 +42,7 @@ const fs = gracefulFs.promises,
|
|
|
44
42
|
try {
|
|
45
43
|
const gitPath = which.sync('git');
|
|
46
44
|
if (gitPath) {
|
|
47
|
-
const dir = path.resolve(path.dirname(gitPath), '..', 'usr', 'bin');
|
|
48
|
-
const r = tryShDir(dir);
|
|
45
|
+
const dir = path.resolve(path.dirname(gitPath), '..', 'usr', 'bin'), r = tryShDir(dir);
|
|
49
46
|
if (r) return r;
|
|
50
47
|
}
|
|
51
48
|
} catch { }
|
|
@@ -54,16 +51,13 @@ const fs = gracefulFs.promises,
|
|
|
54
51
|
if (!pd) continue;
|
|
55
52
|
try {
|
|
56
53
|
if (fsSync.existsSync(path.join(pd, 'git.exe'))) {
|
|
57
|
-
const dir = path.resolve(pd, '..', 'usr', 'bin');
|
|
58
|
-
const r = tryShDir(dir);
|
|
54
|
+
const dir = path.resolve(pd, '..', 'usr', 'bin'), r = tryShDir(dir);
|
|
59
55
|
if (r) return r;
|
|
60
56
|
}
|
|
61
57
|
} catch { }
|
|
62
58
|
}
|
|
63
59
|
// 4) 全盘符常见位置兜底
|
|
64
|
-
const suffixes = [
|
|
65
|
-
'Program Files\\Git', 'Program Files (x86)\\Git', 'Git', 'msys64',
|
|
66
|
-
];
|
|
60
|
+
const suffixes = ['Program Files\\Git', 'Program Files (x86)\\Git', 'Git', 'msys64'];
|
|
67
61
|
for (const drv of ['C:', 'D:', 'E:', 'F:']) {
|
|
68
62
|
for (const sfx of suffixes) {
|
|
69
63
|
const dir = tryShDir(drv + '\\' + sfx);
|
|
@@ -104,7 +98,7 @@ const fs = gracefulFs.promises,
|
|
|
104
98
|
// 注意:msbuild 的 Condition 字符串相等比较不区分大小写
|
|
105
99
|
const archLower = arch.toLowerCase(), p = archLower === 'x64' ? 'x64' : (archLower === 'arm' ? 'ARM'
|
|
106
100
|
: (archLower === 'arm64' ? 'ARM64' : 'Win32'));
|
|
107
|
-
argv.push('/p:Configuration=' + buildType + ';Platform=' + p)
|
|
101
|
+
argv.push('/p:Configuration=' + buildType + ';Platform=' + p);
|
|
108
102
|
if (jobs) {
|
|
109
103
|
j = parseInt(jobs, 10)
|
|
110
104
|
if (!isNaN(j) && j > 0) argv.push('/m:' + j);
|
|
@@ -134,21 +128,18 @@ const fs = gracefulFs.promises,
|
|
|
134
128
|
const shellDir = findWindowsShellDir();
|
|
135
129
|
if (shellDir) {
|
|
136
130
|
process.env.PATH = `${shellDir};${process.env.PATH}`;
|
|
137
|
-
log.verbose('bin
|
|
138
|
-
} else {
|
|
139
|
-
log.warn('bin shell', 'no sh.exe found; GNU make 的 Unix 命令可能失败;建议安装 Git for Windows');
|
|
131
|
+
log.verbose('bin 环境', `已将 ${shellDir} 加入 PATH,供 make 使用`);
|
|
140
132
|
}
|
|
133
|
+
else log.warn('shell 环境', '未找到 sh.exe;GNU make 的 Unix 命令可能失败;建议安装 Git for Windows');
|
|
141
134
|
}
|
|
142
|
-
process.env.PATH = _hostIsWindows
|
|
143
|
-
? `${buildBinsDir};${process.env.PATH}`
|
|
144
|
-
: `${buildBinsDir}:${process.env.PATH}`;
|
|
135
|
+
process.env.PATH = _hostIsWindows ? `${buildBinsDir};${process.env.PATH}` : `${buildBinsDir}:${process.env.PATH}`;
|
|
145
136
|
await fs.mkdir(buildBinsDir, { recursive: true });
|
|
146
137
|
|
|
147
138
|
if (_hostIsWindows) {
|
|
148
139
|
// Windows 无 python3 名称,且创建符号链接需要管理员权限;改用 .cmd 桥接
|
|
149
140
|
const shimPath = path.join(buildBinsDir, 'python3.cmd');
|
|
150
141
|
await fs.writeFile(shimPath, `@echo off\r\n"${python}" %*\r\n`);
|
|
151
|
-
log.verbose('bin
|
|
142
|
+
log.verbose('bin 垫片', `已创建 python3.cmd 指向 "${python}" 并加入 PATH`);
|
|
152
143
|
} else {
|
|
153
144
|
const symlinkDestination = path.join(buildBinsDir, 'python3');
|
|
154
145
|
try {
|
|
@@ -157,16 +148,15 @@ const fs = gracefulFs.promises,
|
|
|
157
148
|
if (err.code !== 'ENOENT') throw err;
|
|
158
149
|
}
|
|
159
150
|
await fs.symlink(python, symlinkDestination);
|
|
160
|
-
log.verbose('bin
|
|
151
|
+
log.verbose('bin 符号链接', `已在 "${buildBinsDir}" 创建指向 "${python}" 的符号链接并加入 PATH`);
|
|
161
152
|
}
|
|
162
153
|
}
|
|
163
154
|
|
|
164
155
|
const proc = gyp.spawn(command, argv);
|
|
165
156
|
await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => {
|
|
166
157
|
if (buildBinsDir) await fs.rm(buildBinsDir, { recursive: true }); // 清理构建期依赖的符号链接
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
if (signal) return reject(new Error('`' + command + '` got signal: ' + signal));
|
|
158
|
+
if (code !== 0) return reject(new Error('`' + command + '` 执行失败,退出码: ' + code));
|
|
159
|
+
if (signal) return reject(new Error('`' + command + '` 收到信号: ' + signal));
|
|
170
160
|
resolve();
|
|
171
161
|
}))
|
|
172
162
|
},
|
|
@@ -174,16 +164,16 @@ const fs = gracefulFs.promises,
|
|
|
174
164
|
* 用 node-which 定位 msbuild / make 可执行文件
|
|
175
165
|
*/
|
|
176
166
|
doWhich = async () => {
|
|
177
|
-
// Windows 使用
|
|
167
|
+
// Windows 使用 nodejs-mobile-gyp configure 提供的 msbuild
|
|
178
168
|
if (win) {
|
|
179
|
-
if (!config.variables.msbuild_path) throw new Error('MSBuild 未设置,请先运行 `
|
|
180
|
-
command = config.variables.msbuild_path, log.verbose('
|
|
169
|
+
if (!config.variables.msbuild_path) throw new Error('MSBuild 未设置,请先运行 `nodejs-mobile-gyp configure`');
|
|
170
|
+
command = config.variables.msbuild_path, log.verbose('使用 MSBuild:', command), await doBuild();
|
|
181
171
|
return;
|
|
182
172
|
}
|
|
183
173
|
|
|
184
174
|
// 先确认构建命令在 PATH 中
|
|
185
175
|
const execPath = await which(command);
|
|
186
|
-
log.verbose('`which`
|
|
176
|
+
log.verbose('`which` 成功解析 `' + command + '`', execPath), await doBuild();
|
|
187
177
|
},
|
|
188
178
|
/**
|
|
189
179
|
* Windows 下查找第一个 build/*.sln 文件
|
|
@@ -191,7 +181,7 @@ const fs = gracefulFs.promises,
|
|
|
191
181
|
findSolutionFile = async () => {
|
|
192
182
|
const files = await glob('build/*.sln')
|
|
193
183
|
if (files.length === 0) throw new Error('未找到 *.sln 文件,是否运行过 configure?');
|
|
194
|
-
guessedSolution = files[0], log.verbose('
|
|
184
|
+
guessedSolution = files[0], log.verbose('找到首个解决方案文件', guessedSolution), await doWhich();
|
|
195
185
|
},
|
|
196
186
|
/**
|
|
197
187
|
* 加载 configure 阶段生成的 config.gypi
|
|
@@ -202,7 +192,7 @@ const fs = gracefulFs.promises,
|
|
|
202
192
|
const configPath = path.resolve('build', 'config.gypi');
|
|
203
193
|
data = await fs.readFile(configPath, 'utf8');
|
|
204
194
|
} catch (err) {
|
|
205
|
-
if (err.code === 'ENOENT') throw new Error('请先运行 `
|
|
195
|
+
if (err.code === 'ENOENT') throw new Error('请先运行 `nodejs-mobile-gyp configure`');
|
|
206
196
|
else throw err;
|
|
207
197
|
}
|
|
208
198
|
|
|
@@ -213,13 +203,13 @@ const fs = gracefulFs.promises,
|
|
|
213
203
|
if ('debug' in gyp.opts) buildType = gyp.opts.debug ? 'Debug' : 'Release';
|
|
214
204
|
if (!buildType) buildType = 'Release';
|
|
215
205
|
|
|
216
|
-
log.verbose('
|
|
217
|
-
log.verbose('
|
|
206
|
+
log.verbose('构建类型', buildType), log.verbose('架构', arch), log.verbose('node 开发目录', nodeDir);
|
|
207
|
+
log.verbose('Python', python);
|
|
218
208
|
|
|
219
209
|
win ? await findSolutionFile() : await doWhich();
|
|
220
210
|
};
|
|
221
211
|
await loadConfigGypi();
|
|
222
212
|
};
|
|
223
213
|
|
|
224
|
-
build.usage = '
|
|
214
|
+
build.usage = '调用 `' + (win ? 'msbuild' : 'make') + '` 构建模块';
|
|
225
215
|
export { build };
|
package/lib/clean.js
CHANGED
|
@@ -8,9 +8,9 @@ const fs = gracefulFs.promises,
|
|
|
8
8
|
clean = async (gyp, argv) => {
|
|
9
9
|
const buildDir = 'build';
|
|
10
10
|
|
|
11
|
-
log.verbose('
|
|
11
|
+
log.verbose('清理', '正在删除 "%s" 目录', buildDir);
|
|
12
12
|
await fs.rm(buildDir, { recursive: true, force: true });
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
clean.usage = '
|
|
15
|
+
clean.usage = '删除生成的构建文件与 "out" 目录';
|
|
16
16
|
export { clean };
|
package/lib/configure.js
CHANGED
|
@@ -30,11 +30,11 @@ const configure = async (gyp, argv) => {
|
|
|
30
30
|
runGyp = async () => {
|
|
31
31
|
if (!~argv.indexOf('-f') && !~argv.indexOf('--format')) {
|
|
32
32
|
if (win) {
|
|
33
|
-
log.verbose('gyp', 'gyp
|
|
33
|
+
log.verbose('gyp', '未指定 gyp 格式,强制使用 "msvs"');
|
|
34
34
|
// 非 Windows 强制使用 make 目标
|
|
35
35
|
argv.push('-f', ('format' in gyp.opts) ? gyp.opts.format : 'msvs');
|
|
36
36
|
} else {
|
|
37
|
-
log.verbose('gyp', 'gyp
|
|
37
|
+
log.verbose('gyp', '未指定 gyp 格式,强制使用 "make"');
|
|
38
38
|
// 非 Windows 强制使用 make 目标
|
|
39
39
|
argv.push('-f', ('format' in gyp.opts) ? gyp.opts.format : 'make');
|
|
40
40
|
}
|
|
@@ -42,7 +42,7 @@ const configure = async (gyp, argv) => {
|
|
|
42
42
|
|
|
43
43
|
configs.forEach(config => argv.push('-I', config)); // 引入所有找到的 .gypi 文件
|
|
44
44
|
// AIX 和 z/OS 需要设置导出文件路径,其中包含链接所需符号
|
|
45
|
-
let nodeExpFile, nodeRootDir, candidates, logprefix = '
|
|
45
|
+
let nodeExpFile, nodeRootDir, candidates, logprefix = '查找导出文件';
|
|
46
46
|
if (process.platform === 'aix' || process.platform === 'os390' || process.platform === 'os400') {
|
|
47
47
|
const ext = process.platform === 'os390' ? 'x' : 'exp';
|
|
48
48
|
nodeRootDir = findNodeDirectory();
|
|
@@ -55,10 +55,10 @@ const configure = async (gyp, argv) => {
|
|
|
55
55
|
'out/Debug/obj.target/libnode', 'lib/libnode'].map(file => file + '.' + ext);
|
|
56
56
|
|
|
57
57
|
nodeExpFile = findAccessibleSync(logprefix, nodeRootDir, candidates);
|
|
58
|
-
if (nodeExpFile !== undefined) log.verbose(logprefix, '
|
|
58
|
+
if (nodeExpFile !== undefined) log.verbose(logprefix, '已找到导出文件: %s', nodeExpFile);
|
|
59
59
|
else {
|
|
60
|
-
const msg = msgFormat('
|
|
61
|
-
log.error(logprefix, '
|
|
60
|
+
const msg = msgFormat('未找到 node.%s 文件,目录: %s', ext, nodeRootDir);
|
|
61
|
+
log.error(logprefix, '未找到导出文件');
|
|
62
62
|
throw new Error(msg);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -66,14 +66,14 @@ const configure = async (gyp, argv) => {
|
|
|
66
66
|
// z/OS 需要设置 zoslib 头文件目录,其中包含 v8config.h 引用的头文件
|
|
67
67
|
let zoslibIncDir;
|
|
68
68
|
if (process.platform === 'os390') {
|
|
69
|
-
logprefix = "
|
|
69
|
+
logprefix = "查找 zoslib 的 zos-base.h:";
|
|
70
70
|
let msg, zoslibIncPath = process.env.ZOSLIB_INCLUDES;
|
|
71
71
|
if (zoslibIncPath) {
|
|
72
72
|
zoslibIncPath = findAccessibleSync(logprefix, zoslibIncPath, ['zos-base.h']);
|
|
73
73
|
if (zoslibIncPath === undefined) {
|
|
74
|
-
msg = msgFormat('
|
|
75
|
-
'
|
|
76
|
-
'
|
|
74
|
+
msg = msgFormat('在 ZOSLIB_INCLUDES 指定的目录中未找到 zos-base.h 文件: ' +
|
|
75
|
+
'%s; 请将其设为正确路径,或取消设置以便在 ' +
|
|
76
|
+
'%s 中搜索', process.env.ZOSLIB_INCLUDES, nodeRootDir)
|
|
77
77
|
}
|
|
78
78
|
} else {
|
|
79
79
|
candidates = [
|
|
@@ -82,14 +82,14 @@ const configure = async (gyp, argv) => {
|
|
|
82
82
|
]
|
|
83
83
|
zoslibIncPath = findAccessibleSync(logprefix, nodeRootDir, candidates);
|
|
84
84
|
if (zoslibIncPath === undefined) {
|
|
85
|
-
msg = msgFormat('
|
|
86
|
-
'
|
|
87
|
-
|
|
85
|
+
msg = msgFormat('未找到 %s 中列出的任意一项(目录: %s); ' +
|
|
86
|
+
'请设置环境变量 ZOSLIB_INCLUDES 指向包含 zos-base.h 的路径',
|
|
87
|
+
candidates.toString(), nodeRootDir);
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
if (zoslibIncPath !== undefined) {
|
|
91
91
|
zoslibIncDir = path.dirname(zoslibIncPath);
|
|
92
|
-
log.verbose(logprefix, "
|
|
92
|
+
log.verbose(logprefix, "在 %s 中找到 zoslib 的 zos-base.h", zoslibIncDir);
|
|
93
93
|
} else if (release.version.split('.')[0] >= 16) {
|
|
94
94
|
// zoslib 只随 Node v16 及以上发布
|
|
95
95
|
log.error(logprefix, msg);
|
|
@@ -148,7 +148,7 @@ const configure = async (gyp, argv) => {
|
|
|
148
148
|
await new Promise((resolve, reject) => {
|
|
149
149
|
const cp = gyp.spawn(python, argv);
|
|
150
150
|
cp.on('exit', code => {
|
|
151
|
-
if (code !== 0) reject(new Error('`gyp`
|
|
151
|
+
if (code !== 0) reject(new Error('`gyp` 执行失败,退出码: ' + code));
|
|
152
152
|
else resolve(); // 完成
|
|
153
153
|
})
|
|
154
154
|
})
|
|
@@ -158,9 +158,9 @@ const configure = async (gyp, argv) => {
|
|
|
158
158
|
if (!name) return runGyp();
|
|
159
159
|
|
|
160
160
|
const fullPath = path.resolve(name);
|
|
161
|
-
log.verbose(name, '
|
|
161
|
+
log.verbose(name, '正在检查 gypi 文件: %s', fullPath);
|
|
162
162
|
try {
|
|
163
|
-
await fs.stat(fullPath), log.verbose(name, '
|
|
163
|
+
await fs.stat(fullPath), log.verbose(name, '已找到 gypi 文件'), configs.push(fullPath);
|
|
164
164
|
} catch (err) {
|
|
165
165
|
if (err.code !== 'ENOENT') throw err; // ENOENT 时继续检查下一个 gypi 文件名
|
|
166
166
|
}
|
|
@@ -177,10 +177,10 @@ const configure = async (gyp, argv) => {
|
|
|
177
177
|
return findConfigs();
|
|
178
178
|
},
|
|
179
179
|
createBuildDir = async () => {
|
|
180
|
-
log.verbose('build
|
|
180
|
+
log.verbose('build 目录', '正在创建 "build" 目录: %s', buildDir);
|
|
181
181
|
|
|
182
182
|
const isNew = await fs.mkdir(buildDir, { recursive: true });
|
|
183
|
-
log.verbose('build
|
|
183
|
+
log.verbose('build 目录', '"build" 目录是否需要创建?', isNew ? '是' : '否');
|
|
184
184
|
vsInfo = win ? await findVisualStudio(release.semver, gyp.opts['msvs-version']) : null;
|
|
185
185
|
return createConfigFile();
|
|
186
186
|
},
|
|
@@ -191,23 +191,23 @@ const configure = async (gyp, argv) => {
|
|
|
191
191
|
if (gyp.opts.nodedir) {
|
|
192
192
|
// 指定了 --nodedir,用它作为开发文件目录
|
|
193
193
|
nodeDir = gyp.opts.nodedir.replace(/^~/, os.homedir());
|
|
194
|
-
log.verbose('
|
|
194
|
+
log.verbose('获取 node 目录', '使用指定的 --nodedir 开发文件编译: %s', nodeDir);
|
|
195
195
|
} else {
|
|
196
196
|
// 未指定 --nodedir,先确保 Node 依赖已安装
|
|
197
197
|
if ('v' + release.version !== process.version) {
|
|
198
198
|
// 指定了 --target,确定目标版本
|
|
199
|
-
log.verbose('
|
|
199
|
+
log.verbose('获取 node 目录', '按 --target 指定的 node 版本编译: %s', release.version);
|
|
200
200
|
} else {
|
|
201
201
|
// 未指定 --target,使用当前主机 Node 版本
|
|
202
|
-
log.verbose('
|
|
203
|
-
'
|
|
202
|
+
log.verbose('获取 node 目录',
|
|
203
|
+
'未指定 --target 版本,回退到宿主 node 版本: %s', release.version);
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
if (!release.semver) throw new Error('版本号无效: ' + release.version); // 无法用 semver 解析版本字符串
|
|
207
207
|
// 如果指定了 tarball 选项,总是删除并重装 headers 到 devdir,
|
|
208
208
|
// 否则只在不存在时安装
|
|
209
209
|
gyp.opts.ensure = !gyp.opts.tarball, await gyp.commands.install([release.version]);
|
|
210
|
-
log.verbose('
|
|
210
|
+
log.verbose('获取 node 目录', '目标 node 版本已安装:', release.versionDir);
|
|
211
211
|
nodeDir = path.resolve(gyp.devDir, release.versionDir);
|
|
212
212
|
}
|
|
213
213
|
|
|
@@ -216,5 +216,5 @@ const configure = async (gyp, argv) => {
|
|
|
216
216
|
return getNodeDir();
|
|
217
217
|
};
|
|
218
218
|
|
|
219
|
-
configure.usage = '
|
|
219
|
+
configure.usage = '为当前模块生成 ' + (win ? 'MSVC 项目文件' : 'Makefile') + ';';
|
|
220
220
|
export { configure };
|
|
@@ -25,7 +25,7 @@ const fs = gracefulFs.promises,
|
|
|
25
25
|
try {
|
|
26
26
|
const baseConfigGypi = await fs.readFile(path.resolve(nodeDir, 'include/node/config.gypi'));
|
|
27
27
|
return parseConfigGypi(baseConfigGypi.toString());
|
|
28
|
-
} catch (err) { log.warn('
|
|
28
|
+
} catch (err) { log.warn('读取 config.gypi', err.message) }
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
return JSON.parse(JSON.stringify(process.config)); // 回退到 process.config
|
|
@@ -89,7 +89,7 @@ const fs = gracefulFs.promises,
|
|
|
89
89
|
createConfigGypi = async ({ gyp, buildDir, nodeDir, vsInfo, python }) => {
|
|
90
90
|
const configFilename = 'config.gypi', configPath = path.resolve(buildDir, configFilename);
|
|
91
91
|
|
|
92
|
-
log.verbose('build/' + configFilename, '
|
|
92
|
+
log.verbose('build/' + configFilename, '正在创建配置文件');
|
|
93
93
|
const config = await getCurrentConfigGypi({ gyp, nodeDir, vsInfo, python }),
|
|
94
94
|
// 保证 config.gypi 中的布尔值都转成字符串
|
|
95
95
|
boolsToString = (k, v) => {
|
|
@@ -101,7 +101,7 @@ const fs = gracefulFs.promises,
|
|
|
101
101
|
// 把 config.gypi 写入 build/ 目录
|
|
102
102
|
const prefix = '# Do not edit. File was generated by node-gyp\'s "configure" step',
|
|
103
103
|
json = JSON.stringify(config, boolsToString, 2);
|
|
104
|
-
log.verbose('build/' + configFilename, '
|
|
104
|
+
log.verbose('build/' + configFilename, '正在写出配置文件: %s', configPath);
|
|
105
105
|
await fs.writeFile(configPath, [prefix, json, ''].join('\n'));
|
|
106
106
|
return configPath;
|
|
107
107
|
};
|
|
@@ -13,20 +13,20 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)),
|
|
|
13
13
|
|
|
14
14
|
// 看看上层目录结构,判断当前所处位置
|
|
15
15
|
const npmParentDirectory = path.join(scriptLocation, '../../../..');
|
|
16
|
-
log.verbose('
|
|
17
|
-
log.verbose('
|
|
16
|
+
log.verbose('nodejs-mobile-gyp 根目录', 'npm 上级目录为 ' + path.basename(npmParentDirectory));
|
|
17
|
+
log.verbose('nodejs-mobile-gyp 根目录', '正在查找 node 根目录');
|
|
18
18
|
let nodeRootDir = '';
|
|
19
19
|
if (path.basename(npmParentDirectory) === 'deps') {
|
|
20
20
|
// 处于构建目录中,脚本位于 deps/npm/node_modules/node-gyp/lib
|
|
21
21
|
nodeRootDir = path.join(npmParentDirectory, '..');
|
|
22
|
-
log.verbose('
|
|
22
|
+
log.verbose('nodejs-mobile-gyp 根目录', '在构建目录中,根目录 = ' + nodeRootDir);
|
|
23
23
|
} else if (path.basename(npmParentDirectory) === 'node_modules') {
|
|
24
24
|
// 处于 Node 安装目录中,脚本位于
|
|
25
25
|
// lib/node_modules/npm/node_modules/node-gyp/lib 或
|
|
26
26
|
// node_modules/npm/node_modules/node-gyp/lib(视平台而定)
|
|
27
27
|
if (processObj.platform === 'win32') nodeRootDir = path.join(npmParentDirectory, '..');
|
|
28
28
|
else nodeRootDir = path.join(npmParentDirectory, '../..');
|
|
29
|
-
log.verbose('
|
|
29
|
+
log.verbose('nodejs-mobile-gyp 根目录', '在安装目录中,根目录 = ' + nodeRootDir);
|
|
30
30
|
} else {
|
|
31
31
|
// 无法判断,尝试从 node 可执行文件的位置推断
|
|
32
32
|
const nodeDir = path.dirname(processObj.execPath), directoryUp = path.basename(nodeDir);
|
package/lib/find-python.js
CHANGED
|
@@ -51,7 +51,7 @@ const win = process.platform === 'win32',
|
|
|
51
51
|
class PythonFinder {
|
|
52
52
|
static findPython = (...args) => new PythonFinder(...args).findPython();
|
|
53
53
|
|
|
54
|
-
log = log.withPrefix('
|
|
54
|
+
log = log.withPrefix('查找 Python');
|
|
55
55
|
argsExecutable = ['-c', 'import sys; print(sys.executable);'];
|
|
56
56
|
argsVersion = ['-c', 'import sys; print("%s.%s.%s" % sys.version_info[:3]);'];
|
|
57
57
|
semverRange = '>=3.6.0';
|
|
@@ -78,8 +78,8 @@ class PythonFinder {
|
|
|
78
78
|
if (this.env.NODE_GYP_FORCE_PYTHON) {
|
|
79
79
|
return [{
|
|
80
80
|
before: () => {
|
|
81
|
-
this.addLog('
|
|
82
|
-
this.addLog('- process.env.NODE_GYP_FORCE_PYTHON
|
|
81
|
+
this.addLog('检查是否通过 NODE_GYP_FORCE_PYTHON 显式指定 Python');
|
|
82
|
+
this.addLog('- process.env.NODE_GYP_FORCE_PYTHON 为 ' + `"${this.env.NODE_GYP_FORCE_PYTHON}"`);
|
|
83
83
|
}, check: () => this.checkCommand(this.env.NODE_GYP_FORCE_PYTHON)
|
|
84
84
|
}];
|
|
85
85
|
}
|
|
@@ -87,21 +87,21 @@ class PythonFinder {
|
|
|
87
87
|
{
|
|
88
88
|
before: () => {
|
|
89
89
|
if (!this.configPython) {
|
|
90
|
-
this.addLog('
|
|
90
|
+
this.addLog('未从命令行或 npm 配置指定 Python');
|
|
91
91
|
return SKIP;
|
|
92
92
|
}
|
|
93
|
-
this.addLog('
|
|
94
|
-
this.addLog('- "--python="
|
|
93
|
+
this.addLog('检查是否从命令行或 npm 配置显式指定 Python');
|
|
94
|
+
this.addLog('- "--python=" 或 "npm config get python" 的值为 ' + `"${this.configPython}"`);
|
|
95
95
|
}, check: () => this.checkCommand(this.configPython)
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
before: () => {
|
|
99
99
|
if (!this.env.PYTHON) {
|
|
100
|
-
this.addLog('
|
|
100
|
+
this.addLog('未从环境变量 PYTHON 指定 Python');
|
|
101
101
|
return SKIP;
|
|
102
102
|
}
|
|
103
|
-
this.addLog('
|
|
104
|
-
this.addLog(`- process.env.PYTHON
|
|
103
|
+
this.addLog('检查是否从环境变量 PYTHON 显式指定 Python');
|
|
104
|
+
this.addLog(`- process.env.PYTHON 为 "${this.env.PYTHON}"`);
|
|
105
105
|
},
|
|
106
106
|
check: () => this.checkCommand(this.env.PYTHON)
|
|
107
107
|
}
|
|
@@ -110,23 +110,23 @@ class PythonFinder {
|
|
|
110
110
|
if (this.win) {
|
|
111
111
|
for (const launcher of pyLauncherCandidates) {
|
|
112
112
|
checks.push({
|
|
113
|
-
before: () => this.addLog(
|
|
113
|
+
before: () => this.addLog(`检查能否用 "${launcher}" 查找 Python 3`),
|
|
114
114
|
check: () => this.checkPyLauncher(launcher)
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
117
|
checks.push({
|
|
118
|
-
before: () => this.addLog('
|
|
118
|
+
before: () => this.addLog('通过 "py -0p" 枚举已安装的 Python'),
|
|
119
119
|
check: () => this.checkPyList()
|
|
120
120
|
});
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
checks.push(...[
|
|
124
124
|
{
|
|
125
|
-
before: () => { this.addLog('
|
|
125
|
+
before: () => { this.addLog('检查能否使用 "python3"'); },
|
|
126
126
|
check: () => this.checkCommand('python3')
|
|
127
127
|
},
|
|
128
128
|
{
|
|
129
|
-
before: () => { this.addLog('
|
|
129
|
+
before: () => { this.addLog('检查能否使用 "python"'); },
|
|
130
130
|
check: () => this.checkCommand('python')
|
|
131
131
|
}
|
|
132
132
|
]);
|
|
@@ -135,7 +135,7 @@ class PythonFinder {
|
|
|
135
135
|
for (let i = 0; i < this.winDefaultLocations.length; ++i) {
|
|
136
136
|
const location = this.winDefaultLocations[i];
|
|
137
137
|
checks.push({
|
|
138
|
-
before: () => this.addLog(
|
|
138
|
+
before: () => this.addLog(`检查 Python 是否位于 ${location}`),
|
|
139
139
|
check: () => this.checkExecPath(location)
|
|
140
140
|
});
|
|
141
141
|
}
|
|
@@ -160,33 +160,33 @@ class PythonFinder {
|
|
|
160
160
|
|
|
161
161
|
// 检查命令是否为可用的 Python;
|
|
162
162
|
async checkCommand(command) {
|
|
163
|
-
this.log.verbose(`-
|
|
163
|
+
this.log.verbose(`- 执行 "${command}" 以获取可执行文件路径`);
|
|
164
164
|
try {
|
|
165
165
|
const execPath = await this.run(command, this.argsExecutable, false);
|
|
166
|
-
this.addLog(`-
|
|
166
|
+
this.addLog(`- 可执行文件路径为 "${execPath}"`);
|
|
167
167
|
return this.checkExecPath(execPath);
|
|
168
168
|
} catch (err) {
|
|
169
|
-
this.addLog(`- "${command}"
|
|
169
|
+
this.addLog(`- "${command}" 不在 PATH 中或执行出错`);
|
|
170
170
|
throw err;
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
173
|
|
|
174
174
|
// 检查指定 py 启动器是否能找到 Python 3;
|
|
175
175
|
async checkPyLauncher(launcher = this.pyLauncher) {
|
|
176
|
-
this.log.verbose(`-
|
|
176
|
+
this.log.verbose(`- 执行 "${launcher}" 以获取 Python 3 可执行文件路径`);
|
|
177
177
|
try {
|
|
178
178
|
const execPath = await this.run(launcher, ['-3', ...this.argsExecutable], false);
|
|
179
|
-
this.addLog(`-
|
|
179
|
+
this.addLog(`- 可执行文件路径为 "${execPath}"`);
|
|
180
180
|
return this.checkExecPath(execPath);
|
|
181
181
|
} catch (err) {
|
|
182
|
-
this.addLog(`- "${launcher}"
|
|
182
|
+
this.addLog(`- "${launcher}" 不在 PATH 中或执行出错`);
|
|
183
183
|
throw err;
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
// 通过 "py -0p" 枚举所有已注册的 Python,覆盖非标准安装位置;
|
|
188
188
|
async checkPyList() {
|
|
189
|
-
this.log.verbose('-
|
|
189
|
+
this.log.verbose('- 执行 "py -0p" 以列出已注册的 Python');
|
|
190
190
|
let lastErr;
|
|
191
191
|
for (const launcher of pyLauncherCandidates) {
|
|
192
192
|
try {
|
|
@@ -196,29 +196,29 @@ class PythonFinder {
|
|
|
196
196
|
const m = line.match(/([A-Za-z]:\\[^\s]+)/);
|
|
197
197
|
if (m && /python\.exe$/i.test(m[1])) paths.push(m[1]);
|
|
198
198
|
}
|
|
199
|
-
this.addLog(`- "py -0p" (via ${launcher})
|
|
199
|
+
this.addLog(`- "py -0p" (via ${launcher}) 找到 ${paths.length} 个路径`);
|
|
200
200
|
for (const p of paths) {
|
|
201
201
|
try {
|
|
202
202
|
return await this.checkExecPath(p);
|
|
203
203
|
} catch (err) {
|
|
204
204
|
lastErr = err;
|
|
205
|
-
this.log.silly('checkPyList:
|
|
205
|
+
this.log.silly('checkPyList: 跳过 %s: %s', p, err.message);
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
208
|
} catch (err) {
|
|
209
209
|
lastErr = err;
|
|
210
|
-
this.addLog(`-
|
|
210
|
+
this.addLog(`- 经 "${launcher}" 执行 "py -0p" 失败`);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
throw lastErr || new Error('py -0p
|
|
213
|
+
throw lastErr || new Error('py -0p 未返回可用的 Python');
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
// 检查某个 Python 可执行文件版本是否可用;
|
|
217
217
|
async checkExecPath(execPath) {
|
|
218
|
-
this.log.verbose(`-
|
|
218
|
+
this.log.verbose(`- 执行 "${execPath}" 以获取版本`);
|
|
219
219
|
try {
|
|
220
220
|
const version = await this.run(execPath, this.argsVersion, false);
|
|
221
|
-
this.addLog(`-
|
|
221
|
+
this.addLog(`- 版本为 "${version}"`);
|
|
222
222
|
|
|
223
223
|
const range = new semver.Range(this.semverRange);
|
|
224
224
|
let valid = false;
|
|
@@ -226,18 +226,18 @@ class PythonFinder {
|
|
|
226
226
|
valid = range.test(version);
|
|
227
227
|
} catch (err) {
|
|
228
228
|
this.log.silly('range.test() threw:\n%s', err.stack);
|
|
229
|
-
this.addLog(`- "${execPath}"
|
|
230
|
-
this.addLog('-
|
|
229
|
+
this.addLog(`- "${execPath}" 的版本无效`);
|
|
230
|
+
this.addLog('- 它是不是 Python 可执行文件?');
|
|
231
231
|
throw err;
|
|
232
232
|
}
|
|
233
233
|
if (!valid) {
|
|
234
|
-
this.addLog(`-
|
|
235
|
-
this.addLog('-
|
|
236
|
-
throw new Error(
|
|
234
|
+
this.addLog(`- 版本为 ${version},应为 ${this.semverRange}`);
|
|
235
|
+
this.addLog('- 此版本的 Python 不受支持');
|
|
236
|
+
throw new Error(`不支持的 Python 版本 ${version}`);
|
|
237
237
|
}
|
|
238
238
|
return this.succeed(execPath, version);
|
|
239
239
|
} catch (err) {
|
|
240
|
-
this.addLog(`- "${execPath}"
|
|
240
|
+
this.addLog(`- "${execPath}" 无法运行`);
|
|
241
241
|
throw err;
|
|
242
242
|
}
|
|
243
243
|
}
|
|
@@ -251,9 +251,9 @@ class PythonFinder {
|
|
|
251
251
|
this.log.silly('execFile: args = %j', args);
|
|
252
252
|
this.log.silly('execFile: opts = %j', opts);
|
|
253
253
|
const [err, stdout, stderr] = await this.execFile(exec, args, opts);
|
|
254
|
-
this.log.silly('execFile
|
|
255
|
-
this.log.silly('execFile
|
|
256
|
-
this.log.silly('execFile
|
|
254
|
+
this.log.silly('execFile 结果: 错误 = %j', (err && err.stack) || err);
|
|
255
|
+
this.log.silly('execFile 结果: 标准输出 = %j', stdout);
|
|
256
|
+
this.log.silly('execFile 结果: 标准错误 = %j', stderr);
|
|
257
257
|
if (err) throw err;
|
|
258
258
|
return stdout.trim();
|
|
259
259
|
}
|
|
@@ -268,15 +268,15 @@ class PythonFinder {
|
|
|
268
268
|
pathExample = this.win ? 'C:\\Path\\To\\python.exe' : '/path/to/pythonexecutable',
|
|
269
269
|
info = [
|
|
270
270
|
'**********************************************************',
|
|
271
|
-
'
|
|
272
|
-
'
|
|
273
|
-
'
|
|
274
|
-
`-
|
|
275
|
-
' (
|
|
276
|
-
'-
|
|
277
|
-
'-
|
|
271
|
+
'需要安装最新版本的 Python;',
|
|
272
|
+
'应能自动找到并使用 Python;若找不到,',
|
|
273
|
+
'可尝试以下任一方式:',
|
|
274
|
+
`- 使用 --python="${pathExample}" 开关`,
|
|
275
|
+
' (npm 与本包均接受该参数)',
|
|
276
|
+
'- 设置环境变量 PYTHON',
|
|
277
|
+
'- 设置 npm 配置项 python:',
|
|
278
278
|
` npm config set python "${pathExample}"`,
|
|
279
|
-
'
|
|
279
|
+
'更多说明见文档:',
|
|
280
280
|
'https://github.com/nodejs/node-gyp#installation',
|
|
281
281
|
'**********************************************************'
|
|
282
282
|
].join('\n');
|