自学内容网 自学内容网

创建一个CMake项目

本文将演示如何使用 CMake 管理一个中等复杂度的项目,从创建项目到编译和运行的整个过程,涵盖了从基本配置到高级特性的实际应用。

实战内容如下:

  1. 创建 CMakeLists.txt 文件:定义项目、库、可执行文件和测试。
  2. 编写源代码和测试:编写代码和测试文件。
  3. 创建构建目录:保持源代码目录整洁。
  4. 配置项目:生成构建系统文件。
  5. 编译项目:生成目标文件。
  6. 运行可执行文件:执行程序。
  7. 运行测试:验证功能正确性。
  8. 使用自定义命令和目标:执行额外操作。
  9. 跨平台和交叉编译:支持不同平台和架构。

构建一个简单的 C++ 项目
假设我们有一个项目,包含一个主程序和一个库,库中有两个不同的功能模块。

项目结构如下:

MyProject/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── lib/
│   │   ├── module1.cpp
│   │   ├── module2.cpp
│   ├── include/
│       └── mylib.h
│└── CMakeLists.txt
└── tests/
    ├── test_main.cpp
    └── CMakeLists.txt

1. 创建 CMakeLists.txt 文件

1.1 根目录 CMakeLists.txt 文件

cmake_minimum_required(VERSION 3.10)   # 指定最低 CMake 版本
project(MyProject VERSION 1.0)          # 定义项目名称和版本

# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 包含头文件路径
include_directories(${PROJECT_SOURCE_DIR}/src/include)

# 添加子目录
add_subdirectory(src)
add_subdirectory(tests)

解释

  • cmake_minimum_required(VERSION 3.10): 确保使用的 CMake 版本至少是 3.10,以支持后续使用的 CMake 特性。
  • project(MyProject VERSION 1.0): 声明项目名称为 MyProject 并指定版本号为 1.0。CMake 会创建一些与项目相关的变量,如 PROJECT_NAMEPROJECT_VERSION
  • set(CMAKE_CXX_STANDARD 11)set(CMAKE_CXX_STANDARD_REQUIRED ON): 将 C++ 标准设置为 C++11,并强制要求使用该标准。
  • include_directories(${PROJECT_SOURCE_DIR}/src/include): 将 src/include 目录添加到包含目录列表中,这样编译器可以找到相应的头文件。
  • add_subdirectory(src)add_subdirectory(tests): 指示 CMake 处理 srctests 子目录下的 CMakeLists.txt 文件。

1.2 src 目录 CMakeLists.txt 文件

# 创建库目标
add_library(MyLib STATIC
    lib/module1.cpp
    lib/module2.cpp
)

# 指定库的头文件
target_include_directories(MyLib PUBLIC ${CMAKE_SOURCE_DIR}/src/include)

# 创建可执行文件目标
add_executable(MyExecutable main.cpp)

# 链接库到可执行文件
target_link_libraries(MyExecutable PRIVATE MyLib)

解释

  • add_library(MyLib STATIC lib/module1.cpp lib/module2.cpp): 创建一个名为 MyLib 的静态库,使用 module1.cppmodule2.cpp 作为源文件。
  • target_include_directories(MyLib PUBLIC ${CMAKE_SOURCE_DIR}/src/include): 将 src/include 目录添加到 MyLib 的公共包含目录中,这意味着任何链接 MyLib 的目标都可以使用这些头文件。
  • add_executable(MyExecutable main.cpp): 创建一个名为 MyExecutable 的可执行文件,使用 main.cpp 作为源文件。
  • target_link_libraries(MyExecutable PRIVATE MyLib): 将 MyLib 库链接到 MyExecutable 可执行文件,并且该链接关系是私有的,意味着 MyExecutable 的依赖不会传递给其他依赖 MyExecutable 的目标。

1.3 tests 目录 CMakeLists.txt 文件

# 查找 GTest 包
find_package(GTest REQUIRED)
include_directories(${GTEST_INCLUDE_DIRS})

# 创建测试目标
add_executable(TestMyLib test_main.cpp)

# 链接库和 GTest 到测试目标
target_link_libraries(TestMyLib PRIVATE MyLib ${GTEST_LIBRARIES})

解释

  • find_package(GTest REQUIRED): 查找 Google Test 测试框架包,REQUIRED 表示如果找不到该包将导致 CMake 报错。
  • include_directories(${GTEST_INCLUDE_DIRS}): 将 Google Test 的包含目录添加到包含目录列表中。
  • add_executable(TestMyLib test_main.cpp): 创建一个名为 TestMyLib 的可执行测试程序,使用 test_main.cpp 作为源文件。
  • target_link_libraries(TestMyLib PRIVATE MyLib ${GTEST_LIBRARIES}): 将 MyLib 库和 Google Test 库链接到 TestMyLib 测试可执行文件,同样是私有的链接关系。

