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

http://scons.org/html

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

 

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

 

在软件项目开发过程当中,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 有联系,固然,还有一个目的,就是为了更方便的输入。vim

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

  • 使用 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 命令进行解压,如:app

 $ tar -zxf scons-2.0.1.tar.gz

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

 $ 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 脚本中第三、四、五、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)
相关文章
相关标签/搜索