从源码分析node-gyp指定node库文件下载地址

2023-05-28 0 610

当他们加装node的C/C++原生植物组件时,牵涉到采用node-gyp对C/C++原生植物组件的校对组织工作(configure、build)。那个操作过程,须要nodejs的头文档和动态库参予(先期称库文档)对C/C++工程项目校对和链接。库文档从这儿浏览,会有很大方法论展开处置,责任编辑将从源代码侧发力展开预测。

撰写单纯的原生植物组件

为的是方便快捷展开预测,他们具体来说建立两个原生植物组件(有关怎样撰写原生植物组件的技术细节无须责任编辑探讨)。

http://hello_world.cc

#include <node.h> void Method(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); args.GetReturnValue().Set(v8::String::NewFromUtf8( isolate, “world”).ToLocalChecked()); } void Initialize(v8::Local<v8::Object> exports) { NODE_SET_METHOD(exports, “hello”, Method); } NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)

binding.gyp

{ “targets”: [ { “target_name”: “hello_world”, “sources”: [ “hello_world.cc” ] } ] }

index.js

const binding = require(./build/Release/hello_world); console.log(binding.hello());

package.json

“scripts”: { “build”: “node-gyp configure && node-gyp build”, “run:demo”: “node index.js” },

整体结构

从源码分析node-gyp指定node库文件下载地址

按照如下命令依次运行:

$ npm run build // 采用node-gyp配置并构建 $ npm run run:demo // 运行Demo

输出如下:

D:\Projects\node-addon-demo>npm run run:demo > node-addon-demo@1.0.0 run:demo > node index.js world

从源代码预测node-gyp浏览库文档的路径

具体来说要直接给出两个结论,库文档并不是每次都要从网络上浏览,库文档浏览后会缓存在本地两个目录,在Windows上为C:\Users\用户\AppData\Local\node-gyp\Cache中,并按照nodejs的版本展开存储:

从源码分析node-gyp指定node库文件下载地址

本人电脑加装的node版本为14.15.0,且曾经已经缓存了对应的库文档。

为的是便于预测,他们具体来说删除该缓存文档,并且在原有的npm命令加上–verbose,输出更加详细的日志:

$ npm run build –verbose

于是,他们可以从众多的输出中,看到两个关键信息:

从源码分析node-gyp指定node库文件下载地址

从日志中可以看出,node-gyp在构建操作过程中,会建立缓存目录,然后从选定URL浏览选定版本的headers文档。

他们利用GrepWin(一款Windows下超好用的文本内容搜索工具,官网),在node-gyp目录中搜索created nodedir那个关键词,因为可以看到gyp http GET上面出现了那个关键词。那么现在有两个新的问题,node-gyp目录在哪儿?其实,从上面的日志往上查看,能够找到:

从源码分析node-gyp指定node库文件下载地址

这里是调用的他们全局加装的npm依赖的node-gyp,于是他们定位到node-gyp所在目录展开搜索:

从源码分析node-gyp指定node库文件下载地址

进入该文档,他们找到:

从源码分析node-gyp指定node库文件下载地址

找到关键词搜索后,继续往先期代码查阅,能够看到两个download函数的调用,入参最后一位是url,此时已经是成型的url,所以接下来他们须要确定,release.tarballUrl那个值,究竟是什么时候确定的。

tarballUrl怎样得到

继续向上翻阅代码,能够在入口处看到那个release是怎样生成的:

从源码分析node-gyp指定node库文件下载地址

进入代码后,能够找到一段核心的构建:

从源码分析node-gyp指定node库文件下载地址

通过上述代码流程,他们总结出来,tarballUrl的baseUrl取决于是否存在overrideDistUrl,若存在,则直接使用;否则采用默认URL:https://nodejs.org/dist。

再查看overrideDistUrl的传入点:

从源码分析node-gyp指定node库文件下载地址

也就是说,gyp对象的opts属性存在dist-url或disturl时,就会采用该值作为库文档浏览的baseUrl。

怎样构建gyp.opts

具体来说检查该函数的调用点:

从源码分析node-gyp指定node库文件下载地址

发现configure和install.js都采用了该函数,且都是入口处展开的调用的:

configure.js

function configure (gyp, argv, callback) { var python var buildDir = path.resolve(build) var configNames = [config.gypi, common.gypi] var configs = [] var nodeDir var release = processRelease(argv, gyp, process.version, process.release) …… } module.exports = configure

install.js

