CMake学习(7): CMake的嵌套

news2024/11/9 9:41:39

博客参考自:爱编程的大丙: https://subingwen.cn/cmake/CMake-primer/ ,仅供学习分享使用

如果项目很大,或者项目中有很多的源码目录,在通过 CMake 管理项目的时候如果只使用一个 CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个 CMakeLists.txt 文件(头文件目录不需要),这样每个文件都不会太复杂,而且更灵活,更容易维护

先来看一下下面的这个的目录结构:

$ tree
.
├── build
├── calc
│   ├── add.cpp
│   ├── CMakeLists.txt
│   ├── div.cpp
│   ├── mult.cpp
│   └── sub.cpp
├── CMakeLists.txt
├── include
│   ├── calc.h
│   └── sort.h
├── sort
│   ├── CMakeLists.txt
│   ├── insert.cpp
│   └── select.cpp
├── test1
│   ├── calc.cpp
│   └── CMakeLists.txt
└── test2
    ├── CMakeLists.txt
    └── sort.cpp

6 directories, 15 files

  • include 目录:头文件目录
  • calc 目录:目录中的四个源文件对应的加、减、乘、除算法
    • 对应的头文件是 include 中的 calc.h
  • sort 目录 :目录中的两个源文件对应的是插入排序和选择排序算法
    • 对应的头文件是include中的 sort.h
  • test1 目录:测试目录,对calc中实现的加、减、乘、除算法进行测试
  • test2 目录:测试目录,对sort中实现排序算法进行测试

从目录结构可以看出,最终会生成两个可执行文件,一个是计算器相关的,一个是排序相关的。如果把所有操作都写在顶层的CMakeLists.txt中,那么这个CMakeLists.txt的内容就会比较复杂了。有些时候为了让这些脚本文件更加容易维护,我们就可以通过逐个击破的方式,把文件的内容拆分,每个源文件中单独的CMakeLists.txt进行处理

calc目录中有若干个源文件,我们在该目录中添加一个CMakeLists去管理这些源文件。sort目录中有两个排序的源文件,我们可以在sort目录中也添加一个CMakeLists.txt;对于testtest2中分别有一个测试文件,我们在test1test2中分别添加一个CMakeLists.txt

把这些文件添加好后,需要通过顶层的CMakeLists.txt来让定义若干个CMakeLists.txt形成父子关系。怎么让父节点绑定这些子节点呢?使用add_subdirectory来添加这些子目录。这样的话,顶层的CMakeLists就知道它的各个子节点。

在每个CMakeLists.txt中我们都可以定一些变量,当我们在父节点定义了某些变量后,父节点中定义的变量是可以被子节点使用的(父节点定义的这些变量是全局变量),在子节点中定义的变量称为局部变量。父节点无法使用子节点的变量,但子节点中可以使用父节点定义的变量。

1 准备工作

1.1 节点关系

众所周知,Linux 的目录是树状结构,所以嵌套的 CMake 也是一个树状结构,最顶层的 CMakeLists.txt 是根节点其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:

  • 根节点 CMakeLists.txt 中的变量全局有效
  • 父节点 CMakeLists.txt 中的变量可以在子节点中使用
  • 子节点 CMakeLists.txt 中的变量只能在当前节点中使用

1.2 添加子目录

接下来我们还需要知道在 CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

注意source_dir指定的是子节点CMakeLists.txt所在的目录

  • source_dir:指定了 CMakeLists.txt 源文件和代码文件的位置,其实就是指定子目录
  • binary_dir:指定了输出文件的路径,一般不需要指定,忽略即可。
  • EXCLUDE_FROM_ALL:在子路径下的目标默认不会被包含到父路径的 ALL 目标里,并且也会被排除在 IDE 工程文件之外。用户必须显式构建在子路径下的目标。

通过这种方式 CMakeLists.txt 文件之间的父子关系就被构建出来了

2 示例说明

项目目录结构图像

