使用相似的规则构建多个可执行文件
我正在写一些东西,就像 C++ 的交互式教程.本教程将包含两部分:一个被编译成一个库(我使用 Scons 来构建它),另一个(课程)与教程一起提供,由最终用户编译.我目前正在寻找一种好的、简单的方法让人们建立这些课程.
I am writing something like an interactive tutorial for C++. The tutorial will consist of two parts: one is compiled into a library (I'm using Scons to build that), and the other (the lessons) is shipped with the tutorial to be compiled by the end user. I'm currently looking for a good, easy way for people to build these lessons.
基本上,第二部分是一个包含所有课程的目录,每个课程都在自己的目录中.每节课至少会有一个lesson.cpp
和一个main.cpp
文件,可能还有其他文件,具体存在后我才知道运送 - 最终用户将创建这些.它看起来像这样:
Basically, the second part is a directory with all the lessons in it, each in its own directory. Each lesson will have at least a lesson.cpp
and a main.cpp
file, there may be also other files, the existence of which I will not know until after it is shipped -- the end user will create these. It will look something like this:
all_lessons/
helloworld/
lesson.cpp
main.cpp
even_or_odd/
lesson.cpp
main.cpp
calculator/
lesson.cpp
main.cpp
user_created_add.cpp
这些都需要根据几乎相同的规则进行编译,并且编译命令应该可以从课程目录之一(helloworld/
等)运行.
Each of these will need to be compiled according to almost the same rules, and the command for compiling should be possible to run from one of the lesson directories (helloworld/
, etc.).
鉴于项目的其余部分是用 Scons 构建的,因此将其用于这一部分也是有意义的.然而,Scons 在它运行的目录中搜索 SConstruct
文件:在每个课程目录中放置一个 SConstruct
文件,加上一个 SConscript 是否可以接受?
在 all_lessons/
目录中给出了一般规则?这似乎与 Scons 期望组织项目的典型方式背道而驰:这种方法的潜在缺陷是什么?我可以放置一个 SConstruct 文件而不是 SConscript 文件,从而可以从任一目录构建(使用导出来避免无限递归,我猜)?
Seeing as the rest of the project is built with Scons, it would make sense to use it for this part, too. However, Scons searches for the SConstruct
file in the directory it is run from: would it be acceptable to put a SConstruct
file in each lesson directory, plus a SConscript
in the all_lessons/
directory that gives the general rules? This seems to go against the typical way Scons expects projects to be organised: what are the potential pitfalls of this approach? Could I put a SConstruct file instead of the SConscript one, and thereby make it possible to build from either directory (using exports to avoid endless recursion, I'm guessing)?
此外,我可能在某个时候想用生成必要文件的 lesson.py
替换 lesson.cpp
;使用 Scons 是否可以让我轻松地与构建器一起执行此操作,或者是否有更方便的框架?
Also, I may at some point want to replace the lesson.cpp
with a lesson.py
that generates the necessary files; will Scons allow me to do this easily with builders, or is there a framework that would be more convenient?
最后,我想得到以下结果(或不同构建系统的等效结果):
In the end, I want to end up with the following (or equivalent with different build systems):
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
需要:
Running scons all
in the all_lessons
directory would need to:
- 运行
even_or_odd/lesson.py
生成even_or_odd/lesson.cpp
. - 意识到
user_created_add.cpp
也需要编译. - 为每节课制作一个可执行文件.
- Run
even_or_odd/lesson.py
to generateeven_or_odd/lesson.cpp
. - Realise that
user_created_add.cpp
also needs to be compiled. - Produce an executable for each lesson.
在 even_or_odd/
中运行 scons
或在 all_lessons/
中运行 scons even_or_odd
应该产生与上面的一个(相同的编译标志).
Running scons
in even_or_odd/
, or scons even_or_odd
in all_lessons/
should produce an executable identical to the one above (same compile flags).
总结:
- Scons 是否适合/能够做到这一点?
- 当
SConscript
文件高于SConstruct
文件时,Scons 是否能正常工作? - Scons 是否可以很好地处理一个项目的多个
SConstrcut
文件,并相互 SConscript? - Scons 构建器系统是否适合使用 Python 脚本生成 C++ 文件?
- 使用不同的构建系统/编写我自己缺少的构建框架有什么好处吗?
- Is Scons suitable for/capable of this?
- Does Scons work well when
SConscript
files are aboveSConstruct
files? - Does Scons work well with multiple
SConstrcut
files for one project, SConscripting each other? - Is the Scons builder system suitable for using Python scripts to generate C++ files?
- Is there any advantage of using a different build system/writing my own build framework that I'm missing?
当然,欢迎任何进一步的评论.
Any further comments are, of course, welcome.
谢谢.
推荐答案
您实际上可以使用几行 GNU Make 来做到这一点.
You can actually do this with a few lines of GNU Make.
以下是允许从 all_lessons
目录和单个项目目录构建和清理的两个 makefile.它假定该目录中的所有 C++ 源代码都包含一个以其目录命名的可执行文件.从顶级源目录 (all_lessons
) 构建和清理时,它会构建和清理所有项目.从项目目录构建和清理时,它只会构建和清理项目的二进制文件.
Below are two makefiles that allow building and cleaning from all_lessons
directory and individual project directories. It assumes that all C++ sources in that directory comprise an executable file which gets named after its directory. When building and cleaning from the top level source directory (all_lessons
) it builds and cleans all the projects. When building and cleaning from a project's directory it only builds and cleans the project's binaries.
这些 makefile 还会自动生成依赖项并且是完全可并行化的(make -j
友好).
These makefiles also automatically generate dependencies and are fully parallelizable (make -j
friendly).
对于以下示例,我使用了与您相同的源文件结构:
For the following example I used the same source file structure as you have:
$ 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
To be able to build from individial project directories project.mk
must be symlinked as project/Makefile
first
[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
现在构建所有项目:
[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
生成文件:
[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))
相关文章