Featured image of post CMake创建c++项目简明教程

CMake创建c++项目简明教程

使用简单的CMake搭建简单的项目

引言

CMake 是一个跨平台的构建系统生成器,用于自动化构建、测试和打包软件项目的过程。CMake 通过一系列源代码文件和配置设置,生成平台特定的构建文件(例如 Makefile、Visual Studio 项目文件等)。

CMake语法

  • 依次使用以下命令生成项目(对于CmakeLists.txt)
1
2
cmake -B build  # 创建build文件夹和相关文件
cmake --build build  # 生成项目
  • 语法简介

    cmake -P *.cmake命令来运行cmake文件

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    # 单行打印
    message("hello")
    message(hello)
    
    # 多行打印
    message("first line
    second line")
    message([[first line
    second line]])
    
    # 打印cmake版本号,使用CMAKE_VERSION变量 ${}
    message(${CMAKE_VERSION})
    
  • set设置变量

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    
    set(Var1 "hello")
    set(Var2 world)
    
    # 打印单行hello world
    message(${Var1}
    ${Var2})
    
    # 打印多行hello world
    message("${Var1}
    ${Var2}") 
    
    message("------------------------")
    
    set(VarLists a1 a2)
    message(${VarLists})    #a1a2
    
    # 打印环境变量
    message($ENV{PATH})
    
    # 创建cmake项目的环境变量
    set(ENV{CXX} "g++")
    message($ENV{CXX})
    
    # 删除刚刚创建的环境变量
    unset(ENV{CXX})
    
  • list

    • list(操作方式 操作对象 可能有操作返回结果的变量)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    list(APPEND port p1 p2 p3)
    message(${port})    # p1p2p3
    
    list(LENGTH port len)
    message(${len})     # 3 (变量长度为3)
    
    list(FIND port p2 index)
    message(${index})   # 1 (p2在1的位置)
    
    list(REMOVE_ITEM port p1)
    message(${port})    # p2p3 (删除p1)
    
    list(INSERT port 1 p2.5)
    message(${port})    # p2p2.5p3 (在1位置插入p2.5)
    
    list(REVERSE port)
    message(${port})    # p3p2.5p2 (进行反转)
    
    list(SORT port)
    message(${port})    # p2p2.5p3 (进行排序)
    
  • 流程控制

    • if流程
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
    set(VAR1 TRUE)
    set(VAR2 FALSE)
    
    if(VAR1)    # 简单判断
        message(TRUE)
    else()
        message(FALSE)
    endif()
    
    if(NOT VAR1) # NOT不能是not,大小写敏感
        message(TRUE)
    else()
        message(FALSE)
    endif()
    
    if(VAR1 AND VAR2)   # OR用法也是如此
        message(TRUE)
    else()
        message(FALSE)
    endif()
    
    if(1 LESS 2)        # 比较,其实还是字符串的比较
        message("1 less 2")
    else()
        message("error")
    endif()
    
    if(2 GREATER 1)
        message("2 greater 1")
    endif()
    
    if(2 EQUAL "2")
        message("2 equal \"2\"")
    endif()
    
    • for流程
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    foreach(VAR RANGE 3)
        message(${VAR}) # 0 1 2 3 (跟一般编程语言区别很大)
    endforeach()
    
    set(MY_LISTS 1 2 3)
    foreach(VAR IN LISTS MY_LISTS ITEMS 4 f)
        message(${VAR}) # 1 2 3 4 f
    endforeach()
    
    message("----------------------")
    
    set(L1 one two three)
    set(L2 1 2 3 4)
    foreach(num IN ZIP_LISTS L1 L2)
        message("word = ${num_0}, num = ${num_1}")
    endforeach()
    # word = one, num = 1
    # word = two, num = 2
    # word = three, num = 3
    # word = , num = 4
    

  • 函数

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    function(MyFunc FirstArg) # 函数名MyFunc
        message("MyFunc Name: ${CMAKE_CURRENT_FUNCTION}")
        message("FIRST ${FirstArg}")    # 使用形参列表使用参数
        set(FirstArg "New value")
        message("FirstArg again: ${FirstArg}")
        message("ARGV0 ${ARGV0}")       # 不使用形参列表使用参数
        message("ARGV1 ${ARGV1}")
        message("ARGV2 ${ARGV2}")
    endfunction()
    
    set(FirstArg "first value")         
    MyFunc(${FirstArg} "value")         # 可以传入和新参列表不一样数目的实参
    message("FirstArg ${FirstArg}")     # 函数内部修改不会改变函数外面
    
    # MyFunc Name: MyFunc
    # FIRST first value
    # FirstArg again: New value
    # ARGV0 first value
    # ARGV1 value
    # ARGV2
    # FirstArg first value
    
  •  1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    macro(Test myVar)
        set(myVar "new value")  # 新建的变量,不是上面的参数
        message("argument: ${myVar}")   #${myVar}会变为传入的myVar
    endmacro()
    
    set(myVar "First value")
    message("myVar: ${myVar}")
    Test("value")   # 将宏代码复制到了这里
    message("myVar: ${myVar}")
    
    # myVar: First value
    # argument: value
    # myVar: new value
    

简单的CMake项目

  • 在这个简单的CMake项目中,有如下的项目结构
  • 整个项目只有一个CMakeLists.txt,所有的源文件和头文件都放在项目目录中,bin中存放的是最终的可执行程序。

简单项目目录结构

  • CMakeLists.txt中的代码如下
1
2
3
4
5
6
7
cmake_minimum_required(VERSION 3.29.0)  # 确定CMake版本
project(planning)                       # 项目名
set(CMAKE_CXX_STANDARD 17)              # 使用的语言和版本

set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)    # 设定可执行文件的路径
aux_source_directory(. SRC_LISTS)                        # 添加当前目录所有源文件路径到SRC_LIST变量中
add_executable(planning_main ${SRC_LISTS})      # 通过源文件创建目标可执行文件

分文件编写的的CMake项目

  • 在这个份文件的CMake项目中,有如下的项目结构
  • 整个项目只有一个CMakeLists.txt,src文件夹里面存放的是源文件,include文件夹中存放的是头文件,bin中存放的是最终的可执行程序。

分文件编写目录结构

  • CMakeLists.txt中的代码如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
cmake_minimum_required(VERSION 3.20.0) # 确定CMake版本
project(my_hello)                      # 项目名
set(CMAKE_CXX_STANDARD 17)             # 使用的语言和版本

include_directories(${PROJECT_SOURCE_DIR}/include)  # 指定头文件搜索路径
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)   # 设定可执行文件的路径

aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC_LIST)    # 添加src目录下所有源文件路径到SRC_LIST变量中

add_executable( # 通过源文件创建目标可执行文件
    my_hello
    ${SRC_LIST}
)

共享库

  • 在创建共享库的项目中,有如下目录结构
  • 注意到bin中多出了libcommon.dll动态链接库文件。同时,main程序在项目目录下,其他.cpp文件存放在了src目录中,头文件存放在了include目录中。

使用共享库目录结构

  • CMakeLists.txt中的代码如下
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
cmake_minimum_required(VERSION 3.29.0)
project(planning)
set(CMAKE_CXX_STANDARD 17)

# 设置生成的动态库存放路径
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
#linux下使用 CMAKE_LIBRARY_OUTPUT_DIRECTORY 变量
#静态库使用 CMAKE_ARCHIVE_OUTPUT_DIRECTORY 变量

aux_source_directory(${PROJECT_SOURCE_DIR}/src SRC_LISTS)

# 创建了名为common的动态库
add_library(common 
SHARED
${SRC_LISTS}
)
# add_library(common STATIC ${SRC_LISTS}) 创建的是静态库

# 为动态库添加头文件目录
target_include_directories(common
PUBLIC
${PROJECT_SOURCE_DIR}/include
)

#生成可执行程序
add_executable(planning_main planning_main.cpp)

# 为目标 planning_main 设置头文件目录
target_include_directories(planning_main
    PUBLIC
    ${PROJECT_SOURCE_DIR}/include
)

# 为目标 planning_main 链接 common 库
target_link_libraries(planning_main
    PUBLIC
    common
)

CmakeLists嵌套

  • 项目有如下目录结构
  • 项目文件夹下有一个CmakeLists文件;src文件夹里有一个CmakeLists文件,主程序和几个类的文件夹;同时src中每个类各自的文件夹都包含了CmakeLists,.cpp,.h三个文件

嵌套调用目录结构

  • 嵌套使用的时候上层CmakeLists文件的变量会传入到下层的CmakeLists文件当中。

  • 项目文件夹下的CmakeLists.txt代码如下

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    cmake_minimum_required(VERSION 3.29.0)
    project(planning
        VERSION 0.0.1
        DESCRIPTION "a demo of cmake planning"
        LANGUAGES CXX
    )
    
    set(CMAKE_CXX_STANDARD 17)
    
    # 一些后面要使用的变量
    set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
    set(PROCESS_DIR ${CMAKE_SOURCE_DIR}/src/process)
    set(PNC_MAP_DIR ${CMAKE_SOURCE_DIR}/src/pnc_map)
    
    # 增加子目录
    add_subdirectory(src)
    
  • src文件夹下的CmakeLists.txt代码如下

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    project(planning_main)
    
    # 添加子目录
    add_subdirectory(pnc_map)
    add_subdirectory(process)
    
    # 根据planning_main.cpp形成可执行文件
    add_executable(${PROJECT_NAME} planning_main.cpp)
    
    # 生成可执行文件需要process.h
    target_include_directories(${PROJECT_NAME}
    PUBLIC
    ${PROCESS_DIR}
    )
    
    # 链接库需要process库
    target_link_libraries(${PROJECT_NAME}
        PUBLIC
        process
    )
    
  • 两个类中的CmakeLists代码分别如下

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    project(process)
    
    add_library( ${PROJECT_NAME}
    SHARED
    process.cpp
    )
    
    # 生成库需要pnc_map.h	注意,这是根据代码的关系决定,本例代码中,类process有pnc_maplei'x的成员变量
    target_include_directories(${PROJECT_NAME} 
    PUBLIC
    ${PNC_MAP_DIR}
    )
    
    # 生成库需要pnc_map库
    target_link_libraries(${PROJECT_NAME}
    PUBLIC
    pnc_map
    )
    
    1
    2
    3
    4
    5
    6
    
    project(pnc_map)
    
    add_library( ${PROJECT_NAME}
    SHARED
    pnc_map.cpp
    )