@flun/nodejs-mobile-gyp 1.2.1 → 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.
@@ -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
- * 解析 config.gypi 内容为 JSON 对象
8
- * 逻辑翻译自 Node.js 的 tools/js2c.py
9
- */
10
- parseConfigGypi = config => {
11
- // 去掉 # 注释,拼接多行字符串,把单引号字符串归一化为双引号
12
- config = config.replace(/#.*/g, ''), config = config.replace(/'$\s+'/mg, ''), config = config.replace(/'/g, '"');
13
- return JSON.parse(config);
14
- },
15
- /**
16
- * 读取基础 config.gypi
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
- return JSON.parse(JSON.stringify(process.config)); // 回退到 process.config
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
- * 基于当前环境生成 config.gypi 内容
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
- const defaults = config.target_defaults, variables = config.variables;
43
- // 不要继承 base config.gypi 的 defaults,
44
- // 否则当 node 可执行文件在另一台机器编译(lib/include 路径不同)时会出问题
45
- defaults.cflags = [], defaults.defines = [], defaults.include_dirs = [], defaults.libraries = [];
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
- // 设置 default_configuration
48
- if ('debug' in gyp.opts) defaults.default_configuration = gyp.opts.debug ? 'Debug' : 'Release'
49
- if (!defaults.default_configuration) defaults.default_configuration = 'Release';
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
- // 设置 target_arch
52
- variables.target_arch = gyp.opts.arch || process.arch || 'ia32';
53
- if (variables.target_arch === 'arm64')
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
- // 设置 Node 开发目录和 Python 路径,默认禁用 -T "thin" 静态归档
57
- variables.nodedir = nodeDir, variables.python = python;
58
- variables.standalone_static_library = gyp.opts.thin ? 0 : 1;
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
- if (process.platform === 'win32') {
61
- defaults.msbuild_toolset = vsInfo.toolset;
62
- if (vsInfo.sdk) defaults.msvs_windows_target_platform_version = vsInfo.sdk;
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
- // 把其余未知选项当作变量写入,支持模块自定义 configure 参数,例如:
77
- // $ node-gyp configure --shared-libxml2
78
- for (const [k, v] of Object.entries(gyp.opts)) {
79
- if (k === 'argv' || k in gyp.configDefs) continue;
80
- variables[k.replace(/-/g, '_')] = v;
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
- return config;
84
- },
91
+ return config;
92
+ };
85
93
 
86
- /**
87
- * 生成并写出 config.gypi 文件
88
- */
89
- createConfigGypi = async ({ gyp, buildDir, nodeDir, vsInfo, python }) => {
90
- const configFilename = 'config.gypi', configPath = path.resolve(buildDir, configFilename);
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
- log.verbose('build/' + configFilename, '正在创建配置文件');
93
- const config = await getCurrentConfigGypi({ gyp, nodeDir, vsInfo, python }),
94
- // 保证 config.gypi 中的布尔值都转成字符串
95
- boolsToString = (k, v) => {
96
- if (typeof v === 'boolean') return String(v);
97
- return v;
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
- log.silly('build/' + configFilename, config);
101
- // 把 config.gypi 写入 build/ 目录
102
- const prefix = '# Do not edit. File was generated by node-gyp\'s "configure" step',
103
- json = JSON.stringify(config, boolsToString, 2);
104
- log.verbose('build/' + configFilename, '正在写出配置文件: %s', configPath);
105
- await fs.writeFile(configPath, [prefix, json, ''].join('\n'));
106
- return configPath;
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, parseConfigGypi, getCurrentConfigGypi }
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
- * 读取 CA 证书文件,按证书边界切分为多个证书
8
- */
9
- readCAFile = async filename => {
10
- // CA 文件可能包含多份证书,按证书边界切分; [\S\s]*? 用于匹配包括换行在内的所有内容;
11
- const ca = await fs.readFile(filename, 'utf8'), re = /(-----BEGIN CERTIFICATE-----[\S\s]*?-----END CERTIFICATE-----)/g;
12
- return ca.match(re);
13
- },
14
- /**
15
- * 使用 make-fetch-happen 下载指定 URL
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
- const requestOpts = {
21
- headers: { 'User-Agent': `node-gyp v${gyp.version} (node ${process.version})`, Connection: 'keep-alive' },
22
- proxy: gyp.opts.proxy, noProxy: gyp.opts.noproxy
23
- }, cafile = gyp.opts.cafile;
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
- if (cafile) requestOpts.ca = await readCAFile(cafile);
26
- const res = await fetch(url, requestOpts);
27
- log.http(res.status, res.url);
28
- return res;
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
- export { readCAFile, download }
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
- * 从脚本位置或进程对象推断 Node 根目录
8
- */
9
- findNodeDirectory = (scriptLocation, processObj) => {
10
- // 未传入时使用默认值,便于回归测试
11
- if (scriptLocation === undefined) scriptLocation = __dirname;
12
- if (processObj === undefined) processObj = process;
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
- const npmParentDirectory = path.join(scriptLocation, '../../../..');
16
- log.verbose('nodejs-mobile-gyp 根目录', 'npm 上级目录为 ' + path.basename(npmParentDirectory));
17
- log.verbose('nodejs-mobile-gyp 根目录', '正在查找 node 根目录');
18
- let nodeRootDir = '';
19
- if (path.basename(npmParentDirectory) === 'deps') {
20
- // 处于构建目录中,脚本位于 deps/npm/node_modules/node-gyp/lib
21
- nodeRootDir = path.join(npmParentDirectory, '..');
22
- log.verbose('nodejs-mobile-gyp 根目录', '在构建目录中,根目录 = ' + nodeRootDir);
23
- } else if (path.basename(npmParentDirectory) === 'node_modules') {
24
- // 处于 Node 安装目录中,脚本位于
25
- // lib/node_modules/npm/node_modules/node-gyp/lib 或
26
- // node_modules/npm/node_modules/node-gyp/lib(视平台而定)
27
- if (processObj.platform === 'win32') nodeRootDir = path.join(npmParentDirectory, '..');
28
- else nodeRootDir = path.join(npmParentDirectory, '../..');
29
- log.verbose('nodejs-mobile-gyp 根目录', '在安装目录中,根目录 = ' + nodeRootDir);
30
- } else {
31
- // 无法判断,尝试从 node 可执行文件的位置推断
32
- const nodeDir = path.dirname(processObj.execPath), directoryUp = path.basename(nodeDir);
33
- if (directoryUp === 'bin') nodeRootDir = path.join(nodeDir, '..');
34
- else if (directoryUp === 'Release' || directoryUp === 'Debug') {
35
- // 如果是刚构建的 node,且目录结构为仓库结构:
36
- // Windows 只需上一层,其余平台两层
37
- if (processObj.platform === 'win32') nodeRootDir = path.join(nodeDir, '..');
38
- else nodeRootDir = path.join(nodeDir, '../..');
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
- return nodeRootDir;
42
+ // 否则返回默认空串 ""
43
43
  }
44
+ return nodeRootDir;
45
+ };
44
46
 
45
- export { findNodeDirectory }
47
+ export { findNodeDirectory };
@@ -1,8 +1,8 @@
1
- import * as log from './log.js';
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
- before: () => { this.addLog('检查能否使用 "python3"'); },
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 };
@@ -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 { regSearchKeys, execFile } from './util.js';
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 };