管理软件项目集合构建

对软件项目的构建,也就是build过程,就是对一堆源代码进行编译,生成最终可执行程序或库。Java和C++不一样,不是编译型语言,是解释型语言,但都需要一个build过程。
下面以C/C++语言为例来对代码的条件编译进行分析。
第一种情况,单个项目的构建,源文件结构如下:
main.c
moduleA.c
moduleB.c
......
......
这是单个项目,包含全部的源码进行编译,每个源文件会生成obj文件,再将这些obj文件进行链接,生成最终的可执行文件。这种方式就是所有的obj文件的内容整合到一起,生成二进制可执行文件或库文件。所以如果object文件中,在同样作用域中,有重名的符号,就会编译错误。C和C++的符号系统是不同的,比如C++的函数符号的签名里使用了函数参数,而C语言就没有。C++可以有不同参数列表的同名函数,而C就不可以有同名函数,即使参数列表不同。C/C++的函数签名和函数返回类型无关。
针对这种情况,只是最简单的软件项目,而从产品角度讲,产品的功能是由软件和硬件共同完成的。比如通用型的PC上运行的软件,或者各种嵌入式电子设备上跑的软件。通用型的PC,硬件平台都有一套规范和标准,比如键盘、显示器、网口、USB接口等,即使硬件上有差异,比如CPU架构不同等,也会在操作系统上进行隔离,所以同样的软件,行为是一样的。这是对庞大的PC市场的一种适配性和兼容性的管理。而对嵌入式设备来说,因为硬件配置可能各不相同,所以同样的软件,体现在产品上,行为就会不同,比如软件控制GPIO输出电平信号,有的硬件配置是使用的LED灯做外设终端,有的使用蜂鸣器做外设终端。
或者这样理解,软件是基于一定的输入,进行运算等处理,然后将结果输出。而输入输出的接口的功能和模式,是依赖于硬件提供的,并且软件的运行也是基于硬件平台。软件基于硬件而设计和实现,硬件需要适配软件的接口模式和兼容运行平台。
第二种情况,软件项目集合的构建。这种情况,对应的一般是多个产品,比如嵌入式电子产品中的多个型号或一系列的产品。这些产品共用一套代码库,按软件架构、功能等,将代码库分成各个组件,不同产品会复用这些组件。不同项目会根据不同配置来进行构建。编译出来的软件,会适配相应的的硬件接口,包括主芯片架构、相关外设等。整个产品的行为则由相应的硬件和软件共同决定。
相比上面第一种所有源文件参与编译和使用固定的构建过程,第二种情况下,会根据项目或产品不同,执行不同的构建过程。比如使用Keil或IAR来创建工程时,不同的产品会使用不同的工程文件,还会按照功能区别,选择添加不同组件,以及组件内的的源文件。一些源文件可以是此项目特有的,一些源文件是公用的。通过管理变和不变,使得各个项目的代码都在一个代码仓库中,方便管理和复用。如果使用Makefile的构建方式,原理也是类似的,Makefile就相当于工程文件。通过传入变量给Makefile或CMake,来改变build脚本的行为。
每套单独的硬件是固定的,而软件是灵活的。软件的许多源文件,可以按组件分类,再按照构建需要,选择组件,并进行相应的设置,编译最终的可运行软件。这种构建时的要求就比较高,复杂度也高一些。比如要使用Make、CMake等工具,来对构建过程进行管理。
构建完成的目标软件,可以不止适配一套硬件,也可以适配多套硬件,比如主芯片相同、外设功能不同的相似硬件。这样构建出的软件可以认为是多套硬件对应的单个软件所组合起来的超集。比如Linux里使用的Device Tree技术,就是将硬件信息通过bootloader传给kernel,Kernel会加载不同的硬件驱动,并且在Linux运行的APP也可以根据不同的硬件信息,提供不同的功能。这是软件的增量式构建,一套软件多种用途。而很多时候我们也需要减法操作,在融合了多个项目的代码仓库中,为某个产品编出一个不多不少的恰到好处的目标软件,就如整篇文章所讲述的情况。
第三种情况,基于第二种情况,引入了库。在构建相应项目时,将每个模块编译成静态库,其他源码使用头文件来引用库的功能。做成静态库,库里面没有使用到的object文件就不会被链接进来。
项目结构如下:
main.c
libA.a (moduleA.c)
libB.a ( moduleB.c)
......
......
将编译出的obj文件打包,就会生成静态库。命令是:
$ gcc -c -o out.o out.c 
$ gcc -c a.c b.c c.c
$ ar rcs libout.a out.o a.o b.o c.o
如果在链接过程中使用了静态库的话,和链接object文件不同,不是所有内容最后都会被链接到最终可执行文件中,没有使用到的代码,比如函数或变量,是不会包含到最终的可执行文件中的。
使用了静态库,在编译期来决定某个源文件是否可能被使用。以C++代码为例,如果使用了某个子类,那父类的代码也需要,就会编译进来。
这里会出现一个问题,将某个组件加入到可执行软件中,是需要调用组件的相关函数才行。那不需要使用的组件,就不要调用。
第四种情况,理想情况下,各个组件是按需参与构建,不要的组件就不会包含到最终软件中。但由于软件功能的复杂性,各个组件之间会存在各种耦合。比如,互相之间存在函数调用。在实际运行中,某组件并不会使用,但在编译期从代码分析上却体现不出来。这就导致不被使用的组件也会参与编译,并被链接进最终的可执行软件。
这样就需要从代码层面来解决组件之间的耦合问题,尽量在软件设计上避免或减少组件间的互相依赖。比如通过引入一个中心消息组件,来减少组件之间的直接函数调用,而改为收发消息。代价就是可能会降低软件性能。如果每个组件是独立进程,就要使用进程间通信来处理组件间通信,处理耦合反而容易。如果每个组件是属于不同线程,或直接使用轮循方式执行任务,处于同一内存空间,则组件之间很容易直接调用,执行和开发效率很高,但增加了软件的熵。有一种方法,可以给每个组件的窗口模块(源文件)或窗口类加一个空实现。如果不要这个组件,那就选择这个空实现的模块文件或类文件参与构建。如果要使用这个组件,在构建时就把正常的窗口类包含进来参与编译。
还有一种方法, 使用预处理宏作为编译开关,将组件中的耦合代码进行隔离。正常情况下,应该有一个关键的调用接口层,比如上面的窗口类,将这些代码注释掉就应该可以了。这个编译开关,是在编译时的命令行参数中加入的宏定义。这种要改代码,引入了额外的工作量。需要评估。
如果是需要一个恰如其分的不引入多余源码的目标软件,在决定引入组件的主函数或窗口类的关键代码处,就应该注意。不使用这个组件的话,就不要引入。否则,就可能把这个组件的大部分代码包含进来了。你引入了组件主函数或窗口类,但其实后面又没用到,就亏了。这部分代码,可以每个产品或项目使用不同的源码文件。或者代码里,使用项目或产品的编译开关来控制组件的引入与否。
能在编译期,通过构建脚本来管理不同项目参与编译的源文件是更好的,一个地方把这个问题都解决。而不要在代码层面引入编译开关和修修改改,这会添加不必要的信息,也让阅读代码的人产生困惑。
实际操作举例
1,我们使用cmake来构建项目,在构建时传给cmake一个项目参数,比如machine name。
2,在cmake中,使用optiotn函数设定一些选项,用于描述machine的功能。后面将这使用这些选项来选择参与构建的组件、选择组件内参与构建的源文件,以及定义源文件内使用的编译开关。
3,在cmake中,根据machine name来设置option项。
4,在cmake中,通过add_definitions命令,通过命令行参数在编译源文件时加入编译开关。
构建命令:
$ mkdir build
$ cd build
$ cmake ../ -DMACHINE=APPLE_DEVICE
$ make 
CMakeLists.txt
option (FEATURE_WIFI "Wifi" OFF)
if ("${MACHINE}" STREQUAL "APPLE_DEVICE")
    set(FEATURE_WIFI ON)
