@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
|
@@ -1,109 +1,120 @@
|
|
|
1
1
|
import gracefulFs from 'graceful-fs';
|
|
2
|
-
import * as log from './log.js';
|
|
3
2
|
import path from 'path';
|
|
3
|
+
import * as log from './log.js';
|
|
4
4
|
|
|
5
|
-
const fs = gracefulFs.promises
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*/
|
|
18
|
-
getBaseConfigGypi = async ({ gyp, nodeDir }) => {
|
|
19
|
-
// 在以下情况优先读取 $nodeDir/include/node/config.gypi:
|
|
20
|
-
// 1. 指定了 --dist-url 或 --nodedir
|
|
21
|
-
// 2. 且没有指定 --force-process-config
|
|
22
|
-
const useCustomHeaders = gyp.opts.nodedir || gyp.opts.disturl || gyp.opts['dist-url'],
|
|
23
|
-
shouldReadConfigGypi = useCustomHeaders && !gyp.opts['force-process-config'];
|
|
24
|
-
if (shouldReadConfigGypi && nodeDir) {
|
|
25
|
-
try {
|
|
26
|
-
const baseConfigGypi = await fs.readFile(path.resolve(nodeDir, 'include/node/config.gypi'));
|
|
27
|
-
return parseConfigGypi(baseConfigGypi.toString());
|
|
28
|
-
} catch (err) { log.warn('读取 config.gypi', err.message) }
|
|
29
|
-
}
|
|
5
|
+
const fs = gracefulFs.promises;
|
|
6
|
+
/**
|
|
7
|
+
* 解析 config.gypi 内容为 JSON 对象,逻辑翻译自 Node.js 的 tools/js2c.py
|
|
8
|
+
* >查看定义:@see {@link parseConfigGypi}
|
|
9
|
+
* @param {string} config config.gypi 文本内容
|
|
10
|
+
* @returns {object} 解析后的配置对象
|
|
11
|
+
*/
|
|
12
|
+
const parseConfigGypi = config => {
|
|
13
|
+
// 去掉 # 注释,拼接多行字符串,把单引号字符串归一化为双引号
|
|
14
|
+
config = config.replace(/#.*/g, ''), config = config.replace(/'$\s+'/mg, ''), config = config.replace(/'/g, '"');
|
|
15
|
+
return JSON.parse(config);
|
|
16
|
+
};
|
|
30
17
|
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
/**
|
|
19
|
+
* 读取基础 config.gypi
|
|
20
|
+
* @param {object} options 含 gyp、nodeDir 的对象
|
|
21
|
+
* @returns {Promise<object>} 基础配置对象
|
|
22
|
+
*/
|
|
23
|
+
const getBaseConfigGypi = async ({ gyp, nodeDir }) => {
|
|
24
|
+
// 在以下情况优先读取 $nodeDir/include/node/config.gypi:
|
|
25
|
+
// 1. 指定了 --dist-url 或 --nodedir
|
|
26
|
+
// 2. 且没有指定 --force-process-config
|
|
27
|
+
const useCustomHeaders = gyp.opts.nodedir || gyp.opts.disturl || gyp.opts['dist-url'],
|
|
28
|
+
shouldReadConfigGypi = useCustomHeaders && !gyp.opts['force-process-config'];
|
|
29
|
+
if (shouldReadConfigGypi && nodeDir) {
|
|
30
|
+
try {
|
|
31
|
+
const baseConfigGypi = await fs.readFile(path.resolve(nodeDir, 'include/node/config.gypi'));
|
|
32
|
+
return parseConfigGypi(baseConfigGypi.toString());
|
|
33
|
+
} catch (err) { log.warn('读取 config.gypi', err.message) }
|
|
34
|
+
}
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
*/
|
|
37
|
-
getCurrentConfigGypi = async ({ gyp, nodeDir, vsInfo, python }) => {
|
|
38
|
-
const config = await getBaseConfigGypi({ gyp, nodeDir });
|
|
39
|
-
if (!config.target_defaults) config.target_defaults = {};
|
|
40
|
-
if (!config.variables) config.variables = {};
|
|
36
|
+
return JSON.parse(JSON.stringify(process.config)); // 回退到 process.config
|
|
37
|
+
};
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
/**
|
|
40
|
+
* 基于当前环境生成 config.gypi 内容
|
|
41
|
+
* >查看定义:@see {@link getCurrentConfigGypi}
|
|
42
|
+
* @param {object} options 含 gyp、nodeDir、vsInfo、python 的对象
|
|
43
|
+
* @returns {Promise<object>} 生成的配置对象
|
|
44
|
+
*/
|
|
45
|
+
const getCurrentConfigGypi = async ({ gyp, nodeDir, vsInfo, python }) => {
|
|
46
|
+
const config = await getBaseConfigGypi({ gyp, nodeDir });
|
|
47
|
+
if (!config.target_defaults) config.target_defaults = {};
|
|
48
|
+
if (!config.variables) config.variables = {};
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
const defaults = config.target_defaults, variables = config.variables;
|
|
51
|
+
// 不要继承 base config.gypi 的 defaults,
|
|
52
|
+
// 否则当 node 可执行文件在另一台机器编译(lib/include 路径不同)时会出问题
|
|
53
|
+
defaults.cflags = [], defaults.defines = [], defaults.include_dirs = [], defaults.libraries = [];
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
defaults.msvs_configuration_platform = 'ARM64', defaults.xcode_configuration_platform = 'arm64';
|
|
55
|
+
// 设置 default_configuration
|
|
56
|
+
if ('debug' in gyp.opts) defaults.default_configuration = gyp.opts.debug ? 'Debug' : 'Release'
|
|
57
|
+
if (!defaults.default_configuration) defaults.default_configuration = 'Release';
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
// 设置 target_arch
|
|
60
|
+
variables.target_arch = gyp.opts.arch || process.arch || 'ia32';
|
|
61
|
+
if (variables.target_arch === 'arm64')
|
|
62
|
+
defaults.msvs_configuration_platform = 'ARM64', defaults.xcode_configuration_platform = 'arm64';
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (variables.target_arch === 'arm64') {
|
|
64
|
-
if (vsInfo.versionMajor > 15 || (vsInfo.versionMajor === 15 && vsInfo.versionMajor >= 9))
|
|
65
|
-
defaults.msvs_enable_marmasm = 1;
|
|
66
|
-
else log.warn('仅 Visual Studio 2017 15.9 及以上版本支持编译 ARM64 汇编');
|
|
67
|
-
}
|
|
68
|
-
variables.msbuild_path = vsInfo.msBuild;
|
|
69
|
-
// Windows 原生构建走 MSVC 工具集:必须关闭 clang 与 LTO,
|
|
70
|
-
// 否则 common.gypi 会把 toolset 设为 ClangCL,并向 MSVC 链接器传入 Clang 专用参数
|
|
71
|
-
// (-flto=thin、/opt:lldltojobs),导致 MSB8020 或 LNK1117
|
|
72
|
-
if (!gyp.opts.format || gyp.opts.format === 'msvs')
|
|
73
|
-
variables.clang = 0, variables.enable_lto = 'false', variables.enable_thin_lto = 'false', variables.lto_jobs = '';
|
|
74
|
-
}
|
|
64
|
+
// 设置 Node 开发目录和 Python 路径,默认禁用 -T "thin" 静态归档
|
|
65
|
+
variables.nodedir = nodeDir, variables.python = python;
|
|
66
|
+
variables.standalone_static_library = gyp.opts.thin ? 0 : 1;
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
if (process.platform === 'win32') {
|
|
69
|
+
defaults.msbuild_toolset = vsInfo.toolset;
|
|
70
|
+
if (vsInfo.sdk) defaults.msvs_windows_target_platform_version = vsInfo.sdk;
|
|
71
|
+
if (variables.target_arch === 'arm64') {
|
|
72
|
+
if (vsInfo.versionMajor > 15 || (vsInfo.versionMajor === 15 && vsInfo.versionMajor >= 9))
|
|
73
|
+
defaults.msvs_enable_marmasm = 1;
|
|
74
|
+
else log.warn('仅 Visual Studio 2017 15.9 及以上版本支持编译 ARM64 汇编');
|
|
81
75
|
}
|
|
76
|
+
variables.msbuild_path = vsInfo.msBuild;
|
|
77
|
+
// Windows 原生构建走 MSVC 工具集:必须关闭 clang 与 LTO,
|
|
78
|
+
// 否则 common.gypi 会把 toolset 设为 ClangCL,并向 MSVC 链接器传入 Clang 专用参数
|
|
79
|
+
// (-flto=thin、/opt:lldltojobs),导致 MSB8020 或 LNK1117
|
|
80
|
+
if (!gyp.opts.format || gyp.opts.format === 'msvs')
|
|
81
|
+
variables.clang = 0, variables.enable_lto = 'false', variables.enable_thin_lto = 'false', variables.lto_jobs = '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 把其余未知选项当作变量写入,支持模块自定义 configure 参数,例如:
|
|
85
|
+
// $ node-gyp configure --shared-libxml2
|
|
86
|
+
for (const [k, v] of Object.entries(gyp.opts)) {
|
|
87
|
+
if (k === 'argv' || k in gyp.configDefs) continue;
|
|
88
|
+
variables[k.replace(/-/g, '_')] = v;
|
|
89
|
+
}
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
return config;
|
|
92
|
+
};
|
|
85
93
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
/**
|
|
95
|
+
* 生成并写出 config.gypi 文件
|
|
96
|
+
* >查看定义:@see {@link createConfigGypi}
|
|
97
|
+
* @param {object} options 含 gyp、buildDir、nodeDir、vsInfo、python 的对象
|
|
98
|
+
* @returns {Promise<string>} 生成的 config.gypi 路径
|
|
99
|
+
*/
|
|
100
|
+
const createConfigGypi = async ({ gyp, buildDir, nodeDir, vsInfo, python }) => {
|
|
101
|
+
const configFilename = 'config.gypi', configPath = path.resolve(buildDir, configFilename);
|
|
91
102
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
log.verbose('build/' + configFilename, '正在创建配置文件');
|
|
104
|
+
const config = await getCurrentConfigGypi({ gyp, nodeDir, vsInfo, python }),
|
|
105
|
+
// 保证 config.gypi 中的布尔值都转成字符串
|
|
106
|
+
boolsToString = (k, v) => {
|
|
107
|
+
if (typeof v === 'boolean') return String(v);
|
|
108
|
+
return v;
|
|
109
|
+
};
|
|
99
110
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
111
|
+
log.silly('build/' + configFilename, config);
|
|
112
|
+
// 把 config.gypi 写入 build/ 目录
|
|
113
|
+
const prefix = '# Do not edit. File was generated by node-gyp\'s "configure" step',
|
|
114
|
+
json = JSON.stringify(config, boolsToString, 2);
|
|
115
|
+
log.verbose('build/' + configFilename, '正在写出配置文件: %s', configPath);
|
|
116
|
+
await fs.writeFile(configPath, [prefix, json, ''].join('\n'));
|
|
117
|
+
return configPath;
|
|
118
|
+
};
|
|
108
119
|
|
|
109
|
-
export { createConfigGypi,
|
|
120
|
+
export { createConfigGypi, getCurrentConfigGypi, parseConfigGypi };
|
package/lib/download.js
CHANGED
|
@@ -2,30 +2,38 @@ import fetch from 'make-fetch-happen';
|
|
|
2
2
|
import gracefulFs from 'graceful-fs';
|
|
3
3
|
import * as log from './log.js';
|
|
4
4
|
|
|
5
|
-
const { promises: fs } = gracefulFs
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
download = async (gyp, url) => {
|
|
18
|
-
log.http('GET', url);
|
|
5
|
+
const { promises: fs } = gracefulFs;
|
|
6
|
+
/**
|
|
7
|
+
* 读取 CA 证书文件,按证书边界切分为多个证书
|
|
8
|
+
* >查看定义:@see {@link readCAFile}
|
|
9
|
+
* @param {string} filename CA 文件路径
|
|
10
|
+
* @returns {Promise<string[]>} 证书字符串数组
|
|
11
|
+
*/
|
|
12
|
+
const readCAFile = async filename => {
|
|
13
|
+
// CA 文件可能包含多份证书,按证书边界切分; [\S\s]*? 用于匹配包括换行在内的所有内容;
|
|
14
|
+
const ca = await fs.readFile(filename, 'utf8'), re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g;
|
|
15
|
+
return ca.match(re);
|
|
16
|
+
};
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
/**
|
|
19
|
+
* 使用 make-fetch-happen 下载指定 URL
|
|
20
|
+
* >查看定义:@see {@link download}
|
|
21
|
+
* @param {object} gyp gyp 实例
|
|
22
|
+
* @param {string} url 下载地址
|
|
23
|
+
* @returns {Promise<object>} fetch 响应对象
|
|
24
|
+
*/
|
|
25
|
+
const download = async (gyp, url) => {
|
|
26
|
+
log.http('GET', url);
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
28
|
+
const requestOpts = {
|
|
29
|
+
headers: { 'User-Agent': `node-gyp v${gyp.version} (node ${process.version})`, Connection: 'keep-alive' },
|
|
30
|
+
proxy: gyp.opts.proxy, noProxy: gyp.opts.noproxy
|
|
31
|
+
}, cafile = gyp.opts.cafile;
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
if (cafile) requestOpts.ca = await readCAFile(cafile);
|
|
34
|
+
const res = await fetch(url, requestOpts);
|
|
35
|
+
log.http(res.status, res.url);
|
|
36
|
+
return res;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { download, readCAFile };
|
|
@@ -2,44 +2,46 @@ import path from 'path';
|
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import * as log from './log.js';
|
|
4
4
|
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
/**
|
|
7
|
+
* 定位 Node.js 源码目录
|
|
8
|
+
* >查看定义:@see {@link findNodeDirectory}
|
|
9
|
+
* @returns {string} 目录路径
|
|
10
|
+
*/
|
|
11
|
+
const findNodeDirectory = (scriptLocation, processObj) => {
|
|
12
|
+
// 未传入时使用默认值,便于回归测试
|
|
13
|
+
if (scriptLocation === undefined) scriptLocation = __dirname;
|
|
14
|
+
if (processObj === undefined) processObj = process;
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
// 否则返回默认空串 ""
|
|
16
|
+
// 看看上层目录结构,判断当前所处位置
|
|
17
|
+
const npmParentDirectory = path.join(scriptLocation, '../../../..');
|
|
18
|
+
log.verbose('nodejs-mobile-gyp 根目录', 'npm 上级目录为 ' + path.basename(npmParentDirectory));
|
|
19
|
+
log.verbose('nodejs-mobile-gyp 根目录', '正在查找 node 根目录');
|
|
20
|
+
let nodeRootDir = '';
|
|
21
|
+
if (path.basename(npmParentDirectory) === 'deps') {
|
|
22
|
+
// 处于构建目录中,脚本位于 deps/npm/node_modules/node-gyp/lib
|
|
23
|
+
nodeRootDir = path.join(npmParentDirectory, '..');
|
|
24
|
+
log.verbose('nodejs-mobile-gyp 根目录', '在构建目录中,根目录 = ' + nodeRootDir);
|
|
25
|
+
} else if (path.basename(npmParentDirectory) === 'node_modules') {
|
|
26
|
+
// 处于 Node 安装目录中,脚本位于
|
|
27
|
+
// lib/node_modules/npm/node_modules/node-gyp/lib 或
|
|
28
|
+
// node_modules/npm/node_modules/node-gyp/lib(视平台而定)
|
|
29
|
+
if (processObj.platform === 'win32') nodeRootDir = path.join(npmParentDirectory, '..');
|
|
30
|
+
else nodeRootDir = path.join(npmParentDirectory, '../..');
|
|
31
|
+
log.verbose('nodejs-mobile-gyp 根目录', '在安装目录中,根目录 = ' + nodeRootDir);
|
|
32
|
+
} else {
|
|
33
|
+
// 无法判断,尝试从 node 可执行文件的位置推断
|
|
34
|
+
const nodeDir = path.dirname(processObj.execPath), directoryUp = path.basename(nodeDir);
|
|
35
|
+
if (directoryUp === 'bin') nodeRootDir = path.join(nodeDir, '..');
|
|
36
|
+
else if (directoryUp === 'Release' || directoryUp === 'Debug') {
|
|
37
|
+
// 如果是刚构建的 node,且目录结构为仓库结构:
|
|
38
|
+
// Windows 只需上一层,其余平台两层
|
|
39
|
+
if (processObj.platform === 'win32') nodeRootDir = path.join(nodeDir, '..');
|
|
40
|
+
else nodeRootDir = path.join(nodeDir, '../..');
|
|
41
41
|
}
|
|
42
|
-
|
|
42
|
+
// 否则返回默认空串 ""
|
|
43
43
|
}
|
|
44
|
+
return nodeRootDir;
|
|
45
|
+
};
|
|
44
46
|
|
|
45
|
-
export { findNodeDirectory }
|
|
47
|
+
export { findNodeDirectory };
|
package/lib/find-python.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
2
3
|
import semver from 'semver';
|
|
4
|
+
import * as log from './log.js';
|
|
3
5
|
import { execFile } from './util.js';
|
|
4
|
-
import os from 'os';
|
|
5
|
-
import fs from 'fs';
|
|
6
6
|
|
|
7
7
|
const win = process.platform === 'win32',
|
|
8
8
|
/**
|
|
@@ -47,6 +47,7 @@ const win = process.platform === 'win32',
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* Python 查找器:按顺序尝试多种途径定位可用的 Python 3.6+
|
|
50
|
+
* >查看定义:@see {@link PythonFinder}
|
|
50
51
|
*/
|
|
51
52
|
class PythonFinder {
|
|
52
53
|
static findPython = (...args) => new PythonFinder(...args).findPython();
|
|
@@ -62,15 +63,10 @@ class PythonFinder {
|
|
|
62
63
|
pyLauncher = 'py.exe';
|
|
63
64
|
winDefaultLocations = win ? scanWindowsPythonLocations() : [];
|
|
64
65
|
|
|
65
|
-
constructor(configPython) {
|
|
66
|
-
this.configPython = configPython, this.errorLog = [];
|
|
67
|
-
}
|
|
66
|
+
constructor(configPython) { this.configPython = configPython, this.errorLog = []; }
|
|
68
67
|
|
|
69
68
|
// 以 verbose 级别记录日志,同时保存下来,出错时以 error 级别展示,便于诊断
|
|
70
|
-
addLog(message) {
|
|
71
|
-
this.log.verbose(message), this.errorLog.push(message);
|
|
72
|
-
}
|
|
73
|
-
|
|
69
|
+
addLog(message) { this.log.verbose(message), this.errorLog.push(message); }
|
|
74
70
|
// 依次尝试多种 Python 定位方式;忽略错误,直到找到可用版本
|
|
75
71
|
async findPython() {
|
|
76
72
|
const SKIP = 0, FAIL = 1,
|
|
@@ -102,8 +98,7 @@ class PythonFinder {
|
|
|
102
98
|
}
|
|
103
99
|
this.addLog('检查是否从环境变量 PYTHON 显式指定 Python');
|
|
104
100
|
this.addLog(`- process.env.PYTHON 为 "${this.env.PYTHON}"`);
|
|
105
|
-
},
|
|
106
|
-
check: () => this.checkCommand(this.env.PYTHON)
|
|
101
|
+
}, check: () => this.checkCommand(this.env.PYTHON)
|
|
107
102
|
}
|
|
108
103
|
];
|
|
109
104
|
|
|
@@ -114,29 +109,19 @@ class PythonFinder {
|
|
|
114
109
|
check: () => this.checkPyLauncher(launcher)
|
|
115
110
|
});
|
|
116
111
|
}
|
|
117
|
-
checks.push({
|
|
118
|
-
before: () => this.addLog('通过 "py -0p" 枚举已安装的 Python'),
|
|
119
|
-
check: () => this.checkPyList()
|
|
120
|
-
});
|
|
112
|
+
checks.push({ before: () => this.addLog('通过 "py -0p" 枚举已安装的 Python'), check: () => this.checkPyList() });
|
|
121
113
|
}
|
|
122
114
|
|
|
123
115
|
checks.push(...[
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
check: () => this.checkCommand('python3')
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
before: () => { this.addLog('检查能否使用 "python"'); },
|
|
130
|
-
check: () => this.checkCommand('python')
|
|
131
|
-
}
|
|
116
|
+
{ before: () => { this.addLog('检查能否使用 "python3"'); }, check: () => this.checkCommand('python3') },
|
|
117
|
+
{ before: () => { this.addLog('检查能否使用 "python"'); }, check: () => this.checkCommand('python') }
|
|
132
118
|
]);
|
|
133
119
|
|
|
134
120
|
if (this.win) {
|
|
135
121
|
for (let i = 0; i < this.winDefaultLocations.length; ++i) {
|
|
136
122
|
const location = this.winDefaultLocations[i];
|
|
137
123
|
checks.push({
|
|
138
|
-
before: () => this.addLog(`检查 Python 是否位于 ${location}`),
|
|
139
|
-
check: () => this.checkExecPath(location)
|
|
124
|
+
before: () => this.addLog(`检查 Python 是否位于 ${location}`), check: () => this.checkExecPath(location)
|
|
140
125
|
});
|
|
141
126
|
}
|
|
142
127
|
}
|
|
@@ -190,8 +175,7 @@ class PythonFinder {
|
|
|
190
175
|
let lastErr;
|
|
191
176
|
for (const launcher of pyLauncherCandidates) {
|
|
192
177
|
try {
|
|
193
|
-
const stdout = await this.run(launcher, ['-0p'], false);
|
|
194
|
-
const paths = [];
|
|
178
|
+
const stdout = await this.run(launcher, ['-0p'], false), paths = [];
|
|
195
179
|
for (const line of stdout.split(/\r?\n/)) {
|
|
196
180
|
const m = line.match(/([A-Za-z]:\\[^\s]+)/);
|
|
197
181
|
if (m && /python\.exe$/i.test(m[1])) paths.push(m[1]);
|
|
@@ -201,13 +185,11 @@ class PythonFinder {
|
|
|
201
185
|
try {
|
|
202
186
|
return await this.checkExecPath(p);
|
|
203
187
|
} catch (err) {
|
|
204
|
-
lastErr = err;
|
|
205
|
-
this.log.silly('checkPyList: 跳过 %s: %s', p, err.message);
|
|
188
|
+
lastErr = err, this.log.silly('checkPyList: 跳过 %s: %s', p, err.message);
|
|
206
189
|
}
|
|
207
190
|
}
|
|
208
191
|
} catch (err) {
|
|
209
|
-
lastErr = err;
|
|
210
|
-
this.addLog(`- 经 "${launcher}" 执行 "py -0p" 失败`);
|
|
192
|
+
lastErr = err, this.addLog(`- 经 "${launcher}" 执行 "py -0p" 失败`);
|
|
211
193
|
}
|
|
212
194
|
}
|
|
213
195
|
throw lastErr || new Error('py -0p 未返回可用的 Python');
|
|
@@ -226,13 +208,11 @@ class PythonFinder {
|
|
|
226
208
|
valid = range.test(version);
|
|
227
209
|
} catch (err) {
|
|
228
210
|
this.log.silly('range.test() threw:\n%s', err.stack);
|
|
229
|
-
this.addLog(`- "${execPath}" 的版本无效`);
|
|
230
|
-
this.addLog('- 它是不是 Python 可执行文件?');
|
|
211
|
+
this.addLog(`- "${execPath}" 的版本无效`), this.addLog('- 它是不是 Python 可执行文件?');
|
|
231
212
|
throw err;
|
|
232
213
|
}
|
|
233
214
|
if (!valid) {
|
|
234
|
-
this.addLog(`- 版本为 ${version},应为 ${this.semverRange}`);
|
|
235
|
-
this.addLog('- 此版本的 Python 不受支持');
|
|
215
|
+
this.addLog(`- 版本为 ${version},应为 ${this.semverRange}`), this.addLog('- 此版本的 Python 不受支持');
|
|
236
216
|
throw new Error(`不支持的 Python 版本 ${version}`);
|
|
237
217
|
}
|
|
238
218
|
return this.succeed(execPath, version);
|
|
@@ -286,4 +266,4 @@ class PythonFinder {
|
|
|
286
266
|
}
|
|
287
267
|
}
|
|
288
268
|
|
|
289
|
-
export { PythonFinder }
|
|
269
|
+
export { PythonFinder };
|
package/lib/find-visualstudio.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import * as log from './log.js';
|
|
2
1
|
import { existsSync } from 'fs';
|
|
3
2
|
import nodePath from 'path';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
|
-
import
|
|
4
|
+
import * as log from './log.js';
|
|
5
|
+
import { execFile, regSearchKeys } from './util.js';
|
|
6
6
|
|
|
7
7
|
const path = nodePath.win32, __dirname = nodePath.dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
/**
|
|
9
9
|
* Visual Studio 查找器:定位可用的 VS 安装
|
|
10
|
+
* >查看定义:@see {@link VisualStudioFinder}
|
|
10
11
|
*/
|
|
11
12
|
class VisualStudioFinder {
|
|
12
13
|
static findVisualStudio = (...args) => new VisualStudioFinder(...args).findVisualStudio();
|
|
@@ -38,8 +39,7 @@ class VisualStudioFinder {
|
|
|
38
39
|
|
|
39
40
|
if (process.env.VCINSTALLDIR) {
|
|
40
41
|
this.envVcInstallDir = path.resolve(process.env.VCINSTALLDIR, '..');
|
|
41
|
-
this.addLog('运行在 VS 命令提示符中,安装路径为:\n' +
|
|
42
|
-
`"${this.envVcInstallDir}"\n- will only use this version`);
|
|
42
|
+
this.addLog('运行在 VS 命令提示符中,安装路径为:\n' + `"${this.envVcInstallDir}"\n- will only use this version`);
|
|
43
43
|
}
|
|
44
44
|
else this.addLog('未设置 VCINSTALLDIR,不在 VS 命令提示符中运行');
|
|
45
45
|
|
|
@@ -63,8 +63,7 @@ class VisualStudioFinder {
|
|
|
63
63
|
|
|
64
64
|
fail() {
|
|
65
65
|
if (this.configMsvsVersion && this.envVcInstallDir)
|
|
66
|
-
this.errorLog.push(
|
|
67
|
-
'msvs_version 与当前 VS 命令提示符不匹配,或该安装不可用');
|
|
66
|
+
this.errorLog.push('msvs_version 与当前 VS 命令提示符不匹配,或该安装不可用');
|
|
68
67
|
else if (this.configMsvsVersion) {
|
|
69
68
|
// 指定了 msvs_version 但没找到 VS,输出可接受的版本
|
|
70
69
|
this.errorLog.push('');
|
|
@@ -107,8 +106,7 @@ class VisualStudioFinder {
|
|
|
107
106
|
this.log.silly('PS 标准错误 = %j', stderr)
|
|
108
107
|
|
|
109
108
|
const failPowershell = () => {
|
|
110
|
-
this.addLog(`无法用 PowerShell 找到 Visual Studio 2017
|
|
111
|
-
可加 '--loglevel silly' 重新运行以获取更多细节`);
|
|
109
|
+
this.addLog(`无法用 PowerShell 找到 Visual Studio 2017 或更新版本,可加 '--loglevel silly' 重新运行以获取更多细节`);
|
|
112
110
|
return null;
|
|
113
111
|
}
|
|
114
112
|
|
|
@@ -158,22 +156,16 @@ class VisualStudioFinder {
|
|
|
158
156
|
|
|
159
157
|
if (info.msBuild) this.addLog('- 找到 "Visual Studio C++ 核心功能"');
|
|
160
158
|
else {
|
|
161
|
-
this.addLog('- 缺少 "Visual Studio C++ 核心功能"');
|
|
162
|
-
continue;
|
|
159
|
+
this.addLog('- 缺少 "Visual Studio C++ 核心功能"'); continue;
|
|
163
160
|
}
|
|
164
|
-
|
|
165
161
|
if (info.toolset) this.addLog(`- 找到 VC++ 工具集: ${info.toolset}`);
|
|
166
162
|
else {
|
|
167
|
-
this.addLog('- 缺少任何 VC++ 工具集');
|
|
168
|
-
continue;
|
|
163
|
+
this.addLog('- 缺少任何 VC++ 工具集'); continue;
|
|
169
164
|
}
|
|
170
|
-
|
|
171
165
|
if (info.sdk) this.addLog(`- 找到 Windows SDK: ${info.sdk}`);
|
|
172
166
|
else {
|
|
173
|
-
this.addLog('- 缺少任何 Windows SDK');
|
|
174
|
-
continue;
|
|
167
|
+
this.addLog('- 缺少任何 Windows SDK'); continue;
|
|
175
168
|
}
|
|
176
|
-
|
|
177
169
|
if (!this.checkConfigVersion(info.versionYear, info.path)) continue;
|
|
178
170
|
return info
|
|
179
171
|
}
|
|
@@ -346,4 +338,4 @@ class VisualStudioFinder {
|
|
|
346
338
|
}
|
|
347
339
|
}
|
|
348
340
|
|
|
349
|
-
export { VisualStudioFinder }
|
|
341
|
+
export { VisualStudioFinder };
|