首页 > 其他分享 >9. 嵌套的 CMake

9. 嵌套的 CMake

时间:2024-07-15 10:56:23浏览次数:23  
标签:sort CMake SOURCE PROJECT 嵌套 test calc DIR

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.hsort.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 和 CXX
  • VERSION 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 文件也添加进来,好的方法就是将源文件放在统一的目录下(如这里就放在了各自的目录 calcsort 中),还是使用 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_executableadd_library 这样的命令生成的,并且决不能是 alias target(引用目标,别名目标):

      target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
        <INTERFACE|PUBLIC|PRIVATE> [items1...]
        [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
      
      • target:指定的目标
      • AFTER|BEFORE:让添加的路径位于搜索列表的开头(BEFORE)或结尾(AFTER)。缺省为结尾

add_executableadd_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

都是能够正常运行的:

image-20231204230418729

image-20231204230440095

看起来动态库在 Linux 上给人带来了岁月静好的使用体验,问题的大头其实在 Windows 下,Windows 下的环境太过复杂,就比较麻烦,尤其是涉及到跨平台的时候。

我的 Windows 本地配置的开发环境:

image-20231204230650285

其中 gcc 不需要修改源代码就能直接构建编译,但是如果使用了 Visual Studio 的环境,则需要修改源代码

此外,一定要记住,在 Windows 环境下动态链接库要和可执行文件放在一起,才能成功执行可执行文件!

标签:sort,CMake,SOURCE,PROJECT,嵌套,test,calc,DIR
From: https://www.cnblogs.com/kobayashilin1/p/18302672

相关文章

  • 2. CMake 的简单使用
    2.CMake的简单使用我们创建一个工程目录,在里面定义一些简单的加减乘除运算,然后定义一个main.cpp的文件:结构如下:tree/f.\D:\SOURCE\CMAKE_PROJ└─proj1add.cppCMakeLists.txtdiv.cpphead.hmain.cppmul.cpp......
  • 1. CMake 概述
    1.CMake概述CMake可以用来构建C/C++工程,可以跨平台。允许开发者指定整个工程的编译流程在CMake没有出来之前,开发者需要手写makefile,但是不同平台下的makefile写法不同,所以移植软件的难度就很大。而CMake可以自动生成本地化的工程文件和makefile,其编译流程如下:蓝色......
  • Jetpack Compose(9)——嵌套滚动
    自定义Composable组件目录一、Composable组件渲染流程1.1组合1.2布局1.3绘制二、自定义组合三、自定义布局3.1LayoutModifier(自定义View)3.2Layout(自定义ViewGroup)3.3固有特性测量Intrinsic3.3.1使用内置组件的固有特性测量3.3.2自定义固有特性测量3.4SubcomposeL......
  • Debug Log - Linux下出现 cmake: command not found
    Bug情况:在用脚本安装一些环境时,出现了cmake:commandnotfound的情况,故需要安装cmake。踩坑:网上有人说通过yum来安装cmake,但我先通过apt安装yum(sudoaptinstallyum),再通过yum安装cmake(sudoyuminstallcmake),发现yum找不到对应匹配的包。解决过程:使用cmake--version......
  • QT6 CMake项目配置 (VSCode)
    QT6CMake项目配置(VSCode)这篇文章我们介绍一下在VSCode下的配置,大体上和VisualStudio上差不多,建议先把之前介绍在VS上的配置过程看一遍,VSCode安装这个就不用说了吧,无脑下一步插件安装先把CMake相关的插件装一下第一个是CMake语言的支持插件,装了这个写CMakeLists.txt就......
  • MyBatis用嵌套ResultMap实现一对多映射
    背景我们知道,MyBatis可以很方便地把SQLselect出来的数据直接映射为对象的属性,把对象取出来。但是,有些对象的属性是集合类型,集合里保存的是数个其他类型的对象。如何用MyBatis把它取出来呢?例子以以下这个应用场景为例:一个教师对应多个课程。数据结构如下:publicclassCour......
  • 易优cms网站notempty功能:判断某个变量是否为空,可以嵌套到任何标签里面使用-Eyoucms
    【基础用法】名称:notempty功能:判断某个变量是否为空,可以嵌套到任何标签里面使用,比如:channel、type等语法:{eyou:notemptyname='$eyou.field.seo_title'/}{$eyou.field.seo_title}{/eyou:notempty}文件:无参数:name=''变量名底层字段:无 【更多示例】-------------------------......
  • CMakeLists.txt编写思路
      近期在linux编写CMakeLists.txt文件,整理了一些思路。一、编写CMakeLists.txt的基本步骤和思路:初始化CMake:使用cmake_minimum_required指令指定CMake的最小版本要求,以确保兼容性。使用project指令定义项目名称和可选的语言。设置变量:使用set指令设置项目相关的变量,......
  • Excel第29享:基于sum嵌套sumifs的多条件求和
    1、需求描述如下图所示,现要统计12.17-12.23这一周各个人员的“上班工时(a1)”。下图为系统直接导出的工时数据明细样例。2、解决思路首先,确定逻辑:“对多个条件(日期、人员)进行“工时”列求和”。故选择sumifs函数,由于是“日期”字段有多个数值,故与sum函数嵌套使用;其次,sumif......
  • Franka Robot cmake demo
    cmake_minimum_required(VERSION3.4)#指定CMake的最低版本要求为3.4project(libfranka-examplesCXX)#定义项目名称为libfranka-examples,并指定语言为C++list(INSERTCMAKE_MODULE_PATH0${CMAKE_CURRENT_LIST_DIR}/../cmake)#将父目录的`cmake`子目录添加到......