NodeJS编译

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

你可能感兴趣的:(脚本,Build,library,makefile,编译器,scripting)