同步操作将从 极简美/utils 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
组件的目标是代码复用!
lib
库(*.a
或者*.so
)对外发布的通用模块。使用cmake
组件构建模板的目标,是为了:
本仓库是基于cmake
构建模块代码的模板,目录与核心文件结构如下:
cmake_template
├── cmake
│ ├── cmake_uninstall.cmake.in
│ ├── commit.cmake
│ ├── config.h.in
│ ├── include.cmake
│ └── option.cmake
├── conf
├── CMakeLists.txt
├── docs
│ └── cmake_template.md
├── example
│ ├── CMakeLists.txt
│ └── example.c
├── LICENSE
├── Makefile
├── README.md
├── source
│ ├── CMakeLists.txt
│ └── source.c
└── unittest
└── CMakeLists.txt
6 directories, 15 files
cmake目录
: 用于存放构建相关文件;
config.h.in文件
: 组件配置文件, 包含版本和宏定义,可用于平台的头文件、函数、库检查,实现跨平台支持;cmake
利用此配置文件生成源码使用的*_config.h
文件;option.cmake文件
: 编译选项,包括版本配置、编译参数、链接参数、宏定义、平台检查选项等,最后的configure_file()
命令会根据编译平台把config.h.in
生成本模块的*_config.h
文件;include.cmake文件
: 构建依赖的头文件路径,根据实际情况配置即可,PROJECT_SOURCE_DIR
指当前组件的根目录;commit.cmake文件
: 提取git
仓库当前构建状态的分支名和commit
编号,分别保存在GIT_BRANCH_NAME
和GIT_COMMIT_HASH
变量中,不要修改此文件内容;cmake_uninstall.cmake.in文件
: 用于提供模块编译后的头文件、库文件和工具的安装和卸载的方法,不需要对其进行修改。conf目录
: 用于存放模块配置文件,比如json
、ini
、yaml
、xml
文件等;docs目录
: 用于存放模块技术总结、设计文档、使用说明文档等,使用markdown
编码,便于统一发布html
或pdf
文档;
cmake_template.md文件
: 介绍组件模板库的使用方法,通用的代码组件化指导说明;example目录
: 用于存放本模块对外提供的示例程序,便于模块使用者快速上手,模块发布时代源码发布;
example.c文件
: 示例程序,描述如何使用构建模板生成的*_config.h
文件;CMakeLists.txt文件
: 示例构建模板,描述如何构建和发布示例程序;source目录
: 用于存放本模块的所有源码均;
source.c文件
: 源码空文件,适配构建模板的使用;CMakeLists.txt文件
: 构建库文件模板,只需要添加要构建的源码即可;unittest目录
: 用于存放本模块单元测试的所有源码;
CMakeLists.txt文件
: 单元测试构建模板,待引入单元测试框架后完善;Makefile文件
: 对cmake
命令的简单封装,简化单一模块的构建验证;CMakeLists.txt文件
: 模块构建总入口,非必要不要修改此文件;README.md文件
: 使用markdown
语法编辑的模块说明文档。LICENSE文件
: MIT
开源协议,保留版权。make
命令,即可完成Linux
平台下组件的编译构建和发布,中间构建文件存放在build
目录,对外释放的文件存放在out
目录:[prifix]$ tree -L 1
out
├── bin # 可执行程序
├── conf # 配置文件
├── example # 示例可执行程序(含源码)
├── include # 对外发布的头文件
├── lib # 对外发布的库文件
├── lib64 # 对外发布的64库文件
└── tools # 对外提供的工具程序
若需要对外进行发布,可以编写打包脚本,根据需要打包要用到的文件即可。如此,将构建与打包分离,还可以实现一次构建多次打包/发布,更利于代码的管理与后期维护。
cmake
命令完成构建,详细步骤如下所示(上面Makefile
也只是对下面命令的封装):mkdir build # 创建构建目录
cd build
cmake ../ -DCMAKE_INSTALL_PREFIX=$(pwd)/../out # 指定安装目录
make VERBOSE=1 # VERBOSE会让编译过程可见
make install # 执行安装
make uninstall # 执行卸载
其中CMAKE_INSTALL_PREFIX
指定了安装目录,还可通过CMAKE_TOOLCHAIN_FILE
指定交叉编译工具链,实现组件的移植适配。
在顶层CMakeLists.txt
文件中,默认使用的当前目录名作为组件的项目名,构建组件库时,库的名称也会使用项目名命名。若组件目录名与项目名不一致时,可修改${component_name}
为实际的项目名称:
#Component name, default the same as directory name
get_filename_component(component_dir ${CMAKE_PARENT_LIST_FILE} DIRECTORY)
get_filename_component(component_name ${component_dir} NAME)
project(${component_name})
默认情况下,项目名称与组件名称是一样的,都是当前组件的目录名,若组件名与项目名不同(比如libcurl
组件,目录名和项目名是libcurl
,但其组件名应为curl
),则修改顶层CMakeLists.txt
文件中如下两行即可:
# set(COMPONENT_NAME template)
set(COMPONENT_NAME ${PROJECT_NAME})
在cmake/option.cmake
文件前面几行,就是设置组件的版本信息的,可根据组件版本进行配置:
COMPONENT_VERSION_MAJOR
: 主版本号;COMPONENT_VERSION_MINOR
: 次版本号;COMPONENT_VERSION_PATCH
: 修订版本号;COMPONENT_VERSION_DRESS
: 调试版本号。若模块比较复杂,头文件都会出现多层目录,编译头文件依赖的路径统一在cmake/include.cmake
中添加即可:
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/source)
组件源码存放在source
目录下,添加目录下所有源码的方法:
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} ${COMPONENT_NAME}_SRCS)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR}/your_source_dir ${COMPONENT_NAME}_SRCS)
其中BUILD_STATIC_LIBS
和BUILD_SHARED_LIBS
选项用于控制是构建动态库还是静态库。
组件一般对外发布库文件、头文件,以及示例程序文件等,可能还包含一些工具文件、配置文件等。要发布一个目标或者文件的方法如下:
# 库文件统一安装到lib目录下
install(TARGETS ${COMPONENT_NAME} DESTINATION lib)
install(TARGETS ${COMPONENT_NAME}_static DESTINATION lib)
# 头文件统一安装到include的${PROJECT_NAME}目录下
install(FILES ${head_file.h} DESTINATION include/${PROJECT_NAME})
install(DIRECTORY ${dir} DESTINATION include/${PROJECT_NAME})
# example统一安装到example的${PROJECT_NAME}目录下
install(TARGETS example DESTINATION example/${PROJECT_NAME})
install(FILES example.c DESTINATION example/${PROJECT_NAME})
一个完整的模块,需要有对应的示例程序,便于使用者快速上手和使用。示例程序统一放在example
目录中,若模块包含很多子模块,也可以在example
目录下再创建子目录。示例程序的构建可参考如下方法:
add_executable(xxx_example xxx_example.c)
target_link_libraries(xxx_example ${COMPONENT_LIBRARY})
install(TARGETS xxx_example DESTINATION example/${PROJECT_NAME})
install(FILES xxx_example.c DESTINATION example/${PROJECT_NAME})
其中COMPONENT_LIBRARY
会根据构建的是动态库还是静态库,连接正确的库文件。
一个完整的模块,同样需要有对应的单元测试程序,以保障模块的高质量的交付。建议所有的单元测试程序全部放在unittest
目录中,根据使用的单元测试框架的不同,使用对应的构建方法自动化的进行构建。
此部分介绍核心聚焦在cmake/option.cmake
和cmake/config.h.in
两个文件的配置上面。
比如添加OPTION_ENABLE
编译选项,对应在cmake/option.cmake
中增加如下一行:
option(OPTION_ENABLE "Enable option" ON)
并在cmake/config.h.in
中增加如下一行:
#cmakedefine OPTION_ENABLE
执行cmake
命令时,就会在组件的config.h
文件中生成如下宏定义:
#define OPTION_ENABLE
在程序源码中,就可以包含组件配置文件并使用宏编译选项了:
#include "**_config.h"
#ifdef OPTION_ENABLE
......
#else
......
#endif
添加编译宏与编译选项的方法类似,直接在cmake/option.cmake
中使用add_compile_definitions()
命令添加宏定义:
add_compile_definitions(OPTION_INT=1234)
add_compile_definitions(OPTION_STR="Hello world!")
源码中即可直接使用如上宏定义了:
#include "**_config.h"
......
printf("option int: %d\n", OPTION_INT);
printf("option str: %s\n", OPTION_STR);
......
为了让程序在各种场景下都能正确的编译,还需要为宏定义添加默认定义,对应在cmake/config.h.in
文件中增加:
#ifndef OPTION_INT
#define OPTION_INT 2345
#endif
#ifndef OPTION_STR
#define OPTION_STR "Hey you!"
#endif
有时候,我们编写的功能依赖于具体平台相关的头文件和函数,当我们在做移植时,首先需要检查目标平台是否具有相关的能力和特性。通过不同平台能力与特性的检测,也有利于编写构建时跨平台的通用组件代码。下面是cmake
提供的标准头文件、函数和库检测的方法,并生成对应的编译宏,以供程序源码中使用:
# C头文件检测方法
include(CheckIncludeFiles)
CHECK_INCLUDE_FILES(stdint.h HAVE_STDINT_H)
# C++头文件检测方法
include(CheckIncludeFileCXX)
CHECK_INCLUDE_FILE_CXX(queue HAVE_QUEUE_H)
# 函数检测方法
include(CheckFunctionExists)
CHECK_FUNCTION_EXISTS(poll HAVE_POLL)
# 符号检测方法
include(CheckSymbolExists)
CHECK_SYMBOL_EXISTS(alloca "alloca.h" HAVE_ALLOCA)
# 库检测方法
find_library(HAVE_LIBRT rt)
include(CheckLibraryExists)
CHECK_LIBRARY_EXISTS(rt timer_gettime "" HAVE_LIBRT)
MCU
如何存储版本号<主版本号>.<次版本号>.<修订版本号>.<调试版本号>-<软件构建号>
推荐MCU
中使用uint64_t
进行保存,主版本号
、次版本号
、修订版本号
、调试版本号
分别对应uint8_t
类型,取值范围为0~255;构建号
对应uint32_t
类型,取值为Git
代码提交的7位简短Hash
值(最高位补零)。
这样不仅解决了MCU
存储空间不足的问题,又可以通过版本同步实现线上软件版本,直接定位到对应的软件状态,还可以直接判断该软件是正式版本、测试版本还是其他任何版本。
调试版本号
有时也叫修饰版本号
,一般有两种用法。
调试版本号根据自动化构建增长,规则是:对外正式发布时,调试版本号始终为0;每次自动构建时,调试版本号占用两个位,奇数代表有代码修改的正式构建,偶数代表没有代码修改的测试构建。这样,每次构建出来对应奇偶两个版本,就可以用于自动化的升级验证。
备注:针对定制化的软件或特殊的软件版本,需要从软件/固件名中进行区分,这样更加直观也更容易维护。
修饰版本号采用十六进制标识,默认情况下为0x00,不同取值代表不同的版本状态:
备注:使用修饰版本号,可以把定制化的软件或特殊版本也用版本进行区分,但并不直观,且软件升级不容易把控与实现。
实际项目应用时,也可以结合公司的实际场景,将这两种方法做一定的融合。
arch[-vendor][-os][-(gnu)eabi]
arch
: 用于哪个体系架构,如ARM
、MIPS
等。vendor
: 工具链提供商,也有以开发板命名的,或者直接是none
或cross
的。os
: 目标操作系统,见过的有Linux
、uclinux
、bare
(无OS)。eabi
: 嵌入式应用二进制接口规范(Embedded Application Binary Interface),如gnu
,gnueabi
等。其中gnu
等价于glibc+oabi
;gnueabi
等价于glibc+eabi
。比如下面的交叉编译工具:
arm-none-eabi-gcc
: 表示编译ARM
架构的裸机系统(包括ARM Linux
的boot
、kernel
,不适用编译Linux
应用),一般适合ARM7
、Cortex-M
和Cortex-R
内核的芯片使用,一般使用的是newlib
专用于嵌入式系统的C库;arm-none-linux-gnueabi-gcc
: 表示基于ARM
架构的Linux
系统,可用于编译ARM
架构的u-boot
、Kernel
和应用等,使用Glibc库。此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。