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
这里关键是了解一下一个脚本是如何编译的。。不管怎么玩,最后本质是调用编译器对一个一个的源文件得到.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' : [], }
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