├── CMakeLists.txt
├── calc
│   ├── add.cpp
│   ├── CMakeLists.txt
│   ├── div.cpp
│   ├── mult.cpp
│   └── sub.cpp
├── include
│   ├── calc.h
│   └── sort.h
├── sort
│   ├── CMakeLists.txt
│   ├── insert.cpp
│   └── select.cpp
├── test1
│   ├── calc.cpp
│   └── CMakeLists.txt
└── test2
    ├── CMakeLists.txt
    └── sort.cpp
  • 首先有一个根节点,在根节点中有一个CMakeList.txt, 然后有4个子节点,分别为calc,sort,test1,test2,这些子节点中都有各自的CMakeLists.txt
  • 分析:test1test2中的CMakeLists作用其实就是分别生成一个可执行程序,假设分别为app1和app2;对于calc目录中的4个源文件add.cpp,div.cpp,mult.cpp,sub.cpp,其实它是提供接口供test1中的calc.cpp使用的,所以我们需要把它生成对应的库文件(动/静态库)。对于sort目录中的源文件 insert.cppselect.cpp也是一样的,它是为test2中的sort.cpp提供接口,我们同样把它生成库文件(动/静态库)
  • 库文件的本质其实还是代码,只不过是从文本格式变成了二进制格式

如何选择生成的是静态库还是动态库?

  • 到底是生成动态库还是静态库呢?其实这两种都是可以的,推荐: 在源文件非常多,推荐生成动态库;在源文件比较少的情况,推荐生成静态库
  • 如果生成动态库,它所对应的源代码是不会打包的可执行文件中的,如果是静态库则会打包到可执行文件的内部。如果我们最终要求,生成的可执行文件非常小,那么我们就可以把这些源文件生成一个动态库,然后再发布应用程序时,一定要把该动态库提供给程序的使用者;如果我们对生成可执行文件的大小没有要求,并且要求使用比较简便,我们就可以把这些源文件生成静态库,生成静态库之后这些源文件就会打包到可执行文件中,所以在发布的时候只要发布一个可执行程序就可以了,不需要发布额外的库文件了。
  • 使用静态库的缺点是最终生成的可执行文件会比较大,另外如果启动多个可执行程序,它占用的物理内存也比动态库要。因为如果程序使用的是动态库,不管启动多少个可执行程序,在内存中动态库有且只有一份,是共享的

2.1 根目录

根目录中的 CMakeLists.txt 可以定义一些全局变量,定义的变量可以给子节点使用,比如:定义头文件的路径,生成可执行文件的名字,以及子节点生成库文件的存储路径等,这些都可以在根目录的CMakeLists.txt中先定义出来。后面在子节点中直接使用根节点定义的变量,就可以找到对应的路径。

根目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(test)
# 定义变量
# 静态库生成的路径
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib) #lib子目录不需要自己去创建,如果lib目录没有,会自动创建
# 测试程序生成的路径
set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/bin) #bin 也会自动创建
# 头文件目录
set(HEAD_PATH ${CMAKE_CURRENT_SOURCE_DIR}/include)
# 静态库的名字
set(CALC_LIB calc)
set(SORT_LIB sort)
# 可执行程序的名字
set(APP_NAME_1 test1)
set(APP_NAME_2 test2)
# 添加子目录
add_subdirectory(calc)
add_subdirectory(sort)
add_subdirectory(test1)
add_subdirectory(test2)

在根节点对应的文件中主要做了两件事情:定义全局变量添加子目录

  • 定义的全局变量主要是给子节点使用,目的是为了提高子节点中的 CMakeLists.txt 文件的可读性和可维护性,避免冗余并降低出差的概率。
  • 一共添加了四个子目录,每个子目录中都有一个 CMakeLists.txt 文件,这样它们的父子关系就被确定下来了。

2.2 子目录中的CMakeList.txt

写完了顶层的CMakeList.txt,现在可以写4个子目录中的CMakeList.txt文件。

2.2.1 calc 目录

calc 目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./  SRC)          # ./   表示当前 cmakelist.txt所在目录下
include_directories(${HEAD_PATH})      # 定义依赖的头文件的搜索路径
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${CALC_LIB} STATIC ${SRC})
  • 第 3 行 aux_source_directory:搜索当前目录(calc 目录)下的所有源文件
  • 第 4 行 include_directories:包含头文件路径,HEAD_PATH 是在根节点文件中定义的
  • 第 5 行 set:设置库的生成的路径,LIB_PATH 是在根节点文件中定义的
  • 第 6 行 add_library:生成静态库,静态库名字 CALC_LIB 是在根节点文件中定义的

