【问题标题】:Build system for an embedded C/C++ project嵌入式 C/C++ 项目的构建系统
【发布时间】:2011-11-28 05:28:08
【问题描述】:

我正在寻找一个高级构建系统/工具,它可以帮助将我的嵌入式 C 项目组织成“模块”和“组件”。请注意,这两个术语非常主观,因此我的定义如下。

  • 模块是 c 和 h 文件的内聚集合,但只有一个公共 h 文件对其他模块可见。
  • 另一方面,组件(或层)是模块的集合(例如应用层、库层、驱动层、RTOS 层等)。

构建系统/工具应该 -

  • 防止组件和模块之间的循环依赖(模块内部的循环依赖是可以的)
  • 防止访问模块的私有屏障。如果其他模块试图包含一个模块私有的头文件,构建系统必须抛出一个错误。但是,私有屏障内的文件必须能够包含该屏障内的其他文件。
  • 支持在主机上自动构建和执行单元测试(TDD 的快速反馈循环)
  • 支持在目标模拟器上运行单元测试
  • 支持代码静态分析
  • 支持代码生成
  • 支持代码重复检测(执行 DRY 原则)
  • 支持代码美化
  • 支持生成单元测试代码覆盖率指标
  • 支持生成代码质量指标
  • 独立于平台

我可以编写自己的构建工具并在上面花费大量时间。但是,这不是我的专业领域,如果有人已经创建了这样的工具,我宁愿不要重新发明轮子。

【问题讨论】:

    标签: c++ c build build-process build-system


    【解决方案1】:

    实现这一点的传统方法是将每个模块的源代码放在一个单独的目录中。每个目录都可以包含模块的所有源文件和头文件。

    每个模块的公共标头都可以放置在一个单独的公共标头目录中。对于每个标头,我可能会使用从公共目录到相关模块目录的符号链接。

    编译规则简单地规定,除了公共目录中的头文件外,任何模块都不能包含来自其他模块的头文件。这实现了没有模块可以包含来自另一个模块的标头的结果 - 除了公共标头(从而强制执行私有屏障)。

    自动防止循环依赖并非易事。问题是你只能通过一次查看几个源文件来确定存在循环依赖,而编译器一次只查看一个。

    考虑一对模块,ModuleA 和 ModuleB,以及一个使用这两个模块的程序 Program1。

    base/include
            ModuleA.h
            ModuleB.h
    base/ModuleA
            ModuleA.h
            ModuleA1.c
            ModuleA2.c
    base/ModuleB
            ModuleB.h
            ModuleB1.c
            ModuleB2.c
    base/Program1
            Program1.c
    

    在编译 Program1.c 时,如果它使用两个模块的服务,那么包含 ModuleA.h 和 ModuleB.h 是完全合法的。因此,如果 ModuleB.h 包含在同一个翻译单元 (TU) 中,ModuleA.h 不会抱怨,如果 ModuleA.h 包含在同一个翻译单元 (TU) 中,ModuleB.h 也不会抱怨。

    让我们假设 ModuleA 使用 ModuleB 的设施是合法的。因此,在编译 ModuleA1.c 或 ModuleA2.c 时,同时包含 ModuleA.h 和 ModuleB.h 不会有任何问题。

    但是,为了防止循环依赖,必须能够禁止ModuleB1.c和ModuleB2.c中的代码使用ModuleA.h。

    据我所知,唯一的方法是使用某种技术,该技术需要 ModuleB 的私有标头,即使它没有显示“ModuleA 已包含”,并且它包含在 ModuleA.h 之前曾经包括在内。

    ModuleA.h 的骨架将是标准格式(与 ModuleB.h 类似):

    #ifndef MODULEA_H_INCLUDED
    #define MODULEA_H_INCLUDED
    ...contents of ModuleA.h...
    #endif
    

    现在,如果 ModuleB1.c 中的代码包含:

    #define MODULEA_H_INCLUDED
    #include "ModuleB.h"
    ...if ModuleA.h is also included, it will declare nothing...
    ...so anything that depends on its contents will fail to compile...
    

    这远非自动。

    您可以对包含的文件进行分析,并要求存在无循环拓扑排序的依赖项。在 UNIX 系统上曾经有一个程序 tsort(和一个配套程序 lorder),它们一起提供所需的服务,以便可以创建一个包含目标文件的静态 (.a) 库,其顺序如下:不需要重新扫描存档。 ranlib 程序,以及最终的 arld 承担了管理单个库的重新扫描的职责,从而使 lorder 变得特别多余。但是tsort 有更一般的用途;它在某些系统上可用(例如 MacOS X;RHEL 5 Linux 也是如此)。

    因此,使用来自 GCC 的依赖跟踪加上tsort,您应该能够检查模块之间是否存在循环。但这必须小心处理。

    可能有一些 IDE 或其他工具集可以自动处理这些内容。但通常情况下,只要仔细记录需求和模块间依赖关系,程序员就可以有足够的纪律性来避免问题。

    【讨论】:

    • Jonathan,实施您建议的解决方案的首选工具是什么? Make、CMake、Rake 还是其他的?
    • 我对更现代的系统没有强烈的看法;我已经看到 CMake 受到称赞,但您可能可以使用其中的任何一个。这可能部分取决于您最熟悉的语言。例如,如果您使用 Ruby,那么 Rake 就很有意义。我仍然倾向于使用普通的旧 Make,但我已经这样做了......相当长的时间......所以我觉得这样做很舒服。这是一种最低公分母的解决方案,但存在可移植性问题。我有时会使用 Autoconf 来处理可移植性问题,但这有其自身的复杂性(并且会显着增加小型项目的规模)。
    • 公平地说,我一直在使用 Rake,但我不能说我特别喜欢它。我基本上不想在构建系统上花费太多时间,因为我可以在一天结束时支付账单的实际项目上工作。是否有任何现成的解决方案?
    • 我收回我之前的评论,我一直在为 C/C++ 项目开发一个灵活的构建框架(使用 Rake),我很快就会开源。它实现了上述所有功能以及更多功能。
    【解决方案2】:

    对于一般解决方案,我完全建议使用 Jonathan Leffler 的解决方案。但是,如果您绝对需要自动化测试您的模块是否独立和独立,您可以尝试 Debian 的构建系统。

    将每个模块打包到一个 Debian 包中(当它已经自动配置时会很快完成),正确声明 Build-Depends 并在 pbuilder 环境中构建包。这确保只有每个模块的公共头文件可用(因为只有那些在由 pbuilder 安装以构建其他包的 .deb 包中)并且有很好的工具来查看 Debian 包树并确保它们是循环的-免费。

    但是,这可能是过度杀戮。只是为了完整起见,您肯定需要一个自动化的解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-22
      • 2010-10-08
      • 1970-01-01
      • 2010-09-12
      • 1970-01-01
      相关资源
      最近更新 更多