|
|
'use strict';
|
|
|
// Descend into a directory structure and, for each file matching *.node, output
|
|
|
// based on the imports found in the file whether it's an N-API module or not.
|
|
|
|
|
|
// 引入Node.js的文件系统模块 'fs',用于执行文件系统相关的操作,比如读取目录内容、获取文件状态等。
|
|
|
const fs = require('fs');
|
|
|
// 引入Node.js的路径模块 'path',用于处理文件路径的拼接、解析等操作,方便在不同操作系统下正确操作文件路径。
|
|
|
const path = require('path');
|
|
|
// 引入Node.js的子进程模块 'child_process',用于创建和管理子进程,可在Node.js应用中执行外部命令等操作。
|
|
|
const child_process = require('child_process');
|
|
|
|
|
|
// 此函数用于检查给定的文件是否为N-API模块。它通过启动一个子进程执行指定的命令,并传递相应的参数,然后监听子进程的标准输出和关闭事件来判断文件的性质。
|
|
|
// 参数说明:
|
|
|
// - file:要检查的文件路径。
|
|
|
// - command:要执行的命令,例如在UNIX系统下可能是 'nm',在Windows系统下可能是 'dumpbin' 等用于查看符号信息的命令。
|
|
|
// - argv:传递给命令的参数数组,用于指定命令的具体执行方式,如额外的选项等。
|
|
|
// - reducer:一个回调函数,用于处理子进程输出的每一行数据,根据一定规则判断文件是否为N-API模块,并逐步汇总判断结果。
|
|
|
function checkFile(file, command, argv, reducer) {
|
|
|
// 使用 'child_process.spawn' 方法创建一个子进程来执行指定的命令,传递相应的参数,并配置子进程的标准输入、输出和错误输出流。
|
|
|
// 这里将标准输入流设置为 'inherit',表示继承父进程(当前Node.js进程)的标准输入;标准输出流设置为 'pipe',表示将子进程的输出通过管道传输,以便后续在Node.js中读取;标准错误输出流设置为 'inherit',同样继承父进程的标准错误输出。
|
|
|
const child = child_process.spawn(command, argv, {
|
|
|
stdio: ['inherit', 'pipe', 'inherit']
|
|
|
});
|
|
|
// 用于存储上一次读取子进程输出数据时剩余未处理完的部分,可能是因为数据刚好在换行符中间截断等情况导致的。
|
|
|
let leftover = '';
|
|
|
// 用于记录文件是否为N-API模块的判断结果,初始值设为 'undefined',表示尚未确定。
|
|
|
let isNapi = undefined;
|
|
|
// 监听子进程的标准输出流的 'data' 事件,当子进程有新的输出数据时会触发该事件,在此事件处理函数中进行相应的处理。
|
|
|
child.stdout.on('data', (chunk) => {
|
|
|
// 如果当前还未确定文件是否为N-API模块(即 'isNapi' 仍为 'undefined'),则进行以下处理。
|
|
|
if (isNapi === undefined) {
|
|
|
// 将剩余未处理的数据和本次接收到的新数据块拼接起来,并按换行符('\r\n' 或 '\n')进行分割,得到一个包含每行数据的数组,模拟按行读取完整输出内容的效果。
|
|
|
chunk = (leftover + chunk.toString()).split(/[\r\n]+/);
|
|
|
// 将分割后的数组的最后一个元素取出,作为剩余未处理完的数据,存储到 'leftover' 变量中,因为它可能是不完整的一行,留待下次处理。
|
|
|
leftover = chunk.pop();
|
|
|
// 使用'reducer' 回调函数对分割后的每一行数据进行处理,将当前的判断结果(初始为 'undefined')和每一行数据作为参数传递给'reducer',通过'reducer' 函数内部的逻辑逐步更新判断结果,并将最终结果重新赋值给 'isNapi'。
|
|
|
isNapi = chunk.reduce(reducer, isNapi);
|
|
|
// 如果经过处理后已经确定了文件是否为N-API模块(即 'isNapi' 不再是 'undefined'),则终止子进程,避免不必要的资源消耗,因为已经得到了需要的判断结果。
|
|
|
if (isNapi!== undefined) {
|
|
|
child.kill();
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
// 监听子进程的 'close' 事件,当子进程结束运行时会触发该事件,在此事件处理函数中根据子进程的退出状态码和终止信号等信息进行相应的处理和输出。
|
|
|
child.on('close', (code, signal) => {
|
|
|
// 如果子进程的退出状态码为 'null' 且终止信号不为 'null',或者退出状态码不为0,表示子进程是非正常结束的,可能是命令执行出错等情况,此时向控制台输出相应的错误提示信息,展示命令的退出状态码和终止信号内容。
|
|
|
if ((code === null && signal!== null) || (code!== 0)) {
|
|
|
console.log(
|
|
|
command +'exited with code:'+ code +'and signal:'+ signal);
|
|
|
} else {
|
|
|
// 如果子进程正常结束,根据文件是否为N-API模块(通过 'isNapi' 的值判断),以不同颜色在控制台输出相应的提示信息,绿色('\x1b[42m' 是设置终端背景色为绿色的ANSI转义序列)表示是N-API模块,红色('\x1b[41m' 是设置终端背景色为红色的ANSI转义序列)表示不是N-API模块,并同时输出文件的路径信息,最后使用 '\x1b[0m' 恢复终端的默认颜色设置。
|
|
|
console.log(
|
|
|
'\x1b[' + (isNapi? '42' : '41') +'m' +
|
|
|
(isNapi?' N-API' : 'Not N-API') +
|
|
|
'\x1b[0m:'+ file);
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 此函数用于在UNIX系统下检查给定的文件是否为N-API模块。它调用 'checkFile' 函数,并传入适用于UNIX系统的命令('nm')及其参数,以及特定的用于处理输出结果的'reducer' 回调函数来进行判断。
|
|
|
function checkFileUNIX(file) {
|
|
|
checkFile(file, 'nm', ['-a', file], (soFar, line) => {
|
|
|
// 如果当前还未确定文件是否为N-API模块(即'soFar' 为 'undefined'),则进行以下处理,尝试从输出的每一行数据中提取相关信息并判断是否包含N-API相关的符号。
|
|
|
if (soFar === undefined) {
|
|
|
// 使用正则表达式匹配每一行数据,尝试提取出符号地址(可能为空)、符号类型和符号名称等信息,将匹配结果存储在 'line' 数组中(如果匹配成功)。
|
|
|
line = line.match(/([0-9a-f]*)? ([a-zA-Z]) (.*$)/);
|
|
|
// 如果匹配到的符号类型为 'U'(表示未定义的符号,通常是依赖外部库提供的符号),进一步检查符号名称是否以 'napi' 开头,如果是,则认为该文件可能是N-API模块,将'soFar' 设置为 'true',表示初步判断为是N-API模块。
|
|
|
if (line[2] === 'U') {
|
|
|
if (/^napi/.test(line[3])) {
|
|
|
soFar = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
// 返回当前的判断结果(可能是 'undefined' 或者 'true'),以便 'checkFile' 函数中的'reduce' 操作能继续汇总判断结果。
|
|
|
return soFar;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 此函数用于在Windows系统下检查给定的文件是否为N-API模块。它同样调用 'checkFile' 函数,但传入适用于Windows系统的命令('dumpbin')及其参数,以及对应的用于处理输出结果的'reducer' 回调函数来进行判断。
|
|
|
function checkFileWin32(file) {
|
|
|
checkFile(file, 'dumpbin', ['/imports', file], (soFar, line) => {
|
|
|
// 如果当前还未确定文件是否为N-API模块(即'soFar' 为 'undefined'),则进行以下处理,尝试从输出的每一行数据中提取相关信息并判断是否包含N-API相关的符号。
|
|
|
if (soFar === undefined) {
|
|
|
// 使用正则表达式匹配每一行数据,尝试提取出符号地址(可能为空)、符号类型和符号名称等信息,将匹配结果存储在 'line' 数组中(如果匹配成功)。注意这里的正则表达式与UNIX系统下的稍有不同,以适配 'dumpbin' 命令输出的格式特点。
|
|
|
line = line.match(/([0-9a-f]*)? +([a-zA-Z0-9]) (.*$)/);
|
|
|
// 如果匹配结果存在(即 'line' 不为 'null'),并且符号名称(取匹配结果数组的最后一个元素)以 'napi' 开头,则认为该文件可能是N-API模块,将'soFar' 设置为 'true',表示初步判断为是N-API模块。
|
|
|
if (line && /^napi/.test(line[line.length - 1])) {
|
|
|
soFar = true;
|
|
|
}
|
|
|
}
|
|
|
// 返回当前的判断结果(可能是 'undefined' 或者 'true'),以便 'checkFile' 函数中的'reduce' 操作能继续汇总判断结果。
|
|
|
return soFar;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 此函数用于递归遍历给定的目录及其子目录结构,对每个以 '.node' 结尾的文件,根据当前操作系统类型调用相应的文件检查函数('checkFileUNIX' 或 'checkFileWin32')来判断是否为N-API模块。
|
|
|
function recurse(top) {
|
|
|
// 使用 'fs.readdir' 方法读取指定目录下的所有文件和子目录名称,读取成功后会触发回调函数,在回调函数中进行后续处理,如果读取过程出现错误,则抛出相应的错误信息,提示读取目录时出错以及具体的错误内容。
|
|
|
fs.readdir(top, (error, items) => {
|
|
|
if (error) {
|
|
|
throw ("error reading directory " + top + ": " + error);
|
|
|
}
|
|
|
// 遍历读取到的文件和子目录名称数组,对每个元素进行相应处理。
|
|
|
items.forEach((item) => {
|
|
|
// 将当前元素(文件或子目录名称)与给定的目录路径拼接起来,得到完整的文件或子目录路径,方便后续操作。
|
|
|
item = path.join(top, item);
|
|
|
// 使用 'fs.stat' 方法获取指定文件或目录的状态信息(例如是文件还是目录、文件大小、创建时间等),获取成功后会触发回调函数,在回调函数中根据状态信息进行进一步处理,如果获取过程出现错误,则抛出相应的错误信息,提示获取该文件或目录状态时出错以及具体的错误内容。
|
|
|
fs.stat(item, ((item) => (error, stats) => {
|
|
|
if (error) {
|
|
|
throw ("error about " + item + ": " + error);
|
|
|
}
|
|
|
// 如果获取到的状态信息表明当前路径对应的是一个目录,则递归调用'recurse' 函数,继续遍历该子目录及其下的内容。
|
|
|
if (stats.isDirectory()) {
|
|
|
recurse(item);
|
|
|
} else if (/[.]node$/.test(item) &&
|
|
|
// 额外的条件判断,明确忽略名为 'nothing.node' 的文件,因为它可能是 'node-addon-api' 在识别Node.js版本时产生的特定产物(根据注释说明),这些文件不需要进行N-API模块的检查。
|
|
|
path.basename(item)!== 'nothing.node') {
|
|
|
// 根据当前Node.js进程运行的操作系统平台类型(通过 'process.platform' 获取),选择调用相应的文件检查函数(在Windows系统下调用 'checkFileWin32',在UNIX系统下调用 'checkFileUNIX')来检查当前的 '.node' 文件是否为N-API模块。
|
|
|
process.platform === 'win32'?
|
|
|
checkFileWin32(item) :
|
|
|
checkFileUNIX(item);
|
|
|
}
|
|
|
})(item));
|
|
|
});
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 根据命令行参数来决定从哪个目录开始进行文件检查。如果命令行参数长度大于3(表示有指定要检查的目录路径作为第三个参数),则从指定的目录开始;否则,从当前目录('.' 表示当前目录)开始,调用'recurse' 函数启动整个目录遍历和文件检查的流程。
|
|
|
recurse(process.argv.length > 3? process.argv[2] : '.');
|