5

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

超级简单,但我仍然不知道在 SCons 或 CMake 中构建它的“最佳”方式是什么(除了盯着最后一天左右的文档外,我还没有任何专业知识)变得悲伤)。我担心在大多数构建系统中我想要的那种解决方案实际上可能是不可能的(或者至少过于复杂),但如果是这样,很高兴知道这一点,这样我就可以放弃并且不那么挑剔了. 自然,我希望我是错的,考虑到我对构建系统的无知(通常,特别是关于 CMake 和 SCons),这并不奇怪。

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

据我了解,处理这部分问题的两个最直接的选择是:

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

CMake 或 SCons 能以任何远程直接的方式做得比这更好吗?我看到了一堆有限的方法来旋转自动生成的依赖图,但没有通用的方法可以交互地这样做(“好的,构建系统,你认为依赖关系是什么?啊。好吧,在此基础上,添加遵循依赖关系并再想一想:...”)。我也不对此感到惊讶。不过,我还没有在任何一个构建系统中找到一种特殊用途的机制来处理链接时依赖项应该反映相应的编译时#include 依赖项的超级常见情况。我是否在阅读文档时遗漏了某些内容(诚然有些粗略),或者每个人都只是选择选项(B)并默默地讨厌自己和/或他们的构建系统?

4

2 回答 2

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 文件的目录”,我建议您将这些文件组织到库中。不是全部在一个库中,而是在逻辑上将它们组织成更小的、有凝聚力的库。然后您的应用程序/二进制文件将链接他们需要的库,从而最大限度地减少由于未使用的对象而导致的重新编译。

于 2013-08-09T09:15:09.680 回答
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))

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

于 2014-01-30T10:17:55.960 回答