9. 嵌套的 CMake
9. 嵌套的 CMake
如果项目很大,或者项目中有很多的源码目录,在通过 CMake 管理项目的时候如果只使用一个 CMakeLists.txt ,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个 CMakeLists.txt 文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护。
还是以先前的简单项目代码为例子,演示嵌套的 CMake 怎么进行项目源码管理,此时的项目结构如下:
tree . -L 2
.
├── CMakeLists.txt
├── calc
│ ├── CMakeLists.txt
│ ├── add.cpp
│ ├── div.cpp
│ ├── mul.cpp
│ └── sub.cpp
├── calc_test
│ ├── CMakeLists.txt
│ └── test.cpp
├── include
│ ├── calc.h
│ └── sort.h
├── sort
│ ├── CMakeLists.txt
│ ├── insert.cpp
│ └── select.cpp
└── sort_test
├── CMakeLists.txt
└── test.cpp
5 directories, 15 files
可以看到,每个源文件目录(除了头文件)里面都有一个 CMakeLists.txt
来管理当前目录,目录功能如下:
include
:头文件,定义了calc.h
和sort.h
calc
:源文件目录,用来生成库libcalc
sort
:源文件目录,用来生成库libsort
calc_test
:测试文件目录,里面的test.cpp
用来生成可执行文件sort_test
:测试文件目录,里面的test.cpp
用来生成可执行文件
打算在工程的根目录创建一个 lib
目录,用于存放生成的库;另外创建一个 bin
目录,用来存放可执行文件。
9.1 解决问题
当前工程全部都是源代码,我们打算先生成库,然后再直接在测试目录中调用生成了库来生成可执行文件。所以顺序:
- 先生成库
- 再生成可执行程序
9.1.1 根目录
我们在根目录下可以定义一些全局的变量,或者完成一些全局的配置,下面是我的 CMakeLists.txt
:
cmake_minimum_required(VERSION 3.17)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
project(test LANGUAGES C CXX VERSION 0.1.0)
if(WIN32)
add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES)
endif()
# Windows 平台相关设置 -- 生成动态库:一定要导出符号表
if(MSVC)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(BUILD_SHARED_LIBS ON)
endif()
if(NOT MSVC)
find_program(CCACHE_PROGRAM ccache)
if(CCACHE_PROGRAM)
message(STATUS "Found CCache: ${CCACHE_PROGRAM}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
set_property(GLOBAL PROPERTY RUEL_LAUNCH_LINK ${CCACHE_PROGRAM})
endif()
endif()
# 添加子目录
add_subdirectory(calc)
add_subdirectory(sort)
add_subdirectory(calc_test)
add_subdirectory(sort_test)
# 打印当前的版本信息
message("PROJECT_NAME: ${PROJECT_NAME}")
message("PROJRCT_VERSION: ${PROJECT_VERSION}")
message("PROJECT_VERSION_MAJOR: ${PROJECT_VERSION_MAJOR}")
message("PROJECT_VERSION_MINOR: ${PROJECT_VERSION_MINOR}")
message("PROJECT_VERSION_PATCH: ${PROJECT_VERSION_PATCH}")
要注意的点有:
- 在设置
project
之前,先完成set
相关的配置,使得这些配置在所有的子目录中生效 project
中的LANGUAGES
如果不指定,就默认为 C 和 CXXVERSION x.y.z
:设定当前项目的版本号为x.y.z
PROJECT_VERSION_MAJOR
:当前程序VERSION
的的主版本号x
PROJECT_VERSION_MINOR
:当前程序VERSION
中的次版本号y
PROJECT_VERSION_PATCH
:当前程序VERSION
中补丁版本号z
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
:导出动态库的符号表,在 Windows 环境下必须声明为ON
set(BUILD_SHARED_LIBS ON)
:用来控制默认的库编译方式,如果 不设置,使用add_library
在没有指定库类型的情况下,默认生成的都是静态库。如果设置了set(BUILD_SHARED_LIBS ON)
后,默认生成动态库
CMAKE_BUILD_TYPE
是 CMake 中一个特殊的变量,用于控制构建类型,他的值可以是:
Debug
调试模式,完全不优化,生成调试信息,方便调试程序Release
发布模式,优化程度最高,性能最佳,但是编译比Debug
慢MinSizeRel
最小体积发布,生成的文件比 Release 更小,不完全优化,减少二进制体积RelWithDebInfo
带调试信息发布,生成的文件比 Release 更大,因为带有调试的符号信息- 默认情况下
CMAKE_BUILD_TYPE
为空字符串,这时相当于Debug
。
9.1.2 calc 目录
库源文件和它的头文件:
tree calc sort include
calc
├── CMakeLists.txt
├── add.cpp
├── div.cpp
├── mul.cpp
└── sub.cpp
sort
├── CMakeLists.txt
├── insert.cpp
└── select.cpp
include
├── calc.h
└── sort.h
0 directories, 2 files
这里我们先处理 calc
目录中的源文件,使得它生成库:
cmake_minimum_required(VERSION 3.17)
# 指定本源文件生成库的路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
# 指定源文件
file(GLOB_RECURSE CALC_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${PROJECT_SOURCE_DIR}/include/*.h)
# 生成静态库
add_library(calc STATIC ${CALC_SRC})
# 指定头文件路径
target_include_directories(calc PUBLIC ${PROJECT_SOURCE_DIR}/include)
CONFIGURE_DEPENDS
:当添加新文件时,自动更新变量CALC_SRC
,否则可能构建时候出错PROJECT_SOURCE_DIR
:最近一次调用 project 的 CMakeLists.txt 所在的源码目录CMAKE_CURRENT_SOURCE_DIR
:当前 CMakeLists.txt 所在的源码目录- 将头文件也添加到源文件列表中,是因为这样在 Visual Studio 中才能导入进去,更方便编辑头文件
值得注意的点:
GLOB_RECURSE
也会带来问题:可能会把生成的临时.cpp
文件也添加进来,好的方法就是将源文件放在统一的目录下(如这里就放在了各自的目录calc
和sort
中),还是使用aux_sorce_directory
更方便一点:# 指定源文件
# file(GLOB_RECURSE CALC_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${PROJECT_SOURCE_DIR}/include/*.h) # 替换成如下形式
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} CALC_SRC)
aux_source_directory(${PROJECT_SOURCE_DIR} CALC_SRC)
aux_source_directory
会自动搜集需要的文件后缀名另外,子模块里也可以用 project 命令,将当前目录作为一个独立的子项目,这样一来
PROJECT_SOURCE_DIR
就会是子模块的源码目录而不是外层了。这时候 CMake 会认为这个子模块是个独立的项目,会额外做一些初始化。这里没有使用
include_directories
,而是使用了target_include_directories
,因为include_directories
会为当前 CMakeLists.txt 的所有目标,以及之后添加的所有子目录的目标添加头文件搜索路径,会导致路径污染,而后者只会为特定的目标添加头文件搜索路径
target_include_directories
作用为指定目标(target)添加搜索路径,指定目标是指通过如add_executable
,add_library
这样的命令生成的,并且决不能是 alias target(引用目标,别名目标):target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
<INTERFACE|PUBLIC|PRIVATE> [items1...]
[<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
target
:指定的目标AFTER|BEFORE
:让添加的路径位于搜索列表的开头(BEFORE
)或结尾(AFTER
)。缺省为结尾
add_executable
,add_library
这两者需要在target_include_directories
之前,因为前两者会先确定target
9.1.3 sort 目录
sort 中的 CMakeList.txt
:
cmake_minimum_required(VERSION 3.17)
# 指定本源文件生成库的路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# 指定源文件路径
file(GLOB_RECURSE SORT_SRC CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp ${PROJECT_SOURCE_DIR}/include/*.h)
# 生成动态库
add_library(sort SHARED ${SORT_SRC})
# 指定头文件路径
target_include_directories(sort PUBLIC ${PROJECT_SOURCE_DIR}/include)
和上面的类似,不过这里生成的是动态库,在 Windows 的 mingw
环境中也可以直接生成成功,如果使用的是 msvc
,就需要修改源代码,否则会生成失败。
常见问题:动态库无法链接静态库:
解决方案:要在静态库编译时生成位置无关的代码(PIC),才能在动态库中调用:
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
我们先注释掉项目根目录的后面两个子模块,先生成库:
...
# add_subdirectory(calc_test)
# add_subdirectory(sort_test)
...
然后开始构架:
cmake -B build
cmake --build build
会自动创建 build
目录且构建出环境,接下来只需要调用 make 就可以了
建议使用 VSCode + Clangd + CMake,体验极佳
然后 lib
目录就生成了库:
tree lib
lib
├── libcalc.a
└── libsort.so
0 directories, 2 files
9.1.4 calc_test 目录
取消项目根目录下 CMakeLists.txt
的注释,打开 calc_test
下的 CMakeLists.txt:
cmake_minimum_required(VERSION 3.17)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
add_executable(calc_test ${CMAKE_CURRENT_SOURCE_DIR}/test.cpp)
target_include_directories(calc_test PUBLIC ${PROJECT_SOURCE_DIR}/include)
link_directories(${PROJECT_SOURCE_DIR}/lib)
target_link_libraries(calc_test calc)
我们看到:这里的 target_include_directories
也是放在 add_executable
之后的,因为我们这里是使用自己写的库或者第三方库,要设定 link_directories
(即设定第三方库的所在目录),否则在编译的时候就会报链接器错误。
9.1.5 sort_test 目录
sort_test
下的 CMakeLists.txt 与 calc_test 下的别无二致,只是路径和名称不一样而已:
cmake_minimum_required(VERSION 3.17)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
add_executable(sort_test ${CMAKE_CURRENT_SOURCE_DIR}/test.cpp)
target_include_directories(sort_test PRIVATE ${PROJECT_SOURCE_DIR}/include)
link_directories(${PROJECT_SOURCE_DIR}/lib)
target_link_libraries(sort_test sort)
不过唯一要注意的是,我们在这里引入的是一个动态库,当然在 Linux 下,动态库的引入还是很简单的
我们再构建一次:
cmake -B build
cmake --build build
都是能够正常运行的:
看起来动态库在 Linux 上给人带来了岁月静好的使用体验,问题的大头其实在 Windows 下,Windows 下的环境太过复杂,就比较麻烦,尤其是涉及到跨平台的时候。
我的 Windows 本地配置的开发环境:
其中
gcc
不需要修改源代码就能直接构建编译,但是如果使用了 Visual Studio 的环境,则需要修改源代码此外,一定要记住,在 Windows 环境下动态链接库要和可执行文件放在一起,才能成功执行可执行文件!
9. 嵌套的 CMake的更多相关文章
- CMake使用教程
转自 RichardXG 原文 CMake使用教程 CMake是一个比make更高级的编译配置工具,它可以根据不同平台.不同的编译器,生成相应的Makefile或者vcproj项目. 通过编写CMak ...
- CMake入门指南-编译教程
CMake是一个比make更高级的编译配置工具,它可以根据不同平台.不同的编译器,生成相应的Makefile或者vcproj项目.通过编写CMakeLists.txt,可以控制生成的Makefile, ...
- CMake初步(2)
转自:<你所不知的OSG>第一章:CMake初步(2) http://bbs.osgchina.org/forum.php?mod=viewthread&tid=1229& ...
- CMake入门指南
原文地址:http://www.cnblogs.com/sinojelly/archive/2010/05/22/1741337.html CMake是一个比make更高级的编译配置工具,它可以根据不 ...
- CMake使用之一
概述 CMake是一个比make更高级的编译配置工具,它可以根据不同平台.不同的编译器,生成相应的Makefile或者vcproj项目. 通过编写CMakeLists.txt,可以控制生成的Makef ...
- CMake基本语法
CMake简介 CMake 是做什么的? CMake是一套类似于automake的跨平台辅助项目编译的工具. 我觉得语法更加简单易用. CMake的工作流程 CMake处理顶级目录的CMakeList ...
- CMake 条件判断
CMake简介 CMake 是做什么的? CMake是一套类似于automake的跨平台辅助项目编译的工具. 我觉得语法更加简单易用. CMake的工作流程 CMake处理顶级目录的CMakeList ...
- cmake安装配置及入门指南
前言 今天,从github下载代码学习,让我用cmake编译,纳尼?make我知道,cmake是啥鬼?天啊,无知很可怕!赶紧mark一波,虽然很耽误学习进度,但感觉还是要get一波! 一.安装准备 感 ...
- CMake 构建项目教程-简介
CMake 构建项目教程-简介 Linux 平台构建项目,选择了CLion作为C++的IDE,而CLion默认就是使用CMake构建项目,所以这里记录了CMake在构建项目过程的一些小知识. 1. 项 ...
- cmake 使用
1.cmake 显示编译命令: 在顶层CMakeLists.txt里设置 set(CMAKE_VERBOSE_MAKEFILE ON) 或者 cmake . 再 m ...
随机推荐
- Python语言:散修笔记
文章目录 前言 转义字符的使用 原字符 变量的定义 类型转换 注释 接收用户信息 运算规则 整除运算 幂运算 比较运算符 布尔运算 运算优先级 对象的布尔值 if else elif分支结构 条件表达 ...
- 微信H5分享不能展示卡片问题
来源与微信开放社区 微信H5已成功接入,微信api以及配置项验证正常,通过手机微信里面链接打开页面进行分享,分享出去的还是链接, 需要从以下场景进入才可以正常分享卡片 从二维码进入 分享卡片进入 公众 ...
- js毫秒转时分秒
const formatSeconds = (value) => { if (value === 0 || value < 1000) return '0秒'; var timestamp ...
- elementui table tree懒加载只能执行一次的解决办法
绑定 table的:key为随机值,在每次查询更新table时,更改key,就能刷新 table tree 懒加载只能第一次有效的问题, 本来那个懒加载只能执行一次,即使重新绑定了数据列表,再展开,也 ...
- skipped: maximum number of running instances reached (1)
apscheduler定时任务报错skipped: maximum number of running instances reached (1) 原因是默认max_instances最大定时任务是1 ...
- 13个优秀的AI工具软件导航网站推荐
人工智能(AI)是现在科技领域的热门话题,它不仅改变了我们的生活方式,也催生了许多创新的工具和应用.AI工具可以帮助我们完成各种任务,如绘画.编程.视频制作.语音合成等,让我们的工作和娱乐更加高效和有 ...
- 为何Mysql数据库上读和代码读取的数据不一致
上周,做了一个同步删除的功能,具体流程是,数据库删除-->调用第三方运行删除接口,同步删除. 具体流程 数据库删除数据后,这里使用标志删除,再调用第三方数据. 在调用第三方数据之前需要通过id查 ...
- react withRouter高阶组件
作用:把不是通过路由切换过来的组件中,将react-router 的 history.location.match 三个对象传入props对象上 默认情况下必须是经过路由匹配渲染的组件才存在this. ...
- abc356
D1.5h没做出,E0.5h做出来啦? E 有两个做法,第一个是枚举分子来计算分母对答案的贡献,另一种是枚举分母来求分子对答案的贡献. 枚举分子来计算分母对答案的贡献需要用到数论分块,所以我们讲枚举分 ...
- Vector + ClickHouse 收集日志
目前业界的日志生态,最常用的是 ELK,其次就是 ClickHouse,本文会演示如何使用 Vector + ClickHouse 来采集 Nginx 日志并做清洗,最终写入 ClickHouse.至 ...