@flun/nodejs-mobile-gyp 1.2.2 → 1.2.3
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 +5 -22
- package/bin/node-gyp.js +5 -9
- package/lib/build.js +135 -131
- package/lib/clean.js +12 -9
- package/lib/configure.js +10 -6
- package/lib/create-config-gypi.js +103 -92
- package/lib/download.js +32 -24
- package/lib/find-node-directory.js +39 -37
- package/lib/find-python.js +17 -37
- package/lib/find-visualstudio.js +10 -18
- package/lib/install.js +270 -267
- package/lib/list.js +21 -18
- package/lib/log.js +11 -3
- package/lib/node-gyp.js +12 -7
- package/lib/process-release.js +77 -76
- package/lib/rebuild.js +2 -1
- package/lib/remove.js +9 -5
- package/lib/util.js +82 -62
- package/package.json +76 -76
package/lib/install.js
CHANGED
|
@@ -1,311 +1,314 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
import { backOff } from 'exponential-backoff';
|
|
1
3
|
import gracefulFs from 'graceful-fs';
|
|
2
4
|
import os from 'os';
|
|
3
|
-
import { backOff } from 'exponential-backoff';
|
|
4
|
-
import * as tar from 'tar';
|
|
5
5
|
import path from 'path';
|
|
6
|
-
import { Transform, promises as streamPromises } from 'stream';
|
|
7
|
-
import crypto from 'crypto';
|
|
8
|
-
import * as log from './log.js';
|
|
9
6
|
import semver from 'semver';
|
|
7
|
+
import { promises as streamPromises, Transform } from 'stream';
|
|
8
|
+
import * as tar from 'tar';
|
|
10
9
|
import { download } from './download.js';
|
|
10
|
+
import * as log from './log.js';
|
|
11
11
|
import { processRelease } from './process-release.js';
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// 按 create-config-gypi.js 的逻辑推断 target_arch,仅 Windows 用
|
|
21
|
-
arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : '';
|
|
22
|
-
let shouldDownloadTarball = true; // 仅 Windows 下只需新 node.lib 时,避免下载 tarball
|
|
13
|
+
/**
|
|
14
|
+
* SHA256 校验流
|
|
15
|
+
*/
|
|
16
|
+
class ShaSum extends Transform {
|
|
17
|
+
constructor(callback) {
|
|
18
|
+
super(), this._callback = callback, this._digester = crypto.createHash('sha256');
|
|
19
|
+
}
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (semver.lt(release.version, '0.8.0')) throw new Error('目标版本最低为 `0.8.0`,当前: ' + release.version);
|
|
28
|
-
// 0.x.y-pre 版本尚未发布,无法安装,直接退出
|
|
29
|
-
if (release.semver.prerelease[0] === 'pre') {
|
|
30
|
-
log.verbose('检测到 "pre" 版 node', release.version);
|
|
31
|
-
if (!gyp.opts.nodedir) throw new Error('无法安装 "pre" 版本的 node,请改用 --nodedir 参数');
|
|
32
|
-
log.verbose('传入了 --nodedir 参数;跳过安装', gyp.opts.nodedir);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
21
|
+
_transform(chunk, _, callback) {
|
|
22
|
+
this._digester.update(chunk), callback(null, chunk)
|
|
23
|
+
}
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
25
|
+
_flush(callback) {
|
|
26
|
+
this._callback(null, this._digester.digest('hex')), callback()
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { createWriteStream, promises: fs } = gracefulFs, { pipeline } = streamPromises, win = process.platform === 'win32';
|
|
31
|
+
/**
|
|
32
|
+
* 下载并安装指定版本的 Node.js 头文件与库
|
|
33
|
+
* >查看定义:@see {@link install}
|
|
34
|
+
* @param {*} gyp
|
|
35
|
+
* @param {*} argv
|
|
36
|
+
* @returns {*}
|
|
37
|
+
*/
|
|
38
|
+
const install = async (gyp, argv) => {
|
|
39
|
+
log.stdout();
|
|
40
|
+
const release = processRelease(argv, gyp, process.version, process.release),
|
|
41
|
+
// 按 create-config-gypi.js 的逻辑推断 target_arch,仅 Windows 用
|
|
42
|
+
arch = win ? (gyp.opts.target_arch || gyp.opts.arch || process.arch || 'ia32') : '';
|
|
43
|
+
let shouldDownloadTarball = true; // 仅 Windows 下只需新 node.lib 时,避免下载 tarball
|
|
44
|
+
|
|
45
|
+
// 确定要安装哪个版本的 Node 开发文件
|
|
46
|
+
log.verbose('安装', '输入版本字符串 %j', release.version);
|
|
47
|
+
if (!release.semver) throw new Error('版本号无效: ' + release.version); // 无法用 semver 解析版本字符串
|
|
48
|
+
if (semver.lt(release.version, '0.8.0')) throw new Error('目标版本最低为 `0.8.0`,当前: ' + release.version);
|
|
49
|
+
// 0.x.y-pre 版本尚未发布,无法安装,直接退出
|
|
50
|
+
if (release.semver.prerelease[0] === 'pre') {
|
|
51
|
+
log.verbose('检测到 "pre" 版 node', release.version);
|
|
52
|
+
if (!gyp.opts.nodedir) throw new Error('无法安装 "pre" 版本的 node,请改用 --nodedir 参数');
|
|
53
|
+
log.verbose('传入了 --nodedir 参数;跳过安装', gyp.opts.nodedir);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
log.verbose('安装', '正在安装版本: %s', release.versionDir); // 版本拍平为字符串
|
|
58
|
+
const devDir = path.resolve(gyp.devDir, release.versionDir), // 开发文件安装目录
|
|
59
|
+
valid = file => {
|
|
60
|
+
// 头文件
|
|
61
|
+
const extname = path.extname(file);
|
|
62
|
+
return extname === '.h' || extname === '.gypi';
|
|
63
|
+
},
|
|
64
|
+
copyDirectory = async (src, dest) => {
|
|
65
|
+
try {
|
|
66
|
+
await fs.stat(src);
|
|
67
|
+
} catch {
|
|
68
|
+
throw new Error(`找不到要复制的源目录: ${src}`);
|
|
69
|
+
}
|
|
70
|
+
await fs.mkdir(dest, { recursive: true });
|
|
71
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (entry.isDirectory()) await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name));
|
|
74
|
+
else if (entry.isFile()) {
|
|
75
|
+
// 并行安装时,Windows 上复制文件可能报文件错误,
|
|
76
|
+
// 因此用指数退避解决冲突
|
|
77
|
+
await backOff(async () => {
|
|
78
|
+
try {
|
|
79
|
+
await fs.copyFile(path.join(src, entry.name), path.join(dest, entry.name));
|
|
80
|
+
} catch (err) {
|
|
81
|
+
// 若设置了 ensure,检查文件是否已存在,存在就足够了
|
|
82
|
+
if (gyp.opts.ensure && err.code === 'EBUSY') {
|
|
83
|
+
try {
|
|
84
|
+
await fs.stat(path.join(dest, entry.name));
|
|
85
|
+
return;
|
|
86
|
+
} catch { }
|
|
68
87
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
rollback = async err => {
|
|
75
|
-
log.warn('安装', '发生错误,正在回滚安装')
|
|
76
|
-
await gyp.commands.remove([release.versionDir]);
|
|
77
|
-
throw err;
|
|
78
|
-
},
|
|
79
|
-
/**
|
|
80
|
-
* EACCES 回退方案,用于处理 npm 的 `sudo` 行为:
|
|
81
|
-
* npm 在调用任何子进程(如 node-gyp)前会先降权,
|
|
82
|
-
* 于是 "nobody" 用户没有权限创建 dev dir;
|
|
83
|
-
* 作为回退,把 tmpdir() 作为本次安装的 dev dir;
|
|
84
|
-
* 这不理想,但至少编译能成功......
|
|
85
|
-
*/
|
|
86
|
-
eaccesFallback = async err => {
|
|
87
|
-
const noretry = '--node_gyp_internal_noretry';
|
|
88
|
-
if (argv.indexOf(noretry) !== -1) throw err;
|
|
89
|
-
const tmpdir = os.tmpdir();
|
|
90
|
-
gyp.devDir = path.resolve(tmpdir, '.node-gyp');
|
|
91
|
-
let userString = '';
|
|
92
|
-
try {
|
|
93
|
-
userString = ` ("${os.userInfo().username}")`; // os.userInfo 在某些系统上会失败,这里不关键
|
|
94
|
-
} catch (e) { }
|
|
95
|
-
log.warn('EACCES', '当前用户%s 没有权限访问开发文件目录 "%s"', userString, devDir);
|
|
96
|
-
log.warn('EACCES', '尝试用临时开发文件目录重新安装 "%s"', gyp.devDir);
|
|
97
|
-
if (process.cwd() === tmpdir) {
|
|
98
|
-
log.verbose('tmpdir 等于 cwd', '将自动删除开发文件以节省磁盘空间');
|
|
99
|
-
gyp.todo.push({ name: 'remove', args: argv });
|
|
88
|
+
throw err;
|
|
89
|
+
}
|
|
90
|
+
})
|
|
100
91
|
}
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
else throw new Error('意外的文件目录条目类型');
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
rollback = async err => {
|
|
96
|
+
log.warn('安装', '发生错误,正在回滚安装')
|
|
97
|
+
await gyp.commands.remove([release.versionDir]);
|
|
98
|
+
throw err;
|
|
99
|
+
},
|
|
100
|
+
/**
|
|
101
|
+
* EACCES 回退方案,用于处理 npm 的 `sudo` 行为:
|
|
102
|
+
* npm 在调用任何子进程(如 node-gyp)前会先降权,
|
|
103
|
+
* 于是 "nobody" 用户没有权限创建 dev dir;
|
|
104
|
+
* 作为回退,把 tmpdir() 作为本次安装的 dev dir;
|
|
105
|
+
* 这不理想,但至少编译能成功......
|
|
106
|
+
*/
|
|
107
|
+
eaccesFallback = async err => {
|
|
108
|
+
const noretry = '--node_gyp_internal_noretry';
|
|
109
|
+
if (argv.indexOf(noretry) !== -1) throw err;
|
|
110
|
+
const tmpdir = os.tmpdir();
|
|
111
|
+
gyp.devDir = path.resolve(tmpdir, '.node-gyp');
|
|
112
|
+
let userString = '';
|
|
113
|
+
try {
|
|
114
|
+
userString = ` ("${os.userInfo().username}")`; // os.userInfo 在某些系统上会失败,这里不关键
|
|
115
|
+
} catch (e) { }
|
|
116
|
+
log.warn('EACCES', '当前用户%s 没有权限访问开发文件目录 "%s"', userString, devDir);
|
|
117
|
+
log.warn('EACCES', '尝试用临时开发文件目录重新安装 "%s"', gyp.devDir);
|
|
118
|
+
if (process.cwd() === tmpdir) {
|
|
119
|
+
log.verbose('tmpdir 等于 cwd', '将自动删除开发文件以节省磁盘空间');
|
|
120
|
+
gyp.todo.push({ name: 'remove', args: argv });
|
|
121
|
+
}
|
|
122
|
+
return gyp.commands.install([noretry].concat(argv));
|
|
123
|
+
},
|
|
124
|
+
go = async () => {
|
|
125
|
+
log.verbose('正在确保 devDir 已创建', devDir);
|
|
105
126
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
// 先创建 Node 开发文件所在目录
|
|
128
|
+
try {
|
|
129
|
+
const created = await fs.mkdir(devDir, { recursive: true });
|
|
130
|
+
if (created) log.verbose('已创建 devDir', created);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
if (err.code === 'EACCES') return eaccesFallback(err);
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
114
135
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
136
|
+
// 下载 Node tarball
|
|
137
|
+
let extractErrors = false, extractCount = 0;
|
|
138
|
+
const tarPath = gyp.opts.tarball, contentShasums = {}, expectShasums = {},
|
|
139
|
+
// 检查从 tarball 解出的文件是否有效;
|
|
140
|
+
// 只有 .h 头文件和 gyp 文件会被解出
|
|
141
|
+
isValid = path => {
|
|
142
|
+
const isValid = valid(path);
|
|
143
|
+
if (isValid) log.verbose('已从 tarball 解出文件', path), extractCount++;
|
|
144
|
+
else log.silly('忽略 tarball 中的该项', path); // 无效
|
|
124
145
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
return isValid;
|
|
147
|
+
},
|
|
148
|
+
onwarn = (code, message) => {
|
|
149
|
+
extractErrors = true, log.error('解压 tarball 出错', code, message);
|
|
150
|
+
},
|
|
151
|
+
// 下载并解压 tarball!
|
|
152
|
+
// 仅 Windows 上只需新 node.lib 时跳过
|
|
153
|
+
// Windows 上并行安装时(多个原生模块,很常见)tar 会报文件错误,
|
|
154
|
+
// 所以先把 tarball 解到临时目录,再复制过去
|
|
155
|
+
tarExtractDir = win ? await fs.mkdtemp(path.join(os.tmpdir(), 'node-gyp-tmp-')) : devDir,
|
|
156
|
+
downloadShasums = async () => {
|
|
157
|
+
log.verbose('检查下载内容校验值,需要下载 `SHASUMS256.txt`...');
|
|
158
|
+
log.verbose('校验文件地址', release.shasumsUrl);
|
|
138
159
|
|
|
139
|
-
|
|
140
|
-
|
|
160
|
+
const res = await download(gyp, release.shasumsUrl);
|
|
161
|
+
if (res.status !== 200) throw new Error(`下载校验文件时返回状态码 ${res.status}`);
|
|
141
162
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
for (const line of (await res.text()).trim().split('\n')) {
|
|
164
|
+
const items = line.trim().split(/\s+/);
|
|
165
|
+
if (items.length !== 2) return;
|
|
145
166
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
167
|
+
// 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
|
|
168
|
+
const name = items[1].replace(/^\.\//, '');
|
|
169
|
+
expectShasums[name] = items[0];
|
|
170
|
+
}
|
|
150
171
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
172
|
+
log.verbose('校验数据', JSON.stringify(expectShasums));
|
|
173
|
+
},
|
|
174
|
+
downloadNodeLib = async () => {
|
|
175
|
+
log.verbose('Windows 上;需要下载 `' + release.name + '.lib`...');
|
|
176
|
+
const dir = path.resolve(tarExtractDir, arch), targetLibPath = path.resolve(dir, release.name + '.lib'),
|
|
177
|
+
{ libUrl, libPath } = release[arch], name = `${arch} ${release.name}.lib`;
|
|
157
178
|
|
|
158
|
-
|
|
159
|
-
|
|
179
|
+
log.verbose(name, '目录', dir), log.verbose(name, '地址', libUrl);
|
|
180
|
+
await fs.mkdir(dir, { recursive: true }), log.verbose('正在流式写入', name, '到:', targetLibPath);
|
|
160
181
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
182
|
+
const res = await download(gyp, libUrl);
|
|
183
|
+
// 只下载所需的 node.lib,若未拿到则抛错
|
|
184
|
+
if (res.status !== 200) throw new Error(`下载 ${name} 时返回状态码 ${res.status}`);
|
|
164
185
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
186
|
+
return pipeline(
|
|
187
|
+
res.body,
|
|
188
|
+
new ShaSum((_, checksum) => {
|
|
189
|
+
contentShasums[libPath] = checksum, log.verbose('内容校验值', libPath, checksum)
|
|
190
|
+
}), createWriteStream(targetLibPath)
|
|
191
|
+
);
|
|
192
|
+
};
|
|
172
193
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
194
|
+
try {
|
|
195
|
+
if (shouldDownloadTarball) {
|
|
196
|
+
if (tarPath) await tar.extract({ file: tarPath, strip: 1, filter: isValid, onwarn, cwd: tarExtractDir });
|
|
197
|
+
else {
|
|
198
|
+
try {
|
|
199
|
+
const res = await download(gyp, release.tarballUrl);
|
|
179
200
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
201
|
+
if (res.status !== 200) throw new Error(`下载 ${release.tarballUrl} 时返回状态码 ${res.status}`);
|
|
202
|
+
await pipeline(
|
|
203
|
+
res.body,
|
|
204
|
+
// 内容校验和
|
|
205
|
+
new ShaSum((_, checksum) => {
|
|
206
|
+
const filename = path.basename(release.tarballUrl).trim();
|
|
207
|
+
contentShasums[filename] = checksum, log.verbose('内容校验值', filename, checksum);
|
|
208
|
+
}),
|
|
209
|
+
tar.extract({ strip: 1, cwd: tarExtractDir, filter: isValid, onwarn })
|
|
210
|
+
)
|
|
211
|
+
} catch (err) {
|
|
212
|
+
// tarball 下载出问题?
|
|
213
|
+
if (err.code === 'ENOTFOUND')
|
|
214
|
+
throw new Error('这通常不是 nodejs-mobile-gyp 或包本身的问题,而是网络问题;\n' +
|
|
215
|
+
'通常是因为你在代理后,或网络设置异常;');
|
|
216
|
+
throw err;
|
|
197
217
|
}
|
|
198
|
-
|
|
199
|
-
// tarball 解压完成后调用
|
|
200
|
-
if (extractErrors || extractCount === 0) throw new Error('下载/解压 tarball 时发生致命错误');
|
|
201
|
-
log.verbose('tarball', '解析 tarball 完成');
|
|
202
218
|
}
|
|
203
219
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
// 写入 "installVersion" 文件
|
|
209
|
-
fs.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
|
|
210
|
-
// 只有在下载了需要 SHA 校验的东西时才下载 SHASUMS.txt
|
|
211
|
-
...(!tarPath || win ? [downloadShasums()] : [])
|
|
212
|
-
])
|
|
220
|
+
// tarball 解压完成后调用
|
|
221
|
+
if (extractErrors || extractCount === 0) throw new Error('下载/解压 tarball 时发生致命错误');
|
|
222
|
+
log.verbose('tarball', '解析 tarball 完成');
|
|
223
|
+
}
|
|
213
224
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
const installVersionPath = path.resolve(tarExtractDir, 'installVersion');
|
|
226
|
+
await Promise.all([
|
|
227
|
+
// 需要下载 node.lib
|
|
228
|
+
...(win ? [downloadNodeLib()] : []),
|
|
229
|
+
// 写入 "installVersion" 文件
|
|
230
|
+
fs.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
|
|
231
|
+
// 只有在下载了需要 SHA 校验的东西时才下载 SHASUMS.txt
|
|
232
|
+
...(!tarPath || win ? [downloadShasums()] : [])
|
|
233
|
+
])
|
|
222
234
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
try {
|
|
230
|
-
await fs.rm(tarExtractDir, { recursive: true }); // 尝试清理临时目录
|
|
231
|
-
} catch { log.warn('清理临时 tarball 解压目录失败') }
|
|
235
|
+
log.verbose('下载内容校验值', JSON.stringify(contentShasums));
|
|
236
|
+
// 校验内容 shasum
|
|
237
|
+
for (const k in contentShasums) {
|
|
238
|
+
log.verbose('正在校验 ' + k + ' 的下载校验值', '(%s == %s)', contentShasums[k], expectShasums[k]);
|
|
239
|
+
if (contentShasums[k] !== expectShasums[k]) {
|
|
240
|
+
throw new Error(k + ' 本地校验值与远程不一致: ' + contentShasums[k] + ' != ' + expectShasums[k]);
|
|
232
241
|
}
|
|
233
242
|
}
|
|
234
|
-
};
|
|
235
243
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
if (err.code === 'ENOENT') {
|
|
243
|
-
log.verbose('安装', '版本尚未安装,继续安装', release.version);
|
|
244
|
+
// 从临时解压目录复制文件到 devDir
|
|
245
|
+
if (tarExtractDir !== devDir) {
|
|
246
|
+
await copyDirectory(tarExtractDir, devDir);
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
if (tarExtractDir !== devDir) {
|
|
244
250
|
try {
|
|
245
|
-
|
|
246
|
-
} catch (
|
|
251
|
+
await fs.rm(tarExtractDir, { recursive: true }); // 尝试清理临时目录
|
|
252
|
+
} catch { log.warn('清理临时 tarball 解压目录失败') }
|
|
247
253
|
}
|
|
248
|
-
else if (err.code === 'EACCES') return eaccesFallback(err);
|
|
249
|
-
throw err;
|
|
250
254
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
log.verbose('需要 "installVersion"', gyp.package.installVersion);
|
|
262
|
-
if (installVersion < gyp.package.installVersion) {
|
|
263
|
-
log.verbose('安装', '版本不可用;重新安装');
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// 如果传了 --ensure,则不总是安装,只在需要时安装
|
|
258
|
+
if (gyp.opts.ensure) {
|
|
259
|
+
log.verbose('安装', '传入了 --ensure,已安装时不再重装');
|
|
260
|
+
try {
|
|
261
|
+
await fs.stat(devDir);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (err.code === 'ENOENT') {
|
|
264
|
+
log.verbose('安装', '版本尚未安装,继续安装', release.version);
|
|
264
265
|
try {
|
|
265
266
|
return await go();
|
|
266
267
|
} catch (err) { return rollback(err) }
|
|
267
268
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
} else {
|
|
269
|
+
else if (err.code === 'EACCES') return eaccesFallback(err);
|
|
270
|
+
throw err;
|
|
271
|
+
}
|
|
272
|
+
log.verbose('安装', '版本已安装,需要检查 "installVersion"');
|
|
273
|
+
const installVersionFile = path.resolve(devDir, 'installVersion');
|
|
274
|
+
let installVersion = 0;
|
|
275
|
+
try {
|
|
276
|
+
const ver = await fs.readFile(installVersionFile, 'ascii');
|
|
277
|
+
installVersion = parseInt(ver, 10) || 0;
|
|
278
|
+
} catch (err) {
|
|
279
|
+
if (err.code !== 'ENOENT') throw err;
|
|
280
|
+
}
|
|
281
|
+
log.verbose('得到 "installVersion"', installVersion);
|
|
282
|
+
log.verbose('需要 "installVersion"', gyp.package.installVersion);
|
|
283
|
+
if (installVersion < gyp.package.installVersion) {
|
|
284
|
+
log.verbose('安装', '版本不可用;重新安装');
|
|
287
285
|
try {
|
|
288
286
|
return await go();
|
|
289
287
|
} catch (err) { return rollback(err) }
|
|
290
288
|
}
|
|
289
|
+
log.verbose('安装', '版本可用');
|
|
290
|
+
if (win) {
|
|
291
|
+
log.verbose('Windows 上;需要检查 node.lib');
|
|
292
|
+
const nodeLibPath = path.resolve(devDir, arch, 'node.lib');
|
|
293
|
+
try {
|
|
294
|
+
await fs.stat(nodeLibPath);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
if (err.code === 'ENOENT') {
|
|
297
|
+
log.verbose('安装', `版本尚未为 ${arch} 安装,继续安装`, release.version);
|
|
298
|
+
try {
|
|
299
|
+
shouldDownloadTarball = false;
|
|
300
|
+
return await go();
|
|
301
|
+
} catch (err) { return rollback(err) }
|
|
302
|
+
}
|
|
303
|
+
else if (err.code === 'EACCES') return eaccesFallback(err);
|
|
304
|
+
throw err;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
try {
|
|
309
|
+
return await go();
|
|
310
|
+
} catch (err) { return rollback(err) }
|
|
291
311
|
}
|
|
312
|
+
};
|
|
292
313
|
|
|
293
|
-
|
|
294
|
-
* SHA256 校验流
|
|
295
|
-
*/
|
|
296
|
-
class ShaSum extends Transform {
|
|
297
|
-
constructor(callback) {
|
|
298
|
-
super(), this._callback = callback, this._digester = crypto.createHash('sha256');
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
_transform(chunk, _, callback) {
|
|
302
|
-
this._digester.update(chunk), callback(null, chunk)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
_flush(callback) {
|
|
306
|
-
this._callback(null, this._digester.digest('hex')), callback()
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
install.usage = '安装指定 node 版本的 node 开发文件;';
|
|
311
|
-
export { install }
|
|
314
|
+
export { install };
|
package/lib/list.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import gracefulFs from 'graceful-fs';
|
|
2
2
|
import * as log from './log.js';
|
|
3
3
|
|
|
4
|
-
const fs = gracefulFs.promises
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
const fs = gracefulFs.promises;
|
|
5
|
+
/**
|
|
6
|
+
* 列出当前项目的编译配置
|
|
7
|
+
* >查看定义:@see {@link list}
|
|
8
|
+
* @param {*} gyp
|
|
9
|
+
* @param {*} args
|
|
10
|
+
* @returns {*}
|
|
11
|
+
*/
|
|
12
|
+
const list = async (gyp, args) => {
|
|
13
|
+
const devDir = gyp.devDir;
|
|
14
|
+
let versions = [];
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return versions;
|
|
16
|
+
log.verbose('列表', '使用 node-gyp 目录:', devDir);
|
|
17
|
+
try {
|
|
18
|
+
const dir = await fs.readdir(devDir);
|
|
19
|
+
if (Array.isArray(dir)) versions = dir.filter((v) => v !== 'current');
|
|
20
|
+
} catch (err) {
|
|
21
|
+
if (err && err.code !== 'ENOENT') throw err;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
return versions;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export { list };
|