[CMake] CMake 基础命令

前言

本文主要是对 https://github.com/ttroy50/cmake-examples 项目进行学习,并记录。本章主要包含了一些常用的 CMake 基本命令,以及存在子项目时的相关内容。

基础指令

指定基本信息

首先需要注意的一点是,开头需要指定使用的 Cmake 版本,以及给 Project 一个名字:

cmake_minimum_required(VERSION 3.5)
project(test C CXX)  # 可以同时指定项目所使用的语言

然后可以根据需求设置相关的编译语言版本:

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)

这里就引入 cmake 中 变量(variable)的概念。 其基本都是全大写的名字,防止与用户输入的其他命令混淆。只是这里有一部分 cmake 自己预留的变量名了。

例如:

CMAKE_SOURCE_DIR : 文件根目录
CMAKE_CURRENT_SOURCE_DIR:目前源文件的根目录
PROJECT_SOURCE_DIR:当前cmake 项目的根目录
CMAKE_CURRENT_BINARY_DIR:当前根路径的 build 目录
CMAKE_BINARY_DIR:根目录的 build 目录
PROJECT_BINARY_DIR:目前项目的 build 目录

添加源文件

其中 set 就是我们直接指定,需要完全把需要 set 给变量的内容写出来,没办法通过通配符省事。

set(SOURCES src/A.cpp  src/B.cpp)

不可以用下面的形式:

