SCons: 替代 make 和 makefile 及 javac 的极好用的c、c++、java 构建工具

http://scons.org/

https://www.ibm.com/developerworks/cn/linux/l-cn-scons/index.html

 

后附:另外,WAF是一个基于scons的构建工具,而且是 Re-design of scons to improve its worst features。

 

在软件项目开发过程中,make 工具通常被用来建造程序。make 工具通过一个被称为 Makefile 的配置文件可以自动的检测文件之间的依赖关系,这对于建造复杂的项目非常有帮助,然而,编写 Makefile 本身却不是一件容易的事情。SCons 是一个用 Python 语言编写的类似于 make 工具的程序。与 make 工具相比较,SCons 的配置文件更加简单清晰明了,除此之外,它还有许多的优点。本文将简单介绍如何在软件开发项目中使用 SCons,通过本文,读者可以学习到如何使用 SCons 来建造自己的程序项目。

前言

make 这个工具自上个世纪 70 年代 Stuart Feldman 在贝尔实验室开发出以来,就一直是类 UNIX 程序员的最爱之一。通过检查文件的修改时间,make 工具可以知道编译目标文件所要依赖的其他文件。在复杂的项目中,如果只有少数几个文件修改过,make 工具知道仅仅需要对哪些文件重新编译就可以确保目标程序被正确的编译链接。这样做的好处就是在编译中,不仅可以节省大量的重复输入,还可以确保程序可以被正确的链接,缩短编译的时间。虽然如此,但是为 make 工具编写建造规则却不是一件容易的事。它复杂的配置规则,即使是有经验的开发者也望而生畏。make 工具的许多替代品便因此而诞生,SCons 就是是其中之一。SCons 是一个用 Python 语言编写的类似于 make 工具的程序。与 make 工具相比较,SCons 的配置文件更加简单清晰明了,除此之外,它还有许多的优点。

SCons 简介

SCons 是一个开放源代码、以 Python 语言编写的下一代的程序建造工具。它最初的名字是 ScCons, 基于由 perl 语言编写的 Cons 软件开发而成,它在 2000 年 8 月获得了由 Software Carpentry 举办的 SC 建造比赛的大奖。现在 ScCons 已经被改名为 SCons,目的是为了表示不再与 Software Carpentry 有联系,当然,还有一个目的,就是为了更方便的输入。

作为下一代的软件建造工具,SCons 的设计目标就是让开发人员更容易、更可靠和更快速的建造软件。与传统的 make 工具比较,SCons 具有以下优点:

  • 使用 Python 脚本做为配置文件
  • 对于 C,C++ 和 Fortran, 内建支持可靠自动依赖分析 . 不用像 make 工具那样需要 执行"make depends"和"make clean"就可以获得所有的依赖关系。
  • 内建支持 C, C++, D, Java, Fortran, Yacc, Lex, Qt,SWIG 以及 Tex/Latex。 用户还可以根据自己的需要进行扩展以获得对需要编程语言的支持。
  • 支持 make -j 风格的并行建造。相比 make -j, SCons 可以同时运行 N 个工作,而 不用担心代码的层次结构。
  • 使用 Autoconf 风格查找头文件,函数库,函数和类型定义。
  • 良好的夸平台性。SCons 可以运行在 Linux, AIX, BSD, HP/UX, IRIX, Solaris, Windows, Mac OS X 和 OS/2 上。

安装 SCons

SCons 支持多种操作系统平台,并为各个系统制作了易于安装的文件,因此在各个系统平台上的安装方法不尽相同,在 SCons 的官方网站上可以查每个平台的具体安装方法。 如果 SCons 没有为你的系统制作相应的安装包,你也可以下载 SCons 的源代码,直接进行安装。 首先,从 SCons 的网站上下载最新的 SCons 源代码包(目前 SCons 的最新版本是 2.0.1)。 其次,解压下载的源代码。视下载的源代码包的格式不同而有不同的方法,在 Windows 平台上,可是使用 winzip 或者其他类似的工具解压。在 Linux 平台上,对于 tar 包,使用 tar 命令进行解压,如:

 $ tar -zxf scons-2.0.1.tar.gz

