CMake简化了针对同一项目的不同平台、不同编译器的构建过程和编译过程,能够管理各种规模的项目。
根据构建目录和源目录的关系,将CMake的构建方式分成两种:
源内构建:
source directory和build directory相同
缺点:
一方面、所有的构建输出都与源文件混在一起,文件管理和版本管理都极为困难
一方面、清除所有构建输出,并使用干净的源代码树重新开始构建可能不容易
源外构建:
source directory和build directory为不同的目录或者在source directory定义build directory,如下所示
源外构建示意
ProjectDir
├── CMakeLists.txt
├── main.cxx
├── build
│ ├── CMakeCache.txt
│ └── ... build output files
├── module1
│ ├── func.cxx
│ ├── func.h
│ └── CMakeLists.txt
├── module2
│ ├── func.cxx
│ └── func.h
└── project_config.h.in
关于构建类型
CMake提供的默认构建类型:
# 在cmake .. 构建时,通过设置CMAKE_BUILD_TYPE缓存变量来指定构建类型
> cmake .. -DCMAKE_BUILD_TYPE=Debug
常见的支持multi-configuration的编译工具:Visual Studio和Xcode
常见的不支持multi-configuration的编译工具:Makefiles和Ninja。
给不支持multi-configuration的编译工具设置–config选项时,会自动忽略。
针对不支持multi-configuration的编译工具:
每个构建目录只支持一种构建类型。对于这些生成器,必须通过设置CMAKE_BUILD_TYPE缓存变量来选择构建类型。
不显示指定CMAKE_BUILD_TYPE缓存变量时,不同编译器可能会对缺省情况做特殊处理,有的以Debug模式,有的却是自己定义的不使用特定于配置的编译器或链接器标志的模式,所以使用过程中,显示指定CMAKE_BUILD_TYPE比较好。
cmake -G Ninja -DCMAKE_BUILD_TYPE=Release ../source
cmake --build . --config Release
需要切换不同的构建类型,常用策略是为不同的构建类型分别指定build目录:
ProjectDir
├── CMakeLists.txt
├── main.cxx
├── build-Debug
│ ├── CMakeCache.txt
│ └── ... build output files
├── build-Release
│ ├── CMakeCache.txt
│ └── ... build output files
├── module1
│ ├── func.cxx
│ ├── func.h
│ └── CMakeLists.txt
├── module2
│ ├── func.cxx
│ └── func.h
└── project_config.h.in
使用命令行示例如下:
cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ../source
cmake --build .
针对支持multi-configuration的编译工具:
支持在一个构建目录下的多个配置。这些生成器忽略CMAKE_BUILD_TYPE缓存变量,而是要求开发人员在IDE中选择构建类型,或者在编译时使用命令行选项。使用命令行示例如下:
> cmake -G Xcode ../source
> cmake --build . --config Debug
当然针对支持multi-configuration的编译工具,也可以考虑为不同构建类型提供不同目录的策略。
对于上述两类编译工具,命令行使用时,并不一定会指定CMAKE_BUILD_TYPE缓存变量,也就是说:
CMakeLists.txt并不总是知道构建类型,所以直接使用CMAKE_BUILD_TYPE进行判断会导致结果异常,如下代码所示
# CMakeLists.txt利用CMAKE_BUILD_TYPE判断构建类型(此用法不可取)
# 此方法适用于基于Makefile的生成器和Ninja,但不适用于Xcode或Visual Studio。
# WARNING: Do not do this!
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
# Do something only for debug builds
endif()
项目应该使用更健壮的替代技术,例如基于 $
# 如果当前构建类型属于cfgs列表当中的一种,则返回1,否则返回0
$<CONFIG:cfgs>
语法:
# cmake构建项目的编译系统
Generate a Project Buildsystem
> cmake [<options>] -B <path-to-build> [-S <path-to-source>]
> cmake [<options>] <path-to-source | path-to-existing-build>
# cmake编译项目
Build a Project
> cmake --build <dir> [<options>] [-- <build-tool-options>]
# cmake安装项目的编译结果
Install a Project
> cmake --install <dir> [<options>]
# Open a Project
> cmake --open <dir>
# Run a Script
> cmake [-D <var>=<value>]... -P <cmake-script-file>
# Run a Command-Line Tool
> cmake -E <command> [<options>]
# Run the Find-Package Tool
> cmake --find-package [<options>]
# Run a Workflow Preset
> cmake --workflow [<options>]
# View Help
> cmake --help[-<topic>]
**cmake构建项目的编译系统**
cmake [<options>] -B <path-to-build> [-S <path-to-source>]
- cmake3.13版本要求
- <path-to-build>:指定build tree的目录
- <path-to-source>:指定source tree的目录
- 文件目录要求可以是绝对路径也可以是基于所在工作目录的相对路径
- source tree的目录必须包含CMakeLists.txt文件
- build tree的目录如果在命令运行前未创建,择自动会创建
# 例子
> cmake -S ./src -B ./build
cmake [<options>] <path-to-source>
- 当前工作目录作为build tree的目录
- <path-to-source>:指定source tree的目录
- 文件目录要求可以是绝对路径也可以是基于所在工作目录的相对路径
- source tree的目录必须包含CMakeLists.txt文件但是不能包含**CMakeCache.txt**文件,因为CMakeCache.txt是用来识别一个已经存在的build tree
# 例子:
> mkdir build ; cd build
> cmake ../src
cmake [<options>] <path-to-existing-build>
- <path-to-build>:指定build tree的目录
- 文件目录要求可以是绝对路径也可以是基于所在工作目录的相对路径
- 指定build tree的目录必须包含**CMakeCache.txt**文件,才能保证cmake加载CMakeCache.txt指定的source tree
cmake构建项目的编译系统的命令行选项如下
# 指定需要cmake构建的项目根目录
> -S <path-to-source>
# 指定cmake为项目构建的编译系统目录
> -B <path-to-build>
# 创建或者更新一个CMake的cache入口,一般就是设定项目的一些自定义配置
> -D <var>:<type>=<value>, -D <var>=<value>
# 指定编译系统生成器
> -G <generator-name>
> 例子: cmake -G "Visual Studio 16 2019"
# 指定编译系统生成器的工具集
> -T <toolset-spec>
> 例子:cmake -G "Visual Studio 16 2019" -T "v141"
# 指定编译系统生成器的平台
> -A <platform-name>
> 例子:cmake -G "Visual Studio 16 2019" -A Win32
# 指定编译成果安装目录
> --install-prefix <directory>
# 指定日志输出级别
> --log-level=<level>
需要注意:
# 如果需要重新构建,需要删除构建目录下的所有内容,再用cmake构建
# 例子:
> rm -rf ./*
> cmake ..
**cmake编译项目**
使用cmake构建出编译系统后,可以使用cmake本身命令编译项目,也可以使用原生工具集编译项目,例如Linux的make、windows的nmake。
cmake --build <dir> [<options>] [-- <build-tool-options>]
cmake --build --preset <preset> [<options>] [-- <build-tool-options>]
cmake编译项目命令行选项:
--build <dir>
指定项目编译目录。
-j [<jobs>], --parallel [<jobs>]
指定用于编译的并发进程最大数量。如果<jobs>未指定,使用默认的数量。
某些原生的编译工具总是并发来编译。可以将<jobs>设置为1,用来限制单个job来编译。
--config <cfg>
对于支持multi-configuration的编译工具,指定configuration
* Debug: 用于在没有优化的情况下,使用带有调试符号构建库或者可执行文件
* Release: 用于构建的优化的库或者可执行文件,不包含调试符号
* RelWithDebInfo:用于构建较少的优化库或者可执行文件,包含调试符号
* MinSizeRel:用于不增加目标代码大小的优化方式,来构建库或者可执行文件
* 请注意,RelWithDebInfo的默认设置将禁用断言。
常见的支持multi-configuration的编译工具:Visual Studio和Xcode
给不支持multi-configuration的编译工具设置--config选项时,会自动忽略
-v, --verbose
表示打印详细编译过程
--clean-first
编译之前先执行clean操作
**cmake安装项目**
编译完成项目后,可以使用cmake本身命令安装项目,也可以使用原生工具集安装项目,例如Linux的make、windows的nmake。
cmake --install <dir> [<options>]
cmake安装项目命令行选项:
--install <dir>
指定编译成果所在目录。
--prefix <prefix>
覆盖默认的安装目录前缀,CMAKE_INSTALL_PREFIX。
-v, --verbose
表示打印详细安装过程
引用参考:
1.CMake官网
2.系统学习CMake
用于在CMakeLists.txt文件中记录日志消息
message([
] “message text” …) # General messages
message(“message text” …) # Reporting checks
message(CONFIGURE_LOG…) # Configure Log
General messages:在日志中记录指定的消息文本。如果给出了多个消息字符串(message string),最终会连接成一条消息,且字符串之间没有分隔符。
可选的关键字确定消息的类型:
FATAL_ERROR:CMake Error,停止处理和生成
SEND_ERROR:CMake Error,继续处理,但跳过生成
WARNING:CMake Warning,继续处理
AUTHOR_WARNING:CMake Warning(dev),继续处理
DEPRECATION:如果分别启用了CMAKE_ERROR_DEPRECATED或CMAKE_WARN_DEPRECATED,则CMake弃用(Deprecation)Error或Warning,否则没有消息
(none)或NOTICE:重要的消息打印到stderr以引起用户的注意。
STATUS:project用户可能感兴趣的主要信息。理想情况下,这些信息应该简明扼要,不超过一行,但仍然信息丰富
VERBOSE:针对project用户的详细信息消息。这些消息应提供在大多数情况下不感兴趣的额外详细信息,但是在编译项目时可以提供更丰富的消息让用户明白编译发生的过程
DEBUG: 针对工作项目的开发者的详细信息消息
TRACE:低级的细粒度消息,使用此日志级别的消息通常只是临时的,一般在发布项目、打包文件之前被删除
CMake3.15版本开始才增加了NOTICE、VERBOSE、DEBUG、TRACE
--log-level=
命令行选项可用于控制显示哪些消息。若不指定,默认不会显示verbose, debug, trace消息。也可通过CMAKE_MESSAGE_LOG_LEVEL变量设置。有效日志级别如下:ERROR, WARNING, NOTICE, STATUS (default), VERBOSE, DEBUG, or TRACE
示例:
# 输出多条字符串组合
MESSAGE(“github addr:” “https://github.com/xxx”) # github addr:https://github.com/xxx
# 输出cmake预定义变量的值
MESSAGE(STATUS “Binary Dir:” ${DEMO_BINARY_DIR}) # Binary Dir: ~/Test
Reporting checks:CMake输出中的一个常见模式是一条消息表明某种检查的开始,然后是另一条消息报告检查的结果。这可以使用message命令的CHECK_…关键字形式可以更强大、更方便地表达这一点。其中必须是以下之一:
用于cmake在编译完成后安装
安装Targets:
CMAKE_INSTALL_PREFIX: 指定安装目录的前缀路径
cmake --install时添加--prefix参数 会覆盖CMAKE_INSTALL_PREFIX指定的目录
> cmake --install . --prefix "/home/myuser/installdir"
install(TARGETS targets... [EXPORT <export-name>]
[RUNTIME_DEPENDENCIES args...|RUNTIME_DEPENDENCY_SET <set-name>]
[[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE|FILE_SET <set-name>|CXX_MODULES_BMI]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[NAMELINK_COMPONENT <component>]
[OPTIONAL] [EXCLUDE_FROM_ALL]
[NAMELINK_ONLY|NAMELINK_SKIP]
] [...]
[INCLUDES DESTINATION [<dir> ...]]
)
参数中的TARGETS后面跟的就是我们通过ADD_EXECUTABLE或者ADD_LIBRARY定义的目标文件,可能是可执行二进制、动态库、静态库。
目标类型:ARCHIVE特指静态库或者windows的dll对应的lib导入库,LIBRARY特指动态库,RUNTIME特指可执行目标二进制。
DESTINATION定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候CMAKE_INSTALL_PREFIX其实就无效了。如果你希望使用CMAKE_INSTALL_PREFIX来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION定义的路径>
CONFIGURATIONS为不同的编译配置设置安装规则(Debug, Release, 等等)。
install(TARGETS target
CONFIGURATIONS Debug
RUNTIME DESTINATION Debug/bin)
install(TARGETS target
CONFIGURATIONS Release
RUNTIME DESTINATION Release/bin)
PERMISSIONS 权限:
OWNER_WRITE,OWNER_READ,GROUP_READ和WORLD_READ组合:表示权限644
举例:
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
# 二进制myrun安装到${CMAKE_INSTALL_PREFIX}/bin目录
# 动态库lib mylib安装${CMAKE_INSTALL_PREFIX}/lib目录
# 静态库lib mystaticlib安装到${CMAKE_INSTALL_PREFIX}/libstatic目录
# 特别注意的是不需要关心TARGETS具体生成的路径,只需要写上TARGETS名称就可以了。
安装Files:
CMAKE_INSTALL_PREFIX: 指定安装目录的前缀路径
cmake --install时添加--prefix参数 会覆盖CMAKE_INSTALL_PREFIX指定的目录
> cmake --install . --prefix "/home/myuser/installdir"
install(<FILES|PROGRAMS> files...
TYPE <type> | DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL])
安装Directories:
CMAKE_INSTALL_PREFIX: 指定安装目录的前缀路径
cmake --install时添加--prefix参数 会覆盖CMAKE_INSTALL_PREFIX指定的目录
> cmake --install . --prefix "/home/myuser/installdir"
install(DIRECTORY dirs...
TYPE <type> | DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>] [EXCLUDE_FROM_ALL]
[FILES_MATCHING]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
DIRECTORY后面的参数是所在Source目录的相对路径,需要注意:
abc和abc/有很大的区别。
PATTERN用于使用正则表达式进行过滤,PERMISSIONS用于指定PATTERN过滤后的文件权限。
例子:
INSTALL(DIRECTORY icons scripts/ DESTINATION test/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE WONER_READ GROUP+EXECUTE GROUP_READ)
# 将icons目录安装到<prefix>/test/myproj,将scripts/中的内容安装到<prefix>/test/myproj
# 不包含名为CVS的目录
# 对于scripts/*文件指定权限为OWNER_EXECUTE OWNER_WRITE WONER_READ GROUP_EXECUTE GROUP_READ
安装时CMAKE脚本的执行
INSTALL([ [SCRIPT < file>] [ CODE < code >]] [...])
# SCRIPT参数用于在安装时调用cmake脚本文件(也就是<abc>.cmake文件)
# CODE参数用于执行CMAKE指令,必须以双引号括起来。比如:
INSTALL(CODE "MESSAGE(\"example install message.\")")
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
这三个变量指代的内容是一致的,如果是内部编译则指的是工程顶层目录。如果是外部编译则指的是工程编译发生的目录,PROJECT_BINARY_DIR跟其他指令稍有区别。
示例:
# 示例采用【call lib cmake例子】
MESSAGE(STATUS "Binary Dir:" ${FIFTH_BINARY_DIR})
MESSAGE(STATUS "CMake Binary Dir:" ${CMAKE_BINARY_DIR})
MESSAGE(STATUS "Project Binary Dir:" ${PROJECT_BINARY_DIR})
CMAKE_SOURCE_DIR
PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR
这三个变量的内容是一致的,不论采用何种编译方式,都是工程顶层目录。
示例:
# 示例采用【call lib cmake例子】
MESSAGE(STATUS "Source Dir:" ${FIFTH_SOURCE_DIR})
MESSAGE(STATUS "CMake Source Dir:" ${CMAKE_SOURCE_DIR})
MESSAGE(STATUS "Project Source Dir:" ${PROJECT_SOURCE_DIR})
CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的CMakeLists.txt所在的路径,比如上面我们提到的liba和libb子目录。
# 示例采用【call lib cmake例子】
# ./CMakeLists.txt
MESSAGE(STATUS "the fifthlib root CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
# liba/CMakeLists.txt
MESSAGE(STATUS "the liba CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
# libb/CMakeLists.txt
MESSAGE(STATUS "the libb CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
CMAKE_CURRENT_BINARY_DIR
如果是内部编译,则它与CMAKE_CURRENT_SOURCE_DIR一致,如果是外部编译则指的是target编译目录。使用我们上面说的ADD_SUBDIRECTORY(liba liba_build)可以更改这个变量的值。使用SET(EXECUTABLE_OUTPUT_PATH <新路径>)并不会对这个变量造成影响,它仅仅修改了最终目标存放的路径。
# 示例采用【call lib cmake例子】
# ./CMakeLists.txt
MESSAGE(STATUS "the fifthlib root CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
# liba/CMakeLists.txt
MESSAGE(STATUS "the liba CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
# libb/CMakeLists.txt
MESSAGE(STATUS "the libb CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
输出结果如下:
如图所见,ADD_SUBDIRECTORY(liba liba_build)改变了CMAKE_CURRENT_BINARY_DIR的值
引用参考:
1.CMake官网
2.CMake中message的使用
3.cmake使用教程(实操版)
代码参见挂载的资源文件
提示:最简单的cmake使用例子:
新建文件夹hello,并进入hello文件夹,文件组织如下
hello
├── build/: CMake构建过程、编译过程的中间文件和结果文件保存目录(这样子可以确保不会污染项目源文件夹)
├── CMakeLists.txt: CMake构建配置文件
├── main.cc: 编译的源码
└── install/: 项目编译结果安装目录
main.cc:
#include
int main() {
std::cout << "hello cmake" << std::endl;
return 0;
}
CMakeLists.txt:
PROJECT(HELLO)
MESSAGE(STATUS "Binary Dir:" ${HELLO_BINARY_DIR})
MESSAGE(STATUS "Source Dir:" ${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello main.cc)
构建过程:
#1. 切换到build目录
> cd ./build
#2. cmake构建
> cmake .. #..表示build的上级目录(即CMakeLists.txt所在的根目录)
#3. 编译(可直接使用cmake自带的编译命令,也可根据编译器选用特定的编译命令,比如Linux的make命令,windows的nmake命令)
#3.1 cmake自带的编译命令
> cmake --build . # .表示build当前目录
#3.2 make编译命令
> make .
#4. 安装(可直接使用cmake自带的安装命令,也可根据编译器选用特定的安装命令,比如Linux的make命令,windows的nmake命令)
> cmake --install . --prefix ../install
cmake .. 命令输出:
此时生成的编译系统及中间文件如下:
cmake --build . 命令输出:
此时编译成果如下:
例子中内容解释:
PROJECT(projectname [CXX] [C] [Java])
用这个语句定义工程名称,并且可以指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。这个指令隐式的定义了两个cmake的变量:
<projectname>_BINARY_DIR:cmake构建的二进制目标目录
<projectname>_SOURCE_DIR:cmake构建项目的源目录
示例:见<cmake .. 命令输出:>截图
使用示例:${HELLO__BINARY_DIR}
这里需要注意:
使用 方式来取得变量中的值,如 {}方式来取得变量中的值,如 方式来取得变量中的值,如{HELLO__BINARY_DIR},而在IF语句中则直接使用变量名。
提示:子目录的cmake使用例子:
新建文件夹second,并进入second文件夹,文件组织如下
second
├── build/: CMake构建过程、编译过程的中间文件和结果文件保存目录(这样子可以确保不会污染项目源文件
├── CMakeLists.txt: CMake根目录构建配置文件
└── src/: 子目录
├── CMakeLists.txt: CMake子目录构建配置文件
└── main.cc: 编译的源码
src/main.cc:
#include
int main() {
std::cout << "hello cmake" << std::endl;
return 0;
}
src/CMakeLists.txt:
SET(SRC_LIST main.cc)
ADD_EXECUTABLE(hello ${SRC_LIST})
CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
PROJECT(HELLO)
MESSAGE(STATUS "Binary Dir:" ${HELLO_BINARY_DIR})
MESSAGE(STATUS "Source Dir:" ${HELLO_SOURCE_DIR})
ADD_SUBDIRECTORY(src bin)
构建过程:
#1. 切换到build目录
> cd ./build
#2. cmake构建
> cmake .. #..表示build的上级目录(即CMakeLists.txt所在的根目录)
#3. 编译(可直接使用cmake自带的编译命令,也可根据编译器选用特定的编译命令,比如Linux的make命令,windows的nmake命令)
#3.1 cmake自带的编译命令
> cmake --build . # .表示build当前目录
#3.2 make编译命令
> make .
例子中内容解释:
SET(SRC_LIST main.cc)
表示将源代码文件列表定义为一个变量,方便后续命令调用
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个命令用于向当前工程添加存放源文件的子目录。并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL参数的含义是将这个目录从编译过程中排除,比如,某个工程中的example,可能就需要工程构建编译完成后,再进入example目录单独进行构建编译(当然,你可以通过定义依赖来解决此类问题)。
本例子定义了将src子目录加入工程,并指定编译输出(包含编译中间结果)路径为bin目录。如果不进行bin目录的指定,那么编译结果(包括中间结果)都将存放在build/src目录(这个目录跟原来的src目录名字对应),指定bin目录后,相当于在编译时将src重命名为bin,所有的中间结果和目标二进制都存放在bin目录中。
可以通过SET指令重新定义EXECUTABLE_OUTPUT_PATH和LIBRARY_OUTPUT_PATH变量来指定最终的编译目标二进制的位置
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
提示:同时生成动态库和静态库的cmake使用例子:
新建文件夹third,并进入third文件夹,文件组织如下
third
├── build/: CMake构建过程、编译过程的中间文件和结果文件保存目录(这样子可以确保不会污染项目源文件
├── CMakeLists.txt: CMake根目录构建配置文件
└── libsrc/: 子目录
├── CMakeLists.txt: CMake子目录构建配置文件
├── liba.cxx: 编译的lib源码
└── liba.h: 编译的lib头文件
libsrc/lib.cxx:
#include "liba.h"
#include
void LibAFunc1() {
std::cout << "liba func1" << std::endl;
}
libsrc/lib.h:
#pragma once
void LibAFunc1();
libsrc/CMakeLists.txt:
SET(LIBSRC liba.cxx)
ADD_LIBRARY(liba SHARED ${LIBSRC})
# shared和static库的目标设置同名,cmake在构建编译系统时,会报错错误如下图
# ADD_LIBRARY(liba STATIC ${LIBSRC})
# 需要设置不同名的目标
ADD_LIBRARY(liba_static STATIC ${LIBSRC})
ADD_LIBRARY的shared和static库的目标设置同名,cmake构建编译系统报错:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
PROJECT(THIRD)
MESSAGE(STATUS "Binary Dir:" ${THIRD_BINARY_DIR})
MESSAGE(STATUS "Source Dir:" ${THIRD_SOURCE_DIR})
ADD_SUBDIRECTORY(libsrc bin)
构建过程:
#1. 切换到build目录
> cd ./build
#2. cmake构建
> cmake .. #..表示build的上级目录(即CMakeLists.txt所在的根目录)
#3. 编译(可直接使用cmake自带的编译命令,也可根据编译器选用特定的编译命令,比如Linux的make命令,windows的nmake命令)
#3.1 cmake自带的编译命令
> cmake --build . # .表示build当前目录
#3.2 make编译命令
> make .
cmake --build . 编译结果:
例子中内容解释:
利用ADD_LIBRARY为shared和static分别设置名字的方式,可以实现同时编译生成动态库和静态库。
考虑动态库和静态库只是后缀的不同,一个是’.so’,一个是’.a’,若要实现同名输出需要采用如下方案:
libsrc/CMakeLists.txt:
SET(LIBSRC liba.cxx)
# 设置lib的输出目录 此处会覆盖根目录CMakeLists.txt中ADD_SUBDIRECTORY指定的输出目录
# 设置后输出如下图
# SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
# MESSAGE (STATUS "This is the LIBRARY_OUTPUT_PATH: " ${LIBRARY_OUTPUT_PATH})
ADD_LIBRARY(liba SHARED ${LIBSRC})
# shared和static库的目标设置同名,cmake在构建编译系统时,会报错错误如下图
# ADD_LIBRARY(liba STATIC ${LIBSRC})
# 需要设置不同名的目标
ADD_LIBRARY(liba_static STATIC ${LIBSRC})
# SET_TARGET_PROPERTIES可以设置target的属性
# 这里将liba_static这个target的${OUTPUT_NAME}属性值设置为与动态库同名
SET_TARGET_PROPERTIES(liba_static PROPERTIES OUTPUT_NAME "liba")
GET_TARGET_PROPERTY(OUT_NAME_VAL liba_static OUTPUT_NAME)
MESSAGE (STATUS "This is the liba_static OUTPUT_NAME: " ${OUT_NAME_VAL})
# SET_TARGET_PROPERTIES可以设置动态库的版本和接口版本
SET_TARGET_PROPERTIES(liba PROPERTIES VERSION 1.2 SOVERSION 1)
GET_TARGET_PROPERTY(LIBA_VER liba VERSION)
MESSAGE (STATUS "This is the liba version: " ${LIBA_VER})
libsrc/CMakeLists.txt设置LIBRARY_OUTPUT_PATH 后,编译输出如下图:
例子中内容解释:
SET_TARGET_PROPERTIES(target1 target2 ...PROPERTIES prop1 value1 prop2 value2 ...)
可以设置target的属性,包括输出的名称、动态库的版本和API版本等等
可以通过GET_TARGET_PROPERTY获取target的属性值,如果不存在,返回xxx-NOTFOUND标记
设置动态库的版本和接口版本可以通过如下命令设置
SET_TARGET_PROPERTIES(liba PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION:表示动态库版本
SOVERSION:表示动态库API版本
提示:同时调用动态库和静态库的cmake使用例子:
构建两个项目:fifth(调用lib的应用)、fifthlib(lib库)
新建文件夹fifthlib,并进入fifthlib文件夹,文件组织如下
fifthlib
├── build/: CMake构建过程、编译过程的中间文件和结果文件保存目录(这样子可以确保不会污染项目源文件
├── CMakeLists.txt: CMake根目录构建配置文件
├── liba/: liba库
│ ├── CMakeLists.txt
│ ├── liba.cxx
│ └── liba.h
└── libb/: libb库
├── CMakeLists.txt
├── libb.cxx
└── libb.h
liba/CMakeLists.txt:
SET(LIBSRC liba.cxx)
MESSAGE(STATUS "the liba CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
MESSAGE(STATUS "the liba CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
# 设置lib的输出目录 此处会覆盖根目录CMakeLists.txt中ADD_SUBDIRECTORY指定的输出目录
# 设置后输出如下图
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
MESSAGE (STATUS "This is the liba LIBRARY_OUTPUT_PATH: " ${LIBRARY_OUTPUT_PATH})
ADD_LIBRARY(liba SHARED ${LIBSRC})
# shared和static库的目标设置同名,cmake在构建编译系统时,会报错错误
# ADD_LIBRARY(liba STATIC ${LIBSRC})
# 需要设置不同名的目标
ADD_LIBRARY(liba_static STATIC ${LIBSRC})
# SET_TARGET_PROPERTIES可以设置target的属性
# 这里将liba_static这个target的${OUTPUT_NAME}属性值设置为与动态库同名,最后可以生成与动态库同样名字的静态库
SET_TARGET_PROPERTIES(liba_static PROPERTIES OUTPUT_NAME "liba")
GET_TARGET_PROPERTY(OUT_NAME_VAL liba_static OUTPUT_NAME)
MESSAGE (STATUS "This is the liba_static OUTPUT_NAME: " ${OUT_NAME_VAL})
# SET_TARGET_PROPERTIES可以设置动态库的版本和接口版本
SET_TARGET_PROPERTIES(liba PROPERTIES VERSION 1.2 SOVERSION 1)
GET_TARGET_PROPERTY(LIBA_VER liba VERSION)
MESSAGE (STATUS "This is the liba version: " ${LIBA_VER})
# cmake --install安装指令
# 最终安装路径:@prefix/lib_inst/lib @prefix/lib_inst/lib_static @prefix/lib_inst/include/liba
INSTALL(TARGETS liba liba_static LIBRARY DESTINATION lib_inst/lib ARCHIVE DESTINATION lib_inst/lib_static)
INSTALL(FILES liba.h DESTINATION lib_inst/include/liba)
liba/liba.cxx:
#include "liba.h"
#include
void LibAFunc1() {
std::cout << "liba func1" << std::endl;
}
liba/liba.h:
#pragma once
void LibAFunc1();
libb/CMakeLists.txt:
SET(LIBSRC libb.cxx)
MESSAGE(STATUS "the libb CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
MESSAGE(STATUS "the libb CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
# 设置lib的输出目录 此处会覆盖根目录CMakeLists.txt中ADD_SUBDIRECTORY指定的输出目录
# 设置后输出如下图
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
MESSAGE (STATUS "This is the libb LIBRARY_OUTPUT_PATH: " ${LIBRARY_OUTPUT_PATH})
ADD_LIBRARY(libb SHARED ${LIBSRC})
# shared和static库的目标设置同名,cmake在构建编译系统时,会报错错误
# ADD_LIBRARY(libb STATIC ${LIBSRC})
# 需要设置不同名的目标
ADD_LIBRARY(libb_static STATIC ${LIBSRC})
# SET_TARGET_PROPERTIES可以设置target的属性
# 这里将libb_static这个target的${OUTPUT_NAME}属性值设置为与动态库同名,最后可以生成与动态库同样名字的静态库
SET_TARGET_PROPERTIES(libb_static PROPERTIES OUTPUT_NAME "libb")
GET_TARGET_PROPERTY(OUT_NAME_VAL libb_static OUTPUT_NAME)
MESSAGE (STATUS "This is the libb_static OUTPUT_NAME: " ${OUT_NAME_VAL})
# SET_TARGET_PROPERTIES可以设置动态库的版本和接口版本
SET_TARGET_PROPERTIES(libb PROPERTIES VERSION 1.2 SOVERSION 1)
GET_TARGET_PROPERTY(LIBB_VER libb VERSION)
MESSAGE (STATUS "This is the libb version: " ${LIBB_VER})
# cmake --install安装指令
# 最终安装路径:@prefix/lib_inst/lib @prefix/lib_inst/lib_static @prefix/lib_inst/include/libb
INSTALL(TARGETS libb libb_static LIBRARY DESTINATION lib_inst/lib ARCHIVE DESTINATION lib_inst/lib_static)
INSTALL(FILES libb.h DESTINATION lib_inst/include/libb)
libb/libb.cxx:
#include "libb.h"
#include
void LibBFunc1() {
std::cout << "libb func1" << std::endl;
}
libb/libb.h:
#pragma once
void LibBFunc1();
./CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
PROJECT(FIFTHLIB)
MESSAGE(STATUS "Binary Dir:" ${FIFTHLIB_BINARY_DIR})
MESSAGE(STATUS "Source Dir:" ${FIFTHLIB_SOURCE_DIR})
MESSAGE(STATUS "the fifthlib root CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
MESSAGE(STATUS "the fifthlib root CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
# ADD_SUBDIRECTORY定义的目标二进制输出文件夹liba_build和libb_build
# 最后的编译结果输出会被liba/CMakeLists.txt和libb/CMakeLists.txt设定的${LIBRARY_OUTPUT_PATH}路径取代
# 但是中间临时文件依然是输出到liba_build和libb_build文件夹
ADD_SUBDIRECTORY(liba liba_build)
ADD_SUBDIRECTORY(libb libb_build)
构建过程:
#1. 切换到build目录
> cd ./build
#2. cmake构建
> cmake .. #..表示build的上级目录(即CMakeLists.txt所在的根目录)
#3. 编译(可直接使用cmake自带的编译命令,也可根据编译器选用特定的编译命令,比如Linux的make命令,windows的nmake命令)
#3.1 cmake自带的编译命令
> cmake --build . # .表示build当前目录
#3.2 make编译命令
> make .
#4. 安装(可直接使用cmake自带的安装命令,也可根据编译器选用特定的安装命令,比如Linux的make命令,windows的nmake命令)
> cmake --install . --prefix ../../fifth
cmake .. 构建编译系统过程:
cmake .. 构建编译系统结果:
cmake --build . 编译结果:
cmake --install . --prefix ../../fifth 安装过程:
cmake --install . --prefix ../../fifth 安装结果:
新建文件夹fifth,并进入fifth文件夹,文件组织如下
fifth
├── build/: CMake构建过程、编译过程的中间文件和结果文件保存目录(这样子可以确保不会污染项目源文件
├── CMakeLists.txt: CMake根目录构建配置文件
├── lib_inst/: fifthlib安装位置,也是fifth引用fifthlib的位置
└── main.cxx
./CMakeLists.txt:
cmake_minimum_required(VERSION 3.20)
PROJECT(FIFTH)
MESSAGE(STATUS "Project Name:" ${PROJECT_NAME})
MESSAGE(STATUS "CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
MESSAGE(STATUS "CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
MESSAGE(STATUS "Binary Dir:" ${FIFTH_BINARY_DIR})
MESSAGE(STATUS "CMake Binary Dir:" ${CMAKE_BINARY_DIR})
MESSAGE(STATUS "Project Binary Dir:" ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "Source Dir:" ${FIFTH_SOURCE_DIR})
MESSAGE(STATUS "CMake Source Dir:" ${CMAKE_SOURCE_DIR})
MESSAGE(STATUS "Project Source Dir:" ${PROJECT_SOURCE_DIR})
ADD_EXECUTABLE(fifth main.cxx)
# 编译过程中引用的include目录
target_include_directories(fifth PUBLIC ${FIFTH_SOURCE_DIR}/lib_inst/include)
# 编译过程中链接的动态库和静态库位置
target_link_directories(fifth PUBLIC ${FIFTH_SOURCE_DIR}/lib_inst/lib PUBLIC ${FIFTH_SOURCE_DIR}/lib_inst/lib_static)
# 编译过程中链接liba的动态库,名字与liba库名字对应
# 编译过程中链接libb的静态库,因为同时安装了libb的动态库和静态库,所以这里需要明确指定链接静态库
# make编译结果会为library自动加上'lib'前缀
target_link_libraries(fifth PUBLIC liba PUBLIC liblibb.a)
# 在指定目录下查找指定库
find_library(TEST_LIB NAMES libtestfnd HINTS ${FIFTH_SOURCE_DIR}/lib_inst/lib ${FIFTH_SOURCE_DIR}/lib_inst/lib_static)
message("TEST_LIB=" ${TEST_LIB})
# find_library若没有找到库,会返回var-NOTFOUND
# 可用var-NOTFOUND完成相关逻辑判断
# if(TEST_LIB-NOTFOUND)模式已经不支持,可采用模式:if(NOT TEST_LIB)和if(${TEST_LIB} STREQUAL "TEST_LIB-NOTFOUND")判断
if(TEST_LIB-NOTFOUND) # 此种判断已经不支持
message(STATUS "find_library test 'TEST_LIB-NOTFOUND' !")
endif()
if(NOT TEST_LIB)
message(STATUS "find_library test 'NOT TEST_LIB' !")
endif()
if(${TEST_LIB} STREQUAL "TEST_LIB-NOTFOUND")
message(STATUS "find_library test 'STREQUAL TEST_LIB-NOTFOUND' !")
endif()
# 查找静态库
find_library(TEST_LIBA_STATIC NAMES libliba.a HINTS ${FIFTH_SOURCE_DIR}/lib_inst/lib ${FIFTH_SOURCE_DIR}/lib_inst/lib_static)
message("TEST_LIBA_STATIC=" ${TEST_LIBA_STATIC})
if(NOT TEST_LIBA_STATIC)
message(STATUS "liba static find_library test 'NOT TEST_LIBA_STATIC' !")
else()
message(STATUS "liba static find_library test success !")
endif()
./main.cxx:
#include
#include
#include
int main() {
LibAFunc1();
LibBFunc1();
return 1;
}
构建过程:
#1. 切换到build目录
> cd ./build
#2. cmake构建
> cmake .. #..表示build的上级目录(即CMakeLists.txt所在的根目录)
#3. 编译(可直接使用cmake自带的编译命令,也可根据编译器选用特定的编译命令,比如Linux的make命令,windows的nmake命令)
#3.1 cmake自带的编译命令
> cmake --build . # .表示build当前目录
#3.2 make编译命令
> make .
例子中内容解释:
最终运行fifth时,可能会提示
./fifth: error while Loading shared libraries: libliba.so: cannot open shared obiect file: No such file or directory
出现错误的原因是:链接器ld找不到库文件。ld默认目录是/lib和/usr/lib,如果放在其他路径也可以,需要让ld知道文件的所在路径。
解决方法如下:
# 1.将用户用到的库统一放到一个目录,如 /usr/local/lib
> cp libXXX.so.X /usr/local/lib/
# 2.向库配置文件中,写入库文件所在目录
> vi /etc/ld.so.conf.d/usr-libs.conf
添加/usr/local/lib
# 3.更新/etc/ld.so.cache文件
> ldconfig
程序编译过程中,链接器ld默认搜索目录:
使用ldd命令可以查看某个应用或者lib的链接情况
ldd ./fifth
利用
find_library (
<VAR>
name | NAMES name1 [name2 ...] [NAMES_PER_DIR]
[HINTS [path | ENV var]... ]
[PATHS [path | ENV var]... ]
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[PATH_SUFFIXES suffix1 [suffix2 ...]]
[VALIDATOR function]
[DOC "cache documentation string"]
[NO_CACHE]
[REQUIRED]
[NO_DEFAULT_PATH]
[NO_PACKAGE_ROOT_PATH]
[NO_CMAKE_PATH]
[NO_CMAKE_ENVIRONMENT_PATH]
[NO_SYSTEM_ENVIRONMENT_PATH]
[NO_CMAKE_SYSTEM_PATH]
[NO_CMAKE_INSTALL_PREFIX]
[CMAKE_FIND_ROOT_PATH_BOTH |
ONLY_CMAKE_FIND_ROOT_PATH |
NO_CMAKE_FIND_ROOT_PATH]
)
find_library: 在指定目录下查找指定库,并把库的绝对路径存放到变量里。如果没找到库,结果将为-NOTFOUND。
# 选项说明
- NAMES:为要查找的库指定一个或多个可能的名字。
注:名字既可以仅是库的名称,也可以是全称,例如liba库,可以是liba,也可以是libliba.so/libliba.a
- HINTS, PATHS:指定除默认位置外要搜索的目录。
注:HINTS是在搜索系统路径之前先搜索HINTS指定的路径。PATHS是先搜索系统路径,然后再搜索PATHS指定的路径。
示例:
find_library(TEST_LIBA_STATIC NAMES libliba.a HINTS ${FIFTH_SOURCE_DIR}/lib_inst/lib ${FIFTH_SOURCE_DIR}/lib_inst/lib_static)
message("TEST_LIBA_STATIC=" ${TEST_LIBA_STATIC})
if(NOT TEST_LIBA_STATIC)
message(STATUS "liba static find_library test 'NOT TEST_LIBA_STATIC' !")
else()
message(STATUS "liba static find_library test success !")
endif()
target_include_directories为taget增加include目录集合
示例:
# 编译fifth的target时,为taget指定include目录
target_include_directories(fifth PUBLIC ${FIFTH_SOURCE_DIR}/lib_inst/include)
提示:存在多个子文件夹参与编译的cmake使用例子:
构建项目:sixth
新建文件夹sixth,并进入sixth文件夹,文件组织如下
sixth
├── build/: CMake构建过程、编译过程的中间文件和结果文件保存目录(这样子可以确保不会污染项目源文件
├── CMakeLists.txt: CMake根目录构建配置文件
├── main.cxx
├── module1
│ ├── func.cxx
│ └── func.h
├── module2
│ ├── func.cxx
│ ├── func.h
│ └── CMakeLists.txt: module2子目录的CMake构建配置文件
├── module3
│ ├── func.cxx
│ └── func.h
└── sixth_config.h.in:CMakeLists.txt定义,配置头文件集成到项目当中,可定义唯一的配置来源
./CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
# project指定项目名、版本号、编程语言
project(SIXTH VERSION 1.1 LANGUAGES C CXX)
# 指定C++编译标准
# 方式一:利用set指定 the C++ standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# 方式二:利用接口库的形式
# add_library(sixth_cxx_compiler_flags INTERFACE)
# target_compile_features(sixth_cxx_compiler_flags INTERFACE cxx_std_11)
# 指定自定义变量
set(TEST_VERBOSE_FLAG "sixth demo")
# configure a header file to pass some of the CMake settings
# to the source code
# 利用configure_file()命令可以实现文件复制,并且替换文件中@var@的变量值
configure_file(sixth_config.h.in ${SIXTH_BINARY_DIR}/include/sixth_config.h)
message(STATUS "Project Name:" ${PROJECT_NAME})
message(STATUS "CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
message(STATUS "Binary Dir:" ${SIXTH_BINARY_DIR})
message(STATUS "Source Dir:" ${SIXTH_SOURCE_DIR})
# 设置编译结果的输出目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
message(STATUS "Executable Binary Out Dir:" ${EXECUTABLE_OUTPUT_PATH})
# 定义一个构建编译系统选项
option(USE_MODULE3 "Use module3 implementation" ON)
# 源文件收集存储
# ./module1
aux_source_directory(${SIXTH_SOURCE_DIR}/module1 M1_SRC_DIR)
# ./module2
# aux_source_directory(${SIXTH_SOURCE_DIR}/module2 M2_SRC_DIR)
# module2中的源码采用object的生成方式
add_subdirectory(${SIXTH_SOURCE_DIR}/module2 module2_obj_out)
message(STATUS "USE_MODULE3:" ${USE_MODULE3})
# ./module3
if(USE_MODULE3)
# 设置module3的源码集合变量
set(M3_SRC_LIST ${SIXTH_SOURCE_DIR}/module3/func.cxx)
endif()
# 根目录的源文件列表
set(MAIN_SRC_LIST main.cxx)
if(USE_MODULE3)
# add_executable(sixth ${M1_SRC_DIR} ${M2_SRC_DIR} ${M3_SRC_LIST} ${MAIN_SRC_LIST})
# sixth基于aux_source_directory将module1纳入编译
# sixth基于add_subdirectory链接module2的object
# sixth基于set将module3纳入编译
# sixth基于set将main.cxx纳入编译
add_executable(sixth ${M1_SRC_DIR} $<TARGET_OBJECTS:module2> ${M3_SRC_LIST} ${MAIN_SRC_LIST})
# 增加编译选项
target_compile_definitions(sixth PRIVATE "USE_MODULE3")
else()
# module3/的源码不参与编译
#add_executable(sixth ${M1_SRC_DIR} ${M2_SRC_DIR} ${MAIN_SRC_LIST})
add_executable(sixth ${M1_SRC_DIR} $<TARGET_OBJECTS:module2> ${MAIN_SRC_LIST})
endif()
# main.cxx参与编译的额外包含目录
target_include_directories(sixth PUBLIC "${SIXTH_BINARY_DIR}/include")
# 指定C++编译标准方式二续
# 假若利用sixth_cxx_compiler_flags接口库的形式 需要添加链接库
# target_link_libraries(sixth PUBLIC sixth_cxx_compiler_flags)
./main.cxx:
#include
#include "sixth_config.h"
#include "module1/func.h"
#include "module2/func.h"
#ifdef USE_MODULE3
#include "module3/func.h"
#endif
int main(int argc, char* argv[]) {
// report version
std::cout << argv[0] << " Version " << SIXTH_PROJ_VERSION_MAJOR << "."
<< SIXTH_PROJ_VERSION_MINOR << " " << SIXTH_PROJ_VERSION << std::endl;
std::cout << "Test Verbose Flag:" << TEST_VERBOSE << std::endl;
int m1 = module1_func();
int m2 = module2_func();
#ifdef USE_MODULE3
int m3 = module3_func();
std::cout << m1 << " " << m2 << " " << m3 << std::endl;
#else
std::cout << m1 << " " << m2 << " " << std::endl;
#endif
return 0;
}
module子目录的函数结构类似,以module1/子目录为例
module1/func1.cxx:
#include "func.h"
#include
int module1_func() {
std::cout << "module1 func call"<<std::endl;
return 1;
}
module1/func1.h:
#pragma once
int module1_func();
module2/子目录中添加生成object的CMakeLists.txtx
set(LIBSRC_LIST func.cxx)
# add_library OBJECT 生成object文件用于程序链接
add_library(module2 OBJECT ${LIBSRC_LIST})
构建过程:
#1. 切换到build目录
> cd ./build
#2. cmake构建
#2.1 默认:-DUSE_MODULE3=ON
> cmake .. #..表示build的上级目录(即CMakeLists.txt所在的根目录)
#2.2 -DUSE_MODULE3=OFF
> cmake .. -DUSE_MODULE3=OFF
#3. 编译(可直接使用cmake自带的编译命令,也可根据编译器选用特定的编译命令,比如Linux的make命令,windows的nmake命令)
#3.1 cmake自带的编译命令
> cmake --build . # .表示build当前目录
#3.2 make编译命令
> make .
cmake --build . 编译结果如图:
./bin/sixth 运行结果如图:
cmake .. -DUSE_MODULE3=OFF 构建编译系统过程:
cmake .. -DUSE_MODULE3=OFF 构建编译系统结果:
例子中内容解释:
收集源码参与编译输出,可以通过aux_source_directory和set
# 将目录内包含的所有源码文件存储到变量
> aux_source_directory(<dir> <variable>)
# 将源码文件列表存储到变量
> set(<variable> <file1> <file2> ...)
可以利用add_library()将子目录的源码生成object,main再利用add_subdirectory()链接此object
# add_library OBJECT 生成object文件用于程序链接
add_library(module2 OBJECT ${LIBSRC_LIST})
add_subdirectory(${SIXTH_SOURCE_DIR}/module2 module2_obj_out)
add_library 指定 OBJECT 来创建一个OBJECT库,调用方编译直接链接此OBJECT即可。
此时,需要注意链接OBJECT时的语法:
$<TARGET_OBJECTS:module2> # 需要TARGET_OBJECTS约束
利用一个xxx_config.h.in文件定义项目通用配置选项,CMake语法中利用@var@作为配置变量定义,再利用configure_file()命令可以实现文件复制,并且基于CMakeLists.txt文件替换文件中@var@的变量值
configure_file(sixth_config.h.in ${SIXTH_BINARY_DIR}/include/sixth_config.h)
关于configure_file命令
configure_file(<input> <output>
[NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
FILE_PERMISSIONS <permissions>...]
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
选项说明:
# 仅拷贝 文件里面的内容到
> COPYONLY
# 使用反斜杠(C语言风格)来进行转义
> ESCAPE_QUOTES
# 限制替换, 仅仅替换 @VAR@ 变量, 不替换 ${VAR} 变量
> @ONLY
# 指定输入文件的新行格式, 例如:Unix 中使用的是 \n, windows 中使用的 \r\n
> NEWLINE_STYLE
# **注意: ** COPYONLY 和 NEWLINE_STYLE 是冲突的,不能同时使用;
使用option()命令定义CMake构建选项
option(<variable> "" [value])
关于option需要注意:
使用target_compile_definitions()命令定义CMake编译选项,以宏定义的形式集成到C++项目代码中
target_compile_definitions(<target>
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
使用target_compile_features()命令为某个target添加**“compiler features”**
target_compile_features(<target> <PRIVATE|PUBLIC|INTERFACE> <feature> [...])
关于
提示:基本上完全使用cmake所提供命令的cmake使用例子:
构建项目:eighth
新建文件夹eighth,并进入eighth文件夹,文件组织如下
eighth
├── build/: CMake构建过程、编译过程的中间文件和结果文件保存目录(这样子可以确保不会污染项目源文件
├── CMakeLists.txt: CMake根目录构建配置文件
├── MathFunctions
│ ├── CMakeLists.txt: MathFunctions子目录的CMake构建配置文件
│ ├── MathFunctions.cxx
│ ├── MathFunctions.h
│ ├── mysqrt.cxx
│ └── mysqrt.h
├── tutorial.cxx
└── TutorialConfig.h.in:CMakeLists.txt定义,配置头文件集成到项目当中,可定义唯一的配置来源
./MathFunctions/CMakeLists.txt:
set(SRC_LST MathFunctions.cxx)
add_library(MathFunctions ${SRC_LST})
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
# 调用程序链接MathFunctions本目标时,需要包含MathFunctions.h
# 但是MathFunctions本目标编译时,不需要链接MathFunctions.h
# 利用INTERFACE作用域限定include作用域:target本身不链接,传递到调用方链接
# ${CMAKE_CURRENT_SOURCE_DIR}: The path to the source directory currently being processed.
# ${CMAKE_CURRENT_SOURCE_DIR}: 当前处理的源目录,即当前CMakeLists.txt所在目录
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
# should we use our own math functions
# option定义CMake构建编译系统的选项
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
# target_compile_definitions定义CMake编译选项
# 如下面语句为C++源码定义了USE_MYMATH宏定义
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
# USE_MYMATH模式下 使用项目自定义sqrt实现
add_library(SqrtLibrary STATIC mysqrt.cxx)
# link our compiler flags interface library
# 链接接口库的编译标记
target_link_libraries(SqrtLibrary PUBLIC tutorial_cxx_compiler_flags)
# 为MathFunctions目标添加链接库SqrtLibrary
target_link_libraries(MathFunctions PUBLIC SqrtLibrary)
endif()
# link our compiler flags interface library
# 链接接口库的编译标记
target_link_libraries(MathFunctions PUBLIC tutorial_cxx_compiler_flags)
./MathFunctions/MathFunctions.cxx:
#include "MathFunctions.h"
#include
#ifdef USE_MYMATH
#include "mysqrt.h"
#endif
namespace mathfunctions {
double sqrt(double x) {
// which square root function should we use?
#ifdef USE_MYMATH
return detail::mysqrt(x);
#else
return std::sqrt(x);
#endif
}
}
./MathFunctions/MathFunctions.h:
#pragma once
namespace mathfunctions {
double sqrt(double);
}
./MathFunctions/mysqrt.cxx:
#include "mysqrt.h"
#include
namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
double result = x;
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
};
};
./MathFunctions/mysqrt.h:
#pragma once
namespace mathfunctions {
namespace detail {
double mysqrt(double);
};
};
./TutorialConfig.h.in:
// the configured options and settings for Tutorial
#define Tutorial_PROJ_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_PROJ_VERSION_MINOR @Tutorial_VERSION_MINOR@
#define TEST_VERBOSE @TEST_VERBOSE_FLAG@
./tutorial.cxx:
// A simple program that computes the square root of a number
#include
#include
#include
#include
#include
int main(int argc, char* argv[])
{
if (argc < 2) {
// report version
std::cout << argv[0] << " Version " << Tutorial_PROJ_VERSION_MAJOR << "."
<< Tutorial_PROJ_VERSION_MINOR
<< " TEST_VERBOSE_FLAG: " << TEST_VERBOSE << std::endl;
std::cout << "Usage: " << argv[0] << " number" << std::endl;
return 1;
}
// convert input to double
const double inputValue = std::stod(argv[1]);
const double outputValue = mathfunctions::sqrt(inputValue);
std::cout << "The square root of " << inputValue << " is " << outputValue
<< std::endl;
return 0;
}
./CMakeLists.txt:
cmake_minimum_required(VERSION 3.15)
# project指定项目名、版本号、编程语言
project(Tutorial VERSION 1.1 LANGUAGES C CXX)
# 指定C++编译标准
# 方式一:利用set指定 the C++ standard
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)
# 方式二:利用接口库的形式
add_library(tutorial_cxx_compiler_flags INTERFACE)
target_compile_features(tutorial_cxx_compiler_flags INTERFACE cxx_std_11)
# 指定自定义变量
set(TEST_VERBOSE_FLAG "Tutorial Demo")
# 判断当前使用的编译器
# 基于生成器表达式
# $: 当项目所用编程语言匹配language,
# 且项目所用编译器匹配compiler_ids时,此表达式返回1,否则返回0
# * Create a new variable gcc_like_cxx that is true if we are using CXX and
# any of the following compilers: ARMClang, AppleClang, Clang, GNU, LCC
# * Create a new variable msvc_cxx that is true if we are using CXX and MSVC
set(gcc_like_cxx "$" )
set(msvc_cxx "$" )
# 为接口库tutorial_cxx_compiler_flags 增加编译警告选项
# 基于条件生成器表达式
# * $ : 当condition为真则返回true_string,否则返回空字符串
# * $ : 当condition为真则返回true_string,否则返回false_string
# 生成器表达式支持嵌套
# INTERFACE作用域表示tutorial_cxx_compiler_flags 本身不需要,但是tutorial_cxx_compiler_flags 调用方需要使用
# BUILD_INTERFACE生成器表达式
# 我们只想在编译阶段使用警告选项,而不会在安装阶段使用,可以利用BUILD_INTERFACE限制
target_compile_options(tutorial_cxx_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$>"
"$<${msvc_cxx}:$>"
)
# configure a header file to pass some of the CMake settings
# to the source code
# 利用configure_file()命令可以实现文件复制,并且替换文件中@var@的变量值
configure_file(TutorialConfig.h.in ${Tutorial_BINARY_DIR}/include/TutorialConfig.h)
message(STATUS "Project Name:" ${PROJECT_NAME})
message(STATUS "CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
message(STATUS "Binary Dir:" ${Tutorial_BINARY_DIR})
message(STATUS "Source Dir:" ${Tutorial_SOURCE_DIR})
# 设置编译结果的输出目录
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
message(STATUS "Executable Binary Out Dir:" ${EXECUTABLE_OUTPUT_PATH})
# 添加MathFunctions 库
add_subdirectory(${Tutorial_SOURCE_DIR}/MathFunctions MathFunctions_Lib_Out)
# 根目录的源文件列表
set(MAIN_SRC_LIST tutorial.cxx)
# add the executable
add_executable(Tutorial ${MAIN_SRC_LIST})
# main.cxx参与编译的额外包含目录
# 此处不需要添加MathFunctions目录:
# 因为MathFunctions库中target_include_directories以INTERFACE作用域提供,调用方自动调用
target_include_directories(Tutorial PUBLIC "${Tutorial_BINARY_DIR}/include")
# 调用方链接库
# 指定C++编译标准方式二续
# 假若利用tutorial_cxx_compiler_flags 接口库的形式 需要添加链接库
target_link_libraries(Tutorial PUBLIC MathFunctions tutorial_cxx_compiler_flags)
例子中内容解释:
关于生成器表达式
生成器表达式,Generator expressions,是CMake在构建编译系统期间计算,生成指向编译配置的信息。比如,可用于生成器表达式可用于根据某些条件设置某些变量和编译选项。
Generator expressions are evaluated during build system generation to produce information specific to each build configuration.
# Generator expressions form
> $<....>
# 常见的生成器表达式:
# COMPILE_LANG_AND_ID
> $<COMPILE_LANG_AND_ID:language,compiler_ids>
# 表示当编译单元的语言与language匹配且CMake编译器id和compiler_ids中任意匹配则为1,否则为0
# 例子:
> set(msvc_cxx "$" )
# 表示当使用c++和msvc编译器时mscv_cxx变量值为1
# 条件生成器表达式
> $<condition:true_string>
> $<IF:condition,true_string,false_string>
# 表示当condition为真则返回true_string,否则返回空字符串或false_string
# 例子:
> target_compile_options(tutorial_compiler_flags INTERFACE
"$<${gcc_like_cxx}:-Wall;-Wextra;-Wshadow;-Wformat=2;-Wunused>"
"$<${msvc_cxx}:-W3>"
)
# 表示当gcc_like_cxx变量为真时,添加-Wall;...等等编译选项。当msvc_cxx变量为真则添加-W3编译选项。
# 嵌套的生成器表达式
> $<${msvc_cxx}:$<BUILD_INTERFACE:-W3>>
# BUILD_INTERFACE表达式包装的构建需求只被在同一个构建系统下,或者使用export()指令导出的目标上使用
# INSTALL_INTERFACE表达式包装的构建需求只被用在使用install(EXPORT)指令安装和导出的目标上
# if(TARGET )生成器表达式
> if(TARGET <target-name>)
# 表示如果表示由add_executable(), add_library(), add_custom_target()定义的target名字,则返回true
# 例子:
> add_library(SqrtLibrary STATIC mysqrt.cxx)
> if(TARGET SqrtLibrary)
# 表示SqrtLibrary是一个有效的由add_library定义的target名字
引用参考:
1、CMake Adding Generator Expressions
2、fangcun-知乎-CMake构建系统
提示:研究export和find_package使用的cmake例子:
构建项目:ninth
新建文件夹ninth,并进入ninth文件夹,文件组织如下
ninth
├── install/ : LibraryProject库的安装目录
│ └── LibraryProject/
│ ├── bin/
│ ├── cmake/ : xxx.cmake的安装目录
│ ├── include/
│ └── lib/
├── LibraryProject/
│ ├── CMakeLists.txt : LibraryProject目录的CMakeLists.txt
│ ├── LibraryProjectConfig.h.in
│ └── MathFunctions/
│ ├── CMakeLists.txt: MathFunctions子目录的CMake构建配置文件
│ ├── MathFunctions.cxx
│ ├── MathFunctions.h
│ ├── mysqrt.cxx
│ └── mysqrt.h
└── TutorialProject
LibraryProject/MathFunctions/CMakeLists.txt
set(SRC_LST MathFunctions.cxx)
# 设置BUILD_SHARED_LIBS 后,此处可以不显式设置类型(STATIC, SHARED),也可以生成具体类型的库
add_library(MathFunctions ${SRC_LST})
# state that anybody linking to us needs to include the current source dir
# to find MathFunctions.h, while we don't.
# 调用程序链接MathFunctions本目标时,需要包含MathFunctions.h
# 但是MathFunctions本目标编译时,不需要链接MathFunctions.h
# 利用INTERFACE作用域限定include作用域:target本身不链接,传递到调用方链接
# ${CMAKE_CURRENT_SOURCE_DIR}: The path to the source directory currently being processed.
# ${CMAKE_CURRENT_SOURCE_DIR}: 当前处理的源目录,即当前CMakeLists.txt所在目录
# target_include_directories只用默认INTERFACE作为约束与生成器表达式$和$作为约束
# 当需要EXPORT到xxx.cmake作为其它cmake项目引用时,会产生本质区别,如target_include_directories的INTERFACE约束和生成器约束区别图所示
# $: 表示库链接过程中需要包含的路径
# $: 表示库安装过程中需要包含的路径(这里不是很理解官方给的定义,根据实践,可能表示的是其他cmake项目引用此cmake项目时的包含路径,具体看图示)
#target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:"${CMAKE_CURRENT_SOURCE_DIR}">
$<INSTALL_INTERFACE:"${PROJECT_NAME}/include">
)
# should we use our own math functions
# option定义CMake构建编译系统的选项
option(USE_MYMATH "Use tutorial provided math implementation" ON)
if(USE_MYMATH)
# target_compile_definitions定义CMake编译选项
# 如下面语句为C++源码定义了USE_MYMATH宏定义
target_compile_definitions(MathFunctions PRIVATE "USE_MYMATH")
# USE_MYMATH模式下 使用项目自定义sqrt实现
add_library(SqrtLibrary STATIC mysqrt.cxx)
# state that SqrtLibrary need PIC when the default is shared libraries
# POSITION_INDEPENDENT_CODE不设置为True,构建会失败,构建失败如图所示
# 原因SqrtLibrary是静态库,MathFunctions是动态库,动态库调用静态库
set_target_properties(SqrtLibrary PROPERTIES
POSITION_INDEPENDENT_CODE ${BUILD_SHARED_LIBS}
)
# link our compiler flags interface library
# 链接接口库的编译标记
target_link_libraries(SqrtLibrary PUBLIC tutorial_cxx_compiler_flags)
# 为MathFunctions目标添加链接库SqrtLibrary
target_link_libraries(MathFunctions PUBLIC SqrtLibrary)
endif()
# 添加BUILD_SHARED_LIBS判断是否编译动态库和静态库
if(BUILD_SHARED_LIBS)
target_compile_definitions(MathFunctions PRIVATE "SHARED_LIB_BUILD")
endif()
# link our compiler flags interface library
# 链接接口库的编译标记
target_link_libraries(MathFunctions PUBLIC tutorial_cxx_compiler_flags)
# 定义windows通用的导入导出符号,否则windows平台不会为dll生成导入库lib
target_compile_definitions(MathFunctions PRIVATE "EXPORTING_MATH_FUNC")
# 安装lib
set(installable_libs MathFunctions tutorial_cxx_compiler_flags)
if(TARGET SqrtLibrary)
# 将SqrtLibrary添加到installable_libs 变量中
list(APPEND installable_libs SqrtLibrary)
endif()
# EXPORT可以导出其他CMake项目直接使用此项目的信息,将导出的信息存储到MathFunctions-Targets变量中
# 可以将MathFunctions-Targets变量存储的信息输出到xxx.cmake文件,方便其它项目find_package调用
install(TARGETS ${installable_libs}
EXPORT MathFunctions-Targets
RUNTIME DESTINATION "${PROJECT_NAME}/bin"
LIBRARY DESTINATION "${PROJECT_NAME}/lib"
ARCHIVE DESTINATION "${PROJECT_NAME}/lib")
# 安装include
# 方法一:
# install(FILES MathFunctions.h DESTINATION "${PROJECT_NAME}/include")
# 方法二:
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DESTINATION "${PROJECT_NAME}/include"
FILES_MATCHING PATTERN "*.h")
./LibraryProject/MathFunctions/MathFunctions.cxx:
#include "MathFunctions.h"
#include
#ifdef USE_MYMATH
#include "mysqrt.h"
#endif
namespace mathfunctions {
double sqrt(double x) {
// which square root function should we use?
#ifdef USE_MYMATH
return detail::mysqrt(x);
#else
return std::sqrt(x);
#endif
}
}
./LibraryProject/MathFunctions/MathFunctions.h:
#pragma once
#if defined(_WIN32)
# if defined(SHARED_LIB_BUILD)
# if defined(EXPORTING_MATH_FUNC)
# define DECLSPEC __declspec(dllexport)
# else
# define DECLSPEC __declspec(dllimport)
# endif
# else // non shared
# define DECLSPEC
# endif
#else // non windows
# define DECLSPEC
#endif
namespace mathfunctions {
double DECLSPEC sqrt(double);
}
./LibraryProject/MathFunctions/mysqrt.cxx:
#include "mysqrt.h"
#include
namespace mathfunctions {
namespace detail {
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
double result = x;
// do ten iterations
for (int i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
double delta = x - (result * result);
result = result + 0.5 * delta / result;
std::cout << "Computing sqrt of " << x << " to be " << result << std::endl;
}
return result;
}
};
};
./LibraryProject/MathFunctions/mysqrt.h:
#pragma once
namespace mathfunctions {
namespace detail {
double mysqrt(double);
};
};
./LibraryProject/CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
# project指定项目名、版本号、编程语言
project(LibProject VERSION 1.1 LANGUAGES C CXX)
# 指定C++编译标准
# 方式一:利用set指定 the C++ standard
# set(CMAKE_CXX_STANDARD 11)
# set(CMAKE_CXX_STANDARD_REQUIRED True)
# 方式二:利用接口库的形式
add_library(tutorial_cxx_compiler_flags INTERFACE)
target_compile_features(tutorial_cxx_compiler_flags INTERFACE cxx_std_11)
# 指定自定义变量
set(TEST_VERBOSE_FLAG "Tutorial Demo")
# 判断当前使用的编译器
# 基于生成器表达式
# $: 当项目所用编程语言匹配language,
# 且项目所用编译器匹配compiler_ids时,此表达式返回1,否则返回0
# * Create a new variable gcc_like_cxx that is true if we are using CXX and
# any of the following compilers: ARMClang, AppleClang, Clang, GNU, LCC
# * Create a new variable msvc_cxx that is true if we are using CXX and MSVC
set(gcc_like_cxx "$" )
set(msvc_cxx "$" )
# 为接口库tutorial_cxx_compiler_flags 增加编译警告选项
# 基于条件生成器表达式
# * $ : 当condition为真则返回true_string,否则返回空字符串
# * $ : 当condition为真则返回true_string,否则返回false_string
# 生成器表达式支持嵌套
# INTERFACE作用域表示tutorial_cxx_compiler_flags 本身不需要,但是tutorial_cxx_compiler_flags 调用方需要使用
# BUILD_INTERFACE生成器表达式
# 我们只想在编译阶段使用警告选项,而不会在安装阶段使用,可以利用BUILD_INTERFACE限制
target_compile_options(tutorial_cxx_compiler_flags INTERFACE
"$<${gcc_like_cxx}:$>"
"$<${msvc_cxx}:$>"
)
# BUILD_SHARED_LIBS控制库类型的生成
option(BUILD_SHARED_LIBS "Build using shared libraries" ON)
# configure a header file to pass some of the CMake settings
# to the source code
# 利用configure_file()命令可以实现文件复制,并且替换文件中@var@的变量值
configure_file(LibraryProjectConfig.h.in ${PROJECT_BINARY_DIR}/include/LibraryProjectConfig.h)
message(STATUS "Project Name:" ${PROJECT_NAME})
message(STATUS "CMake current source dir:" ${CMAKE_CURRENT_SOURCE_DIR})
message(STATUS "CMake current binary dir:" ${CMAKE_CURRENT_BINARY_DIR})
message(STATUS "Binary Dir:" ${LibProject_BINARY_DIR})
message(STATUS "Source Dir:" ${LibProject_SOURCE_DIR})
# 配置默认安装路径
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
# ${CMAKE_SOURCE_DIR}表示源码树顶层目录路径
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/../install)
endif()
# 添加MathFunctions 库
add_subdirectory(${PROJECT_SOURCE_DIR}/MathFunctions MathFunctions_Lib_Out)
# 安装config.h
install(FILES "${PROJECT_BINARY_DIR}/include/LibraryProjectConfig.h" DESTINATION "${PROJECT_NAME}/include")
# 将EXPORT MathFunctions的信息输出到MathFunctionsTargets.cmake
# 可以方便其它cmake项目通过find_package直接引用MathFunctions项目
install(EXPORT MathFunctions-Targets
FILE MathFunctionsTargets.cmake
DESTINATION "${PROJECT_NAME}/cmake"
)
SqrtLibrary 未设置POSITION_INDEPENDENT_CODE时,cmake构建失败图:
针对windows平台,生成动态库时,需要添加__declspec(dllexport)和__declspec(dllimport)符号标记;生成静态库时,和其他平台一样不需要__declspec(dllexport)和__declspec(dllimport)符号标记。
target_include_directories(MathFunctions INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
与
target_include_directories(MathFunctions
INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<INSTALL_INTERFACE:include>
)
target_include_directories的INTERFACE约束和生成器约束区别图所示:
两种模式下MathFunctionsConfig.cmake区别点:
所以在实际生产中,应用生成器表达式 $
1、cmake .. 构建时提示错误
2、xxx.cmake的EXPORT导出文件中,target的INTERFACE_INCLUDE_DIRECTORIES的属性以绝对路径定义,不便于移植到其它机器上工作