草庐IT

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

coder 2024-02-03 原文

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

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

构建系统/工具应该——

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

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

最佳答案

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

每个模块的公共(public)头文件可以放在一个单独的、公共(public)的头文件目录中。我可能会为每个 header 使用从公共(public)目录到相关模块目录的符号链接(symbolic link)。

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

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

考虑一对模块 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 的私有(private) header ,上面写着“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...

这远非自动。

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

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

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

关于c++ - 为嵌入式 C/C++ 项目构建系统,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7606604/

有关c++ - 为嵌入式 C/C++ 项目构建系统的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  3. ruby-on-rails - 项目升级后 Pow 不会更改 ruby​​ 版本 - 2

    我在我的Rails项目中使用Pow和powifygem。现在我尝试升级我的ruby​​版本(从1.9.3到2.0.0,我使用RVM)当我切换ruby​​版本、安装所有gem依赖项时,我通过运行railss并访问localhost:3000确保该应用程序正常运行以前,我通过使用pow访问http://my_app.dev来浏览我的应用程序。升级后,由于错误Bundler::RubyVersionMismatch:YourRubyversionis1.9.3,butyourGemfilespecified2.0.0,此url不起作用我尝试过的:重新创建pow应用程序重启pow服务器更新战俘

  4. ruby-on-rails - 新 Rails 项目 : 'bundle install' can't install rails in gemfile - 2

    我已经像这样安装了一个新的Rails项目:$railsnewsite它执行并到达:bundleinstall但是当它似乎尝试安装依赖项时我得到了这个错误Gem::Ext::BuildError:ERROR:Failedtobuildgemnativeextension./System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/bin/rubyextconf.rbcheckingforlibkern/OSAtomic.h...yescreatingMakefilemake"DESTDIR="cleanmake"DESTDIR="

  5. Ruby 从大范围中获取第 n 个项目 - 2

    假设我有这个范围:("aaaaa".."zzzzz")如何在不事先/每次生成整个项目的情况下从范围中获取第N个项目? 最佳答案 一种快速简便的方法:("aaaaa".."zzzzz").first(42).last#==>"aaabp"如果出于某种原因你不得不一遍又一遍地这样做,或者如果你需要避免为前N个元素构建中间数组,你可以这样写:moduleEnumerabledefskip(n)returnto_enum:skip,nunlessblock_given?each_with_indexdo|item,index|yieldit

  6. ruby - 在 Ruby 中构建长字符串的简洁方法 - 2

    在编写Ruby(客户端脚本)时,我看到了三种构建更长字符串的方法,包括行尾,所有这些对我来说“闻起来”有点难看。有没有更干净、更好的方法?变量递增。ifrender_quote?quote="NowthatthereistheTec-9,acrappyspraygunfromSouthMiami."quote+="ThisgunisadvertisedasthemostpopularguninAmericancrime.Doyoubelievethatshit?"quote+="Itactuallysaysthatinthelittlebookthatcomeswithit:themo

  7. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  8. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  9. ruby - 使用 `+=` 和 `send` 方法 - 2

    如何将send与+=一起使用?a=20;a.send"+=",10undefinedmethod`+='for20:Fixnuma=20;a+=10=>30 最佳答案 恐怕你不能。+=不是方法,而是语法糖。参见http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_expressions.html它说Incommonwithmanyotherlanguages,Rubyhasasyntacticshortcut:a=a+2maybewrittenasa+=2.你能做的最好的事情是:

  10. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

随机推荐