然后切换进入解压后的目录进行安装,如

 $ cd scons-2.0.1 
 $ sudo python setup.py install

命令执行如果没有错误,那么 scons 就被安装到系统上了。对于 Linux 来说,scons 会默认安装到 /usr/loca/bin 目录下,而在 Windows 平台上,则会被安装到 C:\Python25\Scripts 下。

使用 SCons

在 SCons 安装完成后,我们就可以使用 SCons 来建造我们的程序或者项目了。像很多编程书籍那样,在这里我们也通过一个简单的 helloscons 例子来说明如何使用 SCons。例子 helloscons 包含两个文件 :

 $ ls helloscons 
 helloscons.c  SConstruct

其中 helloscons.c 是程序的源文件,SConstruct 是 scons 的配置文件,类似使用 make 工具时的 Makefile 文件,因此,为了编译你的项目,需要手工创建一个 SConstruct 文件(注意:文件名是大小写敏感的)。不过,在编译的时候不需要指定它。 要编译这个例子,切换到 helloscons 的目录下,运行 scons 命令,如下:

 $ cd helloscons/ 
 $ scons 
 scons: Reading SConscript files ... 
 scons: done reading SConscript files. 
 scons: Building targets ... 
 gcc -o helloscons.o -c helloscons.c 
 gcc -o helloscons helloscons.o 
 scons: done building targets.

来查看一下运行 scons 命令后得到的结果 :

 $ ls 
 helloscons  helloscons.c  helloscons.o  SConstruct

建造结束后,得到了二进制文件 helloscons 以及编译的过程中产生的一些以 .o 结尾的目标文件。试运行 helloscons 一下 , 会得到 :

 $ ./helloscons 
 Hello, SCons!

现在让我们回过头来解析一下 helloscons 这个例子 . helloscons.c 是这个例子里的唯一一个源代码文件,它所做的事就是在控制台上输出一行简单的"Hello,SCons", 它的源代码如下:

清单 1. helloscons.c
 #include <stdio.h> 
 #include <stdlib.h> 

 int main(int argc, char* argv[]) 
 { 
        printf("Hello, SCons!\n"); 
        return 0; 
 }

作为项目建造规则的配置文件 SConstruct 的内容如下 :

清单 2. SConstruct 文件
 Program('helloscons.c')

你可能很惊讶 SConstruct 的内容只有一行,然而事实确实如此,它比传统的 Makefile 简单很多。SConstruct 以 Python 脚本的语法编写,你可以像编写 Python 脚本一样来编写它。其中的 Program 是编译的类型,说明你准备想要建造一个可执行的二进制程序,它由 helloscons.c 文件来生成。在这里,没有指定生成的可执行程序的名字。不过不用担心,SCons 会把源代码文件名字的后缀去掉,用来作为可执行文件的名字。在这里,我们甚至不需要像 Makefile 那样指定清理的动作,就可以执行清理任务。在 SCons 中,执行清理任务由参数 -c 指定,如下 :

 $ scons -c 
 scons: Reading SConscript files ... 
 scons: done reading SConscript files. 
 scons: Cleaning targets ... 
 Removed helloscons.o 
 Removed helloscons 
 scons: done cleaning targets. 

 $ ls 
 helloscons.c  SConstruct

如果你不想直接编译可执行的二进制文件,那也没有关系。SCons 支持多种编译类型,你可以根据自己的需要,任意选用其中的一种。SCons 支持的编译类型有:

  • Program: 编译成可执行程序(在 Windows 平台上即是 exe 文件),这是最常用的一种编译类型。
  • Object: 只编译成目标文件。使用这种类型,编译结束后,只会产生目标文件。在 POSIX 系统中,目标文件以 .o 结尾,在 Windows 平台上以 .OBJ 结尾。
  • Library: 编译成库文件。SCons 默认编译的库是指静态链接库。
  • StaticLibrary: 显示的编译成静态链接库,与上面的 Library 效果一样。
  • SharedLibrary: 在 POSIX 系统上编译动态链接库,在 Windows 平台上编译 DLL。