2.2.2 sort 目录

sort目录也是生成库文件,和calc中CMakelists.txt的写法其实是差不多的。sort 目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(SORTLIB)
aux_source_directory(./  SRC)
include_directories(${HEAD_PATH})
set(LIBRARY_OUTPUT_PATH ${LIB_PATH})
add_library(${SORT_LIB} SHARED ${SRC}) # 也可以生成静态库
  • 第 6 行 add_library:生成动态库,动态库名字 SORT_LIB 是在根节点文件中定义的
  • 这个文件中的内容和 calc 节点文件中的内容类似,只不过这次生成的是动态库。

在生成库文件的时候,这个库可以是静态库也可以是动态库,一般需要根据实际情况来确定。如果生成的库比较大,建议将其制作成动态库

2.2.3 test1 目录

test1 生成可执行文件,可执行文件依赖于上面生成的libcalc.a 静态库。需要链接静态库编译得到当前的可执行程序。test1目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(CALCTEST)
aux_source_directory(./ SRC)       # 搜索的还是当前cmakelist目录下的源文件
include_directories(${HEAD_PATH})
# include_directories(${HEAD_PATH})
link_directories(${LIB_PATH})
link_libraries(${CALC_LIB})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
add_executable(${APP_NAME_1} ${SRC})

由于静态库是自己生成的,光指定静态库名,cmake不知道该静态库的存放路径,所以还需要指定需要链接的静态库的路径:link_directories

  • 第 4 行 include_directories:指定头文件路径,HEAD_PATH 变量是在根节点文件中定义的
  • 第 6 行 link_libraries:指定可执行程序要链接的静态库,CALC_LIB 变量是在根节点文件中定义的
  • 第 7 行 set:指定可执行程序生成的路径,EXEC_PATH 变量是在根节点文件中定义的
  • 第 8 行 add_executable:生成可执行程序,APP_NAME_1 变量是在根节点文件中定义的

此处的可执行程序链接的是静态库,最终静态库会被打包到可执行程序中,可执行程序启动之后,静态库也就随之被加载到内存中了。

2.2.4 test2 目录

与test1一样,test2目录的CMakeLists也是生成一个可执行文件,CMakelists.txt中的指令基本差不多。test2 目录中的 CMakeLists.txt 文件内容如下:

