为嵌入式 C/C++ 构建系统项目

发布于 2024-12-07 08:28:47 字数 618 浏览 1 评论 0原文

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

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

构建系统/工具应该 -

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

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

I am looking for a high-level build system/tool that can help organise my embedded C project into "modules" and "components". Note that these two terms are highly subjective so my definitions are given below.

  • A module is a cohesive collection of c and h files but with only one public h file that is visible to other modules.
  • A component (or a layer) on the other hand is a collection of modules (e.g. Application layer, Library layer, Driver layer, RTOS layer etc.).

The build system/tool should -

  • Prevent cyclic dependencies between components and modules (cyclic dependencies inside modules is okay)
  • prevent access to private barriers of a module. If other modules try to include a header file that is private to a module, the build system must throw an error. However, files within a private barrier must be able to include other files inside that barrier.
  • support building and executing of unit tests automatically (fast feedback loop for TDD) on host
  • support unit tests to be run on target simulator
  • support code static analysis
  • support code generation
  • support code duplication detection (enforce DRY principle)
  • support code beautification
  • support generation of unit test code coverage metrics
  • support generation of code quality metrics
  • be platform-independent

I could write my own build tool and spend a lot of time on it. However, that is not my area of expertise and I'd rather not re-invent the wheel if someone has already created such a tool.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

脱离于你 2024-12-14 08:28:47

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

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

编译规则只是规定,除了公共目录中的标头之外,任何模块都不能包含来自其他模块的标头。这实现了任何模块都不能包含来自另一个模块的标头 - 除了公共标头(从而强制执行私有屏障)。

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

考虑一对模块(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< /code> 特别多余。但是tsort有更一般的用途;它在某些系统上可用(例如 MacOS X;RHEL 5 Linux)。

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

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

The conventional way of achieving that would be to place the source code for each module into a separate directory. Each directory can contain all the source and header files for the module.

The public header for each module can be placed into a separate, common directory of headers. I'd probably use a symlink from the common directory to the relevant module directory for each header.

The compilation rules simply state that no module may include headers from other modules except for the headers in the common directory. This achieves the result that no module can include headers from another module - except for the public header (thus enforcing the private barriers).

Preventing cyclic dependencies automatically is not trivial. The problem is that you can only establish that there is a cyclic dependency by looking at several source files at a time, and the compiler only looks at one at a time.

Consider a pair of modules, ModuleA and ModuleB, and a program, Program1, that uses both modules.

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

When compiling Program1.c, it is perfectly legitimate for it to include both ModuleA.h and ModuleB.h if it makes use of the services of both modules. So, ModuleA.h cannot complain if ModuleB.h is included in the same translation unit (TU), and neither can ModuleB.h complain if ModuleA.h is included in the same TU.

Let us suppose it is legitimate for ModuleA to use the facilities of ModuleB. Therefore, when compiling ModuleA1.c or ModuleA2.c, there can be no issue with having both ModuleA.h and ModuleB.h included.

However, to prevent cyclic dependencies, you must be able to prohibit the code in ModuleB1.c and ModuleB2.c from using ModuleA.h.

As far as I can see, the only way to do this is some technique that requires a private header for ModuleB that says "ModuleA is already included" even though it isn't, and this is included before ModuleA.h is ever included.

The skeleton of ModuleA.h will be the standard format (and ModuleB.h will be similar):

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

Now, if the code in ModuleB1.c contains:

#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...

This is far from automatic.

You could do an analysis of the included files, and require that there is a loop-less topological sort of the dependencies. There used to be a program tsort on UNIX systems (and a companion program, lorder) which together provided the services needed so that a static (.a) library could be created that contained the object files in an order that did not require rescanning of the archive. The ranlib program, and eventually ar and ld took on the duties of managing the rescanning of a single library, thus making lorder in particular redundant. But tsort has more general uses; it is available on some systems (MacOS X, for instance; RHEL 5 Linux too).

So, using the dependency tracking from GCC plus tsort, you should be able to check whether there are cycles between modules. But that would have to be handled with some care.

There may be some IDE or other toolset that handles this stuff automatically. But normally programmers can be disciplined enough to avoid problems - as long as the requirements and inter-module dependencies are carefully documented.

可可 2024-12-14 08:28:47

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

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

然而,这可能有点过头了。只是为了完整性而陈述,并且您确实需要一个自动化解决方案。

For a general solution, I'd fully recommend to go with Jonathan Leffler's solution. However, if you absolutely need an automated test whether your modules are self-contained and isolated, you might give Debian's build system a try.

Package each module into a Debian package (which is done very quickly when it's already autoconf'd), declare Build-Depends properly and build the packages inside a pbuilder environment. This ensures that only public headers of each module are available (because only those are in the .deb packages that are installed by pbuilder to build the other packages) and there are excellent tools to look at Debian package trees and make sure they are cycle-free.

However, this is probably over-kill. Just stating it for completeness and the case you definitely need an automated solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文