这个简单的 SConstruct 的配置文件从一个侧面说明了使用 SCons 来建造程序是多么的简单。 在实际的项目开发中,程序的建造规则远比 helloscons 这个例子复杂。不过,这些都不是问题,你可以像扩展你自己的 Python 脚本文件那样去扩展 SConstruct. 如果你不想使用 SConstruct 为你设置的默认可执行文件的名字,而是选择你自己喜欢的名字,如 myscons,可以把 SConstruct 的内容修改为 :

 Program('myscons, 'helloscons.c')

其中 myscons 就是你想要的可执行文件的名字,你可以把它换成任意你喜欢的名字, 不过有点注意的是,这个名字必须放在第一位。 然后在 helloscons 目录下运行 scons 命令,就会得到 myscons 这个可执行文件,如 下:

 $ scons -Q 
 gcc -o helloscons.o -c helloscons.c 
 gcc -o myscons helloscons.o

其中的 -Q 参数是减少编译时的由 scons 产生的冗余信息。 如果你的项目由多个源文件组成,而且你想指定一些编译的宏定义,以及显式的指定使用某些库,这些对于 SCons 来说,都是非常简单的事情。我们的另外一个例子 helloscons2 很好的说明这种情况。 helloscons2 由 3 个源文件组成 , 它们是 helloscon2.c, file1.c, file2.c,另外指定了编译的选项,同时还指定了使用哪些具体的库。让我们来看一下 helloscons2 的 SConstruct 文件 :

 Program('helloscons2', ['helloscons2.c', 'file1.c', 'file2.c'], 
        LIBS = 'm', 
        LIBPATH = ['/usr/lib', '/usr/local/lib'], 
        CCFLAGS = '-DHELLOSCONS')

正如你想像的那样,这样一个配置文件并不复杂 . 该 SConstruct 文件指出,它将生成一个名叫 helloscons2 的可执行程序,该可执行程序由 helloscons2.c, file1.c 和 file2.c 组成。注意,多个源文件需要放在一个 Python 列表中。如果你的源程序代码文件很多,有十几个甚至上百个,那不要一个个的将他们都列出来,你可以使用 glob('*.c') 来代替源代码列表。如下 :

 Program('helloscons2', Glob('*.c')

配置文件中 LIBS,LIBAPTH 和 CCFLAGS 是 SCons 内置的关键字,它们的作用如下:

  • LIBS: 显示的指明要在链接过程中使用的库,如果有多个库,应该把它们放在一个列表里面。这个例子里,我们使用一个称为 m 的库。
  • LIBPATH: 链接库的搜索路径,多个搜索路径放在一个列表中。这个例子里,库的搜索路径是 /usr/lib 和 /usr/local/lib。
  • CCFLAGS: 编译选项,可以指定需要的任意编译选项,如果有多个选项,应该放在一个列表中。这个例子里,编译选项是通过 -D 这个 gcc 的选项定义了一个宏 HELLOSCONS。

运行 scons 命令的时候,可以看到这些变量如何被使用的,让我们执行一下 scons 命令 :

 $ scons -Q 
 gcc -o file1.o -c -DHELLOSCONS file1.c 
 gcc -o file2.o -c -DHELLOSCONS file2.c 
 gcc -o helloscons2.o -c -DHELLOSCONS helloscons2.c 
 gcc -o helloscons2 helloscons2.o file1.o file2.o -L/usr/lib -L/usr/local/lib -lm

scons 命令的输出显示了可执行程序 helloscons2 如何由多个源文件而生成,以及在 SConstruct 中定义的 LIBS,LIBPATH 和 CCFLAGS 如何被使用。 可见,即使对于复杂的项目,SCons 的编译配置文件也很简单。除此之外,SCons 也提供了很多功能以适应不同的需要,如果读者想更深入的了解如何使用 SCons,可以参考 SCons 的帮助手册。

总结

本文简单介绍了 SCons 的特点,如何安装 SCons,以及通过例子来说明如何在项目中使用 SCons。 作为下一代的软件建造工具,SCons 使用 Python 语言作为配置文件,不但功能强大,而且简单易用,对于跨平台的项目,非常适合。 如果你厌烦了 make 工具的那种复杂的编写规则,尝试一下新鲜的 SCons 吧。

 

 

 

scons 交叉编译的例子:

https://bitbucket.org/scons/scons/wiki/PlatformToolConfig#markdown-header-cross-compilation_1

https://stackoverflow.com/questions/23898584/how-can-i-use-a-cross-compiler-with-scons

https://stackoverflow.com/questions/31232955/using-scons-to-compile-c-file-with-std-c11-flag

 

简单的例子:

(1)如果想要生成两个编译器版本的代码,比如在PC机上的GCC编译和ARM Linux gcc交叉编译,并且在编译的时候可以选择,SConstruct内容如下,源代码测试文件还是前一个hello world程序:

SConstruct:

src = Glob('*.c')   
  
platform = ARGUMENTS.get('platform','pc')  
  
if platform == 'arm':  
  
    EXE_PATH = '/opt/arm-2007q1/bin'    
    PREFIX = 'arm-none-linux-gnueabi-'    
    ARMCC = PREFIX + 'gcc'    
    ARMAS = PREFIX + 'gcc'    
    ARMAR = PREFIX + 'ar'    
    ARMLINK = PREFIX + 'gcc'    
    ARMSIZE = PREFIX + 'size'    
    ARMOBJDUMP = PREFIX + 'objdump'    
    ARMOBJCPY = PREFIX + 'objcpy'    
  
    env = Environment (AS = ARMAS,    
                       CC = ARMCC,    
                       AR = ARMAR,    
                       LINK = ARMLINK)    
    env.PrependENVPath ('PATH',EXE_PATH)  
  
elif platform == 'pc':  
    env = Environment()    
else:  
    pass  
  
env.Program(source = src, target = 'hello_world')  

第二条语句指定了若platform缺省时为pc,故执行scons 与scons platform=pc效果一样。运行如下:

    $ ls  
    main.c  SConstruct  
      
    $ scons platform=arm  
    scons: Reading SConscript files ...  
    scons: done reading SConscript files.  
    scons: Building targets ...  
    arm-none-linux-gnueabi-gcc -o main.o -c main.c  
    arm-none-linux-gnueabi-gcc -o hello_world main.o  
    scons: done building targets.  
      
    $ scons platform=pc  
    scons: Reading SConscript files ...  
    scons: done reading SConscript files.  
    scons: Building targets ...  
    gcc -o main.o -c main.c  
    gcc -o hello_world main.o  
    scons: done building targets.  
      
    $ scons -c  
    scons: Reading SConscript files ...  
    scons: done reading SConscript files.  
    scons: Cleaning targets ...  
    Removed main.o  
    Removed hello_world  
    scons: done cleaning targets.  
      
    $ scons  
    scons: Reading SConscript files ...  
    scons: done reading SConscript files.  
    scons: Building targets ...  
    gcc -o main.o -c main.c  
    gcc -o hello_world main.o  
    scons: done building targets.  

(2)同样,这个方法还可以用来编译器生成不同版本的代码(如release版本和debug版本),如下:

> vim SConstruct  
  
src = Glob('*.c')   
debug = ARGUMENTS.get('debug',0)  
vars = Variables(None,ARGUMENTS)  
vars.Add('debug','Set to 1 to build for debug', 0)  
env = Environment(variables = vars)  
Help(vars.GenerateHelpText(env))  
  
if int(debug) == 1:  
    env.Append( CCFLAGS = '-g')    
  
env.Program(source = src, target = 'hello_world')   
  
> ls  
main.c  SConstruct  
  
> scons debug=1  
scons: Reading SConscript files ...  
scons: done reading SConscript files.  
scons: Building targets ...  
gcc -o main.o -c -g main.c  
gcc -o hello_world main.o  
scons: done building targets.  
  
> scons debug=0  
scons: Reading SConscript files ...  
scons: done reading SConscript files.  
scons: Building targets ...  
gcc -o main.o -c main.c  
gcc -o hello_world main.o  
scons: done building targets.  

这样,便可控制debug版本和release版本的生成,另外SConstruct 脚本中第3、4、5、6句用来产生帮助信息,可以加上-h参数以运行使用:

    > scons -h  
    scons: Reading SConscript files ...  
    scons: done reading SConscript files.  
      
    debug: Set to 1 to build for debug  
        default: 0  
        actual: 0  
      
    Use scons -H for help about command-line options.  

这样便可提示用户使用命令。


(3)变量(如debug=1,platform=pc)还可以从文件中引入,详细在用户手册(10.2.3. Reading Build Variables From a File)一节,预编译定义也可由这种方法定义。

 

 

 

 

 

 

以下是我自己的几个 SConstruct 的实例:

# -*- coding: utf-8 -*-

cpp_defines = ['DSO_DLFCN', 'HAVE_DLFCN_H', 'NDEBUG', 'OPENSSL_NO_STATIC_ENGINE', 'OPENSSL_PIC']
#设置预编译选项,相当于在实际的预编译选项里增加 -DL_ENDIAN, -DOPENSSL_USE_NODELETE
cpp_defines.append(['L_ENDIAN', 'OPENSSL_USE_NODELETE'])    
# cpp_defines.append({'OPENSSLDIR': '\\"\\"'})         # 设置预编译选项 OPENSSLDIR=\"\" , 不要用这种方式,这样定义无效,用后面的方式

include_path = ['./include']               # 设置预编译选项
include_path.append('.')
include_path.append('./include/x509v3')

env=Environment(CPPPATH=include_path)     #这里赋值会覆盖以前定义的值,包括scons的默认搜索范围

env.Append(CPPDEFINES=cpp_defines)      #这里是append,不会覆盖原有的值,只是语法中的=号有点让人模棱两可
# 要定义这样的 -DOPENSSLDIR=\"\" 这样的预编译指令,只能按照下面这种方式,不能用cpp_defines = ['DES_DEFAULT_OPTIONS=\"\"'] 这种方式
env.Append(CPPDEFINES={'OPENSSLDIR': '\"\"'})
env.Append(CCFLAGS='-Wall -m64 -fPIC')
env.Append(CCFLAGS='-g -ggdb -g3')

# FILES = env.Glob('*.c')
FILES = [f for f in Glob('*.c') if 'LPdir_unix.c' not in str(f)]    # 过滤掉 LPdir_unix.c 文件,它不会被编译
env.StaticLibrary('libcrypto.a', FILES)
# -*- coding: utf-8 -*-

cpp_defines = ['LIBSSH_EXPORTS', '_LARGEFILE64_SOURCE']

include_path = ['./include']
include_path.append('.')

cflags = []
# cflags.append(['-g', '-ggdb', '-g3', '-fprofile-arcs', '-ftest-coverage'])
cflags.append(['-g', '-ggdb', '-g3'])
cflags.append(['-std=gnu99', '-pedantic', '-pedantic-errors', '-Wall', '-Wextra', '-Wshadow', '-Wmissing-prototypes'])
cflags.append(['-Wdeclaration-after-statement', '-Wunused', '-Wfloat-equal', '-Wpointer-arith', '-fvisibility=hidden'])
cflags.append(['-Wwrite-strings', '-Wformat-security', '-Wmissing-format-attribute', '-fPIC', '-fstack-protector'])

ld_path = []
ld_path.append('./libcrypto')

libs = []
libs.append('crypto')
libs.append('dl')

ldflags = ['-rdynamic', '-fprofile-arcs', '-ftest-coverage', '--coverage']
ldflags.append('-g')
ldflags.append('-ggdb')
ldflags.append('-g3')
ldflags.append(cflags)

env=Environment(CPPPATH=include_path, LIBPATH=ld_path, LIBS=libs)

env.Append(CPPDEFINES=cpp_defines)
env.Append(CCFLAGS=cflags)
env.Append(LINKFLAGS=ldflags)

FILES = env.Glob('*.c')
env.Program('ssh_server', FILES)

 

posted @ 2017-08-09 16:03  微信公众号--共鸣圈  阅读(2998)  评论(0编辑  收藏  举报