set(SOURCEs src/*.cpp)

否则 SOURCES 打印出来的内容就是 “src/*.cpp” 不具有实际意义。

通配符查找可以使用 file 命令来定义:

file(GLOB SOURCES src/*.cpp) 

添加头文件

.
├── CMakeLists.txt
├── include
│ └── Hello.h
└── src
├── Hello.cpp
└── main.cpp

假设文件目录结构如上所示,除了源文件,我们还需要添加头文件才可以完成程序编译。这里可以使用 target_include_directories() 命令添加,它相当于添加编译命令 -I /dir/path。

target_include_directories(target PRIVATE ${PROJECT_SOURCE_DIR}/include)

注意一点,这里第一个参数就是 target,意思是把路径加到哪个目标上,所以我们得现有一个编译的目标,可以是库或者可执行文件。所以定义顺序应该是:

add_executable(hello ${SOURCES})
target_include_directories(hello PRIVATE ${PROJECT_SOURCE_DIR}/include)

编译静态库

编译静态库可以使用下面的命令:

add_library(hello_lib STATIC src/Hello.cpp)

注意这里还是需要在后面添加编译库需要的头文件才可以正常编译。

target_include_directories(hello_lib PUBLIC ${PROJECT_SOURCE_DIR}/include)

上面添加的头文件路径将会用在以下两种情况:

  • 编译本项目的库
  • 编译其他需要链接此库的目标时

PRIVATE : 路径只能在编译当前库中使用

INTERFACE : 路径还会添加到其他任何链接这个库的目标头文件路径中

PUBLIC:上面的总和,即添加到当前 target 的头文件目录中,同时相关路径也会添加到链接此库的 target 中。

这里添加的时 include 的总目录,所以也推荐在代码中包含头文件也带有路径信息

#include “static/Hello.h” 这样即使有同名的头文件,但是在不同子文件夹中也可以分辨。

链接静态库

相应的,我们添加一个可执行对象,然后使用 target_link_libraries() 命令链接相关库就可以了。这个命令同时还会将相关库的任何设为 PUBLIC 或者是 INTERFACE 的头文件路径一块添加到可执行文件编译命令中。如果上面 target_include_directories 使用的是 PRIVATE,这里在编译可执行文件时会报找不到 static/Hello.h

add_executable(hello src/main.cpp)
target_link_libraries(hello PRIVATE hello_lib)

编译动态库

add_library 同样可以用来编译动态链接库,只将上面你的 STATIC 变为 SHARED 即可。

add_library(hello_lib SHARED src/Hello.cpp)

还可以在下面额外添加一个别名目标,可以在上下文中使用别名:

add_library(hello::lib ALIAS hello_lib)
add_executable(hello src/main.cpp)
target_link_libraries(hello PRIVATE hello::lib)

允许安装(make install)

CMake 支持安装操作,运行用户安装自己的二进制文件,库或者是其他文件。基础的安装路径是被 CMAKE_INSTALL_PREFIX 变量控制的。在 Linux 上默认是 /usr/local

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0ryGELP0-1670061089648)(https://s3-us-west-2.amazonaws.com/secure.notion-static.com/42b2e5da-0a5d-438b-a41c-3c668fe24c32/Untitled.png)]

上面是目录组织结构,然后下面是相对完整的一个 Cmake 文件:

cmake_minimum_required(VERSION 3.5)
project(install CXX)
set(CMAKE_CXX_STANDARD 17)

message("INSTALL PREFIX: " ${CMAKE_INSTALL_PREFIX})

add_library(hello_lib SHARED src/Hello.cpp)
target_include_directories(hello_lib PUBLIC ${PROJECT_SOURCE_DIR}/include)

add_executable(hello_bin src/main.cpp)
target_link_libraries(hello_bin PRIVATE hello_lib)

install(TARGETS hello_bin DESTINATION bin)
install(TARGETS hello_lib DESTINATION lib)
install(DIRECTORY ${PROJECT_SOURCE_DIR}/include DESTINATION include)
install(FILES cmake-examples.conf DESTINATION etc)

install 实际可以看作是一个拷贝命令,将 install 命令里的内容拷贝到我们指定的目录下,使在环境中也可以运行。

TARGETS 主要用来移动生成的库和二进制文件,
DIRECTORY 用来批量移动路径下的所有头文件
FILES 用来单独移动一个目标文件

以上 DESTNITION 后面跟的路径,完整路径实际就是:${CMAKE_INSTALL_PREFIX}/folder_name

然后运行我们的编译命令:

cmake -B build .
cd build
make install

./hello_bin

上面的安装路径会保存在本地的 install_manifest.txt 文件中。

编译类型

CMake 支持不同的编译类型选项。不同的类型对应了不同程度的优化等级。

  • Release : -O3 -DNDEBUG
  • Debug : -g
  • MinSizeRel:-Os -DNDEBUG
  • RelWithDebInfo:-O2 -g -DNDEBUG

在编译时增加 -DCMAKE_BUILD_TYPE 命令来控制。 默认的 CMake 是不加任何优化选项的。对于一些项目可能需要添加默认的编译类型,下面的语句可以做到:

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message("Setting build type to 'RelWithDebInfo' as none was specified.")
  set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
  # Set the possible values of build type for cmake-gui
  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
    "MinSizeRel" "RelWithDebInfo")
endif()

设置 C++ 标志

CMake 中比较推荐的设置 C++ flags 的方式是使用 target_compile_definitions() 命令。

taeget_compile_definitions(hello_bin PRIVATE EX3)

上面将会在编译时添加 -DEX3 这个命令。 上面的 PRIVATE 同样是控制是否将相关 flags 传递给依赖这个 target 的目标。
上述同样可以通过 target_compile_options() 命令实现。 当然也可以通过 set 命令来设置:

set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2")

相似的也可以通过编译时添加相关配置:

cmake -DCMAKE_CXX_FLAGS=-DEX3 .

使用第三方库

几乎所有的重要项目都会依赖第三方库 , CMake 支持使用 find_package() 自动查找这些工具路CMake 将会从 CMAKE_MODULE_PATH 中的路径搜索 名字为 Findxxx.cmake 的文件。

以 Boost 库为例:

find_package(Boost 1.46.1 REQUIRED COMPONENTS filesystem system)

其中:

  • Boost : 就是我们要找的目标库。用来查找目标的 FindBoost.cmake 文件。
  • 1.46.1 : 是最小的 boost 版本。
  • REQUIRED :告诉 module 这个库是必需的,如果找不到就要报错
  • COMPONENTS : 是需要使用的 Boost 组件。

运行上面的命令,可能会有下面的内容输出:

-- Found Boost: /usr/include (found suitable version "1.65.1", minimum required is "1.46.1") found components: filesystem system

因为一个库可以包含很多个组件,上面我们只需要使用 system 和 filesystem 组件即可。 在使用上面的同时,会自动定义一个变量是: xxx_FOUND(这里的例子是 Boost_FOUND)来 check 系统中是否找到目标依赖库。

如果找到了也会有

xxx_INCLUDE_DIRS 变量表示依赖库头文件。
xxx_LIBRARY 表示依赖库的路径。

if(Boost_FOUND)
  message("Boost FOUND")
  include_directories(${Boost_INCLUDE_DIRS})
  message(" Boost INCLUDE DIRS: " ${Boost_INCLUDE_DIRS})
else()
  message("Boost NOT FOUND!!")
endif()

找到依赖库之后有两种形式可以使用:

add_exeutable(hello_bin main.cpp)

# 使用 alias nanme
target_link_libraries(hello_bin PRIVATE Boost::filesystem)

# 直接使用变量
# target_include_directories(hello_bin PRIVATE ${Boost_INCLUDE_DIRS})
# target_link_libraries(hello_bin PRIVATE ${Boost_FILESYSTEM_LIBRARY} ${Boost_SYSTEM_LIBRARY})

使用 Clang 编译

CMake 支持指定编译器来编译项目:

  • CMAKE_C_COMPILER : C 语言的编译器
  • CMAKE_CXX_COMPILER:C++ 编译器
  • CMAKE_LINKER:链接器
cmake -DCMAKE_CXX_COMPILER=clang++ 。

使用 Ninja 编译

使用 cmake —help 可以看到 cmake 支持的所有生成器。使用 -G xxx 即可指定生成器。

cmake -G Ninja .

加载模块

正如前面在使用第三方库部分提到的,在 CMake v3.5+ 之后,支持使用 alias targets 来加载模块:

target_link_libraries(hello_bin PRIVATE Boost::filesystem)

C++ 标准

在比较老的版本可以使用以下内容来指定 CXX_STANARD:

# $ cmake --version
cmake_minimum_required(VERSION 2.8)

# Set the project name
project (hello_cpp11)

# try conditional compilation
include(CheckCXXCompilerFlag)
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)

# check results and add flag
if(COMPILER_SUPPORTS_CXX11)#
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
elseif(COMPILER_SUPPORTS_CXX0X)#
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
else()
    message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
endif()

# Add an executable
add_executable(hello_cpp11 main.cpp)

随着升级在 v3.1 及之后可以直接通过 set 来指定:

set(CMAKE_CXX_STANDARD 11)

或者支持自动检测:

# set the C++ standard to the appropriate standard for using auto
target_compile_features(hello_cpp11 PUBLIC cxx_auto_type)

# Print the list of known compile features for this version of CMake
message("List of compile features: ${CMAKE_CXX_COMPILE_FEATURES}")

sub-projects

很多大型项目都是由不同对的库和二进制文件组成的,这样就会形成很多文件夹和子项目。

$ tree
.
├── CMakeLists.txt
├── subbinary
│   ├── CMakeLists.txt
│   └── main.cpp
├── sublibrary1
│   ├── CMakeLists.txt
│   ├── include
│   │   └── sublib1
│   │       └── sublib1.h
│   └── src
│       └── sublib1.cpp
└── sublibrary2
    ├── CMakeLists.txt
    └── include
        └── sublib2
            └── sublib2.h

文件目录结构如上。 其中:
subbinary : 是要编译一个可执行文件
subbinary1 : 是要编译一个依赖库
subbinary2:只有头文件

添加子路径:

一个 CMakeLists.txt 可以包含并调用子路径中的 CMakeLists.txt。 使用 add_subdirectory() 命令。

当使用 project() 命令创建项目时,CMake 会自动定义一系列的变量用表示一些项目的细节,例如:

project(sublibrary1)

就可以使用 ${sublibrary1_SOURCE_DIR} 来表示子路径。相关的变量名如下:

  • PROJECT_NAME : 设置的项目名称
  • CMAKE_PROJECT_NAME:最上层的项目名称
  • PROJECT_SOURCE_DIR:当前项目的目录
  • PROJECT_BINARY_DIR:当前目录下的 build 路径
  • name_SOURCE_DIR:名字叫 name 的项目的目录
  • name_BINARY_DIR:名字叫 name 的二进制文件路径

如果有一个依赖库只有头文件,那么 CMake 支持使用 INTERFACE 来创建一个没有任何输出的 target。

add_library(${PROJECT_NAME} INTERFACE)

然和也要使用 INTERFACE 模式来添加相关的头文件路径,以使后面使用相关 target 的项目都可以找到头文件:

target_include_directories(${PROJECT_Name} INTERFACE ${PROJECT_SOURCE_DIR}/include)

如果一个子项目编译出来的是链接库,则不需要输入路径,可以直接就使用 target_link_libraries() 链接。

target_link_libraries(subbinary PUBLIC sublibrary1)

# 也可是使用别名,使调用更清晰
add_library(sublibrary1 )
add_library(sub::sub1 ALIAS sublibrary1 )

然后使用直接就可以:

target_link_libraries(hello PUBLIC sub::sub1)

下面直接贴出完整的 CMakeLists.txt。

# subprojects
cmake_minimum_required(VERSION 3.5)
project(subprojects)

add_subdirectory(sublibrary1)
add_subdirectory(sublibrary2)
add_subdirectory(sublibrary)

然后下面是各个子目录的 CMakeLists.txt :

sublibrary1 文件夹:

project (sublibrary1)

add_library(${PROJECT_NAME} src/sublib1.cpp)
add_library(sub::lib1 ALIAS ${PROJECT_NAME})

target_include_directories( ${PROJECT_NAME}
    PUBLIC ${PROJECT_SOURCE_DIR}/include
)

sublibrary2 文件夹:


project (sublibrary2)

add_library(${PROJECT_NAME} INTERFACE)
add_library(sub::lib2 ALIAS ${PROJECT_NAME})

target_include_directories(${PROJECT_NAME}
    INTERFACE
        ${PROJECT_SOURCE_DIR}/include
)

subbinary 文件夹:

project(subbinary)

# Create the executable
add_executable(${PROJECT_NAME} main.cpp)

# Link the static library from subproject1 using its alias sub::lib1
# Link the header only library from subproject2 using its alias sub::lib2
# This will cause the include directories for that target to be added to this project
target_link_libraries(${PROJECT_NAME}
    sub::lib1
    sub::lib2
)

可以看到 CMake 版本在最上层指定依次就可以了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值