CMake学习笔记
CMake概述
不同平台编译的问题
GNU Make,MS nmake,这些Make工具遵循着不同的规范和标准,所执行的Makefile格式千差万别。为了跨平台生成Makefile,CMake应时而生。
Cmake组成
CMake是一个构建系统,始于1999年,其开发公司Kitware设计其的目的是提供一组工具,实现在不同平台上配置、构建、测试和部署项目。
CMake软件工具集,主要包括:
- CMake:对可执行文件和库的构建
- CTest:测试相关
- CPack:打包
- CDash:测试结果面板展示
这些软件工具集构成的项目时序图如下所示:
CMake的使用分为两部分:
- 程序管理:书写CMakeList.txt这个文件
- 程序构建:通过CMake生成makefile等平台支持的构建文件
在linux平台下使用CMake生成Makefile并编译的流程如下:
CMake源文件组织结构
由如下三部分组成
- 目录(CMakeLists.txt),编译一个项目时的入口点就是CMakeLists.txt,可以由add_subdirectory()增添子目录,每个子目录也要有CMakeLists.txt,以此构成层级的源码树,编译时也会产生相应的构建树。
- 脚本(< Scripts>.cmake) 由cmake命令行工具直接运行(-P),不产生构建文件
- 模块(< module>.cmake)目录和脚本可以使用include()命令包含模块
CMake构建系统
CMake构建系统是一系列高级别的逻辑目标(targets)组成的,这些构建目标有如下三种:
- 可执行文件
- 库
- 包含特定命令的自定义目标
源文件语法
所有的源文件都是由命令调用组成的
基本概念
- 命令:即CMake中的函数,大部分情况下是设置“变量”,大小不敏感,形式为 name ( arguments)例如add_executable(hello world.c)
- 命令参数:如下三种
- 方括号参数,一般作为一个整体参数,如message([=[This is bracket argument]=])
- 引用参数,一般作为一个参数,如message(“This is a quoted argument”)
- 非引用参数,作为多个参数,foreach(arg
NoSpace
Escaped\ Space
This;Divides;Into;Five;Arguments
Escaped\;Semicolon
)
message("${arg}")
endforeach()
- 变量引用
${ < variable > }
$ENV(< variable>)
$CACHE{< variable>}
- 注释,以#开头,包括如下三种
中括号注释,如#[[ xxx
xxxxx]]
行注释,以#开头的一行
控制结构
- 条件:if()/elseif()/else()/endif()
- 循环:foreach()/endforeach()
- 宏:macro()/endmacro()
- 函数:function()/endfunction()
变量
- 存储空间的基本单位
- set() unset()命令来设置或者销毁变量
- 大小写敏感
- 有四种范围,函数变量,目录变量和持久缓存变量,环境变量
构建系统语法
二进制目标
可执行文件和库分别使用add_executable()和add_library()命令来定义,它们之间的依赖由target_link_libraries()来指明
#将archive.cpp zip.cpp lzma.cpp打包为archive静态库
#默认情况下定位一个STATIC库,除非使用SHARED显示声明
add_library(archive archive.cpp zip.cpp lzma.cpp)
#将zipapp.cpp定义为可执行文件
add_executable(zipapp zipapp.cpp)
#生成zipapp可执行文件时,需要用archive库
target_link_libraries(zipapp archive)
Object Libraries
Object Libraries定义了给定源文件编译后输出的非存档目标文件集合,其可用于其他目标的源文件输入,也可以被连接到其他目标
add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)
add_library(archiveExtras STATIC $<TARGET_OBJECTS:archive> extras.cpp)
add_executable(test_exe $<TARGET_OBJECTS:archive> test.cpp)
add_library(archive OBJECT archive.cpp zip.cpp lzma.cpp)
add_library(archiveExtras STATIC extras.cpp)
target_link_libraries(archiveExtras PUBLIC archive)
add_executable(test_exe test.cpp)
target_link_libraries(test_exe archive)
个性化构建和使用需求
target_include_directories(),target_compile_definitions()和target_compile_options()命令用来满足个性化构建和使用需求,他们分别用来设置目标的
- INCLUDE_DIRECTORIES
- COMPILE_DEFINATIONS
- COMPILE_OPTIONS
它们也有PRIVATE,PUBLIC和INTERFACE三种模式,被用来控制这些目标属性对依赖项的传递。
通常,如果依赖项只在库的实现中使用,而不是在头文件中使用,则应该在target_link_libraries()中指定PRIVATE关键字。如果在库的头文件中额外使用了一个依赖项(例如用于类继承),那么它应该被指定为PUBLIC依赖项。一个不被库的实现使用,而只被它的头文件使用的依赖应该被指定为一个接口依赖。
target_compile_definitions(archive
PRIVATE BUILDING_WITH_LZMA
INTERFACE USING_ARCHIVE_LIB
)
因为通常需要源目录和构建目录被添加到INCLUDE_DERECTORIES,可以启用CMAKE_INCLUDE_CURRENT_DIR变量,方便地将相应的目录添加到所有目标的INCLUDE_DIRECTORIES中
简单实例
- 最基本的CMakeLists.txt应该包含的东西
# 设置最低CMake版本
cmake_minimum_required(VERSION 3.5)
# 工程名称 版本号 使用的语言(c++)
project(test VERSION 0.1 LANGUAGES CXX)
# 非必须,设置工程包含当前目录
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
# 设置c++标准为c++11,且强制c++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找qt5中的库文件,REQUIRED表示为必须的
find_package(QT5 COMPONENTS Widgets REQUIRED)
# 设置自定义变量,将四个文件设置为PROJECT_SOURCES变量
set(PROJECT_SOURCES
main.cpp
widget.cpp
widget.h
widget.ui
)
#添加一个可执行文件,名字是test
add_executable(test
${PROJECT_SOURCES}
)
# test这个可执行文件需要一个第三方库,即Qt5中的Widgets
target_link_libraries(test Qt5::Widgets)
- 库文件怎么搞?
# 这是一个名为add的库文件
cmake_minimum_required(VERSION 3.14)
project(add LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#写两个是因为要兼容QT5和QT6
find_package(QT NAMES QT6 QT5 COMPONETNTS Core REQUIRED)
find_package(QT${QT_VERSION_MAJOR} COMPONENTS Core REQUIRED)
#与add_executable不同,此处library明显告诉是在生成一个库文件
add_library(add SHARED
add_global.h
add.cpp
add.h
)
target_link_libraries(add PRIVATE Qt${QT_VERSION_MAJOR}::core)
#额外的宏定义 在库add中增加ADD_LIBRARY
target_compile_definitions(add PRIVATE ADD_LIBRARY)
在库中增加宏定义的原因是因为一些头文件有一些条件宏定义,如下
#add_globar.h
#ifndef ADD_GLOBAL_H
#define ADD_GLOBAL_H
#include<QtCore/qglobar.h>
#if defined(ADD_LIBRARY)
# define ADD_EXPORT Q_DECL_EXPORT
#else
# define ADD_EXPORT Q_DECL_IMPORT
#endif
#endif
如上有一个自定义的库,但由于不是包,不能使用find_package命令,不必须通过如下方式引用
cmake_minimum_required(VERSION 3.5)
project(test VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_compile_options(-finput-charset=GBK)
find_package(Qt5 COMPONENTS Widgets REQUIRED)
set(PROJECT_SOURCES
main.cpp
widget.cpp
widget.h
widget.ui
)
# 告诉编译器头文件要去哪里找
#${CMAKE_CURRENT_SOURCE_DIR}就是指当前CMakeLists.txt所在目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/../
)
# 告诉编译器库文件本身要去哪里找
link_directories(
${CMKAE_CURRENT_SOURCE_DIR}/../build-add-unknown-Debug/
)
add_executable(test
${PROJECT_SOURCES}
)
target_link_libraries(test Qt5::Widgets)
# 指定要加载的libadd.so库即可
target_link_libraries(test libadd.so)
- 多库之间存在依赖问题
如上有两个程序,一个可执行文件test和一个库add,且test依赖于add
如何考虑这种情况的编译
cmake_minimum_required(VERSION 3.14)
project(ProgramOne Languages CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#通过add_subdirectory实现多程序管理
add_subdirectory(add)
add_subdirectory(test)
可以看到不管是根目录还是子目录,都重复出现了变量的设置。但父目录设置的一些变量会被应用于子目录,对于target_开头的命令其不会被子目录共享(因此其只适用于子对象)。
- make install
为了使所需文件不分散放置在各个源代码目录中,通常希望自动将编译好的文件放置在一个统一的目录中,可以
#通过FILES指定要安装哪些头文件,通过DESTINATION指定文件要放置的目录,如果不使用DESTINATION,则默认安装到默认目录相对于(CMAKE_INSTALL_PREFIX的lib目录)
install(FILES add_global.h add.h
DESTINATION include/add
)
#通过TARGEST指定要安装生成的库文件
install(TARGETS add
)
#不使用DESTINATION则表示安装到默认目录(相对于CMAKE_INSTALL_PREFIX的bin目录)
install(TARGETS test
)
#根据文件的类型指定放置的位置
install(TARGET add
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
)
- target_开头命令
target_开头命令不是一个全局命令,是一个只针对当前当前特定编译目标的设置
target_include_directories(test PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/../
)
target_compile_options(test PRIVATE -finput-charset=GBK)
补充的一些函数
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
|
+--- main.cc
|
+--- MathFunctions.cc
|
+--- MathFunctions.h
#法一
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 指定生成目标
add_executable(Demo main.cc MathFunctions.cc)
#法二
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (Demo2)
# 查找当前目录下的所有源文件
# 并将名称保存到 DIR_SRCS 变量
aux_source_directory(. DIR_SRCS)
# 指定生成目标
add_executable(Demo ${DIR_SRCS})
find_package
目的:用来查找外部包
查找的两种方式:
- Module mode:寻找Find< PackageName>.cmake
- Config mode:寻找< lowercasePackageName>-config[-version].cmake 或者< PackageName> Config.cmake,相较于Module mode,其查找方式更加复杂
基本签名
find_package(<PackageName> [version] [EXACT] [QUIET] [MODULE]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[NO_POLICY_SCOPE])
该基本签名适用于Module和Config两种模式,< PackageName>_FOUND变量的会被自动生设置以表明包被是否找到
全签名
find_package(<PackageName> [version] [EXACT] [QUIET]
[REQUIRED] [[COMPONENTS] [components...]]
[OPTIONAL_COMPONENTS components...]
[CONFIG|NO_MODULE]
[NO_POLICY_SCOPE]
[NAMES name1 [name2 ...]]
[CONFIGS config1 [config2 ...]]
[HINTS path1 [path2 ... ]]
[PATHS path1 [path2 ... ]]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_PACKAGE_REGISTRY]
[NO_CMAKE_BUILDS_PATH] # Deprecated; does nothing.
[NO_CMAKE_SYSTEM_PATH]
[NO_CMAKE_SYSTEM_PACKAGE_REGISTRY]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH])
配置模式试图去定位目标包< PackageName>_DIR目录中的配置文件< PackageName>Config.cmake 或者< lowercasePackageName>-config.cmake
target_include_directories
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
-
PRIVATE - the directory is added to this target’s include directories
-
INTERFACE - the directory is added to the include directories for any targets that link this library.
-
PUBLIC - As above, it is included in this library and also any targets that link this library.
Set
set命令可以设置普通变量、缓存条目、环境变量三种变量的值
- 普通变量
- 命令格式:set( … [PARENT_SCOPE])
- 命令含义:将变量variable设置为值…,变量variable的作用域为调用set命令的函数或者当前目录,如果使用了PARENT_SCOPE选项,意味着该变量的作用域会传递到上一层(也就是上一层目录或者当前函数的调用者,如果是函数则传递到函数的调用者,如果是目录则传递到上一层目录),并且在当前作用域该变量不受带PARENT_SCOPE选项的set命令的影响(如果变量之前没有定义,那么在当前作用域仍然是无定义的;如果之前有定义值,那么值和之前定义的值保持一致)。
- 缓存条目
- 命令格式:set( … CACHE [FORCE])
- 命令含义:将缓存条目variable设置为值…,除非用户进行设置或使用了选项FORCE,默认情况下缓存条目的值不会被覆盖。缓存条目可以通过CMAKE的GUI界面的add entry按钮来增加。缓存条目的实质为可以跨层级进行传递的变量,类似于全局变量。
缓存条目的主要有以下几类:
BOOL:布尔值ON/OFF,CMAKE的GUI界面对此类缓存条目会提供一个复选框。
FILEPATH:文件路径,CMAKE的GUI界面对此类缓存条目会提供一个文件选择框。
PATH:目录路径,CMAKE的GUI界面对此类缓存条目会提供一个目录选择框。
STRING / STRINGS:文本行,CMAKE的GUI界面对此类缓存条目会提供一个文本框(对应STRING)或下拉选择框(对应STRINGS)。
INTERNAL:文本行,但是只用于内部,不对外呈现。主要用于运行过程中存储变量,因此使用该type意味着使用FORCE。
- 环境变量
- 命令格式:set(ENV{} [])
- 命令含义:将环境变量设置为值(注意没有…),接着使用$ENV{}会得到新的值。cmake中的环境变量可以参考:环境变量。
环境变量设置的几个注意事项:
1)该命令设置的环境变量只在当前的cmake进程生效,既不会影响调用者的环境变量,也不会影响系统环境变量。
2)如果值为空或者ENV{}后没有参数,则该命令会清除掉当前环境变量的值。
3)后的参数会被忽略。
get_property
get_property(
<GLOBAL |
DIRECTORY [dir] |
TARGET |
SOURCE |
TEST |
CACHE |
VARIABLE>
PROPERTY
[SET | DEFINED | BRIEF_DOCS | FULL_DOCS])
获取在某个域中一个对象的某种属性值。第一个参数指定了存储属性值的变量。第二个参数确定了获取该属性的域。域的选项仅限于:
GLOBAL 域是唯一的,它不接受域名字。
DIRECTORY域默认为当前目录,但是其他的路径(已经被CMake处理过)可以以相对路径或完整路径的方式跟在该域后面。
TARGET域后面必须跟有一个已有的目标名。
SOURCE域后面必须跟有一个源文件名。
TEST域后面必须跟有一个已有的测试。
CACHE域后面必须跟有一个cache条目。
VARIABLE域是唯一的,它不接受域名字。
add_custom_target
在很多时候,经常需要在cmake中创建一些目标,如clean、copy,这就需要通过add_custom_target来指定。同时,add_custom_command可以用来完成对add_custom_target生成的target的补充
add_custom_command
- 生成文件
2. 构建事件
为某一个目标添加一个定制命令
cmake_minimum_required(VERSION 3.0)
project(test)
add_custom_target(CopyTask)
add_custom_command(TARGET CopyTask
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/config ${CMAKE_CURRENT_SOURCE_DIR}/etc
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/log.txt ${CMAKE_CURRENT_SOURCE_DIR}/etc
)