概述
NodeJS下载:http://nodejs.org/
说明:NodeJS需要openssl-devel库的支持。如果系统中没有安装此库,configure的时候会提示。Ubuntu上安装此库的方式是:sudoapt-get install libssl-dev
环境:Ubuntu LTS 10.04, 64bit
GCC版本:
#gcc --version
gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
编译:
#./configure --prefix=./prefix/
#make
#make install
#./node -v
其中prefix为自己建立的一个文件夹,保存编译结果。
说明:下面的内容很乱,因为说不清楚,只是为了记录一些东西,如有人阅读此文,请跳过这段内容!如果有编译的问题,可以直接探讨!
构建过程:
Nodejs的build,不是使用的autoconf/automake的方式,主要是利用了scons工具,这是一个基于Python的构建系统。下面是Nodejs的configure内容:
#! /bin/sh
# v8 doesn't like ccache
if [ ! -z "`echo $CC | grep ccache`" ]; then
echo "Error: V8 doesn't like ccache. Please set your CC env var to 'gcc'"
echo " (ba)sh: export CC=gcc"
exit 1
fi
CUR_DIR=$PWD
#possible relative path
WORKINGDIR=`dirname $0`
cd "$WORKINGDIR"
#abs path
WORKINGDIR=`pwd`
cd "$CUR_DIR"
"${WORKINGDIR}/tools/waf-light" --jobs=1 configure $*
exit $?
很显然,它调用了tools/waf-light文件,打开可以知道这是一个python脚本。
同样打开makefile文件,里面也会invoke其他的python脚本去执行build过程。下面是大概的一个构建过程中的一些脚本的调用过程(PS:只是大概分析,详细内容可以去专门学习这个构建系统的使用):
(1)makefile调用tools/waf-light
从makefile第一行就可以看到了。另外,configure的脚本也是调用这个waf-light的。
(2)waf-light和Scripting.py
if __name__ == '__main__':
import Scripting
Scripting.prepare(t, cwd, VERSION, wafdir)
其中的主要部分就是这里,调用了Scripting模块,位于tools/wafadmin/Scripting.py,其prepare方法大概就是一些基本的配置啊检查啊什么的,会调用prepare_impl,然后调用main,主要有以下内容:
if x == 'configure':
fun = configure
elif x == 'build':
fun = build
else:
fun = getattr(Utils.g_module, x, None)
这里就看到了有configure和build参数的判断(从makefile中可以知道传递的是build,configure看到传递的是configure),这里就判断参数,从而调用不同的函数,这也就是为啥configure和makefile都是调用同一个脚本,从这里就分开实现其功能了,当然,Scripting模块还有一些如clean的函数。下面就进入build函数来继续向前。
(3)进入到wscript
上面到了Scriptiong中后,main之后调用的关键部分就是:
ctx = getattr(Utils.g_module, x + '_context', Utils.Context)()
if x in ['init', 'shutdown', 'dist', 'distclean', 'distcheck']:
# compatibility TODO remove in waf 1.6
try:
fun(ctx)
except TypeError:
fun()
else:
fun(ctx)
大概就是fun是build或者configure等函数了,上面提到了,然后其参数就是ctx,用一个getattr函数获取,然后看到build函数中,调用build_impl,然后就有bld.compile()这些内容,其中的bld就是前面一路传递下来的参数ctx了,所以接下来何去何从,就主要是那个getarrt函数获取到的是什么玩意了,从而根据名字去找对应的脚本分析,大概就是会去Utils中根据那个g_module动态的获取到什么对象吧,反正,这里面就涉及一些对python的特性的理解,结论是,最后,会去wscript中执行(源码根目录下)。
已经有相关文章介绍Nodejs的这个文件了,说Nodejs是从这个文件开始配置编译的,其实,是曲折的达到这里的。这里面,主要实现了以下函数:参考http://www.grati.org/?p=529吧,里面有更多的介绍,主要就是configure和build函数了,对应于上面的configure和build。那么,接下来就分析build函数把,看看是如何开始调用编译器编译每一个源文件的。
(4)wscript
大概看看wscript中的build函数,比较长,大概就是分开进行编译了,看到类似于build_v8,build_uv等的调用,即分别编译v8、uv等等(这些都是Nodejs需要依赖的中间库,源码在deps文件夹中)。既然如此,就以v8分析了。
(5)build_v8
def build_v8(bld):
v8 = bld.new_task_gen(
source = 'deps/v8/SConstruct '
+ bld.path.ant_glob('v8/include/*')
+ bld.path.ant_glob('v8/src/*'),
target = bld.env["staticlib_PATTERN"] % "v8",
rule = v8_cmd(bld, "Release"),
before = "cxx",
install_path = None)
其定义开头是这样的,从这里,new_task_gen生成了一个任务,其参数source就是SContruct了,关于scons工具了解一下就知道,其就是读取SConstruct脚本去进行构建的。那么,下面就是根据这个SConscruct进行V8编译的一些配置,然后调用scons工具去编译了。。。后面的内容太多。。就不一一分析了。。依照这个思路。。一直就可以分析下去。。。下面举例说明,比如SConscruct中的函数BuildSpecific函数:
def BuildSpecific(env, mode, env_overrides, tools):
...
library_flags = context.AddRelevantFlags(user_environ, LIBRARY_FLAGS)
v8_flags = context.AddRelevantFlags(library_flags, V8_EXTRA_FLAGS)
mksnapshot_flags = context.AddRelevantFlags(library_flags, MKSNAPSHOT_EXTRA_FLAGS)
dtoa_flags = context.AddRelevantFlags(library_flags, DTOA_EXTRA_FLAGS)
cctest_flags = context.AddRelevantFlags(v8_flags, CCTEST_EXTRA_FLAGS)
sample_flags = context.AddRelevantFlags(user_environ, SAMPLE_FLAGS)
preparser_flags = context.AddRelevantFlags(user_environ, PREPARSER_FLAGS)
d8_flags = context.AddRelevantFlags(library_flags, D8_FLAGS)
这里的library flags,v8_flags等等这些选项,就是配置编译器编译v8使用的选项,如果有一个需求:希望将Nodejs中其他的代码使用选项O3编译,而对v8使用O2编译,如何修改脚本?可以临时的在这里直接设置library_flags等内容,具体设置些什么,用print去看这些变量的内容,然后就知道了。当然,这样的需求很少,一般不会需要用不同的选项去编译一个工程。只是通过这样理解,这里的一些设置就是下面编译V8会使用的,当然,修改这些不会改变全局的其他地方使用的选项,因为这里只是把系统的变量读取到这几个临时的变量给v8编译使用。
在编译的过程中,都会调用scons的一些函数,scons的调用大概的顺序会有scons.py->main.py->sconscript.py->job.py->taskmaster.py(->fs.py?)这样的过程,其中,看taskmaster.py吧,真正对每一个文件的编译,会调用到这里的execute函数:
def execute(self):
"""
Called to execute the task.
This method is called from multiple threads in a parallel build,
so only do thread safe stuff here. Do thread unsafe stuff in
prepare(), executed() or failed().
"""
T = self.tm.trace
if T: T.write(self.trace_message('Task.execute()', self.node))
try:
everything_was_cached = 1
for t in self.targets:
if not t.retrieve_from_cache():
everything_was_cached = 0
break
if not everything_was_cached:
self.targets[0].build()
except SystemExit:
exc_value = sys.exc_info()[1]
raise SCons.Errors.ExplicitExit(self.targets[0], exc_value.code)
except SCons.Errors.UserError:
raise
except SCons.Errors.BuildError:
raise
except Exception, e:
buildError = SCons.Errors.convert_to_BuildError(e)
buildError.node = self.targets[0]
buildError.exc_info = sys.exc_info()
raise buildError
不信可以试试在这里添加一句:print self.targets[0]和raw_input("wait here"),然后编译到这里停止,会看到打印的内容,可能会有比如:obj/release/object.o,obj/release/runtime.o,说明是在编译v8的object.cc和runtime.cc文件了,这里的raise就是异常处理,调用了raise就会推出当前的编译,然后提示错误什么的,如果有一个需求:runtime.cc编译出错了?比如编译器的bug导致无法编译,或者需要一些workaround来编译这个文件(比如使用O0禁用优化)等,那么能不能控制呢?可以直接hardcode判断这个self.targets[0]然后修改对这一个文件的编译命令了。总之,也是可行的。。当然。。肯定可能有更多的地方可以修改,由于实际工作,一般都不需要这么干,就不分析了。
这里关键是了解一下一个脚本是如何编译的。。不管怎么玩,最后本质是调用编译器对一个一个的源文件得到.o,链接.o得到库或者可执行文件。大概根据脚本就可以跟踪编译过程了。为何要跟踪编译过程(make过程)呢?一个实际的例子是,有时候make出错了,我们就不知道该怎么办了。。其实,有时候可以根据make自己的输出提示就看到错误是什么,有时候有些软件或库的make过程比较隐蔽,输出的看不懂到底是执行什么编译命令出错的时候,就可以考虑去看看make是如何执行的,一旦知道一个文件是如何被编译导致错误的,或许更容易知道原因了。
NodeJS构建过程中的几个”环境变量“(scons工具会读取的几个环境变量):
上面的编译过程就是用最基本的设置configure和make并make install一下,如果要进行交叉编译?使用非默认编译器(gcc)编译?或者使用gcc编译但是要加入一些编译选项链接选项如何完成呢?
export CC=gcc
export CXX=g++
export CFLAGS="-O3 -g"
export CXXFLAGS=$CFLAGS
export LINKFLAGS=$CFLAGS #export LDFLAGS="$cc_opts"
上面这些是一些最基本的设置了,好像没找到某一个文件能有力的说明Nodejs到底会读取哪些环境变量,但是上面这些测试设置是有效的,当然,在脚本的很多地方可以大概看到scons会读取的一些环境变量,具体(tools/scons/scons-local-1.2.0/SCons/Environment.py)中:
def ParseFlags(self, *flags):
"""
Parse the set of flags and return a dict with the flags placed
in the appropriate entry. The flags are treated as a typical
set of command-line flags for a GNU-like toolchain and used to
populate the entries in the dict immediately below. If one of
the flag strings begins with a bang (exclamation mark), it is
assumed to be a command and the rest of the string is executed;
the result of that evaluation is then added to the dict.
"""
dict = {
'ASFLAGS' : SCons.Util.CLVar(''),
'CFLAGS' : SCons.Util.CLVar(''),
'CCFLAGS' : SCons.Util.CLVar(''),
'CPPDEFINES' : [],
'CPPFLAGS' : SCons.Util.CLVar(''),
'CPPPATH' : [],
'FRAMEWORKPATH' : SCons.Util.CLVar(''),
'FRAMEWORKS' : SCons.Util.CLVar(''),
'LIBPATH' : [],
'LIBS' : [],
'LINKFLAGS' : SCons.Util.CLVar(''),
'RPATH' : [],
}
这里的CFLAGS、CPPDEFINES、CPPPATH等应该都是对应于我们常用的一些对编译过程设置的环境变量,需要说明的是,Nodejs的链接的设置是LINKFLAGS,不是LDFLAGS,而且,好像其默认不会用CFLAG/CCFLAGS来设置LDFLAGS的值,所以如果某些选项是必须要链接时候或者编译链接都需要,一定不能忘记设置LINKFLAGS,否则链接的时候这些选项没有被用上的(PS:一般构建都是编译链接分开来,所以选项也是分开的,一般的autoconf/automake的时候,LDFLAGS会默认被设置为CCFLAGS的值,所以有时候容易被忽略了)。另外,这里有CPPPATH,可能是和C_INCLUDE_PATH的设置的,不知道C_INCLUDE_PATH设置会不会在Nodejs构建时候读取,至少,CC好像也没出现在这里,但是确实可以用CC设置编译器,总之,编译遇到问题试试就是了,如果发现设置了C_INCLUDE_PATH并没有解决问题,试试设置CPPPATH等来取代。
PS:理论上scons的手册可能会有这些说明,没去找,不太清楚!
深入学习构建系统,请参考:
http://scons.org/(scons工具)
http://code.google.com/p/waf/(waf,Waf is a Python-based framework for configuring, compiling and installing applications. )
相关文章参考:
http://www.zhetenga.com/view/linux%E4%B8%8Bnode.js%E7%9A%84%E7%BC%96%E8%AF%91%E5%AE%89%E8%A3%85-f7f7ce54.html
最后
以上就是踏实便当为你收集整理的NodeJS编译的全部内容,希望文章能够帮你解决NodeJS编译所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复