@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/find-python.js
CHANGED
|
@@ -1,303 +1,289 @@
|
|
|
1
|
-
|
|
2
|
-
import semver from 'semver'
|
|
3
|
-
import { execFile } from './util.js'
|
|
4
|
-
import os from 'os'
|
|
1
|
+
import * as log from './log.js';
|
|
2
|
+
import semver from 'semver';
|
|
3
|
+
import { execFile } from './util.js';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import fs from 'fs';
|
|
5
6
|
|
|
6
|
-
const win = process.platform === 'win32'
|
|
7
|
+
const win = process.platform === 'win32',
|
|
8
|
+
/**
|
|
9
|
+
* 获取当前操作系统用户名(失败时返回 undefined)
|
|
10
|
+
*/
|
|
11
|
+
getOsUserInfo = () => {
|
|
12
|
+
try { return os.userInfo().username } catch { }
|
|
13
|
+
},
|
|
14
|
+
systemDrive = process.env.SystemDrive || 'C:',
|
|
15
|
+
username = process.env.USERNAME || process.env.USER || getOsUserInfo(),
|
|
16
|
+
localAppData = process.env.LOCALAPPDATA || `${systemDrive}\\${username}\\AppData\\Local`,
|
|
17
|
+
foundLocalAppData = process.env.LOCALAPPDATA || username,
|
|
18
|
+
programFiles = process.env.ProgramW6432 || process.env.ProgramFiles || `${systemDrive}\\Program Files`,
|
|
19
|
+
programFilesX86 = process.env['ProgramFiles(x86)'] || `${programFiles} (x86)`,
|
|
20
|
+
systemRoot = process.env.SystemRoot || `${systemDrive}\\Windows`,
|
|
21
|
+
/**
|
|
22
|
+
* 动态扫描 Windows 上所有已安装的 Python,不硬编码版本号。
|
|
23
|
+
* 官方安装器目录命名:3.14 -> Python314,3.9 -> Python39,32 位加 -32 后缀。
|
|
24
|
+
*/
|
|
25
|
+
scanWindowsPythonLocations = () => {
|
|
26
|
+
const results = [], bases = [foundLocalAppData ? `${localAppData}\\Programs\\Python` : null, `${programFiles}\\Python`,
|
|
27
|
+
`${programFilesX86}\\Python`].filter(Boolean), seen = new Set();
|
|
7
28
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (foundLocalAppData) {
|
|
27
|
-
winDefaultLocationsArray.push(
|
|
28
|
-
`${localAppData}\\Programs\\Python\\Python${majorMinor}\\python.exe`,
|
|
29
|
-
`${programFiles}\\Python${majorMinor}\\python.exe`,
|
|
30
|
-
`${localAppData}\\Programs\\Python\\Python${majorMinor}-32\\python.exe`,
|
|
31
|
-
`${programFiles}\\Python${majorMinor}-32\\python.exe`,
|
|
32
|
-
`${programFilesX86}\\Python${majorMinor}-32\\python.exe`
|
|
33
|
-
)
|
|
34
|
-
} else {
|
|
35
|
-
winDefaultLocationsArray.push(
|
|
36
|
-
`${programFiles}\\Python${majorMinor}\\python.exe`,
|
|
37
|
-
`${programFiles}\\Python${majorMinor}-32\\python.exe`,
|
|
38
|
-
`${programFilesX86}\\Python${majorMinor}-32\\python.exe`
|
|
39
|
-
)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
29
|
+
for (const base of bases) {
|
|
30
|
+
if (seen.has(base)) continue;
|
|
31
|
+
seen.add(base);
|
|
32
|
+
let entries;
|
|
33
|
+
try { entries = fs.readdirSync(base); } catch { continue; }
|
|
34
|
+
for (const name of entries) {
|
|
35
|
+
if (!/^Python\d+/i.test(name)) continue;
|
|
36
|
+
const exe = `${base}\\${name}\\python.exe`;
|
|
37
|
+
try { if (fs.statSync(exe).isFile()) results.push(exe); } catch { }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return results;
|
|
41
|
+
},
|
|
42
|
+
/**
|
|
43
|
+
* py.exe 启动器候选:先试 PATH 中的,再试常见绝对路径,
|
|
44
|
+
* 避免 Gradle / CI 环境下 PATH 不含 C:\Windows 的情况。
|
|
45
|
+
*/
|
|
46
|
+
pyLauncherCandidates = win ? ['py.exe', `${systemRoot}\\py.exe`, `${systemRoot}\\System32\\py.exe`] : [];
|
|
42
47
|
|
|
43
48
|
/**
|
|
44
49
|
* Python 查找器:按顺序尝试多种途径定位可用的 Python 3.6+
|
|
45
50
|
*/
|
|
46
51
|
class PythonFinder {
|
|
47
|
-
static findPython = (...args) => new PythonFinder(...args).findPython()
|
|
52
|
+
static findPython = (...args) => new PythonFinder(...args).findPython();
|
|
48
53
|
|
|
49
|
-
log = log.withPrefix('find Python')
|
|
50
|
-
argsExecutable = ['-c', 'import sys; print(sys.executable);']
|
|
51
|
-
argsVersion = ['-c', 'import sys; print("%s.%s.%s" % sys.version_info[:3]);']
|
|
52
|
-
semverRange = '>=3.6.0'
|
|
54
|
+
log = log.withPrefix('find Python');
|
|
55
|
+
argsExecutable = ['-c', 'import sys; print(sys.executable);'];
|
|
56
|
+
argsVersion = ['-c', 'import sys; print("%s.%s.%s" % sys.version_info[:3]);'];
|
|
57
|
+
semverRange = '>=3.6.0';
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
winDefaultLocations = winDefaultLocationsArray
|
|
59
|
+
execFile = execFile;
|
|
60
|
+
env = process.env;
|
|
61
|
+
win = win;
|
|
62
|
+
pyLauncher = 'py.exe';
|
|
63
|
+
winDefaultLocations = win ? scanWindowsPythonLocations() : [];
|
|
60
64
|
|
|
61
|
-
constructor
|
|
62
|
-
this.configPython = configPython
|
|
63
|
-
this.errorLog = []
|
|
65
|
+
constructor(configPython) {
|
|
66
|
+
this.configPython = configPython, this.errorLog = [];
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
// 以 verbose 级别记录日志,同时保存下来,出错时以 error 级别展示,便于诊断
|
|
67
|
-
addLog
|
|
68
|
-
this.log.verbose(message)
|
|
69
|
-
this.errorLog.push(message)
|
|
70
|
+
addLog(message) {
|
|
71
|
+
this.log.verbose(message), this.errorLog.push(message);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
// 依次尝试多种 Python 定位方式。忽略错误,直到找到可用版本
|
|
73
|
-
async findPython
|
|
74
|
-
const SKIP = 0
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
async findPython() {
|
|
76
|
+
const SKIP = 0, FAIL = 1,
|
|
77
|
+
toCheck = (() => {
|
|
78
|
+
if (this.env.NODE_GYP_FORCE_PYTHON) {
|
|
79
|
+
return [{
|
|
80
|
+
before: () => {
|
|
81
|
+
this.addLog('checking Python explicitly set from NODE_GYP_FORCE_PYTHON');
|
|
82
|
+
this.addLog('- process.env.NODE_GYP_FORCE_PYTHON is ' + `"${this.env.NODE_GYP_FORCE_PYTHON}"`);
|
|
83
|
+
}, check: () => this.checkCommand(this.env.NODE_GYP_FORCE_PYTHON)
|
|
84
|
+
}];
|
|
85
|
+
}
|
|
86
|
+
const checks = [
|
|
87
|
+
{
|
|
88
|
+
before: () => {
|
|
89
|
+
if (!this.configPython) {
|
|
90
|
+
this.addLog('Python is not set from command line or npm configuration');
|
|
91
|
+
return SKIP;
|
|
92
|
+
}
|
|
93
|
+
this.addLog('checking Python explicitly set from command line or ' + 'npm configuration');
|
|
94
|
+
this.addLog('- "--python=" or "npm config get python" is ' + `"${this.configPython}"`);
|
|
95
|
+
}, check: () => this.checkCommand(this.configPython)
|
|
84
96
|
},
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
{
|
|
98
|
+
before: () => {
|
|
99
|
+
if (!this.env.PYTHON) {
|
|
100
|
+
this.addLog('Python is not set from environment variable ' + 'PYTHON');
|
|
101
|
+
return SKIP;
|
|
102
|
+
}
|
|
103
|
+
this.addLog('checking Python explicitly set from environment ' + 'variable PYTHON');
|
|
104
|
+
this.addLog(`- process.env.PYTHON is "${this.env.PYTHON}"`);
|
|
105
|
+
},
|
|
106
|
+
check: () => this.checkCommand(this.env.PYTHON)
|
|
107
|
+
}
|
|
108
|
+
];
|
|
88
109
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this.addLog('
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`"${this.configPython}"`)
|
|
101
|
-
},
|
|
102
|
-
check: () => this.checkCommand(this.configPython)
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
before: () => {
|
|
106
|
-
if (!this.env.PYTHON) {
|
|
107
|
-
this.addLog('Python is not set from environment variable ' +
|
|
108
|
-
'PYTHON')
|
|
109
|
-
return SKIP
|
|
110
|
-
}
|
|
111
|
-
this.addLog('checking Python explicitly set from environment ' +
|
|
112
|
-
'variable PYTHON')
|
|
113
|
-
this.addLog(`- process.env.PYTHON is "${this.env.PYTHON}"`)
|
|
114
|
-
},
|
|
115
|
-
check: () => this.checkCommand(this.env.PYTHON)
|
|
110
|
+
if (this.win) {
|
|
111
|
+
for (const launcher of pyLauncherCandidates) {
|
|
112
|
+
checks.push({
|
|
113
|
+
before: () => this.addLog(`checking if "${launcher}" can be used to find Python 3`),
|
|
114
|
+
check: () => this.checkPyLauncher(launcher)
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
checks.push({
|
|
118
|
+
before: () => this.addLog('enumerating installed Pythons via "py -0p"'),
|
|
119
|
+
check: () => this.checkPyList()
|
|
120
|
+
});
|
|
116
121
|
}
|
|
117
|
-
]
|
|
118
122
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.
|
|
123
|
-
'checking if the py launcher can be used to find Python 3')
|
|
123
|
+
checks.push(...[
|
|
124
|
+
{
|
|
125
|
+
before: () => { this.addLog('checking if "python3" can be used'); },
|
|
126
|
+
check: () => this.checkCommand('python3')
|
|
124
127
|
},
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
{
|
|
131
|
-
before: () => { this.addLog('checking if "python3" can be used') },
|
|
132
|
-
check: () => this.checkCommand('python3')
|
|
133
|
-
},
|
|
134
|
-
{
|
|
135
|
-
before: () => { this.addLog('checking if "python" can be used') },
|
|
136
|
-
check: () => this.checkCommand('python')
|
|
137
|
-
}
|
|
138
|
-
])
|
|
128
|
+
{
|
|
129
|
+
before: () => { this.addLog('checking if "python" can be used'); },
|
|
130
|
+
check: () => this.checkCommand('python')
|
|
131
|
+
}
|
|
132
|
+
]);
|
|
139
133
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
134
|
+
if (this.win) {
|
|
135
|
+
for (let i = 0; i < this.winDefaultLocations.length; ++i) {
|
|
136
|
+
const location = this.winDefaultLocations[i];
|
|
137
|
+
checks.push({
|
|
138
|
+
before: () => this.addLog(`checking if Python is ${location}`),
|
|
139
|
+
check: () => this.checkExecPath(location)
|
|
140
|
+
});
|
|
141
|
+
}
|
|
147
142
|
}
|
|
148
|
-
}
|
|
149
143
|
|
|
150
|
-
|
|
151
|
-
|
|
144
|
+
return checks;
|
|
145
|
+
})();
|
|
152
146
|
|
|
153
147
|
for (const check of toCheck) {
|
|
154
|
-
const before = check.before()
|
|
155
|
-
if (before === SKIP)
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
if (before === FAIL) {
|
|
159
|
-
return this.fail()
|
|
160
|
-
}
|
|
148
|
+
const before = check.before();
|
|
149
|
+
if (before === SKIP) continue;
|
|
150
|
+
if (before === FAIL) return this.fail();
|
|
161
151
|
try {
|
|
162
|
-
return await check.check()
|
|
152
|
+
return await check.check();
|
|
163
153
|
} catch (err) {
|
|
164
|
-
this.log.silly('runChecks: err = %j', (err && err.stack) || err)
|
|
154
|
+
this.log.silly('runChecks: err = %j', (err && err.stack) || err);
|
|
165
155
|
}
|
|
166
156
|
}
|
|
167
157
|
|
|
168
|
-
return this.fail()
|
|
158
|
+
return this.fail();
|
|
169
159
|
}
|
|
170
160
|
|
|
171
|
-
// 检查命令是否为可用的 Python
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
let exec = command
|
|
175
|
-
let args = this.argsExecutable
|
|
176
|
-
let shell = false
|
|
177
|
-
if (this.win) {
|
|
178
|
-
// 参数需手动加引号
|
|
179
|
-
exec = `"${exec}"`
|
|
180
|
-
args = args.map(a => `"${a}"`)
|
|
181
|
-
shell = true
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
this.log.verbose(`- executing "${command}" to get executable path`)
|
|
185
|
-
// 可能结果:
|
|
186
|
-
// - 报错:不在 PATH、不可执行或执行失败
|
|
187
|
-
// - 乱码:后续版本检查会失败
|
|
188
|
-
// - 可执行文件的绝对路径
|
|
161
|
+
// 检查命令是否为可用的 Python。
|
|
162
|
+
async checkCommand(command) {
|
|
163
|
+
this.log.verbose(`- executing "${command}" to get executable path`);
|
|
189
164
|
try {
|
|
190
|
-
const execPath = await this.run(
|
|
191
|
-
this.addLog(`- executable path is "${execPath}"`)
|
|
192
|
-
return this.checkExecPath(execPath)
|
|
165
|
+
const execPath = await this.run(command, this.argsExecutable, false);
|
|
166
|
+
this.addLog(`- executable path is "${execPath}"`);
|
|
167
|
+
return this.checkExecPath(execPath);
|
|
193
168
|
} catch (err) {
|
|
194
|
-
this.addLog(`- "${command}" is not in PATH or produced an error`)
|
|
195
|
-
throw err
|
|
169
|
+
this.addLog(`- "${command}" is not in PATH or produced an error`);
|
|
170
|
+
throw err;
|
|
196
171
|
}
|
|
197
172
|
}
|
|
198
173
|
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
// 由于 "py.exe -3" 对 execFile 来说不是合法可执行文件,需要用启动器找出真正的 python.exe 路径。
|
|
203
|
-
async checkPyLauncher () {
|
|
204
|
-
this.log.verbose(`- executing "${this.pyLauncher}" to get Python 3 executable path`)
|
|
205
|
-
// 可能结果:与 checkCommand 相同
|
|
174
|
+
// 检查指定 py 启动器是否能找到 Python 3。
|
|
175
|
+
async checkPyLauncher(launcher = this.pyLauncher) {
|
|
176
|
+
this.log.verbose(`- executing "${launcher}" to get Python 3 executable path`);
|
|
206
177
|
try {
|
|
207
|
-
const execPath = await this.run(
|
|
208
|
-
this.addLog(`- executable path is "${execPath}"`)
|
|
209
|
-
return this.checkExecPath(execPath)
|
|
178
|
+
const execPath = await this.run(launcher, ['-3', ...this.argsExecutable], false);
|
|
179
|
+
this.addLog(`- executable path is "${execPath}"`);
|
|
180
|
+
return this.checkExecPath(execPath);
|
|
210
181
|
} catch (err) {
|
|
211
|
-
this.addLog(`- "${
|
|
212
|
-
throw err
|
|
182
|
+
this.addLog(`- "${launcher}" is not in PATH or produced an error`);
|
|
183
|
+
throw err;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 通过 "py -0p" 枚举所有已注册的 Python,覆盖非标准安装位置。
|
|
188
|
+
async checkPyList() {
|
|
189
|
+
this.log.verbose('- executing "py -0p" to list registered Pythons');
|
|
190
|
+
let lastErr;
|
|
191
|
+
for (const launcher of pyLauncherCandidates) {
|
|
192
|
+
try {
|
|
193
|
+
const stdout = await this.run(launcher, ['-0p'], false);
|
|
194
|
+
const paths = [];
|
|
195
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
196
|
+
const m = line.match(/([A-Za-z]:\\[^\s]+)/);
|
|
197
|
+
if (m && /python\.exe$/i.test(m[1])) paths.push(m[1]);
|
|
198
|
+
}
|
|
199
|
+
this.addLog(`- "py -0p" (via ${launcher}) found ${paths.length} path(s)`);
|
|
200
|
+
for (const p of paths) {
|
|
201
|
+
try {
|
|
202
|
+
return await this.checkExecPath(p);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
lastErr = err;
|
|
205
|
+
this.log.silly('checkPyList: skipping %s: %s', p, err.message);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch (err) {
|
|
209
|
+
lastErr = err;
|
|
210
|
+
this.addLog(`- "py -0p" via "${launcher}" failed`);
|
|
211
|
+
}
|
|
213
212
|
}
|
|
213
|
+
throw lastErr || new Error('py -0p returned no usable Python');
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
-
// 检查某个 Python
|
|
217
|
-
async checkExecPath
|
|
218
|
-
this.log.verbose(`- executing "${execPath}" to get version`)
|
|
219
|
-
// 可能结果:
|
|
220
|
-
// - 报错:可执行文件无法运行(多半上一步输出了乱码)
|
|
221
|
-
// - 乱码:上一步不知为何输出了可执行文件路径,验证版本时会失败
|
|
222
|
-
// - Python 可执行文件的版本号
|
|
216
|
+
// 检查某个 Python 可执行文件版本是否可用。
|
|
217
|
+
async checkExecPath(execPath) {
|
|
218
|
+
this.log.verbose(`- executing "${execPath}" to get version`);
|
|
223
219
|
try {
|
|
224
|
-
const version = await this.run(execPath, this.argsVersion, false)
|
|
225
|
-
this.addLog(`- version is "${version}"`)
|
|
220
|
+
const version = await this.run(execPath, this.argsVersion, false);
|
|
221
|
+
this.addLog(`- version is "${version}"`);
|
|
226
222
|
|
|
227
|
-
const range = new semver.Range(this.semverRange)
|
|
228
|
-
let valid = false
|
|
223
|
+
const range = new semver.Range(this.semverRange);
|
|
224
|
+
let valid = false;
|
|
229
225
|
try {
|
|
230
|
-
valid = range.test(version)
|
|
226
|
+
valid = range.test(version);
|
|
231
227
|
} catch (err) {
|
|
232
|
-
this.log.silly('range.test() threw:\n%s', err.stack)
|
|
233
|
-
this.addLog(`- "${execPath}" does not have a valid version`)
|
|
234
|
-
this.addLog('- is it a Python executable?')
|
|
235
|
-
throw err
|
|
228
|
+
this.log.silly('range.test() threw:\n%s', err.stack);
|
|
229
|
+
this.addLog(`- "${execPath}" does not have a valid version`);
|
|
230
|
+
this.addLog('- is it a Python executable?');
|
|
231
|
+
throw err;
|
|
236
232
|
}
|
|
237
233
|
if (!valid) {
|
|
238
|
-
this.addLog(`- version is ${version} - should be ${this.semverRange}`)
|
|
239
|
-
this.addLog('- THIS VERSION OF PYTHON IS NOT SUPPORTED')
|
|
240
|
-
throw new Error(`Found unsupported Python version ${version}`)
|
|
234
|
+
this.addLog(`- version is ${version} - should be ${this.semverRange}`);
|
|
235
|
+
this.addLog('- THIS VERSION OF PYTHON IS NOT SUPPORTED');
|
|
236
|
+
throw new Error(`Found unsupported Python version ${version}`);
|
|
241
237
|
}
|
|
242
|
-
return this.succeed(execPath, version)
|
|
238
|
+
return this.succeed(execPath, version);
|
|
243
239
|
} catch (err) {
|
|
244
|
-
this.addLog(`- "${execPath}" could not be run`)
|
|
245
|
-
throw err
|
|
240
|
+
this.addLog(`- "${execPath}" could not be run`);
|
|
241
|
+
throw err;
|
|
246
242
|
}
|
|
247
243
|
}
|
|
248
244
|
|
|
249
|
-
//
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
env.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
this.log.silly('execFile:
|
|
256
|
-
this.log.silly('execFile:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return stdout.trim()
|
|
264
|
-
} catch (err) {
|
|
265
|
-
this.log.silly('execFile: threw:\n%s', err.stack)
|
|
266
|
-
throw err
|
|
267
|
-
}
|
|
245
|
+
// 执行外部命令。execFile 包装不 reject,通过 [err, stdout, stderr] 返回结果,
|
|
246
|
+
// 必须显式检查 err,否则失败会被当成"成功但空输出",掩盖真实原因。
|
|
247
|
+
async run(exec, args, shell) {
|
|
248
|
+
const env = Object.assign({}, this.env), opts = { env, shell };
|
|
249
|
+
env.TERM = 'dumb';
|
|
250
|
+
this.log.silly('execFile: exec = %j', exec);
|
|
251
|
+
this.log.silly('execFile: args = %j', args);
|
|
252
|
+
this.log.silly('execFile: opts = %j', opts);
|
|
253
|
+
const [err, stdout, stderr] = await this.execFile(exec, args, opts);
|
|
254
|
+
this.log.silly('execFile result: err = %j', (err && err.stack) || err);
|
|
255
|
+
this.log.silly('execFile result: stdout = %j', stdout);
|
|
256
|
+
this.log.silly('execFile result: stderr = %j', stderr);
|
|
257
|
+
if (err) throw err;
|
|
258
|
+
return stdout.trim();
|
|
268
259
|
}
|
|
269
260
|
|
|
270
|
-
succeed
|
|
271
|
-
this.log.info(`using Python version ${version} found at "${execPath}"`)
|
|
272
|
-
return execPath
|
|
261
|
+
succeed(execPath, version) {
|
|
262
|
+
this.log.info(`using Python version ${version} found at "${execPath}"`);
|
|
263
|
+
return execPath;
|
|
273
264
|
}
|
|
274
265
|
|
|
275
|
-
fail
|
|
276
|
-
const errorLog = this.errorLog.join('\n')
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
'
|
|
292
|
-
` npm config set python "${pathExample}"`,
|
|
293
|
-
'For more information consult the documentation at:',
|
|
294
|
-
'https://github.com/nodejs/node-gyp#installation',
|
|
295
|
-
'**********************************************************'
|
|
296
|
-
].join('\n')
|
|
266
|
+
fail() {
|
|
267
|
+
const errorLog = this.errorLog.join('\n'),
|
|
268
|
+
pathExample = this.win ? 'C:\\Path\\To\\python.exe' : '/path/to/pythonexecutable',
|
|
269
|
+
info = [
|
|
270
|
+
'**********************************************************',
|
|
271
|
+
'You need to install the latest version of Python.',
|
|
272
|
+
'Node-gyp should be able to find and use Python. If not,',
|
|
273
|
+
'you can try one of the following options:',
|
|
274
|
+
`- Use the switch --python="${pathExample}"`,
|
|
275
|
+
' (accepted by both node-gyp and npm)',
|
|
276
|
+
'- Set the environment variable PYTHON',
|
|
277
|
+
'- Set the npm configuration variable python:',
|
|
278
|
+
` npm config set python "${pathExample}"`,
|
|
279
|
+
'For more information consult the documentation at:',
|
|
280
|
+
'https://github.com/nodejs/node-gyp#installation',
|
|
281
|
+
'**********************************************************'
|
|
282
|
+
].join('\n');
|
|
297
283
|
|
|
298
|
-
this.log.error(`\n${errorLog}\n\n${info}\n`)
|
|
299
|
-
throw new Error('
|
|
284
|
+
this.log.error(`\n${errorLog}\n\n${info}\n`);
|
|
285
|
+
throw new Error('未找到可用的 Python 安装');
|
|
300
286
|
}
|
|
301
287
|
}
|
|
302
288
|
|
|
303
|
-
export { PythonFinder }
|
|
289
|
+
export { PythonFinder }
|