使用类似规则构建多个可执行文件

时间:2011-08-19 14:55:18

标签: c++ build build-automation scons

我正在编写something,就像C ++的交互式教程一样。本教程将由两部分组成:一部分编译成一个库(我使用Scons构建它),另一部分(课程)随最终用户编译的教程一起提供。我正在寻找一种方便人们建立这些课程的方法。

基本上,第二部分是一个包含所有课程的目录,每个目录都在自己的目录中。每节课至少会有一个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/等)运行。

看到项目的其余部分是使用Scons构建的,因此将它用于此部分也是有意义的。但是,Scons在运行它的目录中搜索SConstruct文件:是否可以在每个课程目录中放置SConstruct文件,并在{{1}中添加SConscript给出一般规则的目录?这似乎违背了Scons期望项目组织的典型方式:这种方法的潜在缺陷是什么?我可以放一个SConstruct文件而不是SConscript文件,从而可以从任一目录构建(使用导出来避免无休止的递归,我猜)?

此外,我可能在某些时候想要用all_lessons/替换生成必要文件的lesson.cpp; Scons是否允许我轻松地与构建者一起完成这项工作,还是有一个更方便的框架?

最后,我想最终得到以下内容(或具有不同构建系统的等效内容):

lesson.py

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 目录中运行scons all需要:

  • 运行all_lessons以生成even_or_odd/lesson.py
  • 意识到还需要编译even_or_odd/lesson.cpp
  • 为每节课制作可执行文件。

user_created_add.cpp中运行scons或在even_or_odd/中运行scons even_or_odd应生成与上述相同的可执行文件(相同的编译标记)。

要点:

  1. Scons是否适合/能够做到这一点?
  2. all_lessons/文件高于SConscript个文件时,Scons能否正常运作?
  3. Scons是否适用于一个项目的多个SConstruct文件,相互之间进行SConscripting?
  4. Scons构建器系统是否适合使用Python脚本生成C ++文件?
  5. 使用不同的构建系统/编写我自己的构建框架是否有任何优势?
  6. 当然,任何进一步的评论都是受欢迎的。

    感谢。

5 个答案:

答案 0 :(得分:10)

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

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

这些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

要能够从单个项目目录构建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

现在构建所有项目:

[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

makefile:

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

答案 1 :(得分:1)

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

  1. Scons 适合/能够做到这一点。 (这正是构建工具的用途。)
  2. 不适用。 (我不知道。)
  3. Scons 似乎运行良好,包含一个项目的多个SConstrcut文件,互相扫描。
  4. Scons构建器系统可以使用Python脚本生成C ++文件。
  5. 一个不同的构建系统?对他自己而言。
  6. 使用您定义的层次结构,每个文件夹中都有一个SConstruct文件。您可以在子文件夹中运行scons来构建该项目,也可以在顶层运行以构建所有项目(不确定如何将“all”别名为默认构建)。您可以运行scons -c来清理项目,并且scons会自动确定它创建的文件并清除它们(包括生成的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,并且可以从子文件夹中提取代码构建细节,但是您需要运行scons -u来构建子文件夹(向上搜索根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)
    

答案 2 :(得分:0)

编译命令是否必须从课程目录运行?如果没有,那么我将亲自使用以下内容创建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”构建所有课程,或者使用例如“make helloworld / main”。

答案 3 :(得分:0)

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

目录的结构方式相同,但课程不是包含多个SConstruct文件,而是每个文件都有一个SConscript文件,默认情况下会根据需要覆盖默认值。 <{1}}文件由外部脚本根据需要生成,并调用SCons。

概述:

SConstruct

使用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扩展名的所有文件。它还可以使用生成课程的构建器(一个调用简单命令或完全成熟的命令),这意味着甚至可以将课程存储为元数据并将其生成到现场。

所以,回答问题:

  1. 我不知道,但出于此目的不需要。
  2. 据我所知,没有(与cpp相关的路径问题,除其他外)。
  3. 是的,有几个选项可用。
  4. 我不知道。
  5. 缺少建议的方法:这需要单独制作元构建系统。可以指定选项的文件数量较多,SConstruct文件会给错误留出很大空间。

答案 4 :(得分:0)

这是我的方式。

# 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 - 克隆核心环境,但如果你有 在特定课程目录中的lesson.scons,您可以进行额外配置 那个环境或重写一些参数。