##在这里详述 PyScons/CharlesWang. = Charles Wang 个人实感 = {{{ Charles Wang reply-to python-cn@googlegroups.com to python-cn`CPyUG`华蟒用户组 date Tue, Sep 9, 2008 at 09:34 subject [CPyUG:65156] Re: 这里有人对 scons 感兴趣吗? }}} <> === 整体 === 开始:: * 呵呵,看来这个软件在国内接触的人确实不多。我注意BuildSystem是相当早了,最早在1999年 的时候发狠把当时的autoconf、automake手册给翻译成中文了。在相当长的一段时间之内,个人 感觉autoconf/automake/make的组合是可以满足基本需求的。所以当时还进一步翻译了GNU 软件 发行惯例。这些译文大家都可以在 http://www.linuxforum.net/books/index.php 看到。 * 大约在2001年吧,我开始在Linux下做嵌入式开发。此前虽然发现了一些autoconf/automake/make 的问题,但还没到痛苦的地步。因为嵌入式Linux系统,要多出交叉编译和系统生成两大块任务, 用autoconf/automake/make就开始感受到限制了。当然那时候还不知道什么其他的创建工具的存在, 所以就仿照 kernel build system 的方法,用make来做。其间也确实遭受了不少痛苦经历,呵呵。 选择:: * 后来在2003年终于忍无可忍,开始寻找新工具。找来找去,由于偶然或非偶然的机会,发现了scons。 用过之后就再也没改了。那时候scons的版本还比较低,不过已经相当好用了。这里列举一下区别吧: * 对交叉编译的支持: autoconf/automake/make 把所有的变量名都放在一个命名空间之中,这使得我们只能用不同的变量 名来区分用于不同体系的编译宏。例如,在kernel build system里面,CC这个变量是用于编译目标 体系结构的代码的,而HOSTCC则是用于编译当前体系结构的代码的。所有其它变量都必须通过加前缀 的方式加以区分; * scons 提供 Environment 对象,每个对象都是一个编译变量容器。所以针对不同的目标体系结构, 在scons中可以存在大量的 Environment 对象。例如像 kernel build system 那样,可以分别为目标 体系结构和当前体系结构定义两个明确区分的 Environment 对象。而不会造成任何混淆; * 如何确定一个文件是否改变: * make 是使用文件的timestampe来检查文件是否已经被改变了。可是这就要求当前系统具有正确的 系统时钟。这一问题在使用CVS时会更加突出一些。因为CVS会纪录提交时间,加入CVS服务器的系统 时钟超前或者落后,那么肯定会造成混乱。还有一个问题就是如果通过CVS取回以前的版本,那么它 会把这个文件的时间设置成该文件提交的时间。如果再以时间戳为准判断文件是否改变,就会出现 把改变了的文件误认为没有改变的文件的情况了。 * scons 以文件内容的校验和为标志来确认文件是否被改变。以校验和为标志可以脱离对系统时间的 依赖,当然付出的代价是速度比时间戳慢一点。不过通过获得精确的依赖性关系所节省的时间,要比 依赖关系不全而被迫使用 make clean 要短许多吧。同时scons也支持使用时间戳作为判断依据。 * 是否参考命令行的变化: make 在检查依赖性的时候,并不检查命令行的变化。可是命令行的变化却对目标文件的生成有着 很大的影响。例如,在命令行里面,加-g或不加-g、加-DNDEBUG或不加-DNDEBUG。 scons 把命令行的变化考虑在内,如果命令行发生变化了,那么目标也必须重新编译。 * 是否拥有全局依赖性信息: * autoconf/automake/make 组合,以递归的方式使用 make。但是在这样的情况下,会出现问题: 假定源代码目录结构为根目录下有: lib、src、tools 几个目录,其中均还有源代码。那么如果在 src 目录中执行 make,lib 目录中的文件即时发生了变化,也不会重新编译lib中的源代码。当然 这是由于递归方式使用make导致make没有获得完整的依赖关系造成的。具体参见: http://aegis.sourceforge.net/auug97.pdf * scons 则是每次都完整地运行整个buildsystem代码,以获得完整的依赖关系。它不会出现上述缺陷。 * 编程语言: * autoconf/automake/make 组合,混杂了 m4、make、shell 等众多不同的脚本语言,没有一样是常用的。 * scons,纯python。由于python是全功能流行的强攻能语言,这使得组建编译系统的能力和便捷性大大增强。 * 跨平台: * autoconf/automake/make 在 Windows 下,必须在mingw之类的模拟环境中运行。这是因为autoconf生成的 configure脚本使用了许多shell命令,这些都要由环境来提供。而且不同系统中的命令集合有可能不同,同一 命令的具体功能也可能不同。同时,make存在众多不同版本。为了弥补这些似是而非的区别,导致了 autoconf/automake的高度复杂性。 * scons,支持跨平台,除了python 之外没有其它依赖。在Windows下即可以在mingw中运行,又可以在普通 Windows命令行中运行。 * 复杂度: * autoconf/automake/make 组合,必须编写 configure.ac、Makefile.am。而最终使用的 configure 脚本和 Makefile 可能长达数万行,我本人就曾被这些几万行的东西搞得天昏地暗。打开一次都折寿。 * scons,脚本相当简单。在scons内部就可以自动完成各种自动依赖性的扫描。 * 对代码生成、信息传播的支持: * 我们知道,一项信息如果只存在于一个地方是最好的。如果存在于两个地方就可能因为不一致而造成BUG。 例如说,屏幕的尺寸。但是需要信息的地方可能不止一个,这时候就需要我们把仅存在于一处的信息传播 到所有需要它的地方。这种传播可能发生在运行时,也可能发生在编译时。发生在编译时的传播适用于在 软件的特定版本中不变的常量,优点是不必在运行时付出任何代价。一般而言比较简单的这类传播是 通过宏定义实现的,但更为复杂的就会涉及到源代码的生成。 * autoconf/automake/make 只能使用shell脚本生成,不能实现过于复杂的处理 * scons 可以用python 函数实现任意复杂度的处理。例如我曾经在我的scons脚本里面,调用 subversion-python 的模块,自动把svn版本号潜入到源代码中。 体验:: * 上面列举的区别,有些是质的区别,有些是量的区别。但是无论如何,以较低的代价获得较好的效果,使得 我们能够把更多的注意力放到完成BuildSystem的功能上去。所以我认为即使仅仅是量变、简化也有意义。 * 呵呵,不过我发现长久以来似乎国内无人注意到这一软件。我想python爱好者当中总会有人感兴趣吧? 在2003年我曾经使用scons开发过一个项目。 * '''http://trac.magiclinux.org/magicinstaller/wiki/MagicInstaller''' * 不过那时候对scons的了解还很肤浅,没能做到一条命令生成整个嵌入式目标文件系统。现在已经是piece of cake了。 === 技巧 === {{{ Charles Wang reply-to python-cn@googlegroups.com to python-cn`CPyUG`华蟒用户组 date Tue, Sep 9, 2008 at 11:35 subject [CPyUG:65177] Re: 这里有人对 scons 感兴趣吗? }}} 你这个问题我以前经历过的,那次我好像是要做自动从网上下载包文件吧。 * 这里牵涉到一个概念,用久了scons会注意到,它把它的运行过程明确划分成两个阶段: 1. 运行所有SConstruct/SConscript,收集到所有的依赖性关系; 2. 分析依赖性关系,执行必须执行的操作; * 其中对依赖性关系的分析,是由scons自动完成的。 * 我们写SConstruct/SConscript必须特别注意的一点是,要明确区分那些代码是在第一阶段运行的, 那些代码是第二阶段要执行的动作中运行的。不清楚的话会被搞晕的,呵呵。 * 所以你说的从 FooBean.java 生成 Foo.java, FooLocal.java ...,其实是在完成某些特定动作 之后,才能够确定的依赖性关系。这就打破了前面所说的两个阶段的划分,scons就不容易处理了。 * 对于这类问题,我一般都是在第一阶段中调用一个从scons进程,让这个从scons进程去调用你说的 工具去生成 Foo.java、FooLocal.java ..... 这样主scons就可以在第一阶段完成之前获得 FooBean.class 完整的依赖关系列表了。 * 所以你这个问题跟 `.y -> .c -> .o` 的过程是不同的,它的依赖性关系是不必通过某些工具来获得, 可以直接写出来的。因此也没必要去调用从scons获得依赖性关系列表了。 * 根据你的描述,FooBean.java 应该是含有了用于生成FooBean.class的完整信息了的,毕竟Foo.java、 FooLocal.java都是从FooBean.java中生成的啊。那么javac 为什么不能直接从FooBean.java生成 FooBean.class 呢?中间文件难道还可能被修改或者被其他文件所依赖吗?呵呵,我不懂java的啊。 * 如果没有什么特殊要求,我看可以做一个脚本,实现从FooBean.java到FooBean.class的转换。并 用这个脚本代替 javac,这样就不必调用从scons进程了。 === 对比 === {{{ Charles Wang reply-to python-cn@googlegroups.com to python-cn`CPyUG`华蟒用户组 date Tue, Sep 9, 2008 at 13:39 subject [CPyUG:65188] Re: 这里有人对 scons 感兴趣吗? }}} `> ANT没有这样的过程,它不处理依赖,写build.xml时由作者控制` * 我觉得依赖关系在某些情况下会是动态的。比如说有三个源文件:main.c、core-arm.c 和 core-x86.c,当目标机 为 arm 的时候,源代码是: main.c 和 core-arm.c,当目标机是 x86 的时候,源代码是 main.c、core- x86.c, 这时候是不是要给ant准备不同的build.xml啊?如果这种选项组合太多了怎么办?在scons里面有python做后盾, 怎么复杂的变换处理都能实现了。 `> 2、分析依赖性关系,执行必须执行的操作;ANT不分析依赖关系,按build.xml的task出现先后顺序调用` * 这个我不太理解,如果ant不分析依赖关系,仅仅是按照task的顺序调用,那还不如直接用shell脚本把编译的命令罗列一番。 我想你的意思是,它就只是按顺序独立地考察每条规则。看依赖关系列表中的文件是否发生了改变,如果发生了改变就运行 对应动作,是这样吧?这等于说是把规则与规则之间的关联任务交给用户了。而且给并行编译造成了相当的困难吧?照这么说 ant应该不支持类似 make -j 3 这样的功能了? * 既然谈到依赖关系分析这点,其实我们可以想想BuildSystem存在的目的。我个人感觉目的有二:一、通过智能化的依赖关系分析, 尽最大可能减少耗费时间的编译操作(也可以推广到其他长操作);二、传播统一管理的配置信息。 * 其中第一点是获得广泛注意的,也是所有BuildSystem工具都做了的。呵呵,我看如果不是因为这个,这些工具也没必要存在了。 而且,不需要利用依赖性分析优化减少长操作的任务,也没必要使用make、cmake、scons 或者 ant 了。比如说scons没有对 install/uninstall操作的直接支持,我看到过许多邮件列表里有抱怨。但我想作者的意思应该是:这些没有依赖性分析的序列操作, 应该是用shell脚本来完成的。 * 当然我觉得第二点也相当重要,原因就是如前面所说,配置信息只出现在一处可以避免多处出现同一配置可能造成的不一致的BUG。 比如说,kernel、busybox 的 build system 就在配置信息管理方面做得比较完善。 * 照你个刚才对ant的描述,第二个问题压根就没涉及、第一个问题也只做了规则之内的依赖性分析,而没有做规则之间的依赖性分析。 而考虑到scons以强大的python为后盾,实现个配置信息管理是很容易的了。比如说要谈出一个kernel 的 xconfig 那种界面, 我们只要装个 pygtk 就轻松搞定了。 `> 其实这些都是Java无聊的spec定义出来的,`这些Foo.java,FooLocal.java本应由程序开发人员编写,但是有人要偷懒。这几个文件的内容-,FooBean.java就可以提供了,何不做个工具。javac只是单纯的从.java生成.class文件。貌似JDK5之后出现的apt可以代替jav-ac,并且提供自定义的编译过程(例如:编译时生成文件,再编译生成的文件),但是目前较少看到自定义的apt工具库。 * 呵呵,历史总是要给我们留下一些遗产的啊,这是没办法的哦。 === 成果 === 我正在做的一个小项目,从 Python Package Index 自动生成 ebuild。因为我发现 PyPI 上好多包,gentoo portage 里面都没有及时更新。而这些包的 ebuild 又都差不多,所以产生了自动生成 ebuild 的想法。有空去看看了: * http://code.google.com/p/pypi2pkgsys/ * http://pypi.python.org/pypi/pypi2pkgsys