@flun/nodejs-mobile-gyp 1.0.1 → 1.0.2
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 +56 -36
- package/README.md +256 -252
- package/addon.gypi +13 -1
- package/bin/node-gyp.js +66 -109
- package/gyp/pylib/gyp/MSVSSettings.py +13 -0
- package/lib/build.js +127 -209
- package/lib/clean.js +13 -16
- package/lib/configure.js +183 -265
- 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 +162 -198
- 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/install.js
CHANGED
|
@@ -1,419 +1,310 @@
|
|
|
1
|
-
import gracefulFs from 'graceful-fs'
|
|
2
|
-
import os from 'os'
|
|
3
|
-
import { backOff } from 'exponential-backoff'
|
|
4
|
-
import * as tar from 'tar'
|
|
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
|
-
import semver from 'semver'
|
|
10
|
-
import { download } from './download.js'
|
|
11
|
-
import { processRelease } from './process-release.js'
|
|
12
|
-
|
|
13
|
-
const { createWriteStream, promises: fs } = gracefulFs
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (semver.lt(release.version, '0.8.0')) {
|
|
37
|
-
throw new Error('Minimum target version is `0.8.0` or greater. Got: ' + release.version)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 0.x.y-pre 版本尚未发布,无法安装,直接退出
|
|
41
|
-
if (release.semver.prerelease[0] === 'pre') {
|
|
42
|
-
log.verbose('detected "pre" node version', release.version)
|
|
43
|
-
if (!gyp.opts.nodedir) {
|
|
44
|
-
throw new Error('"pre" versions of node cannot be installed, use the --nodedir flag instead')
|
|
1
|
+
import gracefulFs from 'graceful-fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import { backOff } from 'exponential-backoff';
|
|
4
|
+
import * as tar from 'tar';
|
|
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
|
+
import semver from 'semver';
|
|
10
|
+
import { download } from './download.js';
|
|
11
|
+
import { processRelease } from './process-release.js';
|
|
12
|
+
|
|
13
|
+
const { createWriteStream, promises: fs } = gracefulFs, { pipeline } = streamPromises, win = process.platform === 'win32',
|
|
14
|
+
/**
|
|
15
|
+
* 为指定 Node 版本安装开发文件
|
|
16
|
+
*/
|
|
17
|
+
install = async (gyp, argv) => {
|
|
18
|
+
log.stdout();
|
|
19
|
+
const release = processRelease(argv, gyp, process.version, process.release),
|
|
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
|
|
23
|
+
|
|
24
|
+
// 确定要安装哪个版本的 Node 开发文件
|
|
25
|
+
log.verbose('install', 'input version string %j', release.version);
|
|
26
|
+
if (!release.semver) throw new Error('版本号无效: ' + release.version); // 无法用 semver 解析版本字符串
|
|
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('detected "pre" node version', release.version);
|
|
31
|
+
if (!gyp.opts.nodedir) throw new Error('"pre" versions of node cannot be installed, use the --nodedir flag instead');
|
|
32
|
+
log.verbose('--nodedir flag was passed; skipping install', gyp.opts.nodedir);
|
|
33
|
+
return;
|
|
45
34
|
}
|
|
46
|
-
log.verbose('--nodedir flag was passed; skipping install', gyp.opts.nodedir)
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 版本拍平为字符串
|
|
51
|
-
log.verbose('install', 'installing version: %s', release.versionDir)
|
|
52
|
-
|
|
53
|
-
// 开发文件安装目录
|
|
54
|
-
const devDir = path.resolve(gyp.devDir, release.versionDir)
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
36
|
+
log.verbose('install', 'installing version: %s', release.versionDir); // 版本拍平为字符串
|
|
37
|
+
const devDir = path.resolve(gyp.devDir, release.versionDir), // 开发文件安装目录
|
|
38
|
+
valid = file => {
|
|
39
|
+
// 头文件
|
|
40
|
+
const extname = path.extname(file);
|
|
41
|
+
return extname === '.h' || extname === '.gypi';
|
|
42
|
+
},
|
|
43
|
+
copyDirectory = async (src, dest) => {
|
|
64
44
|
try {
|
|
65
|
-
|
|
66
|
-
} catch
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
} else if (err.code === 'EACCES') {
|
|
70
|
-
return eaccesFallback(err)
|
|
71
|
-
}
|
|
72
|
-
throw err
|
|
73
|
-
}
|
|
74
|
-
log.verbose('install', 'version is already installed, need to check "installVersion"')
|
|
75
|
-
const installVersionFile = path.resolve(devDir, 'installVersion')
|
|
76
|
-
let installVersion = 0
|
|
77
|
-
try {
|
|
78
|
-
const ver = await fs.readFile(installVersionFile, 'ascii')
|
|
79
|
-
installVersion = parseInt(ver, 10) || 0
|
|
80
|
-
} catch (err) {
|
|
81
|
-
if (err.code !== 'ENOENT') {
|
|
82
|
-
throw err
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
log.verbose('got "installVersion"', installVersion)
|
|
86
|
-
log.verbose('needs "installVersion"', gyp.package.installVersion)
|
|
87
|
-
if (installVersion < gyp.package.installVersion) {
|
|
88
|
-
log.verbose('install', 'version is no good; reinstalling')
|
|
89
|
-
try {
|
|
90
|
-
return await go()
|
|
91
|
-
} catch (err) {
|
|
92
|
-
return rollback(err)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
log.verbose('install', 'version is good')
|
|
96
|
-
if (win) {
|
|
97
|
-
log.verbose('on Windows; need to check node.lib')
|
|
98
|
-
const nodeLibPath = path.resolve(devDir, arch, 'node.lib')
|
|
99
|
-
try {
|
|
100
|
-
await fs.stat(nodeLibPath)
|
|
101
|
-
} catch (err) {
|
|
102
|
-
if (err.code === 'ENOENT') {
|
|
103
|
-
log.verbose('install', `version not already installed for ${arch}, continuing with install`, release.version)
|
|
104
|
-
try {
|
|
105
|
-
shouldDownloadTarball = false
|
|
106
|
-
return await go()
|
|
107
|
-
} catch (err) {
|
|
108
|
-
return rollback(err)
|
|
109
|
-
}
|
|
110
|
-
} else if (err.code === 'EACCES') {
|
|
111
|
-
return eaccesFallback(err)
|
|
45
|
+
await fs.stat(src);
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error(`Missing source directory for copy: ${src}`);
|
|
112
48
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function copyDirectory (src, dest) {
|
|
125
|
-
try {
|
|
126
|
-
await fs.stat(src)
|
|
127
|
-
} catch {
|
|
128
|
-
throw new Error(`Missing source directory for copy: ${src}`)
|
|
129
|
-
}
|
|
130
|
-
await fs.mkdir(dest, { recursive: true })
|
|
131
|
-
const entries = await fs.readdir(src, { withFileTypes: true })
|
|
132
|
-
for (const entry of entries) {
|
|
133
|
-
if (entry.isDirectory()) {
|
|
134
|
-
await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name))
|
|
135
|
-
} else if (entry.isFile()) {
|
|
136
|
-
// 并行安装时,Windows 上复制文件可能报文件错误,
|
|
137
|
-
// 因此用指数退避解决冲突
|
|
138
|
-
await backOff(async () => {
|
|
139
|
-
try {
|
|
140
|
-
await fs.copyFile(path.join(src, entry.name), path.join(dest, entry.name))
|
|
141
|
-
} catch (err) {
|
|
142
|
-
// 若设置了 ensure,检查文件是否已存在,存在就足够了
|
|
143
|
-
if (gyp.opts.ensure && err.code === 'EBUSY') {
|
|
49
|
+
await fs.mkdir(dest, { recursive: true });
|
|
50
|
+
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.isDirectory()) await copyDirectory(path.join(src, entry.name), path.join(dest, entry.name));
|
|
53
|
+
else if (entry.isFile()) {
|
|
54
|
+
// 并行安装时,Windows 上复制文件可能报文件错误,
|
|
55
|
+
// 因此用指数退避解决冲突
|
|
56
|
+
await backOff(async () => {
|
|
144
57
|
try {
|
|
145
|
-
await fs.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
58
|
+
await fs.copyFile(path.join(src, entry.name), path.join(dest, entry.name));
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// 若设置了 ensure,检查文件是否已存在,存在就足够了
|
|
61
|
+
if (gyp.opts.ensure && err.code === 'EBUSY') {
|
|
62
|
+
try {
|
|
63
|
+
await fs.stat(path.join(dest, entry.name));
|
|
64
|
+
return;
|
|
65
|
+
} catch { }
|
|
66
|
+
}
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
})
|
|
150
70
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
71
|
+
else throw new Error('意外的文件目录条目类型');
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
rollback = async err => {
|
|
75
|
+
log.warn('install', '发生错误,正在回滚安装')
|
|
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 没有权限访问 dev dir "%s"', userString, devDir);
|
|
96
|
+
log.warn('EACCES', '尝试用临时 dev dir 重新安装 "%s"', gyp.devDir);
|
|
97
|
+
if (process.cwd() === tmpdir) {
|
|
98
|
+
log.verbose('tmpdir == cwd', 'automatically will remove dev files after to save disk space');
|
|
99
|
+
gyp.todo.push({ name: 'remove', args: argv });
|
|
100
|
+
}
|
|
101
|
+
return gyp.commands.install([noretry].concat(argv));
|
|
102
|
+
},
|
|
103
|
+
go = async () => {
|
|
104
|
+
log.verbose('ensuring devDir is created', devDir);
|
|
175
105
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
106
|
+
// 先创建 Node 开发文件所在目录
|
|
107
|
+
try {
|
|
108
|
+
const created = await fs.mkdir(devDir, { recursive: true });
|
|
109
|
+
if (created) log.verbose('created devDir', created);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
if (err.code === 'EACCES') return eaccesFallback(err);
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
182
114
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
115
|
+
// 下载 Node tarball
|
|
116
|
+
let extractErrors = false, extractCount = 0;
|
|
117
|
+
const tarPath = gyp.opts.tarball, contentShasums = {}, expectShasums = {},
|
|
118
|
+
// 检查从 tarball 解出的文件是否有效。
|
|
119
|
+
// 只有 .h 头文件和 gyp 文件会被解出
|
|
120
|
+
isValid = path => {
|
|
121
|
+
const isValid = valid(path);
|
|
122
|
+
if (isValid) log.verbose('extracted file from tarball', path), extractCount++;
|
|
123
|
+
else log.silly('ignoring from tarball', path); // 无效
|
|
124
|
+
|
|
125
|
+
return isValid;
|
|
126
|
+
},
|
|
127
|
+
onwarn = (code, message) => {
|
|
128
|
+
extractErrors = true, log.error('error while extracting tarball', code, message);
|
|
129
|
+
},
|
|
130
|
+
// 下载并解压 tarball!
|
|
131
|
+
// 仅 Windows 上只需新 node.lib 时跳过
|
|
132
|
+
// Windows 上并行安装时(多个原生模块,很常见)tar 会报文件错误,
|
|
133
|
+
// 所以先把 tarball 解到临时目录,再复制过去
|
|
134
|
+
tarExtractDir = win ? await fs.mkdtemp(path.join(os.tmpdir(), 'node-gyp-tmp-')) : devDir,
|
|
135
|
+
downloadShasums = async () => {
|
|
136
|
+
log.verbose('check download content checksum, need to download `SHASUMS256.txt`...');
|
|
137
|
+
log.verbose('checksum url', release.shasumsUrl);
|
|
138
|
+
|
|
139
|
+
const res = await download(gyp, release.shasumsUrl);
|
|
140
|
+
if (res.status !== 200) throw new Error(`${res.status} status code downloading checksum`);
|
|
141
|
+
|
|
142
|
+
for (const line of (await res.text()).trim().split('\n')) {
|
|
143
|
+
const items = line.trim().split(/\s+/);
|
|
144
|
+
if (items.length !== 2) return;
|
|
145
|
+
|
|
146
|
+
// 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
|
|
147
|
+
const name = items[1].replace(/^\.\//, '');
|
|
148
|
+
expectShasums[name] = items[0];
|
|
149
|
+
}
|
|
196
150
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
151
|
+
log.verbose('checksum data', JSON.stringify(expectShasums));
|
|
152
|
+
},
|
|
153
|
+
downloadNodeLib = async () => {
|
|
154
|
+
log.verbose('on Windows; need to download `' + release.name + '.lib`...');
|
|
155
|
+
const dir = path.resolve(tarExtractDir, arch), targetLibPath = path.resolve(dir, release.name + '.lib'),
|
|
156
|
+
{ libUrl, libPath } = release[arch], name = `${arch} ${release.name}.lib`;
|
|
201
157
|
|
|
202
|
-
|
|
203
|
-
|
|
158
|
+
log.verbose(name, 'dir', dir), log.verbose(name, 'url', libUrl);
|
|
159
|
+
await fs.mkdir(dir, { recursive: true }), log.verbose('streaming', name, 'to:', targetLibPath);
|
|
204
160
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
161
|
+
const res = await download(gyp, libUrl);
|
|
162
|
+
// 只下载所需的 node.lib,若未拿到则抛错
|
|
163
|
+
if (res.status !== 200) throw new Error(`${res.status} status code downloading ${name}`);
|
|
208
164
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
onwarn,
|
|
217
|
-
cwd: tarExtractDir
|
|
218
|
-
})
|
|
219
|
-
} else {
|
|
220
|
-
try {
|
|
221
|
-
const res = await download(gyp, release.tarballUrl)
|
|
165
|
+
return pipeline(
|
|
166
|
+
res.body,
|
|
167
|
+
new ShaSum((_, checksum) => {
|
|
168
|
+
contentShasums[libPath] = checksum, log.verbose('content checksum', libPath, checksum)
|
|
169
|
+
}), createWriteStream(targetLibPath)
|
|
170
|
+
);
|
|
171
|
+
};
|
|
222
172
|
|
|
223
|
-
|
|
224
|
-
|
|
173
|
+
try {
|
|
174
|
+
if (shouldDownloadTarball) {
|
|
175
|
+
if (tarPath) await tar.extract({ file: tarPath, strip: 1, filter: isValid, onwarn, cwd: tarExtractDir });
|
|
176
|
+
else {
|
|
177
|
+
try {
|
|
178
|
+
const res = await download(gyp, release.tarballUrl);
|
|
179
|
+
|
|
180
|
+
if (res.status !== 200) throw new Error(`${res.status} response downloading ${release.tarballUrl}`);
|
|
181
|
+
await pipeline(
|
|
182
|
+
res.body,
|
|
183
|
+
// 内容校验和
|
|
184
|
+
new ShaSum((_, checksum) => {
|
|
185
|
+
const filename = path.basename(release.tarballUrl).trim();
|
|
186
|
+
contentShasums[filename] = checksum, log.verbose('content checksum', filename, checksum);
|
|
187
|
+
}),
|
|
188
|
+
tar.extract({ strip: 1, cwd: tarExtractDir, filter: isValid, onwarn })
|
|
189
|
+
)
|
|
190
|
+
} catch (err) {
|
|
191
|
+
// tarball 下载出问题?
|
|
192
|
+
if (err.code === 'ENOTFOUND')
|
|
193
|
+
throw new Error('这通常不是 node-gyp 或包本身的问题,而是网络问题;\n' + '通常是因为你在代理后,或网络设置异常;');
|
|
194
|
+
throw err;
|
|
195
|
+
}
|
|
225
196
|
}
|
|
226
197
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
'
|
|
198
|
+
// tarball 解压完成后调用
|
|
199
|
+
if (extractErrors || extractCount === 0) throw new Error('下载/解压 tarball 时发生致命错误');
|
|
200
|
+
log.verbose('tarball', 'done parsing tarball');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const installVersionPath = path.resolve(tarExtractDir, 'installVersion');
|
|
204
|
+
await Promise.all([
|
|
205
|
+
// 需要下载 node.lib
|
|
206
|
+
...(win ? [downloadNodeLib()] : []),
|
|
207
|
+
// 写入 "installVersion" 文件
|
|
208
|
+
fs.writeFile(installVersionPath, gyp.package.installVersion + '\n'),
|
|
209
|
+
// 只有在下载了需要 SHA 校验的东西时才下载 SHASUMS.txt
|
|
210
|
+
...(!tarPath || win ? [downloadShasums()] : [])
|
|
211
|
+
])
|
|
212
|
+
|
|
213
|
+
log.verbose('download contents checksum', JSON.stringify(contentShasums));
|
|
214
|
+
// 校验内容 shasum
|
|
215
|
+
for (const k in contentShasums) {
|
|
216
|
+
log.verbose('validating download checksum for ' + k, '(%s == %s)', contentShasums[k], expectShasums[k]);
|
|
217
|
+
if (contentShasums[k] !== expectShasums[k]) {
|
|
218
|
+
throw new Error(k + ' local checksum ' + contentShasums[k] + ' not match remote ' + expectShasums[k]);
|
|
248
219
|
}
|
|
249
|
-
throw err
|
|
250
220
|
}
|
|
251
|
-
}
|
|
252
221
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
222
|
+
// 从临时解压目录复制文件到 devDir
|
|
223
|
+
if (tarExtractDir !== devDir) {
|
|
224
|
+
await copyDirectory(tarExtractDir, devDir);
|
|
225
|
+
}
|
|
226
|
+
} finally {
|
|
227
|
+
if (tarExtractDir !== devDir) {
|
|
228
|
+
try {
|
|
229
|
+
await fs.rm(tarExtractDir, { recursive: true }); // 尝试清理临时目录
|
|
230
|
+
} catch { log.warn('清理临时 tarball 解压目录失败') }
|
|
231
|
+
}
|
|
256
232
|
}
|
|
233
|
+
};
|
|
257
234
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
])
|
|
270
|
-
|
|
271
|
-
log.verbose('download contents checksum', JSON.stringify(contentShasums))
|
|
272
|
-
// 校验内容 shasum
|
|
273
|
-
for (const k in contentShasums) {
|
|
274
|
-
log.verbose('validating download checksum for ' + k, '(%s == %s)', contentShasums[k], expectShasums[k])
|
|
275
|
-
if (contentShasums[k] !== expectShasums[k]) {
|
|
276
|
-
throw new Error(k + ' local checksum ' + contentShasums[k] + ' not match remote ' + expectShasums[k])
|
|
235
|
+
// 如果传了 --ensure,则不总是安装,只在需要时安装
|
|
236
|
+
if (gyp.opts.ensure) {
|
|
237
|
+
log.verbose('install', '--ensure was passed, so won\'t reinstall if already installed');
|
|
238
|
+
try {
|
|
239
|
+
await fs.stat(devDir);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
if (err.code === 'ENOENT') {
|
|
242
|
+
log.verbose('install', 'version not already installed, continuing with install', release.version);
|
|
243
|
+
try {
|
|
244
|
+
return await go();
|
|
245
|
+
} catch (err) { return rollback(err) }
|
|
277
246
|
}
|
|
247
|
+
else if (err.code === 'EACCES') return eaccesFallback(err);
|
|
248
|
+
throw err;
|
|
278
249
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
250
|
+
log.verbose('install', 'version is already installed, need to check "installVersion"');
|
|
251
|
+
const installVersionFile = path.resolve(devDir, 'installVersion');
|
|
252
|
+
let installVersion = 0;
|
|
253
|
+
try {
|
|
254
|
+
const ver = await fs.readFile(installVersionFile, 'ascii');
|
|
255
|
+
installVersion = parseInt(ver, 10) || 0;
|
|
256
|
+
} catch (err) {
|
|
257
|
+
if (err.code !== 'ENOENT') throw err;
|
|
283
258
|
}
|
|
284
|
-
|
|
285
|
-
|
|
259
|
+
log.verbose('got "installVersion"', installVersion);
|
|
260
|
+
log.verbose('needs "installVersion"', gyp.package.installVersion);
|
|
261
|
+
if (installVersion < gyp.package.installVersion) {
|
|
262
|
+
log.verbose('install', 'version is no good; reinstalling');
|
|
286
263
|
try {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
} catch {
|
|
290
|
-
log.warn('failed to clean up temp tarball extract directory')
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
async function downloadShasums () {
|
|
296
|
-
log.verbose('check download content checksum, need to download `SHASUMS256.txt`...')
|
|
297
|
-
log.verbose('checksum url', release.shasumsUrl)
|
|
298
|
-
|
|
299
|
-
const res = await download(gyp, release.shasumsUrl)
|
|
300
|
-
|
|
301
|
-
if (res.status !== 200) {
|
|
302
|
-
throw new Error(`${res.status} status code downloading checksum`)
|
|
264
|
+
return await go();
|
|
265
|
+
} catch (err) { return rollback(err) }
|
|
303
266
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
267
|
+
log.verbose('install', 'version is good');
|
|
268
|
+
if (win) {
|
|
269
|
+
log.verbose('on Windows; need to check node.lib');
|
|
270
|
+
const nodeLibPath = path.resolve(devDir, arch, 'node.lib');
|
|
271
|
+
try {
|
|
272
|
+
await fs.stat(nodeLibPath);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
if (err.code === 'ENOENT') {
|
|
275
|
+
log.verbose('install', `version not already installed for ${arch}, continuing with install`, release.version);
|
|
276
|
+
try {
|
|
277
|
+
shouldDownloadTarball = false;
|
|
278
|
+
return await go();
|
|
279
|
+
} catch (err) { return rollback(err) }
|
|
280
|
+
}
|
|
281
|
+
else if (err.code === 'EACCES') return eaccesFallback(err);
|
|
282
|
+
throw err;
|
|
309
283
|
}
|
|
310
|
-
|
|
311
|
-
// 0035d18e2dcf9aad669b1c7c07319e17abfe3762 ./node-v0.11.4.tar.gz
|
|
312
|
-
const name = items[1].replace(/^\.\//, '')
|
|
313
|
-
expectShasums[name] = items[0]
|
|
314
284
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
async function downloadNodeLib () {
|
|
320
|
-
log.verbose('on Windows; need to download `' + release.name + '.lib`...')
|
|
321
|
-
const dir = path.resolve(tarExtractDir, arch)
|
|
322
|
-
const targetLibPath = path.resolve(dir, release.name + '.lib')
|
|
323
|
-
const { libUrl, libPath } = release[arch]
|
|
324
|
-
const name = `${arch} ${release.name}.lib`
|
|
325
|
-
log.verbose(name, 'dir', dir)
|
|
326
|
-
log.verbose(name, 'url', libUrl)
|
|
327
|
-
|
|
328
|
-
await fs.mkdir(dir, { recursive: true })
|
|
329
|
-
log.verbose('streaming', name, 'to:', targetLibPath)
|
|
330
|
-
|
|
331
|
-
const res = await download(gyp, libUrl)
|
|
332
|
-
|
|
333
|
-
// 只下载所需的 node.lib,若未拿到则抛错
|
|
334
|
-
if (res.status !== 200) {
|
|
335
|
-
throw new Error(`${res.status} status code downloading ${name}`)
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return pipeline(
|
|
339
|
-
res.body,
|
|
340
|
-
new ShaSum((_, checksum) => {
|
|
341
|
-
contentShasums[libPath] = checksum
|
|
342
|
-
log.verbose('content checksum', libPath, checksum)
|
|
343
|
-
}),
|
|
344
|
-
createWriteStream(targetLibPath)
|
|
345
|
-
)
|
|
346
|
-
} // downloadNodeLib()
|
|
347
|
-
} // go()
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* 检查给定文件名是否对本次安装"有效"
|
|
351
|
-
*/
|
|
352
|
-
|
|
353
|
-
function valid (file) {
|
|
354
|
-
// 头文件
|
|
355
|
-
const extname = path.extname(file)
|
|
356
|
-
return extname === '.h' || extname === '.gypi'
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
async function rollback (err) {
|
|
360
|
-
log.warn('install', 'got an error, rolling back install')
|
|
361
|
-
// 出错则回滚安装
|
|
362
|
-
await gyp.commands.remove([release.versionDir])
|
|
363
|
-
throw err
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* EACCES 回退方案,用于处理 npm 的 `sudo` 行为:
|
|
368
|
-
* npm 在调用任何子进程(如 node-gyp)前会先降权,
|
|
369
|
-
* 于是 "nobody" 用户没有权限创建 dev dir。
|
|
370
|
-
* 作为回退,把 tmpdir() 作为本次安装的 dev dir。
|
|
371
|
-
* 这不理想,但至少编译能成功……
|
|
372
|
-
*/
|
|
373
|
-
|
|
374
|
-
async function eaccesFallback (err) {
|
|
375
|
-
const noretry = '--node_gyp_internal_noretry'
|
|
376
|
-
if (argv.indexOf(noretry) !== -1) {
|
|
377
|
-
throw err
|
|
378
|
-
}
|
|
379
|
-
const tmpdir = os.tmpdir()
|
|
380
|
-
gyp.devDir = path.resolve(tmpdir, '.node-gyp')
|
|
381
|
-
let userString = ''
|
|
382
|
-
try {
|
|
383
|
-
// os.userInfo 在某些系统上会失败,这里不关键
|
|
384
|
-
userString = ` ("${os.userInfo().username}")`
|
|
385
|
-
} catch (e) {}
|
|
386
|
-
log.warn('EACCES', 'current user%s does not have permission to access the dev dir "%s"', userString, devDir)
|
|
387
|
-
log.warn('EACCES', 'attempting to reinstall using temporary dev dir "%s"', gyp.devDir)
|
|
388
|
-
if (process.cwd() === tmpdir) {
|
|
389
|
-
log.verbose('tmpdir == cwd', 'automatically will remove dev files after to save disk space')
|
|
390
|
-
gyp.todo.push({ name: 'remove', args: argv })
|
|
285
|
+
} else {
|
|
286
|
+
try {
|
|
287
|
+
return await go();
|
|
288
|
+
} catch (err) { return rollback(err) }
|
|
391
289
|
}
|
|
392
|
-
return gyp.commands.install([noretry].concat(argv))
|
|
393
290
|
}
|
|
394
|
-
}
|
|
395
291
|
|
|
396
292
|
/**
|
|
397
293
|
* SHA256 校验流
|
|
398
294
|
*/
|
|
399
295
|
class ShaSum extends Transform {
|
|
400
|
-
constructor
|
|
401
|
-
super()
|
|
402
|
-
this._callback = callback
|
|
403
|
-
this._digester = crypto.createHash('sha256')
|
|
296
|
+
constructor(callback) {
|
|
297
|
+
super(), this._callback = callback, this._digester = crypto.createHash('sha256');
|
|
404
298
|
}
|
|
405
299
|
|
|
406
|
-
_transform
|
|
407
|
-
this._digester.update(chunk)
|
|
408
|
-
callback(null, chunk)
|
|
300
|
+
_transform(chunk, _, callback) {
|
|
301
|
+
this._digester.update(chunk), callback(null, chunk)
|
|
409
302
|
}
|
|
410
303
|
|
|
411
|
-
_flush
|
|
412
|
-
this._callback(null, this._digester.digest('hex'))
|
|
413
|
-
callback()
|
|
304
|
+
_flush(callback) {
|
|
305
|
+
this._callback(null, this._digester.digest('hex')), callback()
|
|
414
306
|
}
|
|
415
307
|
}
|
|
416
308
|
|
|
417
|
-
install.usage = 'Install node development files for the specified node version.'
|
|
418
|
-
|
|
419
|
-
export { install }
|
|
309
|
+
install.usage = 'Install node development files for the specified node version.';
|
|
310
|
+
export { install }
|