使用类似规则构建多个可执行文件 我写的是C++的交互式教程。本教程将由两部分组成:一部分编译到库中(我正在使用Scons构建库),另一部分(课程)随教程一起提供,由最终用户编译。我目前正在为人们寻找一个好的、简单的方法来构建这些课程
基本上,第二部分是一个包含所有课程的目录,每个课程都在自己的目录中。每节课至少有一个使用类似规则构建多个可执行文件 我写的是C++的交互式教程。本教程将由两部分组成:一部分编译到库中(我正在使用Scons构建库),另一部分(课程)随教程一起提供,由最终用户编译。我目前正在为人们寻找一个好的、简单的方法来构建这些课程,c++,build,build-automation,scons,C++,Build,Build Automation,Scons,基本上,第二部分是一个包含所有课程的目录,每个课程都在自己的目录中。每节课至少有一个lesson.cpp和一个main.cpp文件,可能还有其他文件,在发货之前我不知道是否存在这些文件——最终用户将创建这些文件。它看起来像这样: all_lessons/ helloworld/ lesson.cpp main.cpp even_or_odd/ lesson.cpp main.cpp calculator/
lesson.cpp
和一个main.cpp
文件,可能还有其他文件,在发货之前我不知道是否存在这些文件——最终用户将创建这些文件。它看起来像这样:
all_lessons/
helloworld/
lesson.cpp
main.cpp
even_or_odd/
lesson.cpp
main.cpp
calculator/
lesson.cpp
main.cpp
user_created_add.cpp
其中每一个都需要根据几乎相同的规则进行编译,编译命令应该可以从其中一个课程目录(helloworld/
等)运行
鉴于项目的其余部分都是使用SCON构建的,因此在这一部分中使用它也是有意义的。但是,Scons会在其运行的目录中搜索SConstruct
文件:是否可以在每个课程目录中放置SConstruct
文件,并在all_lessons/
目录中添加一个SConscript
,以给出一般规则?这似乎与Scons期望项目组织的典型方式背道而驰:这种方法的潜在陷阱是什么?我是否可以放置一个sconstuct文件而不是SConscript文件,从而可以从任意一个目录进行构建(我猜是使用导出来避免无休止的递归)
另外,我可能在某个时候想用生成必要文件的lessource.py
替换lessource.cpp
;Scons是否允许我轻松地使用构建器来实现这一点,或者是否有一个更方便的框架
最后,我希望以以下内容结束(或使用不同构建系统的等效内容):
运行all\u lessons
目录中的scons all
需要:
- 运行
生成偶数或奇数/lesson.py
偶数或奇数/lesson.cpp
- 意识到
也需要编译user\u created\u add.cpp
- 为每节课生成一个可执行文件
偶数或奇数
中运行scons
,或在所有课程
中运行scons偶数或奇数
时,应生成与上面相同的可执行文件(相同的编译标志)
总结:
SConscript
文件位于SConstruct
文件上方时,scon是否工作正常sconstruct
文件,并相互说明谢谢。编译命令必须从课程目录运行吗?如果不是这样,我将亲自创建包含以下内容的所有课程/制作文件:
lessons = helloworld even_or_odd calculator
all: $(lessons)
# for each $lesson, the target is $lesson/main built from $lesson/main.cpp and $lesson/lesson.cpp
# NB: the leading space on the second line *must* be a tab character
$(lessons:%=%/main): %/main: %/main.cpp %/lesson.cpp
g++ -W -Wall $+ -o $@
然后,所有课程都可以使用All_lessons目录中的“make”或“make All”构建,或者使用例如“make helloworld/main”的特定课程构建。据我所知,这是可用的最佳解决方案: 目录的结构与此相同,但不是有多个
SConstruct
文件,而是每个课程都有一个SConscript
文件,其中默认值会根据需要被覆盖。必要时由外部脚本生成SConstruct
文件,并调用SCons
概述:
all_lessons/
helloworld/
SConscript
lesson.cpp
main.cpp
even_or_odd/
SConscript
lesson.py
main.cpp
calculator/
SConscript
lesson.cpp
main.cpp
user_created_add.cpp
使用Glob
,SConscript
文件可以编译扩展名为cpp
的所有文件。它还可以使用生成课程的构建器(调用简单命令的构建器或完全成熟的构建器),这意味着甚至可以将课程存储为元数据并当场生成
因此,要回答这些问题:
SConstruct
相关的路径问题,以及其他问题)建议方法的缺点:这确实需要单独创建元构建系统。可以指定选项的文件数量更高,而且
SConscript
文件有很大的错误空间。您实际上可以使用几行GNU Make来完成这项工作
下面是两个makefile,它们允许从所有课程
目录和单个项目目录进行构建和清理。它假定该目录中的所有C++源包括一个可执行文件,该文件以其目录命名。从顶级源目录(all_lessons
)生成和清理时,它会生成和清理所有项目。从项目目录生成和清理时,它只生成和清理项目的二进制文件
这些makefile还自动生成依赖项,并且是完全可并行的(make-j
friendly)
对于以下示例,我使用了与您相同的源文件结构:
$ find all_lessons
all_lessons
all_lessons/even_or_odd
all_lessons/even_or_odd/main.cpp
all_lessons/Makefile
all_lessons/helloworld
all_lessons/helloworld/lesson.cpp
all_lessons/helloworld/main.cpp
all_lessons/project.mk
all_lessons/calculator
all_lessons/calculator/lesson.cpp
all_lessons/calculator/user_created_add.cpp
all_lessons/calculator/main.cpp
要能够从单个项目目录中生成project.mk
必须首先符号链接为project/Makefile
[all_lessons]$ cd all_lessons/calculator/
[calculator]$ ln -s ../project.mk Makefile
[helloworld]$ cd ../helloworld/
[helloworld]$ ln -s ../project.mk Makefile
[even_or_odd]$ cd ../even_or_odd/
[even_or_odd]$ ln -s ../project.mk Makefile
让我们构建一个项目:
[even_or_odd]$ make
make -C .. project_dirs=even_or_odd all
make[1]: Entering directory `/home/max/src/all_lessons'
g++ -c -o even_or_odd/main.o -Wall -Wextra -MD -MP -MF even_or_odd/main.d even_or_odd/main.cpp
g++ -o even_or_odd/even_or_odd even_or_odd/main.o
make[1]: Leaving directory `/home/max/src/all_lessons'
[even_or_odd]$ ./even_or_odd
hello, even_or_odd
[all_lessons]$ cd helloworld/
[helloworld]$ make clean
make -C .. project_dirs=helloworld clean
make[1]: Entering directory `/home/max/src/all_lessons'
rm -f helloworld/lesson.o helloworld/main.o helloworld/main.d helloworld/lesson.d helloworld/helloworld
make[1]: Leaving directory `/home/max/src/all_lessons'
现在构建所有项目:
[even_or_odd]$ cd ..
[all_lessons]$ make
g++ -c -o calculator/lesson.o -Wall -Wextra -MD -MP -MF calculator/lesson.d calculator/lesson.cpp
g++ -c -o calculator/user_created_add.o -Wall -Wextra -MD -MP -MF calculator/user_created_add.d calculator/user_created_add.cpp
g++ -c -o calculator/main.o -Wall -Wextra -MD -MP -MF calculator/main.d calculator/main.cpp
g++ -o calculator/calculator calculator/lesson.o calculator/user_created_add.o calculator/main.o
g++ -c -o helloworld/lesson.o -Wall -Wextra -MD -MP -MF helloworld/lesson.d helloworld/lesson.cpp
g++ -c -o helloworld/main.o -Wall -Wextra -MD -MP -MF helloworld/main.d helloworld/main.cpp
g++ -o helloworld/helloworld helloworld/lesson.o helloworld/main.o
[all_lessons]$ calculator/calculator
hello, calculator
[all_lessons]$ helloworld/helloworld
hello, world
[helloworld]$ cd ..
[all_lessons]$ make clean
rm -f calculator/lesson.o calculator/user_created_add.o calculator/main.o even_or_odd/main.o helloworld/lesson.o helloworld/main.o calculator/user_created_add.d calculator/main.d calculator/lesson.d even_or_odd/main.d calculator/calculator even_or_odd/even_or_odd helloworld/helloworld
清洁一个项目:
[even_or_odd]$ make
make -C .. project_dirs=even_or_odd all
make[1]: Entering directory `/home/max/src/all_lessons'
g++ -c -o even_or_odd/main.o -Wall -Wextra -MD -MP -MF even_or_odd/main.d even_or_odd/main.cpp
g++ -o even_or_odd/even_or_odd even_or_odd/main.o
make[1]: Leaving directory `/home/max/src/all_lessons'
[even_or_odd]$ ./even_or_odd
hello, even_or_odd
[all_lessons]$ cd helloworld/
[helloworld]$ make clean
make -C .. project_dirs=helloworld clean
make[1]: Entering directory `/home/max/src/all_lessons'
rm -f helloworld/lesson.o helloworld/main.o helloworld/main.d helloworld/lesson.d helloworld/helloworld
make[1]: Leaving directory `/home/max/src/all_lessons'
清理所有项目:
[even_or_odd]$ cd ..
[all_lessons]$ make
g++ -c -o calculator/lesson.o -Wall -Wextra -MD -MP -MF calculator/lesson.d calculator/lesson.cpp
g++ -c -o calculator/user_created_add.o -Wall -Wextra -MD -MP -MF calculator/user_created_add.d calculator/user_created_add.cpp
g++ -c -o calculator/main.o -Wall -Wextra -MD -MP -MF calculator/main.d calculator/main.cpp
g++ -o calculator/calculator calculator/lesson.o calculator/user_created_add.o calculator/main.o
g++ -c -o helloworld/lesson.o -Wall -Wextra -MD -MP -MF helloworld/lesson.d helloworld/lesson.cpp
g++ -c -o helloworld/main.o -Wall -Wextra -MD -MP -MF helloworld/main.d helloworld/main.cpp
g++ -o helloworld/helloworld helloworld/lesson.o helloworld/main.o
[all_lessons]$ calculator/calculator
hello, calculator
[all_lessons]$ helloworld/helloworld
hello, world
[helloworld]$ cd ..
[all_lessons]$ make clean
rm -f calculator/lesson.o calculator/user_created_add.o calculator/main.o even_or_odd/main.o helloworld/lesson.o helloworld/main.o calculator/user_created_add.d calculator/main.d calculator/lesson.d even_or_odd/main.d calculator/calculator even_or_odd/even_or_odd helloworld/helloworld
生成文件:
[all_lessons]$ cat project.mk
all :
% : forward_ # build any target by forwarding to the main makefile
$(MAKE) -C .. project_dirs=$(notdir ${CURDIR}) $@
.PHONY : forward_
[all_lessons]$ cat Makefile
# one directory per project, one executable per directory
project_dirs := $(shell find * -maxdepth 0 -type d )
# executables are named after its directory and go into the same directory
exes := $(foreach dir,${project_dirs},${dir}/${dir})
all : ${exes}
# the rules
.SECONDEXPANSION:
objects = $(patsubst %.cpp,%.o,$(wildcard $(dir ${1})*.cpp))
# link
${exes} : % : $$(call objects,$$*) Makefile
g++ -o $@ $(filter-out Makefile,$^) ${LDFLAGS} ${LDLIBS}
# compile .o and generate dependencies
%.o : %.cpp Makefile
g++ -c -o $@ -Wall -Wextra ${CPPFLAGS} ${CXXFLAGS} -MD -MP -MF ${@:.o=.d} $<
.PHONY: clean
clean :
rm -f $(foreach exe,${exes},$(call objects,${exe})) $(foreach dir,${project_dirs},$(wildcard ${dir}/*.d)) ${exes}
# include auto-generated dependency files
-include $(foreach dir,${project_dirs},$(wildcard ${dir}/*.d))
[所有课程]$cat project.mk
全部:
%:forward#通过转发到主makefile来构建任何目标
美元(MAKE)-C。。项目_dirs=$(notdir${CURDIR})$@
.冒牌货:转发_
[所有课程]$cat Makefile
def default_build(env):
env.Program('program', Glob('*.cpp'))
env = Environment()
env.default_build = default_build
Export('env')
env.SConscript(dirs=['calculator', 'even_or_odd', 'helloworld'])
Import('env')
env.default_build(env)
# SConstruct or SConscript
def getSubdirs(dir) :
lst = [ name for name in os.listdir(dir) if os.path.isdir(os.path.join(dir, name)) and name[0] != '.' ]
return lst
env = Environment()
path_to_lessons = '' # path to lessons
# configure your environment, set common rules and parameters for all lessons
for lesson in getSubdirs(path_to_lessons) :
lessonEnv = env.Clone()
# configure specific lesson, for example i'h ve checked SConscript file in lesson dir
# and if it exist, execute it with lessonEnv and append env specific settings
if File(os.path.join(path_to_lessons, lesson, 'lesson.scons')).exists() :
SConscript(os.path.join(lesson, 'lesson.scons', export = ['lessonEnv'])
# add lesson directory to include path
lessonEnv.Append(CPPPATH = os.path.join(path_to_lessons, lesson));
lessonEnv.Program(lesson, Glob(os.path.join(path_to_lessons, lesson, '*.cpp'))