8

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

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

构建系统/工具应该 -

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

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

4

2 回答 2

4

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

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

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

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

考虑一对模块 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程序最终ar承担ld了管理单个库的重新扫描的职责,因此lorder特别冗余。但tsort有更一般的用途;它在某些系统上可用(例如 MacOS X;RHEL 5 Linux 也是)。

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

可能有一些 IDE 或其他工具集可以自动处理这些东西。但通常情况下,只要仔细记录需求和模块间的依赖关系,程序员就可以受到足够的训练以避免出现问题。

于 2011-09-30T07:08:33.030 回答
2

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

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

但是,这可能是过度杀戮。只是为了完整性而说明它,并且您肯定需要一个自动化的解决方案。

于 2011-09-30T07:39:37.933 回答