使用不同的预处理器宏在同一源树上构建多个目标

Building multiple targets off same source tree with different preprocessor macros

本文关键字:构建 目标 预处理 处理器      更新时间:2023-10-16

我对make自动工具(我还没有在这个项目中使用)的了解充其量只是初步的,尽管经过了长时间的谷歌搜索和实验。我有一个像下面这样的源层次结构,我正试图找到一种尽可能无缝构建的方法。

该应用程序由一个主应用程序组成,其源代码位于app/src下的各个子文件夹中。这些文件是使用该文件夹根目录中的相应Makefile构建的。

然后,我有多个其他实用程序,它们位于应用程序/工具下的不同文件夹中,每个文件夹都有自己的Makefile。

app/src/module1/file1.cpp
app/src/module1/file1.hpp
app/src/module2/file2.cpp
app/src/module2/file2.hpp
app/src/module3/file3.cpp
app/src/module3/file3.hpp
app/src/main.cpp
app/src/main.hpp
app/src/Makefile
app/tools/util1/file1.cpp
app/tools/util1/file1.hpp
app/tools/util1/Makefile
app/tools/util2/file2.cpp
app/tools/util2/file2.hpp
app/tools/util2/Makefile

对我来说,问题是其中一些工具依赖于app/src源文件夹中的源文件,但启用了预处理宏EXTERNAL_TOOL。因此,编译主应用程序和varous实用程序生成的对象文件是不兼容的。

目前,为了构建项目的每一部分,我必须清理其间的源代码树。这很痛苦,最终肯定不是我想要的。解决这个问题的最佳方法是什么?我一直无法付诸实践的想法是:

  1. 项目的每个部分都有单独的生成目录
  2. 构建外部工具时,以某种方式在主应用程序源代码树中标记其对象文件(util.file1.o?)

我不太确定我是否有掌握制作/自动工具所需的时间和耐心。其他构建工具(scons?cmake?)是否会使这类任务更容易完成?如果是,是哪一个?

更新:这就是我现在得到的

SOURCES := util1.cpp util2.cpp util3.cpp 
    ../../src/module1/file1.cpp 
    ../../src/module1/file2.cpp 
    ../../src/module1/file3.cpp 
    ../../src/module2/file4.cpp 
    ../../src/module3/file5.cpp 
    ../../src/module3/file6.cpp 
    ../../src/module4/file7.cpp 
    ../../src/module4/file8.cpp 
    ../../src/module3/file9.cpp 
    ../../src/module4/file10.cpp 
    ../../src/module5/file11.cpp 
    ../../src/module3/file12.cpp 
    ../../src/module1/file13.cpp 
    ../../src/module3/file14.cpp 
    ../../src/module3/file15.cpp
OBJECTS = $(join $(addsuffix .util/, $(dir $(SOURCES))), $(notdir $(SOURCES:.cpp=.o)))
.PHONY: all mkdir
all: util
util: $(OBJECTS)
    $(CXX) $(CXXFLAGS) $(OBJECTS) $(LIBS) -o util
$(OBJECTS): | mkdir
    $(CXX) -c $(CXXFLAGS) -o $@ $(patsubst %.o,%.cpp,$(subst .util/,,$@))
mkdir:
    @mkdir -p $(sort $(dir $(OBJECTS)))
clean:
    -@rm -f $(OBJECTS) util
    -@rmdir $(sort $(dir $(OBJECTS))) 2>/dev/null

我是在大量谷歌搜索SO浏览后才想到这一点的。这似乎有效,但这部分看起来并不特别好(感觉有点像黑客):

$(OBJECTS): | mkdir
    $(CXX) -c $(CXXFLAGS) -o $@ $(patsubst %.o,%.cpp,$(subst .util/,,$@))

特别是,我不太喜欢我早些时候从源创建对象列表并添加后缀的事实,只是在这里做相反的事情。我似乎无法以任何其他方式让它发挥作用。

CMake有add_definitionsremove_definitions命令。您可以使用它们为项目的不同部分定义宏:

# building tools #
add_definitions(-DEXTERNAL_TOOL)
add_subdirectory($TOOL1$ $BUILD_DIR$)
add_subdirectory($TOOL2$ $BUILD_DIR$)
...
# building main app #
remove_definitions(-DEXTERNAL_TOOL)
add_executable(...)

这可以用SCons轻松完成。对于使用不同预处理器宏构建的对象,您肯定需要一个构建目录层次结构。用SCons的术语来说,创建这样的构建目录称为variant_dir。我建议使用以下SCons分层构建结构:

app/SConstruct
app/src/module1/file1.cpp
app/src/module1/file1.hpp
app/src/module2/file2.cpp
app/src/module2/file2.hpp
app/src/module3/file3.cpp
app/src/module3/file3.hpp
app/src/main.cpp
app/src/main.hpp
app/src/SConscript_modules
app/src/SConscript_main
app/tools/util1/file1.cpp
app/tools/util1/file1.hpp
app/tools/util2/file2.cpp
app/tools/util2/file2.hpp
app/tools/SConscript
app/build/main/
app/build/target1/modules/
app/build/target2/modules/
app/build/tools/utils/

为了能够用不同的预处理器宏构建相同的源文件,您需要用几个不同的环境构建相同的文件。这些env可以在src/module SConscript脚本中设置,也可以从根SConstruct中设置并向下传递。我更喜欢第二个选项,因为它将使src/module SCons脚本模块化,并且不知道(不可知)预处理器宏。

这是根构建脚本,它创建不同的env并协调子目录构建脚本:

app/SConstruct

defines1 = ['MACRO1']
defines2 = ['MACRO2']
env1 = Environment(CPPDEFINES = defines1)
env2 = Environment(CPPDEFINES = defines2)
includePaths = [
  'src/module1',
  'src/module2',
  'src/module3',
]
env1.Append(CPPPATH = includePaths)
env2.Append(CPPPATH = includePaths)
# Build different versions of the module libs
SConscript('src/SConscript_modules',
           variant_dir = '#build/target1/modules',
           exports = {'env':env1},
           duplicate=0)
SConscript('src/SConscript_modules',
           variant_dir = '#build/target2/modules',
           exports = {'env':env2},
           duplicate=0)
# Build main with env1
SConscript('src/SConscript_main',
           variant_dir = '#build/main',
           exports = {'env':env2},
           duplicate=0)
# Build tools with env2
SConscript('tools/SConscript',
           variant_dir = '#build/utils',
           exports = {'env':env2},
           duplicate=0)

这是main的构建脚本app/src/SConscript_main

Import('env')
sourceFiles = ['main.cpp']
# If you want to modify the env here, Clone() it first, otherwise
# the changes will be visible to all other SConscripts
env.Program(target = 'main', source = sourceFiles)

这是模块库的构建脚本,它将被调用两次,每次都使用不同的envapp/src/SConscript_modules

Import('env')
module1SourceFiles = ['file1.cpp']
module2SourceFiles = ['file2.cpp']
module3SourceFiles = ['file3.cpp']
# If you want to modify the env here, Clone() it first, otherwise
# the changes will be visible to all other SConscripts
env.Library(target = 'module1', source = module1SourceFiles)
env.Library(target = 'module2', source = module2SourceFiles)
env.Library(target = 'module3', source = module3SourceFiles)