如果您已经开发了一段时间的嵌入式软件,您就会知道makefile是我们这个行业中最常用的工具之一。许多供应商提供的IDE(集成开发环境)都会为您生成makefile,因此您不必为要自己编写它们而担心。问题是,如果您想利用TDD(测试驱动开发)、DevOps和CI/CD(持续集成/交付)等现代开发实践,就需要编写自己的makefile,或使用可以帮助简化跨多个平台构建的工具。
CMake工具在软件行业中已证明了效率和多功能性,它作为一个开源、跨平台的工具系列,专为构建、测试和打包软件而设计。它使您能够使用简单的平台和独立于编译器的配置文件来控制软件编译过程。其生成的本地makefile可以无缝地用于您所选择的编译器环境。
让我们来看看在嵌入式软件中使用CMake的几个技巧。
每当我与团队讨论设置他们的构建环境时,我都会提到他们应该支持五种类型的构建:
每种构建类型对构建系统的配置要求略有不同。交叉编译对于目标构建必不可少。针对主机进行构建并替换低级实现仿真必不可少。测试则需要引入测试工具进行。
要管理这些复杂问题,可以利用CMake的工具链文件。例如,您可能有一个用于配置仿真环境的工具链host.cmake文件。该文件可以像下面这样简单:
# Use the system's default C and C++ compilers
set(CMAKE_SYSTEM_NAME Darwin) # Adjust this as necessary (e.g., Windows, Darwin for macOS, Linux)
# Optionally specify the compiler (GCC/Clang) if the default selection needs to be overridden
set(CMAKE_C_COMPILER gcc)
set(CMAKE_CXX_COMPILER g++)
# Add any simulation-specific preprocessor definitions
add_compile_definitions(SIMULATION)
# Specify any host-specific compiler flags if necessary
set(COMMON_FLAGS "-O2 -g")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COMMON_FLAGS}" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMMON_FLAGS}" CACHE STRING "" FORCE)
# Set the C standard and C++ standard that your project requires
set(CMAKE_C_STANDARD 17)
set(CMAKE_CXX_STANDARD 17)
您还可以为每个其他构建创建单独的工具链文件。不过,我发现一般只需两个文件即可:一个用于主机构建,另一个用于目标构建。
成功开发嵌入式软件的关键在于尽可能实现开发周期的自动化,这包括编写脚本来简化软件构建。CMake命令可能很复杂。例如,请看这条用于编译仿真的命令:
cmake -DCMAKE_TOOLCHAIN_FILE=config/toolchain-host.cmake -G Ninja -B build/release -S. -DCMAKE_BUILD_TYPE=Simulation
是的,我可以使用向上箭头来执行我的最后一个命令,但没人真的想记住它!
相反,编写一个可以执行所需操作的脚本。例如,我使用一个project.sh文件,其中包含我之前提到的所有构建类型,以及清理和执行其他在开发过程中有用的实用程序的操作。
在我的脚本中,我必须记住的那个长命令变成了以下内容:
cmake -DCMAKE_TOOLCHAIN_FILE=$TOOLCHAIN_FILE -G Ninja -B $BUILD_DIR -S. -DCMAKE_BUILD_TYPE=$BUILD_TYPE
我提供给project.sh的参数会选择工具链文件、构建类型和其他有用的参数,这样我就不必记住它们了。(它还会将它们设置为在CI/CD系统中使用)。
您可能已经注意到,在上一条技巧中,我使用的命令中含有Ninja。将CMake与Ninja搭配使用是加快构建速度的好方法。
Ninja是一个小型、高速的构建系统,专注于提高效率。它由Google工程师Evan Martin创建,旨在比现有构建系统更有效地处理Google复杂而庞大的构建工作。与Make等传统构建工具相比,Ninja的目标是通过最少的工作来保持文件的最新状态,从而大大提高构建速度。
根据我的经验,用Ninja替换Make可使编译速度提高四到六倍!现在,您可能会说,“谁在乎呢!”但如果您遵循静态编译时检查的最佳实践来节省运行时性能,您就需要更快地构建速度!即使不是这样,您也很可能会连续快速地编译代码。更快的构建将限制“等待时间”并提高效率和吞吐量。
将Ninja和CMake结合使用非常简单。首先,我通常将其安装在我的Docker容器中。Dockerfile代码非常简单:
RUN apt-get update -y && \\
apt-get install -y --no-install-recommends \\
cmake \\
ninja-build && \\
apt-get clean && \\
rm -rf /var/lib/apt/lists/*
(注意:还有其他方法可以强制使用特定版本的CMake和Ninja,但这些超出了本文的讨论范围。您可以谷歌搜索或询问AI!)。
使用CMake为Ninja构建的一般形式是:
cmake -G Ninja -B build
我们之前看到,如果添加工具链文件和其他定义,情况可能会有所不同。-G告诉CMake使用哪个生成器。有很多选项,例如:
一旦CMake为您生成文件,您所要做的就是运行以下命令:
ninja -C build
然后您就会发现,使用正确的工具,您的代码构建速度会有多快!
随着嵌入式软件开发领域的不断发展,采用现代工具和实践变得越来越重要。CMake拥有强大而灵活的构建系统,是开发人员简化工作流程、缩短构建时间并促进跨平台开发的不可或缺的工具。通过利用工具链文件、自定义构建脚本和Ninja等高效构建系统,您可以显著增强开发流程,从而将更多精力放在创新上,而不是错综复杂的构建管理上。
这些策略简化了管理不同构建配置的复杂性,并与TDD、DevOps和CI/CD等现代实践保持一致。在继续完善嵌入式软件开发实践时,请记住,正确的工具和技术可以将挑战转化为提高效率和生产力的机遇。
(原文刊登于EDN姊妹网站Embedded,参考链接:3 tips for using CMake with embedded software,由Ricardo Xie编译。)