网站设计模板是什么,医疗网站不备案,网站样式用什么做的,公司展厅设计公司哪家好1. 嵌套的CMake
如果项目很大#xff0c;或者项目中有很多的源码目录#xff0c;在通过CMake管理项目的时候如果只使用一个CMakeLists.txt#xff0c;那么这个文件相对会比较复杂#xff0c;有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件#xff…1. 嵌套的CMake
如果项目很大或者项目中有很多的源码目录在通过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.cpp6 directories, 15 filesinclude 目录头文件目录calc 目录目录中的四个源文件对应的加、减、乘、除算法 对应的头文件是include中的calc.h sort 目录 目录中的两个源文件对应的是插入排序和选择排序算法 对应的头文件是include中的sort.h test1 目录测试目录对加、减、乘、除算法进行测试test2 目录测试目录对排序算法进行测试
可以看到各个源文件目录所需要的CMakeLists.txt文件现在已经添加完毕了。接下来庖丁解牛我们依次分析一下各个文件中需要添加的内容。
1.1 准备工作
1.1.1 节点关系
众所周知Linux的目录是树状结构所以嵌套的 CMake 也是一个树状结构最顶层的 CMakeLists.txt 是根节点其次都是子节点。因此我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息
根节点CMakeLists.txt中的变量全局有效父节点CMakeLists.txt中的变量可以在子节点中使用子节点CMakeLists.txt中的变量只能在当前节点中使用
1.1.2 添加子目录
接下来我们还需要知道在 CMake 中父子节点之间的关系是如何建立的这里需要用到一个 CMake 命令
add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])source_dir指定了CMakeLists.txt源文件和代码文件的位置其实就是指定子目录binary_dir指定了输出文件的路径一般不需要指定忽略即可。EXCLUDE_FROM_ALL在子路径下的目标默认不会被包含到父路径的ALL目标里并且也会被排除在IDE工程文件之外。用户必须显式构建在子路径下的目标。
通过这种方式CMakeLists.txt文件之间的父子关系就被构建出来了。
1.2 解决问题
在上面的目录中我们要做如下事情
通过 test1 目录中的测试文件进行计算器相关的测试通过 test2 目录中的测试文件进行排序相关的测试 现在相当于是要进行模块化测试对于calc和sort目录中的源文件来说可以将它们先编译成库文件可以是静态库也可以是动态库然后在提供给测试文件使用即可。库文件的本质其实还是代码只不过是从文本格式变成了二进制格式。
1.2.1 根目录
根目录中的 CMakeLists.txt文件内容如下
cmake_minimum_required(VERSION 3.0)
project(test)
# 定义变量
# 静态库生成的路径
set(LIB_PATH ${CMAKE_CURRENT_SOURCE_DIR}/lib)
# 测试程序生成的路径
set(EXEC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/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文件这样它们的父子关系就被确定下来了。
1.2.2 calc 目录
calc 目录中的 CMakeLists.txt文件内容如下
cmake_minimum_required(VERSION 3.0)
project(CALCLIB)
aux_source_directory(./ SRC)
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是在根节点文件中定义的
1.2.3 sort 目录
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节点文件中的内容类似只不过这次生成的是动态库。
在生成库文件的时候这个库可以是静态库也可以是动态库一般需要根据实际情况来确定。如果生成的库比较大建议将其制作成动态库。
1.2.4 test1 目录
test1 目录中的 CMakeLists.txt文件内容如下
cmake_minimum_required(VERSION 3.0)
project(CALCTEST)
aux_source_directory(./ SRC)
include_directories(${HEAD_PATH})
link_directories(${LIB_PATH})
link_libraries(${CALC_LIB})
set(EXECUTABLE_OUTPUT_PATH ${EXEC_PATH})
add_executable(${APP_NAME_1} ${SRC})第4行include_directories指定头文件路径HEAD_PATH变量是在根节点文件中定义的第6行link_libraries指定可执行程序要链接的静态库CALC_LIB变量是在根节点文件中定义的第7行set指定可执行程序生成的路径EXEC_PATH变量是在根节点文件中定义的第8行add_executable生成可执行程序APP_NAME_1变量是在根节点文件中定义的
此处的可执行程序链接的是静态库最终静态库会被打包到可执行程序中可执行程序启动之后静态库也就随之被加载到内存中了。
1.2.5 test2 目录
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指定可执行程序要链接的动态库的名字
在生成可执行程序的时候动态库不会被打包到可执行程序内部。当可执行程序启动之后动态库也不会被加载到内存只有可执行程序调用了动态库中的函数的时候动态库才会被加载到内存中且多个进程可以共用内存中的同一个动态库所以动态库又叫共享库。
1.2.6 构建项目
一切准备就绪之后开始构建项目进入到根节点目录的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 将库文件路径指定出来。
2. 流程控制
在 CMake 的 CMakeLists.txt 中也可以进行流程控制也就是说可以像写 shell 脚本那样进行条件判断和循环。
2.1 条件判断
关于条件判断其语法格式如下
if(condition)commands
elseif(condition) # 可选快, 可以重复commands
else() # 可选快commands
endif()在进行条件判断的时候如果有多个条件那么可以写多个elseif最后一个条件可以使用else但是开始和结束是必须要成对出现的分别为if和endif。
2.1.1 基本表达式
if(expression)如果是基本表达式expression 有以下三种情况常量、变量、字符串。
如果是1, ON, YES, TRUE, Y, 非零值非空字符串时条件判断返回True如果是 0, OFF, NO, FALSE, N, IGNORE, NOTFOUND空字符串时条件判断返回False
2.1.2 逻辑判断
NOT
if(NOT condition)其实这就是一个取反操作如果条件condition为True将返回False如果条件condition为False将返回True。
AND
if(cond1 AND cond2)如果cond1和cond2同时为True返回True否则返回False。
OR
if(cond1 OR cond2)如果cond1和cond2两个条件中至少有一个为True返回True如果两个条件都为False则返回False。
2.1.3 比较
基于数值的比较
if(variable|string LESS variable|string)
if(variable|string GREATER variable|string)
if(variable|string EQUAL variable|string)
if(variable|string LESS_EQUAL variable|string)
if(variable|string GREATER_EQUAL variable|string)LESS如果左侧数值小于右侧返回TrueGREATER如果左侧数值大于右侧返回TrueEQUAL如果左侧数值等于右侧返回TrueLESS_EQUAL如果左侧数值小于等于右侧返回TrueGREATER_EQUAL如果左侧数值大于等于右侧返回True基于字符串的比较
if(variable|string STRLESS variable|string)
if(variable|string STRGREATER variable|string)
if(variable|string STREQUAL variable|string)
if(variable|string STRLESS_EQUAL variable|string)
if(variable|string STRGREATER_EQUAL variable|string)STRLESS如果左侧字符串小于右侧返回TrueSTRGREATER如果左侧字符串大于右侧返回TrueSTREQUAL如果左侧字符串等于右侧返回TrueSTRLESS_EQUAL如果左侧字符串小于等于右侧返回TrueSTRGREATER_EQUAL如果左侧字符串大于等于右侧返回True
2.1.4 文件操作
判断文件或者目录是否存在
if(EXISTS path-to-file-or-directory)如果文件或者目录存在返回True否则返回False。
判断是不是目录
if(IS_DIRECTORY path)此处目录的 path 必须是绝对路径如果目录存在返回True目录不存在返回False。
判断是不是软连接
if(IS_SYMLINK file-name)此处的 file-name 对应的路径必须是绝对路径如果软链接存在返回True软链接不存在返回False。软链接相当于 Windows 里的快捷方式
判断是不是绝对路径
if(IS_ABSOLUTE path)关于绝对路径: 如果是Linux该路径需要从根目录开始描述如果是Windows该路径需要从盘符开始描述 如果是绝对路径返回True如果不是绝对路径返回False。
2.1.5 其它
判断某个元素是否在列表中
if(variable|string IN_LIST variable)CMake 版本要求大于等于3.3如果这个元素在列表中返回True否则返回False。比较两个路径是否相等
if(variable|string PATH_EQUAL variable|string)CMake 版本要求大于等于3.24如果这个元素在列表中返回True否则返回False。
关于路径的比较其实就是另个字符串的比较如果路径格式书写没有问题也可以通过下面这种方式进行比较
if(variable|string STREQUAL variable|string)我们在书写某个路径的时候可能由于误操作会多写几个分隔符比如把/a/b/c写成/a//b///c此时通过STREQUAL对这两个字符串进行比较肯定是不相等的但是通过PATH_EQUAL去比较两个路径得到的结果确实相等的可以看下面的例子
cmake_minimum_required(VERSION 3.26)
project(test)if(/home//robin///Linux PATH_EQUAL /home/robin/Linux)message(路径相等)
else()message(路径不相等)
endif()message(STATUS )if(/home//robin///Linux STREQUAL /home/robin/Linux)message(路径相等)
else()message(路径不相等)
endif()输出的日志信息如下:
路径相等路径不相等通过得到的结果我们可以得到一个结论在进行路径比较的时候如果使用 PATH_EQUAL 可以自动剔除路径中多余的分割线然后再进行路径的对比使用 STREQUAL 则只能进行字符串比较。
2.2 循环
在 CMake 中循环有两种方式分别是foreach和while。
2.2.1 foreach
使用 foreach 进行循环语法格式如下
foreach(loop_var items)commands
endforeach()通过foreach我们就可以对items中的数据进行遍历然后通过loop_var将遍历到的当前的值取出在取值的时候有以下几种用法 方法1
foreach(loop_var RANGE stop)RANGE关键字表示要遍历范围stop这是一个正整数表示范围的结束值在遍历的时候从 0 开始最大值为 stop。loop_var存储每次循环取出的值
举例说明
cmake_minimum_required(VERSION 3.2)
project(test)
# 循环
foreach(item RANGE 10)message(STATUS 当前遍历的值为: ${item} )
endforeach()输出的日志信息是这样的
$ cmake ..
-- 当前遍历的值为: 0
-- 当前遍历的值为: 1
-- 当前遍历的值为: 2
-- 当前遍历的值为: 3
-- 当前遍历的值为: 4
-- 当前遍历的值为: 5
-- 当前遍历的值为: 6
-- 当前遍历的值为: 7
-- 当前遍历的值为: 8
-- 当前遍历的值为: 9
-- 当前遍历的值为: 10
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build再次强调在对一个整数区间进行遍历的时候得到的范围是这样的 【0stop】右侧是闭区间包含 stop 这个值。
方法2
foreach(loop_var RANGE start stop [step])这是上面方法1的加强版我们在遍历一个整数区间的时候除了可以指定起始范围还可以指定步长。
RANGE关键字表示要遍历范围start这是一个正整数表示范围的起始值也就是说最小值为 startstop这是一个正整数表示范围的结束值也就是说最大值为 stopstep控制每次遍历的时候以怎样的步长增长默认为1可以不设置loop_var存储每次循环取出的值
举例说明
cmake_minimum_required(VERSION 3.2)
project(test)foreach(item RANGE 10 30 2)message(STATUS 当前遍历的值为: ${item} )
endforeach()输出的结果如下:
$ cmake ..
-- 当前遍历的值为: 10
-- 当前遍历的值为: 12
-- 当前遍历的值为: 14
-- 当前遍历的值为: 16
-- 当前遍历的值为: 18
-- 当前遍历的值为: 20
-- 当前遍历的值为: 22
-- 当前遍历的值为: 24
-- 当前遍历的值为: 26
-- 当前遍历的值为: 28
-- 当前遍历的值为: 30
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build再次强调在使用上面的方式对一个整数区间进行遍历的时候得到的范围是这样的 【startstop】左右两侧都是闭区间包含 start 和 stop 这两个值步长 step 默认为1可以不设置。 方法3
foreach(loop_var IN [LISTS [lists]] [ITEMS [items]])这是foreach的另一个变体通过这种方式我们可以对更加复杂的数据进行遍历前两种方式只适用于对某个正整数范围内的遍历。
IN关键字表示在 xxx 里边LISTS关键字对应的是列表list通过set、list可以获得ITEMS关键字对应的也是列表loop_var存储每次循环取出的值
cmake_minimum_required(VERSION 3.2)
project(test)
# 创建 list
set(WORD a b c d)
set(NAME ace sabo luffy)
# 遍历 list
foreach(item IN LISTS WORD NAME)message(STATUS 当前遍历的值为: ${item} )
endforeach()在上面的例子中创建了两个 list 列表在遍历的时候对它们两个都进行了遍历可以根据实际需求选择同时遍历多个或者只遍历一个。输出的日志信息如下
$ cd build/
$ cmake ..
-- 当前遍历的值为: a
-- 当前遍历的值为: b
-- 当前遍历的值为: c
-- 当前遍历的值为: d
-- 当前遍历的值为: ace
-- 当前遍历的值为: sabo
-- 当前遍历的值为: luffy
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build一共输出了7个字符串说明遍历是没有问题的。接下来看另外一种方式
cmake_minimum_required(VERSION 3.2)
project(test)set(WORD a b c d e f)
set(NAME ace sabo luffy)
foreach(item IN ITEMS ${WORD} ${NAME})message(STATUS 当前遍历的值为: ${item} )
endforeach()在上面的例子中遍历过程中将关键字LISTS改成了ITEMS后边跟的还是一个或者多个列表只不过此时需要通过${}将列表中的值取出。其输出的信息和上一个例子是一样的
$ cd build/
$ cmake ..
-- 当前遍历的值为: a
-- 当前遍历的值为: b
-- 当前遍历的值为: c
-- 当前遍历的值为: d e f
-- 当前遍历的值为: ace
-- 当前遍历的值为: sabo
-- 当前遍历的值为: luffy
-- Configuring done
-- Generating done
-- Build files have been written to: /home/robin/abc/a/build小细节在通过 set 组织列表的时候如果某个字符串中有空格可以通过双引号将其包裹起来具体的操作方法可以参考上面的例子。
方法4 注意事项这种循环方式要求CMake的版本大于等于 3.17。
foreach(loop_var... IN ZIP_LISTS lists)通过这种方式遍历的还是一个或多个列表可以理解为是方式3的加强版。因为通过上面的方式遍历多个列表但是又想把指定列表中的元素取出来使用是做不到的在这个加强版中就可以轻松实现。
loop_var存储每次循环取出的值可以根据要遍历的列表的数量指定多个变量用于存储对应的列表当前取出的那个值。 如果指定了多个变量名它们的数量应该和列表的数量相等如果只给出了一个 loop_var那么它将一系列的 loop_var_N 变量来存储对应列表中的当前项也就是说 loop_var_0 对应第一个列表loop_var_1 对应第二个列表以此类推......如果遍历的多个列表中一个列表较短当它遍历完成之后将不会再参与后续的遍历因为其它列表还没有遍历完。 IN关键字表示在 xxx 里边ZIP_LISTS关键字对应的是列表list通过set 、list可以获得
cmake_minimum_required(VERSION 3.17)
project(test)
# 通过list给列表添加数据
list(APPEND WORD hello world hello world)
list(APPEND NAME ace sabo luffy zoro sanji)
# 遍历列表
foreach(item1 item2 IN ZIP_LISTS WORD NAME)message(STATUS 当前遍历的值为: item1 ${item1}, item2${item2} )
endforeach()message()
# 遍历列表
foreach(item IN ZIP_LISTS WORD NAME)message(STATUS 当前遍历的值为: item1 ${item_0}, item2${item_1} )
endforeach()在这个例子中关于列表数据的添加是通过list来实现的。在遍历列表的时候一共使用了两种方式一种提供了多个变量来存储当前列表中的值另一种只有一个变量但是实际取值的时候需要通过变量名_0、变量名_1、变量名_N 的方式来操作注意事项第一个列表对应的编号是0第一个列表对应的编号是0第一个列表对应的编号是0。
上面的例子输出的结果如下
$ cd build/
$ cmake ..
-- 当前遍历的值为: item1 hello, item2ace
-- 当前遍历的值为: item1 world, item2sabo
-- 当前遍历的值为: item1 hello world, item2luffy
-- 当前遍历的值为: item1 , item2zoro
-- 当前遍历的值为: item1 , item2sanji-- 当前遍历的值为: item1 hello, item2ace
-- 当前遍历的值为: item1 world, item2sabo
-- 当前遍历的值为: item1 hello world, item2luffy
-- 当前遍历的值为: item1 , item2zoro
-- 当前遍历的值为: item1 , item2sanji
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/robin/abc/a/build2.2.2 while
除了使用foreach也可以使用 while 进行循环关于循环结束对应的条件判断的书写格式和if/elseif 是一样的。while的语法格式如下
while(condition)commands
endwhile()while循环比较简单只需要指定出循环结束的条件即可
cmake_minimum_required(VERSION 3.5)
project(test)
# 创建一个列表 NAME
set(NAME luffy sanji zoro nami robin)
# 得到列表长度
list(LENGTH NAME LEN)
# 循环
while(${LEN} GREATER 0)message(STATUS names ${NAME})# 弹出列表头部元素list(POP_FRONT NAME)# 更新列表长度list(LENGTH NAME LEN)
endwhile()输出的结果如下:
$ cd build/
$ cmake ..
-- names luffy;sanji;zoro;nami;robin
-- names sanji;zoro;nami;robin
-- names zoro;nami;robin
-- names nami;robin
-- names robin
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/robin/abc/a/build可以看到当列表中的元素全部被弹出之后列表的长度变成了0此时while循环也就退出了。