我正在寻找一个高级构建系统/工具,可以帮助我将嵌入式C项目组织成“模块”和“组件”。请注意,这两个术语非常主观,所以我的定义如下。
构建系统/工具应该 -
我可以编写自己的构建工具并花费大量时间。然而,这不是我的专业领域,如果有人已经创造了这样的工具,我宁愿不重新发明轮子。
答案 0 :(得分: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加上tsort
的依赖关系跟踪,您应该能够检查模块之间是否存在循环。但这必须要小心处理。
可能会有一些IDE或其他工具集自动处理这些内容。但通常程序员可以足够规范以避免出现问题 - 只要仔细记录需求和模块间依赖关系。
答案 1 :(得分:2)
对于一般解决方案,我完全建议采用Jonathan Leffler的解决方案。但是,如果您绝对需要自动化测试,无论您的模块是独立的还是独立的,您都可以尝试使用Debian的构建系统。
将每个模块打包成Debian软件包(当它已经自动配置时非常快速地完成),正确地声明Build-Depends并在pbuilder环境中构建软件包。这确保了只有每个模块的公共头文件可用(因为只有那些是由pbuilder安装的.deb软件包来构建其他软件包),并且有很好的工具可以查看Debian软件包树并确保它们是循环的 - 免费。
然而,这可能是过度杀戮。只是说明它的完整性和案例,你肯定需要一个自动化解决方案。