初识CMake:学习笔记与速查
# CMake学习
主要参考CMake菜谱 (opens new window)教程总结的学习笔记,仅供笔者参考,完整教程请移步CMake菜谱 (opens new window) 😃
# 从可执行文件到库
# 编译单个文件
# 设置CMake所需的最低版本
cmake_minimum_required (VERSION 3.8)
# 声明项目的名称和支持的编程语言(CXX代表C++)
project(hello_cmake_world LANGUAGES CXX)
# 将源代码添加到此项目的可执行文件。
add_executable (hello-cmake "hello_cmake_world.cpp")
该文件与源文件hello_cmake_world.cpp放在一起,执行
mkdir -p build
cd build
cmake ..
cmake --build .
注意:
CMake语言不区分大小写,但是参数是区分大小写的
CMake中,C++是默认的编程语言。不过,建议使用
LANGUAGES选项在project命令中显式地声明项目的语言使用
cmake -help查看完整的帮助信息# mkdir -p sirius_build # cd sirius_build # cmake .. # 以上构建步骤可用跨平台命令代替 # -H表示当前目录中搜索根CMakeLists.txt文件 # -Bsirius_build告诉CMake在一个名为sirius_build的目录中生成所有的文件 cmake -H. -Bsirius_build
# 构建和链接静态库
cmake_minimum_required (VERSION 3.8)
project(hello_cmake_world LANGUAGES CXX)
# 创建目标——静态库。库的名称和源码文件名相同
add_library(message
STATIC
"Message.hpp"
"Message.cpp"
)
add_executable (hello-cmake "hello_cmake_world.cpp")
# 最后需要将目标库链接到可执行目标
target_link_libraries(hello-cmake message)
# 构建和链接动态库
# 与构建静态库一样,只需将STATIC改为SHARED
add_library(message
SHARED
"Message.hpp"
"Message.cpp"
)
如果考虑跨平台,有两种方法:
可为动态库的头文件添加:
#pragma once /* 添加一下内容以实现跨平台 */ #ifdef _WIN32 #ifdef MessageDLL #define MessageDLL __declspec(dllexport) #else #define MessageDLL __declspec(dllimport) #endif #else #define MessageDLL #endif //////////END//////////// class MessageDLL Message { public: // ... DO SOMETING ... };在
CMakeLists.txt里添加set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
在 Windows 平台中,导出动态库时除了会生成
.dll动态库之外还会生成一个.lib文件。这个.lib文件和静态库的.lib文件不同,它里面不保存代码生成的二进制文件,而是所有需要导出符号的符号表。如果希望将一个符号(symbol)导出(这里的符号可以指类、函数等各种类型):
方法一:需要在其前面加上
__declspec(dllexport)标志。这样这个符号的相关信息就会存在于.lib中的符号表中; 方法二:在CMakeLists.txt里添加set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
# 条件句控制编译
先看一个例子:
cmake_minimum_required(VERSION 3.8)
project(hello_cmake_world LANGUAGES CXX)
# 引入变量USE_LIBRARY,message打印相关信息
set(USE_LIBRARY OFF)
message(STATUS "Compile sources into a library? ${USE_LIBRARY}")
# BUILD_SHARED_LIBS在下面第二点有具体解释
set(BUILD_SHARED_LIBS OFF)
# 引入变量包括*.hpp\*.cpp文件
list(APPEND _sources "Message.hpp" "Message.cpp")
# 逻辑控制编译
if (USE_LIBRARY)
add_library(message ${_sources})
add_executable(hello-cmake "hello_cmake_world.cpp")
target_link_libraries(hello-cmake message)
else()
add_executable(hello-cmake "hello_cmake_world.cpp" ${_sources})
endif()
- 使用
set为变量设置值,其中逻辑变量可以是以下几种之一:- 逻辑
true:1、ON、YES、true、Y或非零数; - 逻辑
false:0、OFF、NO、false、N、IGNORE、NOTFOUND、空字符串或以-NOTFOUND为后缀。
- 逻辑
BUILD_SHARED_LIBS是CMake的一个全局标志,可以决定为生成动态库还是静态库。如果为false或未定义,将生成一个静态库;_sources变量是一个局部变量,不应该在当前范围之外使用,可以在名称前加下划线。
# 显示选项
上一节中(条件句控制编译)用硬编码控制逻辑,只能从内部修改。
本节使用
option()命令,以选项的形式在外部设置编译逻辑
语法:
option可接受三个参数:
option(<option_variable> "help string" [initial value])
<option_variable>变量名"help string"在终端可见的帮助性文字[initial value]选项的默认值,可以是ON或OFF。
因而对于上一节中的CMakeLists.txt,只需将set(USE_LIBRARY OFF)使用option()代替,并设置默认值即可:
option(USE_LIBRARY "Compile sources into a library" OFF)
现在,可以通过CMake的-DCLI选项,将信息传递给CMake来切换库的行为
cmake -D USE_LIBRARY=ON ..
-D开关用于为CMake设置任何类型的变量:逻辑变量、路径等等。
如果有依赖关系,如:用户设置不构建库,但设置了构建动态库,此时应该不构建库。CMake提供cmake_dependent_option()命令用来定义依赖于其他选项的选项:
需要引入
CMakeDependentOption模块
语法:
cmake_dependent_option可接受五个参数:
cmake_dependent_option(<OPTION_NAME> "help string" <default_value> <depends> <force_value>)
<OPTION_NAME>变量名"help string"帮助性文字<default_value>默认值<depends>该值为true时 开启此选项并设置为默认值,否则强制该选项默认值为 <force_value><depends>可以用分号代表与的关系,例如:"ENABLE_BUILD;NOT DISABLE_TESTING"表示(ENABLE_BUILD&& (!DISABLE_TESTING) )
因而添加是否编译为库和是否编译为动态库两个选项时,可修改为:
option(USE_LIBRARY "Compile sources into a library" OFF)
include(CMakeDependentOption)
# 当USE_LIBRARY为ON时,MAKE_STATIC_LIBRARY是;MAKE_STATIC_LIBRARY是
# 当USE_LIBRARY为ON时,MAKE_STATIC_LIBRARY是;MAKE_STATIC_LIBRARY是
cmake_dependent_option(
MAKE_STATIC_LIBRARY "Compile sources into a static library" OFF
"USE_LIBRARY" ON
)
cmake_dependent_option(
MAKE_SHARED_LIBRARY "Compile sources into a shared library" ON
"USE_LIBRARY" ON
)
# 设置编译器选项
参考如下一个例子:
cmake_minimum_required(VERSION 3.8)
project(recipe-08 LANGUAGES CXX)
message("C++ compiler flags: ${CMAKE_CXX_FLAGS}")
# 准备编译选项列表
list(APPEND flags "-fPIC" "-Wall")
if(NOT WIN32)
list(APPEND flags "-Wextra" "-Wpedantic")
endif()
add_library(geometry
STATIC
geometry_circle.cpp
geometry_circle.hpp
geometry_polygon.cpp
geometry_polygon.hpp
geometry_rhombus.cpp
geometry_rhombus.hpp
geometry_square.cpp
geometry_square.hpp
)
# 设置编译选项
target_compile_options(geometry
PRIVATE
${flags}
)
add_executable(compute-areas compute-areas.cpp)
target_compile_options(compute-areas
PRIVATE
"-fPIC"
)
target_link_libraries(compute-areas geometry)
- 编译选项
target_compile_options()可以添加三个级别的可见性:INTERFACE、PUBLIC和PRIVATE;- PRIVATE,编译选项应用于指定的目标,不会传递给与目标相关的目标。
- INTERFACE,给定的编译选项将只应用于指定目标,并传递给与目标相关的目标。
- PUBLIC,编译选项将应用于指定目标和使用它的目标。
# 语言设定标准
set_target_properties(animals
PROPERTIES
CXX_STANDARD 14
CXX_EXTENSIONS OFF
CXX_STANDARD_REQUIRED ON
POSITION_INDEPENDENT_CODE 1
)
这里为目标设置了一些属性:
- CXX_STANDARD,会设置我们想要的C++标准。
- CXX_EXTENSIONS,告诉CMake,只启用
ISO C++标准的编译器标志,而不使用特定编译器的扩展。 - CXX_STANDARD_REQUIRED,指定所选标准的版本。如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为
OFF时,CMake将寻找下一个标准的最新版本,直到一个合适的标志。这意味着,首先查找C++20,然后是C++17,然后是C++14。
如果语言标准是所有目标共享的全局属性,那么可以将
CMAKE_<LANG>_STANDARD、CMAKE_<LANG>_EXTENSIONS和CMAKE_<LANG>_STANDARD_REQUIRED变量设置为相应的值。所有目标上的对应属性都将使用这些设置。
# 关于目标的一些选项汇总
target_include_directories(myapp PUBLIC /path) # 添加头文件搜索目录
target_link_libraries(myapp PUBLIC hellolib) # 添加要链接的库
target_add_definitions(myapp PUBLIC MY_MACRO=1) # 添加一个宏定义
target_add_definitions(myapp PUBLIC -DMY_MACRO=1) # 与MY_MACRO=1等价
target_compile_options(myapp PUBLIC -fopenmp) # 添加编译器命令行选项
target_sources(myapp PUBLIC hello.cpp other.cpp) # 添加要编译的源文件
## 以下指令不推荐使用
include_directories(/path/to/include)
link_directories(/opt/cuda) # 添加库的搜索路径
add_definitions(MY_MACRO=1)
add_compile_options(-fopenmp)