cmake_minimum_required(VERSION 3.0)
project(SORTTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
link_directories(${LIB_PATH})
add_executable(${APP_NAME_2} ${SRC})
target_link_libraries(${APP_NAME_2} ${SORT_LIB})
  • 第四行 include_directories:包含头文件路径,HEAD_PATH 变量是在根节点文件中定义的
  • 第五行 set:指定可执行程序生成的路径,EXEC_PATH 变量是在根节点文件中定义的
  • 第六行 link_directories:指定可执行程序要链接的动态库的路径,LIB_PATH 变量是在根节点文件中定义的
  • 第七行 add_executable:生成可执行程序,APP_NAME_2 变量是在根节点文件中定义的
  • 第八行 target_link_libraries:指定可执行程序要链接的动态库的名字, target_link_libraries需要在add_executable命令之后,因为先必须生成可执行文件,然后target_link_libraries才能将库文件链接到可执行文件中

在生成可执行程序的时候,动态库不会被打包到可执行程序内部。当可执行程序启动之后动态库也不会被加载到内存,只有可执行程序调用了动态库中的函数的时候,动态库才会被加载到内存中,且多个进程可以共用内存中的同一个动态库,所以动态库又叫共享库

2.2 构建项目

一切准备就绪之后,开始构建项目,进入到根节点目录的 build 目录中,执行cmake命令,如下:

$ cmake ..
-- The C compiler identification is GNU 5.4.0
-- The CXX compiler identification is GNU 5.4.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/cmake/calc/build

可以看到在 build 目录中生成了一些文件目录,如下所示:

$ tree build -L 1     
build
├── calc                  # 目录
├── CMakeCache.txt        # 文件
├── CMakeFiles            # 目录
├── cmake_install.cmake   # 文件
├── Makefile              # 文件
├── sort                  # 目录
├── test1                 # 目录
└── test2                 # 目录

然后在 build 目录下执行 make 命令:
在这里插入图片描述
通过上图可以得到如下信息:

  • 在项目根目录的lib目录中生成了静态库 libcalc.a
  • 在项目根目录的 lib 目录中生成了动态库 libsort.so
  • 在项目根目录的 bin 目录中生成了可执行程序 test1
  • 在项目根目录的 bin 目录中生成了可执行程序 test2

最后再来看一下上面提到的这些文件是否真的被生成到对应的目录中了:

$ tree bin/ lib/

bin/
├── test1
└── test2
lib/
├── libcalc.a
└── libsort.so

至此,项目构建完毕。

在项目中,如果将程序中的某个模块制作成了动态库或者静态库并且在CMakeLists.txt 中指定了库的输出目录,而后其它模块又需要加载这个生成的库文件,此时直接使用就可以了,如果没有指定库的输出路径或者需要直接加载外部提供的库文件,此时就需要使用 link_directories 将库文件路径指定出来

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/635672.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【嵌入式环境下linux内核及驱动学习笔记-(16)linux总线、设备、驱动模型之input框架】

目录 1、Linux内核输入子系统概念导入1.1 输入设备工作机制1.2 运行框架1.3 分层思想 2、驱动开发步骤2.1 在init()或probe()函数中2.2 在exit()或remove()函数中:2.3 上报事件2.4 input驱动要素导图2.5 input驱动的总…

代码生成器实现

代码生成器实现 实现封装元数据的工具类实现代码生成器的代码编写掌握模板创建的 构造数据模型 需求分析 借助Freemarker机制可以方便的根据模板生成文件,同时也是组成代码生成器的核心部分。对于Freemarker而 言,其强调 数据模型 模板 文件 的思…

chatgpt赋能python:Python与硬件结合的现实价值

Python与硬件结合的现实价值 Python是当今最受欢迎和广泛使用的编程语言之一,因其易学易用、开放源代码和灵活性而备受欢迎。但是当我们将它与硬件相结合,它能做到什么? 在这篇文章中,我们将向您介绍如何将Python与硬件结合&…

戴尔外星人m16r1国行中文原厂Windows11系统自带Support Assist OS Recovery恢复出厂设置

戴尔外星人m16r1国行中文原厂系统自带Support Assist OS Recovery恢复出厂设置 文件地址https://pan.baidu.com/s/1Pq09oDzmFI6hXVdf8Vqjqw?pwd3fs8 提取码:3fs8 支持Support Assist OS recovery型号: 戴尔外星人m18r1国行中文版Windows11系统 戴尔外星人x16r1国行中文版…

2023/6/9总结

CSS Less嵌套 子元素的选择器可以直接写在父元素里面。 如果不是它的后代元素,比如你想写伪类选择器、交集选择器,需要在前面加&号。 Less运算: 加减乘除都可以,运算符必须用空格隔开。如果俩个元素都有单位&#xff0…

【SpringBoot 3.x】使用starter整合Druid

Druid介绍 Druid是阿里巴巴的一个开源项目,号称为监控而生的数据库连接池,在功能、性能、扩展性方面都超过其他例如DBCP、C3P0、BoneCP、Proxool、JBoss DataSource等连接池,而且Druid已经在阿里巴巴部署了超过600个应用,通过了极为严格的考…

网络作业9【计算机网络】

网络作业9【计算机网络】 前言推荐网络作业9一. 单选题(共12题,36分)二. 多选题(共1题,3分)三. 填空题(共2题,10分)四. 阅读理解(共1题,17分&…

C++【STL】之string的使用

STL简介 STL是C标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。STL由六大组件构成:仿函数、算法、迭代器、空间配置器、容器和配接器。 其中各种容器可以很大帮助的提升我们编写程序的效率&#…

静态NAT配置与验证实验

静态NAT配置与验证实验 【实验目的】 部署静态NAT。熟悉静态NAT的应用方法。验证配置。 【实验拓扑】 实验拓扑如图所示。 实验拓扑 设备参数如表所示。 设备参数表 设备 接口 IP地址 子网掩码 默认网关 R1 f0/0 192.168.10.1 255.255.255.0 N/A S1/0 10.0.0.1…

GlyphControl: Glyph Conditional Control for Visual Text Generation

GlyphControl: Glyph Conditional Control for Visual Text Generation (Paper reading) Yukang Yang, Microsoft Research Asia, arXiv2023, Cited: 0, Code, Paper 1. 前言 最近,人们对开发基于扩散的文本到图像生成模型的兴趣日益增长,这些模型能够…

软件工程开发文档写作教程(11)—需求分析书的编写

本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl本文参考资料:电子工业出版社《软件文档写作教程》 马平,黄冬梅编著 需求分析书主要内容 按照国家《软件需求说明书GB8567-88》所定义的标准,软件需求…

2023去水印小程序saas系统源码修复独立版v1.0.3+uniapp前端

🎈 限时活动领体验会员:可下载程序网创项目短视频素材 🎈 🎉 有需要的朋友记得关赞评,阅读文章底部来交流!!! 🎉 ✨ 源码介绍 一个基于uniapp写的小程序,后端…

MATLAB | 绘图复刻(九) | 泰勒图及组合泰勒图

有粉丝问我这个图咋画: 我一看,这不就泰勒图嘛,就fileexchange上搜了一下泰勒图绘制代码,但是有的代码比较新的版本运行要改很多地方,有的代码需要包含一些压缩包没并没有的别人写的函数,于是我干脆自己写了…

JAVA-八种基础数据类型和包装类型及相关面试题

文章目录 前言一、基本数据类型1.1 分类1.2 概念1.3 代码1.4 二维表 二、各基本数据类型间强制转换2.1 为什么Java中有强制转换?2.2 示例代码 三、包装类型3.1 为什么有包装类型?3.2 基本概念3.3 转换方法 四、转换过程中使用的自动装箱和自动拆箱4.1 来…

Redis Lua脚本原理

Lua脚本执行过程 创建并修改Lua环境 1 创建基础Lua环境2 载入函数库3 创建全局表格Lua4 替换随机函数5 创建排序辅助函数6 创建redis.pcall函数7 全局环境保护8 修改后的Lua环境保存到服务器状态的Lua属性,等待脚本执行 Redis中带有不确定性的命令: …

RK3588平台开发系列讲解(以太网篇)PHY状态机

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、PHY状态机定义二、PHY的状态变化三、PHY的状态变化打印沉淀、分享、成长,让自己和他人都能有所收获!😄 一、PHY状态机定义 phy状态机: 目录:include/linux/phy.h enum phy_state {PHY_DOWN = 0,</

开源模型的力量

2 月&#xff0c;Meta 发布了其大型语言模型&#xff1a;LLaMA。与 OpenAI 及其 ChatGPT 不同&#xff0c;Meta 不仅仅为世界提供了一个可以玩的聊天窗口。 相反&#xff0c;它将代码发布到开源社区&#xff0c;此后不久模型本身就被泄露了。研究人员和程序员立即开始修改、改…

Protobuf实战:通讯录

网络版通讯录 需求 Protobuf常⽤于通讯协议、服务端数据交换场景。接下来将实现⼀个⽹络版本的通讯录&#xff0c;模拟实现客⼾端与服务端的交互&#xff0c;通过Protobuf来实现各端之间的协议序列化。 需求如下&#xff1a; 客⼾端可以选择对通讯录进⾏以下操作&#xff1a;…

电子科技大学编译原理复习笔记(七):自下而上语法分析

目录 前言 重点一览 引言 自下而上分析 分析方法 规范规约&#xff08;最左规约&#xff0c;对应最右推导&#xff09; 算符优先分析法 算符优先文法 最左素短语 举个例子 优先关系表的构造 规范规约与算符优先分析 LR分析法 概述 LR&#xff08;0&#xff09…

系统架构设计师 2:计算机基础

一、计算机硬件 1 处理器&#xff08;CPU&#xff09; 处理器是计算机系统运算和控制的核心部件。 1.1 指令集 处理器的指令集按照其复杂程度可分为复杂指令集&#xff08;CISC&#xff09;与精简指令集&#xff08;RISC&#xff09;。 随着研究的深入&#xff0c;RISC已经…