function install (fs, gyp, argv, callback) { var release = processRelease(argv, gyp, process.version, process.release) …… } module.exports = function (gyp, argv, callback) { return install(fs, gyp, argv, callback) }

可以看到confiigure.js和install.js都作为函数形式导出,也就是说,gyp那个对象是在这两个组件在被导入并以函数形式调用时被传入的。那么接下来他们须要看这两个组件在何处采用的。

在上文他们查看当前执行的node-gyp目录的时候,他们就看到过:

gyp verb cli [ gyp verb cli D:\\Programs\\nodejs\\node.exe, gyp verb cli D:\\Programs\\nodejs\\global_modules\\node_modules\\npm\\node_modules\\node-gyp\\bin\\node-gyp.js, gyp verb cli configure gyp verb cli ]

入口函数是:node-gyp根目录/bin/node-gyp.js。所以,他们将node-gyp以工程项目的形式添加到IDEA中,尝试以相同的形式调用这些命令,通过开启DEBUG模式,来一探究竟。

从源码分析node-gyp指定node库文件下载地址

在bin/node-gyp.js中的最下方展开了两个名为run的函数调用:

// bin/node-gyp.js // …… // 还有很多省略的代码…… // start running the given commands!run()

根据注释可以,run()执行所提供的命令。翻阅该函数:

从源码分析node-gyp指定node库文件下载地址

总体分为两步:

从对象prog的todo那个数组中取出首个command命令对象,不存在判定为所有命令执行完成。从对象prog的命令数组(commands)中找到对应命令名称(command.name),通过代码可知,该命令实际上对应两个函数。传入参数(command.args)完成该函数的调用。

那么那个prog是什么呢?通过向上阅读代码,可以知道来自于上层目录提供的组件:

从源码分析node-gyp指定node库文件下载地址

而上层所指代的组件是通过package.json的main字段可知是lib/node-gyp.js:

// 根目录下的package.json “main”: “./lib/node-gyp.js”,

进入该文档的gyp函数,返回的是类Gyp的实例,而Gyp实例的构造操作过程如下:

从源码分析node-gyp指定node库文件下载地址
采用self变量指代Gyp实例,并建立devDir和commands字段。遍历上方的commands字符串数组,给self(也就是Gyp实例)的commands属性中,逐步添加对应命令名称的函数,函数的实现是:require和command同名的js模块,这些组件又本身是以函数形式导出的,最终是调用对应组件函数。举例说明:当遍历到command为configure的时候,就是如下的形式:
self.commands[configure] = function (argv, callback) { log.verbose(command, configure, argv) return require(./configure)(self, argv, callback) }

那么在展开node-gyp configure时的调用栈就如下:

执行nodegyp configure: => run() => gyp.commands[configure](argv, cb); => require(./configure)(self, argv, cb); // self就是Gyp实例

前文他们已经知道了configure.js那个组件导出的就是两个函数:

// configure.jsfunction configure (gyp, argv, callback) { var python var buildDir = path.resolve(build) var configNames = [config.gypi, common.gypi] var configs = [] var nodeDir // 那个gyp,就是入参gyp,也就是上面的gyp实例 var release = processRelease(argv, gyp, process.version, process.release) } module.exports = configure

所以,他们终于知道processRelease的入参的gyp,就是上面的gyp实例。那么gyp实例中的opts属性,是哪儿来的呢?采用IDEA的Debug展开断点调式,调试bin/node-gyp.js:

从源码分析node-gyp指定node库文件下载地址

可以看到,在执行parseArgv那个函数前,gyp实例里面还不存在opts属性,而执行后,又在采用opts属性的devdir。也就是说,parseArgv那个函数很大构建了opts,接下来他们重点预测那个函数。

从源码分析node-gyp指定node库文件下载地址

入口的argv就是他们的运行时入参:

“dev”: “node ./bin/node-gyp.js configure”

具体来说会经过nopt函数,看样子,是对命令行参数和短命令的处置:

从源码分析node-gyp指定node库文件下载地址

然后是该函数其他的部分:

从源码分析node-gyp指定node库文件下载地址

主要分为两个部分:

对argv的解析对环境变量的解析

对argv的解析不牵涉设置opts属性,他们重点看对环境变量的解析:

// support for inheriting config env variables from npm var npmConfigPrefix = npm_config_ Object.keys(process.env).forEach(function (name) { if (name.indexOf(npmConfigPrefix) !== 0) { return } var val = process.env[name] if (name === npmConfigPrefix + loglevel) { log.level = val } else { // add the user-defined options to the config name = name.substring(npmConfigPrefix.length) // gyp@741b7f1 enters an infinite loop when it encounters // zero-length options so ensure those dont get through. if (name) { this.opts[name] = val } } }, this)