2. 编写源代码和测试

2.1 src/main.cpp 文件代码

#include <iostream>
#include "mylib.h"

int main() {
    std::cout << "Hello, CMake!" << std::endl;
    return 0;
}

解释

  • 包含了 iostream 用于标准输入输出,mylib.h 应该包含了 MyLib 库的相关声明。
  • main() 函数输出一条消息并返回 0。

2.2 src/lib/module1.cpp 文件代码

#include "mylib.h"

// Implementation of module1

解释

  • 包含 mylib.h 头文件,这是 MyLib 库的一部分,应该在此文件中实现 module1 的具体功能,但这里只是一个占位。

2.3 src/lib/module2.cpp 文件代码

#include "mylib.h"

// Implementation of module2

解释

  • 类似 module1.cpp,包含 mylib.h 并应该实现 module2 的具体功能,这里未给出具体实现。

2.4 src/include/mylib.h 文件代码

#ifndef MYLIB_H
#define MYLIB_H

// Declarations of module functions

#endif // MYLIB_H

解释

  • 这是一个典型的头文件保护宏,避免头文件被重复包含。在 #ifndef#endif 之间应该包含 MyLib 库中函数的声明,但这里没有给出具体声明。

2.5 tests/test_main.cpp 文件代码

#include <gtest/gtest.h>

// Test cases for MyLib
TEST(MyLibTest, BasicTest) {
    EXPECT_EQ(1, 1);
}

解释

  • 包含 Google Test 的头文件 gtest/gtest.h
  • 使用 TEST 宏定义了一个名为 MyLibTest 的测试套件,其中包含一个名为 BasicTest 的测试用例,该测试用例使用 EXPECT_EQ 断言来验证 1 是否等于 1,这是一个非常简单的测试,通常可以在此基础上添加更多复杂的测试逻辑。

3. 创建构建目录

mkdir build
cd build

解释

  • 创建一个名为 build 的目录,这是一个良好的实践,可以将生成的构建文件与源代码分离,使源代码目录保持整洁。
  • 进入 build 目录,后续的 CMake 操作将在此目录中进行。

4. 配置项目

cmake..

解释

  • 运行 cmake.. 命令,.. 表示 CMake 会查找上一级目录中的 CMakeLists.txt 文件,并根据该文件生成构建系统文件(如 Makefile 或 Visual Studio 项目文件等)。

5. 编译项目

make

解释

  • 使用 make 命令进行编译,该命令将根据 build 目录中生成的构建文件(如 Makefile)编译源文件,生成目标文件(可执行文件和库文件)。

6. 运行可执行文件

./MyExecutable

解释

  • 执行生成的可执行文件 MyExecutable,将输出 Hello, CMake!

7. 运行测试

./TestMyLib

解释

  • 执行测试可执行文件 TestMyLib,将运行 test_main.cpp 中定义的测试用例,这里只有一个简单的测试,用于验证 1 是否等于 1

8. 使用自定义命令和目标

8.1 自定义命令

add_custom_command(
    TARGET MyExecutable
    POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E echo "Build complete!"
)

解释

  • add_custom_command 用于添加一个自定义命令,这里添加到 MyExecutable 目标的 POST_BUILD 阶段。
  • COMMAND ${CMAKE_COMMAND} -E echo "Build complete!" 表示在构建完成 MyExecutable 后,使用 CMake 的 -E 选项执行 echo 命令输出 Build complete!

8.2 自定义目标

add_custom_target(run
    COMMAND ${CMAKE_BINARY_DIR}/MyExecutable
    DEPENDS MyExecutable
)

解释

  • add_custom_target 创建一个名为 run 的自定义目标。
  • COMMAND ${CMAKE_BINARY_DIR}/MyExecutable 表示该目标的命令是执行 MyExecutable 可执行文件。
  • DEPENDS MyExecutable 表示 run 目标依赖于 MyExecutable,当 MyExecutable 被更新时,run 目标会重新执行。

9. 跨平台和交叉编译

9.1 指定平台

cmake -DCMAKE_SYSTEM_NAME=Linux..

解释

  • 通过 -DCMAKE_SYSTEM_NAME=Linux 命令行选项告诉 CMake 要构建的目标系统是 Linux 系统,CMake 会根据这个信息调整生成的构建文件。

9.2 使用工具链文件

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

解释

  • 创建一个工具链文件 toolchain.cmake,设置 CMAKE_SYSTEM_NAMELinuxCMAKE_SYSTEM_PROCESSORarm,用于指定构建的系统是 Linux 且处理器是 ARM 架构。
  • cmake -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake.. 命令使用该工具链文件进行构建,这对于交叉编译非常有用,例如在 x86 主机上为 ARM 平台编译代码。

原文地址:https://blog.csdn.net/ke_wu/article/details/145124274

免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!