SCons和/或CMake:从“编译时包含的头”到“相应的目标文件必须链接”的任何方式自动映射?

时间:2013-08-09 07:05:39

标签: build cmake scons

超级简单,完全无聊的设置:我有一个充满.hpp和.cpp文件的目录。其中一些.cpp文件需要构建成可执行文件;当然,这些.cpp文件#include同一目录中的一些.hpp文件,然后可能包含其他文件等。大多数.hpp文件都有相应的.cpp文件,也就是说:if some_application.cpp #includes foo.hpp,无论是直接还是传递,那么很可能还有一个需要编译并链接到some_application可执行文件的foo.cpp文件。

超级简单,但我仍然无法理解构建它的最佳方式是什么,无论是在SCons还是CMake(我还没有任何专业知识,除了盯着最后一天的文档左右,变得悲伤)。我担心我想要的那种解决方案在大多数构建系统中可能实际上是不可能的(或至少过于复杂),但如果是这样的话,那么知道我能这样做是件好事。只是放弃,不那么挑剔。当然,我希望我错了,考虑到我对构建系统一无所知(一般而言,特别是关于CMake和SCons),这并不奇怪。

当然,CMake和SCons都可以自动检测some_application.cpp是否需要在它所依赖的任何头文件(直接或可传递)发生变化时重新编译,因为它们可以很好地“解析”C ++文件挑出那些依赖。好的,好的:我们不必手动列出每个.cpp-#includes-.hpp依赖项。但是:我们仍需要确定在实际生成每个可执行文件时需要将哪些目标文件子集发送到链接器。

据我了解,处理这部分问题的两个最直接的替代方案是:

  • 甲。明确且费力地枚举“使用此对象文件的任何东西也需要手动使用这些其他对象文件”依赖性,即使这些依赖性完全镜像由相应的-cpp-transitively-includes-the - 相应的.hpp依赖关系,构建系统已经遇到了为我们搞清楚的麻烦。为什么?因为电脑。
  • B中。将此目录中的所有目标文件转储到单个“库”中,然后让所有可执行文件依赖并链接到该库中。这更简单,我理解大多数人会这样做,但它也有点草率。大多数可执行文件实际上并不需要该库中的所有内容,如果只更改了一个或两个.cpp文件的内容,则实际上不需要重建。这不是设置所谓的“构建系统”应该避免的那种不必要的计算吗? (我想如果库是动态链接的话,也许不需要重建它们,但是我可以说我不喜欢动态链接的库。)

CMake或SCons可以以任何远程直接的方式做得比这更好吗?我看到了一些有限的方法来改变自动生成的依赖图,但没有通用的方法交互式(“好的,构建系统,你想到什么依赖是什么?啊。好吧,基于此,添加以下依赖项并再次思考:...“)。我对此并不感到惊讶。我还没有在构建系统中找到一个特殊用途的机制来处理超常见的情况,其中链接时依赖性应该反映相应的编译时#include依赖性。我是否遗漏了我的(粗略地有点粗略)阅读文档,或者每个人都选择(B)并且悄悄地讨厌自己和/或他们的构建系统?

2 个答案:

答案 0 :(得分:2)

你在A)中的陈述“任何使用这个目标文件的东西都需要使用这些其他目标文件”,这确实需要手工完成。编译器不会自动查找二进制文件所需的目标文件。您必须在链接时明确列出它们。如果我正确理解您的问题,您不希望必须明确列出二进制文件所需的对象,但希望构建工具自动找到它们。我怀疑是否有任何构建这样做:SCons和Cmake绝对不会这样做。

如果您的应用程序some_application.cpp包含foo.hpp(或这些cpp文件使用的其他标头),并且随后需要链接foo.cpp对象,那么在SCons中,您将需要做这样的事情:

env = Environment()
env.Program(target = 'some_application',
            source = ['some_application.cpp', 'foo.cpp'])

当'some_application.cpp','foo.hpp'或'foo.cpp'发生变化时,这将 链接。假设g ++,这将有效地转换为类似下面的内容,独立于SCons或Cmake。

g++ -c foo.cpp -o foo.o
g++ some_application.cpp foo.o -o some_application

你提到你有“一个充满.hpp和.cpp文件的目录”,我建议你将这些文件组织到库中。不是所有的都在一个库中,而是在逻辑上将它们组织成更小,更有凝聚力的库。然后,您的应用程序/二进制文件将链接它们所需的库,从而最大限度地减少因未使用的对象而导致的重新编译。

答案 1 :(得分:1)

我或多或少有同样的问题,我按如下方式解决了问题:

import SCons.Scanner
import os

def header_to_source(header_file):
    """Specify the location of the source file corresponding to a given
    header file."""
    return header_file.replace('include/', 'src/').replace('.hpp', '.cpp')

def source_files(main_file, env):
    """Returns list of source files the given main_file depends on.  With
    the function header_to_source one must specify where to look for
    the source file corresponding to a given header.  The resulting
    list is filtered for existing files.  The resulting list contains
    main_file as first element."""
    ## get the dependencies 
    node = File(main_file)
    scanner = SCons.Scanner.C.CScanner()
    path = SCons.Scanner.FindPathDirs("CPPPATH")(env)
    deps = node.get_implicit_deps(env, scanner, path)

    ## collect corresponding source files
    root_path = env.Dir('#').get_abspath()
    res = [main_file]
    for dep in deps:
        source_path = header_to_source(
            os.path.relpath(dep.get_abspath(), root_path))
        if os.path.exists(os.path.join(root_path, source_path)):
            res.append(source_path)

    return res

header_to_source方法是您需要修改的方法,它返回与给定头文件对应的源文件。然后方法source_file为您提供构建给定main_file所需的所有源文件(包括main_file作为第一个元素)。将自动删除不存在的文件。因此,以下内容足以定义可执行文件的目标:

env.Program(source_files('main.cpp', env))

我不确定这是否适用于所有可能的设置,但至少对我来说它是有效的。