上篇文章直接单刀直入讲述了node运行的一个整体流程,导致很多人在群里或者cnode中询问如何可以在本地断点调试node。因此献上这篇文章,让大家对node编译过程和开发有个整体的认知
c++编译三部曲
$ ./configure
$ make
$ make install
相信看到这三个命令后,大家应该是十分熟悉。没错,node编译也就是这三步。
- 第一步:配置。软件装到哪里、什么参数、什么os、装什么东西,全都是configure来确定的
- 第二步:编译。make会读取makefile的配置,进行编译,生成可执行文件
- 第三步:安装。make install 会根据设定好的路径,把软件安装到系统中
接下来,我们从这三个步骤入手,来串一下node整体的编译过程。
node 编译过程
./configure
目标很明确,我们直奔configure文件,来查看node的configure究竟都做了些什么。
额,原来关键代码就在这里,其中import了三个模块,分别为:nodedownload(下载模块)、getmoduleversion(获取版本)、以及gyp_node。大家知道,node其实是基于gyp进行编译的那么这个gyp_node模块具体是什么呢?让我们继续扒到tools/gyp_node
。
通过gyp_node中的这段代码可以读出来,其实最后运行的是node.gyp,我们视线跳转到node.gyp,而node.gyp就是构建文件内容,即python的一个数据结构(类似一个json串)。通过筛查其中的target_name
我们可以发现这个gyp_node做了有如下几件事情(只说比较重要的,有兴趣的同学可以自己翻看node.gyp):
- 关联node可执行文件的sources
- 定义node_js2c的输出文件为node_javascript.cc
- node_dtrace动态跟踪框架一系列定义
- cctest 测试相关定义
接下来视线回到gyp_node.py。下面有几行有意思的代码:
if sys.platform != 'win32' and 'ninja' not in args:
# Tell gyp to write the Makefiles into output_dir
args.extend(['--generator-output', output_dir])
# Tell make to write its output into the same dir
args.extend(['-Goutput_dir=' + output_dir])
这几行代码会根据node.gyp中的target_name和type在./out文件夹中,生成Makefile和*.mk文件: 回到./configure中,有几个配置参数需要我们注意一下:
parser.add_option('--prefix',
action='store',
dest='prefix',
default='/usr/local',
help='select the install prefix [default: %default]')
parser.add_option('--debug',
action='store_true',
dest='debug',
help='also build debug build')
请大家注意下这两个参数,一个是–prefix,如果不设定prefix,默认路径相当于安装到了全局,不利于我们进行调试,–debug参数在运行configure的时候也是一定要加的。加了–debug,make的才会生成相应的/out/Debug文件夹。即,如果你想在本地调试的话,需要运行的第一个步骤的代码是:
$ ./configure --preifx=your repo --debug
make
入手make的话,我们直接切入到makefile就好了。然后我们就会发现如下代码:
ifeq ($(BUILDTYPE),Release)
all: out/Makefile $(NODE_EXE)
else
all: out/Makefile $(NODE_EXE) $(NODE_G_EXE)
endif
在这里,分为了两种情况,上面为Release的情况,下面是configure --debug的情况。可以看到debug多一个步骤$(NODE_G_EXE)
,经过查找其实是node_g,用于debug模式用的。$(NODE_EXE)就不过多介绍了,就是node的执行文件,下面主要看一下out/Makefile。不知道大家对out/文件夹还熟悉吗?没错,就是上一步./configure生成的。我们翻到out/Makefile中看一下这个makefile做了什么。
这里只做一段代码的截图,不过我们已经可以发现这里面引用了所有的mk文件,之后进行编译,因为他监听了所有的mk,相当于这里监听了所有的node相关的文件,只要include的关联文件有改动,在make的时候都会造成out/Makefile的重新编译,接下来我们回到主文件./Makefile:
.PHONY: $(NODE_EXE) $(NODE_G_EXE)
$(NODE_EXE): config.gypi out/Makefile
$(MAKE) -C out BUILDTYPE=Release V=$(V)
if [ ! -r $@ -o ! -L $@ ]; then ln -fs out/Release/$(NODE_EXE) $@; fi
$(NODE_G_EXE): config.gypi out/Makefile
$(MAKE) -C out BUILDTYPE=Debug V=$(V)
if [ ! -r $@ -o ! -L $@ ]; then ln -fs out/Debug/$(NODE_EXE) $@; fi
.PHONY的目标是$(NODE_EXE) $(NODE_G_EXE),说明在每次make的时候,这两个target一定会运行,看上面的代码这两个target都关联了 out/Makefile,而out/Makefile刚才已经讲过了,他可以监听到所有的node源码文件,只要有改动,在make的时候就会重新编译。通过这一套流程,其实可以实现如下的功能:
修改node源码文件->make重新编译->node重新编译生成
如果我们在调试的时候,如果一直用的是编译之后的node,那么我们其实在修改源码之后,只需要重新make一下就好了。
make install
如果只是本地做调试用,其实到第二步就可以了,make install 相当于把可执行文件安装到第一步定义的preifx中,在这里我建议大家,在git clone 代码的时候,直接clone到你心仪的目录下。
本地调试开发node源码
本地编译
在本地开发的时候,我选择的ide是cLion,除了开始生成的cMakefile比较坑爹外,还是不错的。
首先,把node代码 clone下来:
$ git clone https://github.com/nodejs/node.git
然后,进入到node中运行:
$ ./configure --prefix=your repo --debug
然后进行make操作:
$ make
这样,相当于已经编译完成node了。
导入项目
我们打开cLion把这个项目到cLion中,导入进来之后会自动生成一个CMakeLists.txt,我们不需要管这个文件,在进行调试的时候,通过make就可以了。CMakeLists.txt由于是自动生成的,只是简单的遍历了一遍c++基础文件,没有相应的链接库,所以即使跑也是跑不通的。
关联node
在cLion顶部菜单run->Edit Configuration中,进行一下配置: 一定要确保before launch中是空的,不要加build,如果加了build就会在运行前调用cmake读取CMakeLists.txt,这并不是我们所希望的。executable可执行文件,选择out/Debug/node。
断点调试
还是刚才那张图,在你的programming arguments中加上要调试的js文件地址,然后去node.cc中打断点,然后运行debug试一下,看看是否会走到你的断点中,不出意外的话,是完全ok的。
node源码修改调试
如果你想对node源码进行修改调试,认真看了上面的文章的同学应该已经想到了:在修改完代码之后,通过make进行编译,就会触发out/Makefile,从而重新生成out/Debug/node文件,注意一下make的控制台输出:
if [ ! -r node_g -o ! -L node_g ]; then ln -fs out/Debug/node node_g; fi
在这里,对out/Debug/node做了一个软链,如果是第一次编译,会建立一个软链,连接到node_g。
原文链接:https://github.com/xtx1130/blog/issues/9
如果您觉得文章写的对您来说有价值,欢迎star,如果其中有分析的不到位或者错误的地方,欢迎大佬在评论或者issue中指正
by小菜