序
不知道从什么地方开始写起,后面慢慢补吧。CMake 系列学习源自CMake-Cookbook ,一本翻译来的书。作者提供了书中的源代码,我自己学习的过程中手动码了一遍,放在了MyCmakeExample
中文版在线网站CMake Cookbook ,本书中的绝大部分内容源自该中文版翻译,在此只是为了做个学习上的记录,以后项目中用到的时候方便查找,可自由转载。
Chapter 1
chapter 1.1 对单个源文件编译
配置项目
生成构建器的命令:
1 2 3 $ mkdir -p build$ cd build$ cmake ..
等效于
构建项目
构建命令:
后面可以带参数,详情如下
1 2 3 4 5 6 7 8 9 10 11 12 $ cmake --build . --target help The following are some of the valid targets for this Makefile: ... all (the default if no target is provided) ... clean ... depend ... rebuild_cache ... hello-world ... edit_cache ... hello-world.o ... hello-world.i ... hello-world.s
chapter 1.2 切换生成器
Tips: 何为生成器呢?cmake会通过CMakeLists.txt
文件,生成适用于不同项目类型的makefile文件,然后makefile文件被不同的编译器使用进行编译,考虑到C/C++的开发环境之多,有非常多的种类的项目开发环境,但是cmake基本上都考虑到了,这里有一个小的汇总:cmake的generator详解 。
使用命令 cmake --help
可以查看本机支持的生成器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Generators The following generators are available on this platform (* marks default): * Unix Makefiles = Generates standard UNIX makefiles. Green Hills MULTI = Generates Green Hills MULTI files (experimental, work-in-progress). Ninja = Generates build.ninja files. Watcom WMake = Generates Watcom WMake makefiles. CodeBlocks - Ninja = Generates CodeBlocks project files. CodeBlocks - Unix Makefiles = Generates CodeBlocks project files. CodeLite - Ninja = Generates CodeLite project files. CodeLite - Unix Makefiles = Generates CodeLite project files. Sublime Text 2 - Ninja = Generates Sublime Text 2 project files. Sublime Text 2 - Unix Makefiles = Generates Sublime Text 2 project files. Kate - Ninja = Generates Kate project files. Kate - Unix Makefiles = Generates Kate project files. Eclipse CDT4 - Ninja = Generates Eclipse CDT 4.0 project files. Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
带 *
的是默认的生成器,如果想切换成别的,命令如下
1 2 3 $ mkdir -p build$ cd build$ cmake -G Ninja ..
chapter 1.3 静态库和动态库
add_library(message STATIC Message.hpp Message.cpp)
:生成必要的构建指令,将指定的源码编译到库中。add_library
的第一个参数是目标名。整个CMakeLists.txt
中,可使用相同的名称来引用库。生成的库的实际名称将由CMake通过在前面添加前缀lib
和适当的扩展名作为后缀来形成。生成库是根据第二个参数(STATIC
或SHARED
)和操作系统确定的。
target_link_libraries(hello-world message)
: 将库链接到可执行文件 。此命令还确保hello-world
可执行文件可以正确地依赖于消息库。因此,在消息库链接到hello-world
可执行文件之前,需要完成消息库的构建。
编译成功后,构建目录包含libmessage.a
一个静态库(在GNU/Linux上)和hello-world
可执行文件
CMake接受其他值作为add_library
的第二个参数的有效值,本书会用到的值:
STATIC :用于创建静态库,即编译文件的打包存档,以便在链接其他目标时使用,例如:可执行文件。
SHARED :用于创建动态库,即可以动态链接,并在运行时加载的库。可以在CMakeLists.txt
中使用add_library(message SHARED Message.hpp Message.cpp)
从静态库切换到动态共享对象(DSO)。
OBJECT :可将给定add_library
的列表中的源码编译到目标文件,不将它们归档到静态库中,也不能将它们链接到共享对象中。如果需要一次性创建静态库和动态库,那么使用对象库尤其有用。我们将在本示例中演示。
MODULE :又为DSO组。与SHARED
库不同,它们不链接到项目中的任何目标,不过可以进行动态加载。该参数可以用于构建运行时插件。
chapter 1.4 条件语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 cmake_minimum_required (VERSION 3.12 .0 FATAL_ERROR)project (recipe-04 LANGUAGES CXX)set (USE_LIBRARY OFF )message (STATUS "Compile sources into a library? ${USE_LIBRARY}" )set (BUILD_SHARED_LIBS OFF )list (APPEND _sources Message .hpp Message .cpp)if (USE_LIBRARY) add_library (message ${_sources} ) add_executable (hello-world hello-world.cpp) target_link_libraries (hello-world message ) else () add_executable (hello-world hello-world.cpp ${_sources} ) endif ()
TIPS :_sources
变量是一个局部变量,不应该在当前范围之外使用,可以在名称前加下划线。
BUILD_SHARED_LIBS
是CMake的一个全局标志。因为CMake内部要查询BUILD_SHARED_LIBS
全局变量,所以add_library
命令可以在不传递STATIC/SHARED/OBJECT
参数的情况下调用;如果为false
或未定义,将生成一个静态库。
chapter 1.5 通过外部传参修改变量
通过option
替代set
,可从外部传值
1 option (USE_LIBRARY "Compile sources into a library" OFF )
1 2 3 $ mkdir -p build$ cd build$ cmake -D USE_LIBRARY=ON ..
-D
开关用于为CMake设置任何类型的变量:逻辑变量、路径等等。
chapter 1.6 指定编译器
CMake将语言的编译器存储在 CMAKE_<LANG>_COMPILER
变量中,建议通过下面的命令显式地设置。
1 $ cmake -D CMAKE_CXX_COMPILER=clang++ ..
CMake提供--system-information
标志,它将把关于系统的所有信息转储到屏幕或文件中。可以看到CMAKE_CXX_COMPILER
、CMAKE_C_COMPILER
和CMAKE_Fortran_COMPILER
的默认值。
1 $ cmake --system-information information.txt
CMake提供了额外的变量来与编译器交互:
CMAKE_<LANG>_COMPILER_LOADED
:如果为项目启用了语言<LANG>
,则将设置为TRUE
。
CMAKE_<LANG>_COMPILER_ID
:编译器标识字符串,编译器供应商所特有。例如,GCC
用于GNU编译器集合,AppleClang
用于macOS上的Clang, MSVC
用于Microsoft Visual Studio编译器。注意,不能保证为所有编译器或语言定义此变量。
CMAKE_COMPILER_IS_GNU<LANG>
:如果语言<LANG>
是GNU编译器集合的一部分,则将此逻辑变量设置为TRUE
。注意变量名的<LANG>
部分遵循GNU约定:C语言为CC
, C++语言为CXX
, Fortran语言为G77
。
CMAKE_<LANG>_COMPILER_VERSION
:此变量包含一个字符串,该字符串给定语言的编译器版本。版本信息在major[.minor[.patch[.tweak]]]
中给出。但是,对于CMAKE_<LANG>_COMPILER_ID
,不能保证所有编译器或语言都定义了此变量。
尝试使用不同的编译器,下面是个示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 # 设置CMake所需的最低版本,低于该版本将报错 cmake_minimum_required(VERSION 3.12.0 FATAL_ERROR) # 声明项目名称recipe-04,编程语言CXX表示C++ project(recipe-06 LANGUAGES CXX) message(STATUS "Is the C++ compiler loaded? ${CMAKE_CXX_COMPILER_LOADED}") if(CMAKE_CXX_COMPILER_LOADED) message(STATUS "The C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Is the C++ from GNU? ${CMAKE_COMPILER_IS_GNUCXX}") message(STATUS "The C++ compiler version is: ${CMAKE_COMPILER_VERSION}") endif() message(STATUS "Is the C compiler loaded? ${CMAKE_C_COMPILER_LOADED}") if(CMAKE_C_COMPILER_LOADED) message(STATUS "The C compiler ID is: ${CMAKE_C_COMPILER_ID}") message(STATUS "Is the C from GNU? ${CMAKE_COMPILER_IS_GNUCC}") message(STATUS "The C compiler version is: ${CMAKE_COMPILER_VERSION}") endif()
chapter 1.7 切换构建类型
控制生成构建系统使用的配置变量是CMAKE_BUILD_TYPE
。该变量默认为空,CMake识别的值为:
Debug :用于在没有优化的情况下,使用带有调试符号构建库或可执行文件。
Release :用于构建的优化的库或可执行文件,不包含调试符号。
RelWithDebInfo :用于构建较少的优化库或可执行文件,包含调试符号。
MinSizeRel :用于不增加目标代码大小的优化方式,来构建库或可执行文件。
切换构建类型:
1 $ cmake -D CMAKE_BUILD_TYPE=Debug ..
下面是对Visual Studio的CMake调用:
1 2 3 $ mkdir -p build$ cd build$ cmake .. -G "Visual Studio 12 2017 Win64" -D CMAKE_CONFIGURATION_TYPES="Release;Debug"
将为Release和Debug配置生成一个构建树。然后,您可以使--config
标志来决定构建这两个中的哪一个:
1 $ cmake --build . --config Release
chapter 1.8 设置编译器选项
编译标志
1 2 3 4 list (APPEND flags "-fPIC" "-Wall" )if (NOT WIN32) list (APPEND flags "-Wextra" "-Wpedantic" ) endif ()
添加一个新的静态库geometry
1 2 3 4 5 6 7 8 9 10 11 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 )
设置编译选项
1 2 3 4 target_compile_options (geometry PRIVATE ${flags} )
编译选项可以添加三个级别的可见性:INTERFACE
、PUBLIC
和PRIVATE
。
可见性的含义如下:
PRIVATE ,编译选项会应用于给定的目标,不会传递给与目标相关的目标。我们的示例中, 即使compute-areas
将链接到geometry
库,compute-areas
也不会继承geometry
目标上设置的编译器选项。
INTERFACE ,给定的编译选项将只应用于指定目标,并传递给与目标相关的目标。
PUBLIC ,编译选项将应用于指定目标和使用它的目标。
这种方式添加编译选项,不会影响全局CMake变量CMAKE_<LANG>_FLAGS_<CONFIG>
,并能更细粒度控制在哪些目标上使用哪些选项。
如何确定项目在CMake构建时,实际使用了哪些编译标志?一种方法是,使用CMake将额外的参数传递给本地构建工具。
1 2 3 4 $ mkdir -p build$ cd build$ cmake .. $ cmake --build . -- VERBOSE=1
chapter 1.9 为语言设定标准
从CMake 3.1版本开始,引入了一个独立于平台和编译器的机制,用于为C++
和C
设置语言标准:为目标设置 <LANG>_STANDARD
属性。
1 2 3 4 5 6 7 set_target_properties (animals PROPERTIES CXX_STANDARD 14 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON POSITION_INDEPENDENT_CODE 1 )
这些属性的含义如下
CXX_STANDARD 会设置我们想要的标准。
CXX_EXTENSIONS 告诉CMake,只启用ISO C++
标准的编译器标志,而不使用特定编译器的扩展。
CXX_STANDARD_REQUIRED 指定所选标准的版本。如果这个版本不可用,CMake将停止配置并出现错误。当这个属性被设置为OFF
时,CMake将寻找下一个标准的最新版本,直到一个合适的标志。这意味着,首先查找C++14
,然后是C++11
,然后是C++98
。(译者注:目前会从C++20
或C++17
开始查找)
chapter 1.10 使用控制流
使用 1.8 中的示例代码
使用-O3
编译器优化级别编译库,对目标设置一个私有编译器选项:
1 2 3 4 target_compile_options (geometry PRIVATE -O3 )
然后,生成一个源文件列表,以较低的优化选项进行编译:
1 2 3 4 5 list ( APPEND sources_with_lower_optimization geometry_circle.cpp geometry_rhombus.cpp )
循环这些源文件,将它们的优化级别调到-O2
。使用它们的源文件属性完成:
1 2 3 4 5 message (STATUS "Setting source properties using IN LISTS syntax:" )foreach (_source IN LISTS sources_with_lower_optimization) set_source_files_properties (${_source} PROPERTIES COMPILE_FLAGS -O2) message (STATUS "Appending -O2 flag for ${_source}" ) endforeach ()
为了确保设置属性,再次循环并在打印每个源文件的COMPILE_FLAGS
属性:
1 2 3 4 5 message (STATUS "Querying sources properties using plain syntax:" )foreach (_source ${sources_with_lower_optimization} ) get_source_file_property (_flags ${_source} COMPILE_FLAGS) message (STATUS "Source ${_source} has the following extra COMPILE_FLAGS: ${_flags}" ) endforeach ()
Chapter 2
chapter 2.1 检测操作系统
根据变量CMAKE_SYSTEM_NAME
判断操作系统类型
1 2 3 4 5 6 7 8 9 10 11 if (CMAKE_SYSTEM_NAME STREQUAL "Linux" ) message (STATUS "Configuring on/for Linux" ) elseif (CMAKE_SYSTEM_NAME STREQUAL "Darwin" ) message (STATUS "Configuring on/for macOS" ) elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows" ) message (STATUS "Configuring on/for Windows" ) elseif (CMAKE_SYSTEM_NAME STREQUAL "AIX" ) message (STATUS "Configuring on/for IBM AIX" ) else () message (STATUS "Configuring on/for ${CMAKE_SYSTEM_NAME}" ) endif ()
chapter 2.2 处理与平台相关的源代码
hello-world.cpp
,基于预处理器定义IS_WINDOWS
、IS_LINUX
、IS_MACOS
的条件编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #include <cstdlib> #include <iostream> #include <string> std::string say_hello () {#ifdef IS_WINDOWS return std::string ("Hello from Windows!" ); #elif IS_LINUX return std::string ("Hello from Linux!" ); #elif IS_MACOS return std::string ("Hello from macOS!" ); #else return std::string ("Hello from an unknown system!" ); #endif } int main () { std::cout << say_hello () << std::endl; return EXIT_SUCCESS; }
CMakeLists.txt
,通过使用target_compile_definition
在预处理阶段使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 cmake_minimum_required (VERSION 3.12 FATAL_ERROR)project (recipe-02 LANGUAGES CXX)add_executable (hello-world hello-world.cpp)if (CMAKE_SYSTEM_NAME STREQUAL "Linux" ) target_compile_definitions (hello-world PUBLIC "IS_LINUX" ) endif ()if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" ) target_compile_definitions (hello-world PUBLIC "IS_MACOS" ) endif ()if (CMAKE_SYSTEM_NAME STREQUAL "Windows" ) target_compile_definitions (hello-world PUBLIC "IS_WINDOWS" ) endif ()
PRIVATE|INTERFACE|PUBLIC
限定符在 chapter 1.8 中有介绍。
chapter 2.3 处理与编译器相关的源代码
hello-world.cpp
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 #include <cstdlib> #include <iostream> #include <string> std::string say_hello () {#ifdef IS_INTEL_CXX_COMPILER return std::string ("Hello Intel compiler!" ); #elif IS_GNU_CXX_COMPILER return std::string ("Hello GNU compiler!" ); #elif IS_PGI_CXX_COMPILER return std::string ("Hello PGI compiler!" ); #elif IS_XL_CXX_COMPILER return std::string ("Hello XL compiler!" ); #else return std::string ("Hello unknown compiler - have we met before?" ); #endif } int main () { std::cout << say_hello () << std::endl; std::cout << "compiler name is " COMPILER_NAME << std::endl; return EXIT_SUCCESS; }
CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 target_compile_definitions (hello-world PUBLIC "COMPILER_NAME=\"${CMAKE_CXX_COMPILER_ID}\"" )if (CMAKE_CXX_COMPILER_ID MATCHES Intel) target_compile_definitions (hello-world PUBLIC "IS_INTEL_CXX_COMPILER" ) endif ()if (CMAKE_CXX_COMPILER_ID MATCHES GNU) target_compile_definitions (hello-world PUBLIC "IS_GNU_CXX_COMPILER" ) endif ()if (CMAKE_CXX_COMPILER_ID MATCHES PGI) target_compile_definitions (hello-world PUBLIC "IS_PGI_CXX_COMPILER" ) endif ()if (CMAKE_CXX_COMPILER_ID MATCHES XL) target_compile_definitions (hello-world PUBLIC "IS_XL_CXX_COMPILER" ) endif ()
chapter 2.4 检测处理器体系结构
通过检测空指针CMAKE_SIZEOF_VOID_P
的大小来判断是32位还是64位系统,通过CMAKE_HOST_SYSTEM_PROCESSOR MATCHES
来检测处理器架构。
1 2 3 4 5 6 7 if (CMAKE_SIZEOF_VOID_P EQUAL 8 ) target_compile_definitions (arch-dependent PUBLIC "IS_64_BIT_ARCH" ) message (STATUS "Target is 64 bits" ) else () target_compile_definitions (arch-dependent PUBLIC "IS_32_BIT_ARCH" ) message (STATUS "Target is 32 bits" ) endif ()
1 2 3 4 5 6 7 8 9 10 11 12 13 if (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i386" ) message (STATUS "i386 architecture detected" ) elseif (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "i686" ) message (STATUS "i686 architecture detected" ) elseif (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64" ) message (STATUS "x86_64 architecture detected" ) else () message (STATUS "host processor architecture is unknown" ) endif ()target_compile_definitions (arch-dependent PUBLIC "ARCHITECTURE=${CMAKE_HOST_SYSTEM_PROCESSOR}" )
chapter 2.5 检测处理器指令集
核心代码在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 39 40 41 42 43 cmake_minimum_required (VERSION 3.12 FATAL_ERROR)project (recipe-05 LANGUAGES CXX)add_executable (processor-info "" )target_sources (processor-info PRIVATE processor-info.cpp ) target_include_directories (processor-info PRIVATE ${PROJECT_BINARY_DIR} ) foreach (key IN ITEMS NUMBER_OF_LOGICAL_CORES NUMBER_OF_PHYSICAL_CORES TOTAL_VIRTUAL_MEMORY AVAILABLE_VIRTUAL_MEMORY TOTAL_PHYSICAL_MEMORY AVAILABLE_PHYSICAL_MEMORY IS_64BIT HAS_FPU HAS_MMX HAS_MMX_PLUS HAS_SSE HAS_SSE2 HAS_SSE_FP HAS_SSE_MMX HAS_AMD_3DNOW HAS_AMD_3DNOW_PLUS HAS_IA64 OS_NAME OS_RELEASE OS_VERSION OS_PLATFORM ) cmake_host_system_information (RESULT _${key} QUERY ${key} ) endforeach ()configure_file (config.h.in config.h @ONLY)
add_library
将指定的源文件生成链接文件(编译成库)并添加到工程中,add_executable
生成可执行文件并与库链接。
target_sources
往目标中追加源文件 。
target_include_directories
指定目标包含的头文件路径
target_link_libraries
将目标文件与库文件进行链接
target_compile_options
指定目标的编译选项
CMakeLists.txt
中的foreach
循环会查询多个键值,并定义相应的变量。此示例的核心函数是cmake_host_system_information
,它查询主机的系统信息。本例中,我们对每个键使用了一个函数调用。然后,使用这些变量来配置config.h.in
中的占位符,输入并生成config.h
。此配置使用configure_file
命令完成。最后,config.h
包含在processor-info.cpp
中。编译后,它将把值打印到屏幕上。
configure_file
命令的使用:
1 2 3 configure_file (<input> <output> [COPYONLY] [ESCAPE_QUOTES] [@ONLY] [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
拷贝 input 文件到其它地方 output,并更改其中的内容。
把 input 文件中@VAR@
或者 ${VAR}
引用的变量值替换成当前的变量值。
@ONLY是啥意思?
限制变量的替换,因为configure_file
命令会把输入文件中@或类型的变量都会替换成其变量值。 @ O N L Y 的意思是只替换 @ 类型的变量,而不替换 类型的变量都会替换成其变量值。@ONLY的意思是只替换@类型的变量,而不替换 类型的变量都会替换成其变量值。 @ ON L Y 的意思是只替换 @ 类型的变量,而不替换 类型的变量。这在配置 ${VAR} 语法的脚本时是非常有用的。
configure.h.in
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #pragma once #define NUMBER_OF_LOGICAL_CORES @_NUMBER_OF_LOGICAL_CORES@ #define NUMBER_OF_PHYSICAL_CORES @_NUMBER_OF_PHYSICAL_CORES@ #define TOTAL_VIRTUAL_MEMORY @_TOTAL_VIRTUAL_MEMORY@ #define AVAILABLE_VIRTUAL_MEMORY @_AVAILABLE_VIRTUAL_MEMORY@ #define TOTAL_PHYSICAL_MEMORY @_TOTAL_PHYSICAL_MEMORY@ #define AVAILABLE_PHYSICAL_MEMORY @_AVAILABLE_PHYSICAL_MEMORY@ #define IS_64BIT @_IS_64BIT@ #define HAS_FPU @_HAS_FPU@ #define HAS_MMX @_HAS_MMX@ #define HAS_MMX_PLUS @_HAS_MMX_PLUS@ #define HAS_SSE @_HAS_SSE@ #define HAS_SSE2 @_HAS_SSE2@ #define HAS_SSE_FP @_HAS_SSE_FP@ #define HAS_SSE_MMX @_HAS_SSE_MMX@ #define HAS_AMD_3DNOW @_HAS_AMD_3DNOW@ #define HAS_AMD_3DNOW_PLUS @_HAS_AMD_3DNOW_PLUS@ #define HAS_IA64 @_HAS_IA64@ #define OS_NAME "@_OS_NAME@" #define OS_RELEASE "@_OS_RELEASE@" #define OS_VERSION "@_OS_VERSION@" #define OS_PLATFORM "@_OS_PLATFORM@"
processor-info.cpp
只是把这些宏定义打印了一下。
Tips: 关于PROJECT_BINARY_DIR
和PROJECT_SOURCE_DIR
的区别。
如果目录结构是这样:
1 2 3 4 5 . ├── build ├── CMakeLists.txt ├── config.h.in └── processor-info.cpp
也就是把cmake编译生成的文件放在build文件夹中
那么PROJECT_BINARY_DIR
的值为/home/lushuangning/CodeHub/CMake/MyCmakeExample/chapter2/2.5/build
PROJECT_SOURCE_DIR
的值为/home/lushuangning/CodeHub/CMake/MyCmakeExample/chapter2/2.5/
如果让cmake编译生成的文件就生成在当前目录下(cmake .
),那么PROJECT_BINARY_DIR
的值为/home/lushuangning/CodeHub/CMake/MyCmakeExample/chapter2/2.5
chapter 2.6 为 Eigen 库使能向量化
暂未用到,如有需要后期补充。