处置流程为:

判断环境变量的名称(name),如果不是以npm_config_开头,则跳过该次处置,否则进入下一步。如果变量名是npm_config_loglevel(npm的日志等级变量),则采用该日志等级作为node-gyp在采用npm时候的日志变量(这是对日志等级的特殊处置)。否则(一般处置),截断该变量的名,例如name = npm_config_my_key,则得到my_key,设置到opts中:opts[my_key] = 变量值。

那么,回到他们一开始的目的,他们知道了要实现从选定的地方浏览node的库文档,只要opts里面存在dist-url或是disturl即可。有些读者可能会说,那这样就行了呀:

从源码分析node-gyp指定node库文件下载地址

实际上,并不行:

从源码分析node-gyp指定node库文件下载地址

解析结束后,会发现,gyp.opts中是不存在dist-url字段的,只有dist_url。这一切的缘由,都是因为,npm在处置环境变量的时候,会将-替换为下划线_(config | npm Docs (npmjs.com))。

好在,node-gyp还能够处置opts中的disturl字段。所以他们只须要在采用npm来采用node-gyp的时候,加入参数–disturl。现在,让他们回到他们一开始的node-addon-demo,添加设置变量的参数:

“scripts”: { “build”: “node-gyp configure && node-gyp build”, “build:custom”: “npm run build –verbose –disturl=this_is_my_custom_url”, “run:demo”: “node index.js” },

上述build:custom就是他们新加的配置,通过运行,果然,加载的是他们制定的url:

gyp verb created nodedir C:\Users\w4ngzhen\AppData\Local\node-gyp\Cache\14.15.0 // 这里报错忽略,因为采用的是两个无效的url: this_is_my_custom_url // 主要是为的是验证确实是改变了 gyp http GET this_is_my_custom_url/v14.15.0/node-v14.15.0-headers.tar.gz gyp WARN install got an error, rolling back install gyp verb command remove [ 14.15.0 ]

node-gyp的直接采用和npm采用的区别

那么,有的细心的读者可能会说,明明这里通过npm采用的时候会转为下划线,那在node-gyp的官方github,说是可以采用dist-url那个参数呢?。

nodejs/node-gyp: Node.js native addon build tool (github.com)

从源码分析node-gyp指定node库文件下载地址

实际上,官方文档给出的参数,须要你直接采用node-gyp方式展开设置,也就是说,–dist-url那个参数必须紧跟node-gyp的命令:

node-gyp configure –dist-url=xxx

像是上面的npm run ${采用node-gyp的脚本名} –dist-url=xxx,那个dist-url是作为npm的参数来被识别,而非node-gyp。所以,对于demo,他们还可以如下:

“scripts”: { “build”: “node-gyp configure –dist-url=this_is_my_custom_url && node-gyp build –dist-url=this_is_my_custom_url”, “build:custom”: “npm run build –verbose”, “run:demo”: “node index.js” },

注意,这一次,我把–dist-url是放在和node-gyp命令的参数的。但是,他们知道有些npm包,内部就直接采用node-gyp展开配置校对的操作,那个操作过程没法通过–dist-url紧跟node-gyp命令方式,所以只能在例如.npmrc文档中配置兼容的不会被下划线处置的disturl。

总结

要想让node-gyp浏览node库文档的时候,能够走选定的镜像,可以通过配置–dist-url或是–disturl的方式,但配置dist-url形式参数只能是参数紧跟node-gyp的形式:

node-gyp configure –dist-url=xxx

而不能是如下的形式:

// 你的package.json scripts字段 “build”: “node-gyp configure” // 然后在命令行调用 npm run build –dist-url=xxx //

因为此时–dist-url参数是npm的参数,且会被处置为npm_config_dist_url下划线形式,进而在gyp.opts只有dist_url属性。

所以,最安全的方式是采用disturl参数:

情况1:

node-gyp configure –disturl=xxx

情况2:

// 你的package.json scripts字段 “build”: “node-gyp configure” // 然后在命令行调用 npm run build –disturl=xxx

情况1下,disturl是作为node-gyp的参数展开解析,能够被设置到opts中。

情况2,disturl是作为npm的参数被加入到npm环境变量:npm_config_disturl,此时,node-gyp解析process.env的时候,也能解析到disturl进而设置到opts。

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务