endif()
if(FEATURE_WIFI)
add_library(wifi
    src/wifi_main.c
    src/wifi_driver.c
    src/wifi_msg.c
    )
endif()
if(FEATURE_WIFI)
    add_definitions(-DENABLE_WIFI=1)
endif()
代码里:
int communication_wifi(void)
{
#if ENABLE_WIFI
   // Call Wifi function
#endif
  return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/759581.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微软聘请了谷歌DeepMind的联合创始人

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

MFC 自定义分发消息方法

重点: 1.创建一个专门自定义消息的头文件 constValue.h #define WM_MY_CUSTOM_MESSAGE (WM_USER 101) // 自定义消息ID 2.在你需要发送和接收该消息的类中,首先注册这个自定义消息。一般在窗口类(如CWnd派生类)的OnInitDialog…

​HTTP与HTTPS:网络通信的安全卫士

✨✨谢谢大家捧场,祝屏幕前的小伙伴们每天都有好运相伴左右,一定要天天开心哦!✨✨ 🎈🎈作者主页: 喔的嘛呀🎈🎈 ✨✨ 帅哥美女们,我们共同加油!一起进步&am…

【高并发服务器 01】—— 基础知识回顾

接下来四周时间,我将会做一个高并发服务器相关的项目。 前置知识:操作系统系统编程、网络编程、基础的数据结构、C语言。 开发环境:VMware虚拟机:Ubuntu 20.04.6 LTS、vscode 今天先回顾一些基础知识。 1.文件与IO 标准IO&#…

HTML世界之标签Ⅴ

目录 一、meter 标签 二、nav 标签 三、noscript 标签 四、object 标签 五、ol 标签 六、optgroup 标签 七、option 标签 八、output 标签 九、param 标签 十、pre 标签 十一、picture 标签 一、meter 标签 <meter> 标签定义度量衡。仅用于已知最大和最小值的…

Windows下hydra(海德拉/九头蛇)暴力猜解RDP的简单渗透实践

attscker machine&#xff1a;windows10 靶机&#xff1a;windoes server 2003 环境&#xff1a;网络可达 && mstsc开启 hydra字典&#xff1a; 123456 123admin admin123 123Com&#xff08;正确密码&#xff09; 进入hydra目录&#xff0c;字典与hydar.exe同一目录…

MySQL分组查询与子查询 + MySQL表的联结操作

目录 1 MySQL分组查询与子查询 1.1 数据分组查询 1.2 过滤分组 1.3 分组结果排序 1.4 select语句中子句的执行顺序 1.5 子查询 2 MySQL表的联结操作 2.1 关系表 2.2 表联结 2.3 笛卡尔积 2.4 内部联结 2.5 外联结 2.6 自联结 2.7 组合查询 1 MySQL分组查询与子查询…

Python 解析json文件 使用Plotly绘制地理散点图

目录 0、任务说明 1、解析json文件 2、使用Plotly绘制地理散点图 2.1 函数scatter_geo介绍 2.2 官方示例 3、根据json文件数据&#xff0c;准备绘制地理散点图的‘数据结构’ 4、完整代码及运行效果 0、任务说明 json文件中存放了关于地震的地理信息。 使用plotly模块…

Java柠檬班Java全栈自动化课程

Java柠檬班Java全栈自动化课程旨在教授学员Java编程技能与全栈开发知识&#xff0c;包括自动化测试、前端开发和后端开发。学员将学习如何构建完整的应用程序&#xff0c;并掌握自动化测试框架&#xff0c;为职业发展打下坚实基础。 课程大小&#xff1a;14G 课程下载&#x…

关闭蓝牙hci日志

echo off ::1.获取最开始的文件夹路径 set "Start_BT%~dp0" for /f %%A in (powershell -Command "Get-Date -Format yyyy_MMdd_HHmmss") do set "timestamp%%A" echo Timestamp: %timestamp% title Set_Debug_Set_HCI_LOG%timestamp% ::打开工程…

使用verilog设计实现16位CPU及仿真

这是一个简单的16位CPU(中央处理单元)的设计实验。这个CPU包括指令存储器、数据存储器、ALU(算术逻辑单元)、寄存器文件和控制单元。 设计一个简单的16位CPU的实验通常可以分为以下几个步骤: 指令集设计:首先确定CPU支持的指令集架构,包括指令格式、寄存器组织、地址模…

流畅的 Python 第二版(GPT 重译)(四)

第二部分&#xff1a;函数作为对象 第七章&#xff1a;函数作为一等对象 我从未认为 Python 受到函数式语言的重大影响&#xff0c;无论人们说什么或想什么。我更熟悉命令式语言&#xff0c;如 C 和 Algol 68&#xff0c;尽管我将函数作为一等对象&#xff0c;但我并不认为 Py…

C++11 新特性:常量表达式 constexpr(下)

接上篇文章&#xff0c;继续说说常量表达式 constexpr 在模板编程中的使用场景。 constexpr 用于模板编程 在模板编程中&#xff0c;constexpr 的应用非常广泛&#xff0c;主要是因为它能够在编译时进行计算&#xff0c;这对于模板元编程、编译时断言、模板特化选择等场合尤为…

爬虫基础:Web网页基础

爬虫基础&#xff1a;Web网页基础 前言Web网页基础网页的组成网页的结构节点树及节点间的关系选择器 前言 用浏览器访问不同的网站时&#xff0c;呈现的页面各不相同&#xff0c;你有没有想过为何会这样呢&#xff1f;了解一下网页的组成、结构和节点等内容。了解这些内容有助于…

挖掘网络宝藏:利用Scala和Fetch库下载Facebook网页内容

介绍 在数据驱动的世界里&#xff0c;网络爬虫技术是获取和分析网络信息的重要工具。本文将探讨如何使用Scala语言和Fetch库来下载Facebook网页内容。我们还将讨论如何通过代理IP技术绕过网络限制&#xff0c;以爬虫代理服务为例。 技术分析 Scala是一种多范式编程语言&…

设计模式学习笔记 - 设计模式与范式 - 创建型:2.单例模式(中):为什么不推荐使用单例模式?又有何替代方案?

前言 尽管单例是一个很常用的实际模式&#xff0c;在实际的开发中&#xff0c;也经常使用&#xff0c;但是&#xff0c;有些人认为单例是一种反模式&#xff08;anti-pattern&#xff09;&#xff0c;并不推荐使用。所以&#xff0c;今天就针对这个说法详细地讲讲。 单例模式…

软考 系统架构设计师系列知识点之系统性能(3)

接前一篇文章&#xff1a;软考 系统架构设计师系列知识点之系统性能&#xff08;2&#xff09; 所属章节&#xff1a; 第2章. 计算机系统基础知识 第9节. 系统性能 系统性能是一个系统提供给用户的所有性能指标的集合。它既包括硬件性能&#xff08;如处理器主频、存储器容量、…

用pdf2docx将PDF转换成word文档

pdf2docx是一个Python模块&#xff0c;可以将PDF文件转换为docx格式的Word文档。 pdf2docx模块基于Python的pdfminer和python-docx库开发&#xff0c;可以在Windows、Linux和Mac系统上运行。它可以从PDF文件中提取文本和图片&#xff0c;并将其转换成可编辑的Word文档&#xf…

ZC3201 耐压40V输出12V 300mA LDO

概述 ZC3201是一款40V高精度微安级功率LDO稳压器。只有luA的功耗使其适用于大多数高压节电系 统。其最大工作电压高达40V. 其他功能包括低压差&#xff0c;1%的极高输出精度&#xff0c;限流保护和高纹波抑制比。 ZC3201采用SOT89-3&#xff0c;SOT23…

分布式游戏服务器

1、概念介绍 分布式游戏服务器是一种专门为在线游戏设计的大型系统架构。这种架构通过将游戏服务器分散部署到多台计算机&#xff08;节点&#xff09;上&#xff0c;实现了数据的分散存储和计算任务的并行处理。每个节点都负责处理一部分游戏逻辑和玩家请求&#xff0c;通过高…