用相似的规则构build多个可执行文件

我正在写一些类似C ++的交互式教程。 本教程将由两部分组成:一部分编译成一个库(我使用Scons来编译),另一部分(课程)随教程一起提供,由最终用户编译。 我目前正在寻找一个好的,简单的方法让人们build立这些经验教训。

基本上,第二部分是一个包含所有课程的目录,每个目录都在自己的目录中。 每个课程至less有一个lesson.cpp和一个main.cpp文件,可能还有其他的文件,这些文件的存在直到它出厂之后才会知道 – 最终用户将会创build这些文件。 它看起来像这样:

 all_lessons/ helloworld/ lesson.cpp main.cpp even_or_odd/ lesson.cpp main.cpp calculator/ lesson.cpp main.cpp user_created_add.cpp 

每一个都需要按照几乎相同的规则进行编译,编译命令应该可以从一个课程目录( helloworld/等)运行。

看到这个项目的其余部分是用Scons构build的,这个部分也是有意义的。 但是,Scons在它运行的目录中searchSConstruct文件:在每个课程目录中放置一个SConstruct文件以及在all_lessons/目录中提供一般规则的all_lessons/是否可以接受? 这似乎违背了Scons预计项目组织的典型方式:这种方法的潜在缺陷是什么? 我可以把一个SConstruct文件,而不是SConscript之一,从而使它可能从任一目录(使用出口,以避免无限recursion,我猜测)build立?

另外,我可能在某个时候想用lesson.cppreplacelesson.py来生成必要的文件; Scons会允许我如何轻松地与build设者做这件事,还是有一个更方便的框架?

最后,我想结束以下(或与不同的构build系统等效):

 all_lessons/ SConstruct helloworld/ SConstruct lesson.cpp main.cpp even_or_odd/ SConstruct lesson.py main.cpp calculator/ SConstruct lesson.cpp main.cpp user_created_add.cpp 

all_lessons目录中运行scons all将需要:

  • 运行even_or_odd/lesson.py生成even_or_odd/lesson.cpp
  • 认识到user_created_add.cpp也需要编译。
  • 为每个课程生成一个可执行文件。

even_or_odd/运行scons ,或者在even_or_odd/运行scons even_or_odd/应该生成一个与上面相同的可执行文件(相同的编译标志)。

概要:

  1. Scons适合/有能力吗?
  2. SConscript文件高于SConstruct文件时, SConscript是否工作正常?
  3. Scons是否与一个项目的多个SConstrcut文件很好地SConstrcut ,SCS脚本彼此?
  4. Scons构build器系统是否适合使用Python脚本生成C ++文件?
  5. 使用不同的构build系统/写我自己的构build框架,我有什么优势吗?

当然,欢迎任何进一步的评论。

谢谢。

你可以用几行GNU Make做到这一点。

下面是两个允许从all_lessons目录和单个项目目录进行构build和清理的makefile。 它假定该目录中的所有C ++源代码都包含一个以其目录命名的可执行文件。 当从顶级源代码目录( all_lessons )构build和清理时,它build立和清理所有的项目。 从项目目录构build和清理时,只会构build和清理项目的二进制文件。

这些makefile也会自动生成依赖关系,并且完全可并行化( make -j友好的)。

对于以下示例,我使用了与以下相同的源文件结构:

 $ 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 

为了能够从个别项目目录中build立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 

我们来构build一个项目:

 [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 

现在build立所有项目:

 [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 

清理一个项目:

 [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' 

清理所有项目:

 [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 

makefiles:

 [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)) 

作为学习scons的练习,我试着回答你的问题。 不幸的是,我不是专家,所以我不能告诉你什么是最好的/理想的方式,但是这是一种有效的方法。

  1. Scons适合/能够做到这一点。 (这正是构build工具的目的。)
  2. 不适用。 (我不知道。)
  3. 对于一个项目,Scons 似乎可以很好地处理多个SConstrcut文件,相互之间进行SCS脚本编写。
  4. Scons构build器系统可以使用Python脚本来生成C ++文件。
  5. 一个不同的构build系统? 为了他自己。

使用您定义的层次结构,每个文件夹中都有一个SConstruct文件。 您可以在子文件夹中运行scons来构build该项目,或者在顶层构build所有项目(不知道如何将“all”全部scons为默认构build)。 您可以运行scons -c来清理项目,scons会自动找出它创build的文件并清除它们(包括生成的lesson.cpp)。

但是,如果你想让编译器标志从顶层文件传播下来,我认为最好使用SConscript文件 – 除了我不确定自己编译这些文件。

./SConstruct

 env = Environment() env.SConscript(dirs=['calculator', 'even_or_odd', 'helloworld'], name='SConstruct') 

./calculator/SConstruct和./calculator/helloworld

 env = Environment() env.Program('program', Glob('*.cpp')) 

./even_or_odd/SConstruct

 env = Environment() def add_compiler_builder(env): # filename transformation suffix = '.cpp' src_suffix = '.py' # define the build method rule = 'python $SOURCE $TARGET' bld = Builder(action = rule, suffix = suffix, src_suffix = src_suffix) env.Append(BUILDERS = {'Lesson' : bld}) return env add_compiler_builder(env) env.Lesson('lesson.py') env.Program('program', Glob('*.cpp')) 

使用SConscripts

我将子文件夹的SConstructs转换为SConscripts,并可以从子文件夹中提取代码构build细节,但是需要运行scons -u以构build子文件夹(向上search根SConstruct)。

./SConstruct

 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']) 

./helloworld/SConscript等…

 Import('env') env.default_build(env) 

从课程目录运行编译命令是否至关重要? 如果没有,那么我会亲自创build具有以下内容的all_lessons / makefile:

 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”来构build,或者用例如“make helloworld / main”来制作一个特定的课程。

据我所知,这是最好的解决scheme:

该目录的结构相同,但不是具有多个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 

使用GlobSConscript文件可以编译带有cpp扩展名的所有文件。 它也可以使用一个生成器(可以调用一个简单的命令,或者一个完整的命令)来生成这个教程,这意味着甚至可以将这个教程作为元数据存储起来,并在现场生成。

所以,要回答这个问题:

  1. 是。
  2. 我不知道,但这不是必需的。
  3. 据我所见,不(有关SConstruct等path的问题)。
  4. 是的,有几个选项可用。
  5. 我不知道。

build议的方法落后:这需要分开制作一个元构build系统。 可以指定选项的文件数较多, SConscript文件给出了很大的出错空间。

这是我的方式。

 # 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')) 

现在你有:

  • env – 核心包含所有课程共同规则和参数的环境
  • lessonEnv – 克隆核心env,但是如果在特定课程目录中有lesson.scons,则可以额外configuration该环境或重写一些参数。