旧博文,搬到 csdn
原文:http://rebootcat.com/2020/08/30/scons/
我是一个 linux c++ 开发者,但是一直对 Makefile 的语法很是头痛,每次都记不住,所以每次写 Makefile 都很痛苦,Makefile 里需要你自己编写依赖和推导规则,这个过程能不能简单点呢?
对于编译一个 C++ 工程来说,也许需要的就是头文件路径、库路径、编译参数,剩下的东西基本也不重要,这三样足够去编译一个工程了。所以有没有一个工具能简单点的去实现 C++ 项目的构建呢?
答案是有的,Scons 就是答案。
这里直接引用官网的解释:
What is SCons?
SCons is an Open Source software construction tool—that is, a next-generation build tool. Think of SCons as an improved, cross-platform substitute for the classic Make utility with integrated functionality similar to autoconf/automake and compiler caches such as ccache. In short, SCons is an easier, more reliable and faster way to build software.
What makes SCons better?
最大特点就是使用 Python 语法来编写编译构建脚本,并且支持依赖自动推导,支持编译 C/C++/D/Java/Fortran等项目,并且是跨平台的(因为 python 是跨平台的)。
所以如果你对 python 熟悉的话,而且你和我对 C++ Makefile 有一样的烦恼,那么这对你将是一个好消息。 你将可以用 python 来编写构建脚本,而且会很简单,对于复杂的大型项目也能快速构建好。(也许只要 30 分钟)
因为 scons 是基于 python 来构建的,所以毋容置疑,首先是需要准备好 python 环境,然后使用下述命令安装 scons 工具。
pip install scons
注:本文以一个多源文件,多目录结构的项目 mux 为例,介绍 cmake 的使用,相关源文件以及cmake 脚本可以直接查看源项目。
scons 构建脚本由一个 SConstruct 文件和多个 SConscript 文件构成。
SConstruct 通常位于项目顶层目录,然后 SConscript 通常位于子目录(子模块)。
那么来看一下 SConstruct 脚本长啥样?
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import sys
import os
import platform
import re
env = Environment()
abs_path = os.getcwd()
print('workspace path:{0}'.format(abs_path))
sbuild_dir = 'sbuild'
headers = ['.', 'third-party/include']
libs = ['./third-party/lib']
abs_headers = []
abs_libs = []
for item in headers:
abs_item = os.path.join(abs_path, item)
abs_headers.append(abs_item)
for item in libs:
abs_item = os.path.join(abs_path, item)
abs_libs.append(abs_item)
build_dir = os.path.join(abs_path, sbuild_dir)
abs_libs.append(os.path.join(build_dir, 'lib'))
CCFLAGS = '-ggdb -std=c++11'
print('\nheaders path:')
print(abs_headers)
print('\n')
print('libs path:')
print(abs_libs)
print('\n')
print("begin load SConscript")
env["headers"] = abs_headers
env["libs"] = abs_libs
env["MUX_DIR"] = abs_path
env['ccflags'] = CCFLAGS
env['build_dir'] = build_dir
Export('env')
SConscript(['./mbase/SConscript'])
SConscript(['./message_handle/SConscript'])
SConscript(['./epoll/SConscript'])
SConscript(['./transport/SConscript'])
SConscript(['./demo/bench/SConscript'])
SConscript(['./demo/echo/SConscript'])
print("\n All Done, Please Check {0}".format(env['build_dir']))
来分析一下这个文件,源文件可以直接在 我的github下载。
SConstruct 文件主要做了两件事:
需要注意的是 SConstruct 和 SConscript 共享变量使用的就是 env 这个变量,你可以看到上面有一句:
Export('env')
这句很重要。
那么位于子模块或者子目录的 SConscript 文件长啥样呢?
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import os
import sys
Import('env')
project_dir = env['MUX_DIR']
epoll_lib = 'epoll'
epoll_src_path = os.path.join(project_dir, 'epoll/src')
epoll_sources = []
for item in os.listdir(epoll_src_path):
if item.endswith('.cc') or item.endswith('.cpp') or item.endswith('.cxx'):
abs_item = os.path.join(epoll_src_path, item)
epoll_sources.append(abs_item)
print('\nbuild target:lib{0}.a'.format(epoll_lib))
print(epoll_sources)
lib_dir = os.path.join(env['build_dir'], 'lib')
link_libraries = ['mbase']
for lib_name in link_libraries:
lib_name = "{0}{1}{2}".format(env['LIBPREFIX'], lib_name, env['LIBSUFFIX'])
abs_lib_name = os.path.join(lib_dir, lib_name)
epoll_sources.append(abs_lib_name)
env.StaticLibrary(target = os.path.join(lib_dir, epoll_lib),
source = epoll_sources,
CPPPATH = env['headers'], # include
LIBPATH = env['libs'], # lib path
LIBS = ['pthread'], # link lib
CCFLAGS = env['ccflags']
)
来分析一下这个文件,源文件可以直接在 我的github下载。
SConscript 主要做了两件事:
当然,还有一点很重要,上面其实提到了,SConscript 和 SConstruct 用来共享变量使用的是 env 这个变量,所以你可以看到一句很重要的:
Import('env')
构造源文件列表,对于 Python 来说,简直是小菜一碟,太简单了;
然后如何生成目标文件呢?
1 生成二进制文件
env.Program(target = os.path.join(bin_dir, echo_server_bin),
source = echo_server_sources,
CPPPATH = env['headers'],
LIBPATH = env['libs'],
LIBS = ['transport','msghandler','epoll', 'mbase', 'pthread'],
CCFLAGS = env['ccflags']
)
2 生成静态库
env.StaticLibrary(target = os.path.join(lib_dir, epoll_lib),
source = epoll_sources,
CPPPATH = env['headers'], # include
LIBPATH = env['libs'], # lib path
LIBS = ['pthread'], # link lib
CCFLAGS = env['ccflags']
)
3 生成动态库
env.SharedLibrary(target = os.path.join(lib_dir, epoll_lib),
source = epoll_sources,
CPPPATH = env['headers'], # include
LIBPATH = env['libs'], # lib path
LIBS = ['pthread'], # link lib
CCFLAGS = env['ccflags']
)
上面 3 个函数的参数都是类似的:
attention:
上面有一个坑我自己碰到的,当我构建目标生成一个静态库的时候,需要链接其他的静态库,如果使用 $LIBPATH 和 $LIBS 指定链接库的话,scons 并没有链接这些库。尝试了很多方法,搜索了很多,也没有解决这个问题。
最后是这样解决的。把需要链接的静态库添加到 source 参数中,和其他 cc/cpp 源文件一样放在一起,并且这些库需要使用绝对路径。
通常为了跨平台的方便,需要考虑lib 的前后缀,可以这样写:
link_libraries = ['test1', 'test2']
for lib_name in link_libraries:
lib_name = "{0}{1}{2}".format(env['LIBPREFIX'], lib_name, env['LIBSUFFIX'])
abs_lib_name = os.path.join(lib_dir, lib_name)
sources.append(abs_lib_name)
上面详细讲解了如何使用 python 编写构建脚本,那么写好之后怎么用呢?
常用的几个命令:
编译:
scons
如果需要并行编译:
scons -j4
清理:
scons -c
然后就会按照你脚本里写的方式去构建目标了。
这里贴一下 我的项目 编译的输出:
$ scons
scons: Reading SConscript files ...
workspace path:/mnt/centos-share/workspace/mux
headers path:
['/mnt/centos-share/workspace/mux/.', '/mnt/centos-share/workspace/mux/third-party/include']
libs path:
['/mnt/centos-share/workspace/mux/./third-party/lib', '/mnt/centos-share/workspace/mux/sbuild/lib']
begin load SConscript
build target:libmbase.a
['/mnt/centos-share/workspace/mux/mbase/src/packet.cc']
build target:libmsghandler.a
['/mnt/centos-share/workspace/mux/message_handle/src/message_handler.cc']
build target:libepoll.a
['/mnt/centos-share/workspace/mux/epoll/src/epoll_tcp_client.cc', '/mnt/centos-share/workspace/mux/epoll/src/epoll_tcp_server.cc']
build target:libtransport.a
['/mnt/centos-share/workspace/mux/transport/src/tcp_transport.cc']
build target:bench_server
['bench_server.cc']
build target:bench_client
['client.cc']
build target:echo_server
['echo_server.cc']
build target:echo_client
['client.cc']
All Done, Please Check /mnt/centos-share/workspace/mux/sbuild
scons: done reading SConscript files.
scons: Building targets ...
g++ -o demo/bench/bench_server.o -c -ggdb -std=c++11 -I. -Ithird-party/include demo/bench/bench_server.cc
g++ -o demo/bench/client.o -c -ggdb -std=c++11 -I. -Ithird-party/include demo/bench/client.cc
g++ -o demo/echo/client.o -c -ggdb -std=c++11 -I. -Ithird-party/include demo/echo/client.cc
g++ -o demo/echo/echo_server.o -c -ggdb -std=c++11 -I. -Ithird-party/include demo/echo/echo_server.cc
g++ -o epoll/src/epoll_tcp_client.o -c -ggdb -std=c++11 -I. -Ithird-party/include epoll/src/epoll_tcp_client.cc
g++ -o epoll/src/epoll_tcp_server.o -c -ggdb -std=c++11 -I. -Ithird-party/include epoll/src/epoll_tcp_server.cc
g++ -o mbase/src/packet.o -c -ggdb -std=c++11 -I. -Ithird-party/include mbase/src/packet.cc
g++ -o message_handle/src/message_handler.o -c -ggdb -std=c++11 -I. -Ithird-party/include message_handle/src/message_handler.cc
g++ -o transport/src/tcp_transport.o -c -ggdb -std=c++11 -I. -Ithird-party/include transport/src/tcp_transport.cc
ar rc sbuild/lib/libmbase.a mbase/src/packet.o
ranlib sbuild/lib/libmbase.a
ar rc sbuild/lib/libepoll.a epoll/src/epoll_tcp_client.o epoll/src/epoll_tcp_server.o sbuild/lib/libmbase.a
ranlib sbuild/lib/libepoll.a
ar rc sbuild/lib/libtransport.a transport/src/tcp_transport.o sbuild/lib/libepoll.a sbuild/lib/libmbase.a
ranlib sbuild/lib/libtransport.a
ar rc sbuild/lib/libmsghandler.a message_handle/src/message_handler.o sbuild/lib/libmbase.a
ranlib sbuild/lib/libmsghandler.a
g++ -o sbuild/bin/bench_client demo/bench/client.o -Lthird-party/lib -Lsbuild/lib -ltransport -lmsghandler -lepoll -lmbase -lpthread
g++ -o sbuild/bin/bench_server demo/bench/bench_server.o -Lthird-party/lib -Lsbuild/lib -ltransport -lmsghandler -lepoll -lmbase -lpthread
g++ -o sbuild/bin/echo_client demo/echo/client.o -Lthird-party/lib -Lsbuild/lib -ltransport -lmsghandler -lepoll -lmbase -lpthread
g++ -o sbuild/bin/echo_server demo/echo/echo_server.o -Lthird-party/lib -Lsbuild/lib -ltransport -lmsghandler -lepoll -lmbase -lpthread
scons: done building targets.
$ scons -c
scons: Reading SConscript files ...
workspace path:/mnt/centos-share/workspace/mux
headers path:
['/mnt/centos-share/workspace/mux/.', '/mnt/centos-share/workspace/mux/third-party/include']
libs path:
['/mnt/centos-share/workspace/mux/./third-party/lib', '/mnt/centos-share/workspace/mux/sbuild/lib']
begin load SConscript
build target:libmbase.a
['/mnt/centos-share/workspace/mux/mbase/src/packet.cc']
build target:libmsghandler.a
['/mnt/centos-share/workspace/mux/message_handle/src/message_handler.cc']
build target:libepoll.a
['/mnt/centos-share/workspace/mux/epoll/src/epoll_tcp_client.cc', '/mnt/centos-share/workspace/mux/epoll/src/epoll_tcp_server.cc']
build target:libtransport.a
['/mnt/centos-share/workspace/mux/transport/src/tcp_transport.cc']
build target:bench_server
['bench_server.cc']
build target:bench_client
['client.cc']
build target:echo_server
['echo_server.cc']
build target:echo_client
['client.cc']
All Done, Please Check /mnt/centos-share/workspace/mux/sbuild
scons: done reading SConscript files.
scons: Cleaning targets ...
Removed demo/bench/bench_server.o
Removed demo/bench/client.o
Removed demo/echo/client.o
Removed demo/echo/echo_server.o
Removed epoll/src/epoll_tcp_client.o
Removed epoll/src/epoll_tcp_server.o
Removed mbase/src/packet.o
Removed message_handle/src/message_handler.o
Removed transport/src/tcp_transport.o
Removed sbuild/lib/libmbase.a
Removed sbuild/lib/libepoll.a
Removed sbuild/lib/libtransport.a
Removed sbuild/lib/libmsghandler.a
Removed sbuild/bin/bench_client
Removed sbuild/bin/bench_server
Removed sbuild/bin/echo_client
Removed sbuild/bin/echo_server
scons: done cleaning targets.
scons 使用 python 脚本来构建项目,如果对 python 熟悉的话,那么编写编译构建脚本将会大大提高效率,再也不用局限在 Makefile 的蛋疼语法里面了。
当然 scons 的缺点也有,据说在大型项目的时候,可能会很慢。这个我还没碰到过,因为没有用到大型项目中。
下一篇,分享下 cmake 构建 C++ 项目的一些语法和步骤。
另外,文中涉及到的项目可以在我的github 找到。
Blog:
2020-08-30 于杭州
By 史矛革