CMake学习笔记(2)
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 files
include
目录:头文件目录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 逻辑判断
1、NOT
if(NOT <condition>)
其实这就是一个取反操作,如果条件condition
为True
将返回False
,如果条件condition
为False
将返回True
。
2、AND
if(<cond1> AND <cond2>)
如果cond1
和cond2
同时为True
,返回True
否则返回False
。
3、OR
if(<cond1> OR <cond2>)
如果cond1
和cond2
两个条件中至少有一个为True
,返回True
,如果两个条件都为False
则返回False
。
2.1.3 比较
1、基于数值的比较
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:如果左侧数值小于右侧,返回True
- GREATER:如果左侧数值大于右侧,返回True
- EQUAL:如果左侧数值等于右侧,返回True
- LESS_EQUAL:如果左侧数值小于等于右侧,返回True
- GREATER_EQUAL:如果左侧数值大于等于右侧,返回True
2、基于字符串的比较
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:如果左侧字符串小于右侧,返回True
- STRGREATER:如果左侧字符串大于右侧,返回True
- STREQUAL:如果左侧字符串等于右侧,返回True
- STRLESS_EQUAL:如果左侧字符串小于等于右侧,返回True
- STRGREATER_EQUAL:如果左侧字符串大于等于右侧,返回True
2.1.4 文件操作
1、判断文件或者目录是否存在
if(EXISTS path-to-file-or-directory)
如果文件或者目录存在返回True
,否则返回False
。
2、判断是不是目录
if(IS_DIRECTORY path)
- 此处目录的 path 必须是绝对路径
- 如果目录存在返回True,目录不存在返回False。
3、判断是不是软连接
if(IS_SYMLINK file-name)
- 此处的
file-name
对应的路径必须是绝对路径 - 如果软链接存在返回
True
,软链接不存在返回False
。 - 软链接相当于 Windows 里的快捷方式
4、判断是不是绝对路径
if(IS_ABSOLUTE path)
- 关于绝对路径:
- 如果是
Linux
,该路径需要从根目录开始描述 - 如果是
Windows
,该路径需要从盘符开始描述
- 如果是
- 如果是绝对路径返回
True
,如果不是绝对路径返回False
。
2.1.5 其它
1、判断某个元素是否在列表中
if(<variable|string> IN_LIST <variable>)
- CMake 版本要求:大于等于3.3
- 如果这个元素在列表中返回True,否则返回False。
2、比较两个路径是否相等
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 则只能进行字符串比较。
关于 if 的更多条件判断,请参考官方文档
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
再次强调:在对一个整数区间进行遍历的时候,得到的范围是这样的 【0,stop】,右侧是闭区间包含 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
再次强调:在使用上面的方式对一个整数区间进行遍历的时候,得到的范围是这样的 【start,stop】,左右两侧都是闭区间,包含 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, item2=ace
-- 当前遍历的值为: item1 = world, item2=sabo
-- 当前遍历的值为: item1 = hello world, item2=luffy
-- 当前遍历的值为: item1 = , item2=zoro
-- 当前遍历的值为: item1 = , item2=sanji
=============================
-- 当前遍历的值为: item1 = hello, item2=ace
-- 当前遍历的值为: item1 = world, item2=sabo
-- 当前遍历的值为: item1 = hello world, item2=luffy
-- 当前遍历的值为: item1 = , item2=zoro
-- 当前遍历的值为: item1 = , item2=sanji
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/robin/abc/a/build
2.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
循环也就退出了。
相关文章:

CMake学习笔记(2)
1. 嵌套的CMake 如果项目很大,或者项目中有很多的源码目录,在通过CMake管理项目的时候如果只使用一个CMakeLists.txt,那么这个文件相对会比较复杂,有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件ÿ…...

Linux下源码编译安装Nginx1.24及服务脚本实战
1、下载Nginx [rootlocalhost ~]# wget -c https://nginx.org/download/nginx-1.24.0.tar.gz2、解压 [rootlocalhost ~]# tar xf nginx-1.24.0.tar.gz -C /usr/local/src/3、安装依赖 [rootlocalhost ~]# yum install gcc gcc-c make pcre-devel openssl-devel -y4、 准备 N…...

4、dockerfile实现lnmp和elk
dockerfile实现lnmp 使用dockerfile n:nginx,172.111.0.10 m:mysql,172.111.0.20 p:php,172.111.0.30 安装配置nginx 1、准备好nginx和wordpress安装包 2、配置dockerfile 3、配置nginx主配置文件ngin…...

Portainer.io安装并配置Docker远程访问及CA证书
Portainer.io安装并配置Docker远程访问及CA证书 文章目录 Portainer.io安装并配置Docker远程访问及CA证书一.安装 Portainer.io2.启动容器 二.docker API远程访问并配置CA安全认证1.配置安全(密钥)访问2.补全CA证书信息3.生成server-key.pem4.创建服务端签名请求证书文件5.创建…...

探索 Transformer²:大语言模型自适应的新突破
目录 一、来源: 论文链接:https://arxiv.org/pdf/2501.06252 代码链接:SakanaAI/self-adaptive-llms 论文发布时间:2025年1月14日 二、论文概述: 图1 Transformer 概述 图2 训练及推理方法概述 图3 基于提示的…...

Flutter 多终端测试 自定义启动画面 更换小图标和应用名称
多终端测试 flutter devices flutter run -d emulator-5554 flutter run -d emulator-5556 自定义启动画面 之前: 进入assert 3x 生成 1x 2x dart run flutter_native_splash:create dart run flutter_native_splash:remove 现在(flutter_nativ…...

rsarsa-给定pqe求私钥对密文解密
题目: Math is cool! Use the RSA algorithm to decode the secret message, c, p, q, and e are parameters for the RSA algorithm.p 96484230290105156765905517400104265349457376392357398006439893520398525072984913995610350091634270503701075707336333…...

flutter开发-figma交互设计图可以转换为flutter源代码-如何将设计图转换为flutter源代码-优雅草央千澈
flutter开发-figma交互设计图可以转换为flutter源代码-如何将设计图转换为flutter源代码-优雅草央千澈 开发背景 可能大家听过过蓝湖可以转ui设计图为vue.js,react native代码,那么请问听说过将figma的设计图转换为flutter源代码吗?本文优雅草央千澈带…...

Deep4SNet: deep learning for fake speech classification
Deep4SNet:用于虚假语音分类的深度学习 摘要: 虚假语音是指即使通过人工智能或信号处理技术产生的语音记录。生成虚假录音的方法有"深度语音"和"模仿"。在《深沉的声音》中,录音听起来有点合成,而在《模仿》中…...

3 前端: Web开发相关概念 、HTML语法、CSS语法
文章目录 前言:导学1 Web开发相关概念2 Web标准(网页标准)3 软件架构(CS/BS)(1)C/S: Client/Server 客户端 / 服务器端(2)B/S: Browser/Server 浏览器 / 服务器端VSCode配置前段开发环境一、HTML概念1 概念2 HTML快速入门(1)语法快速入门(2)VSCode一个 !(快捷键…...

SpringBoot工程快速启动
1.问题导入 以后我们和前端开发人员协同开发,而前端开发人员需要测试前端程序就需要后端开启服务器,这就受制于后端开发人员。 为了摆脱这个受制,前端开发人员尝试着在自己电脑上安装 Tomcat 和 Idea ,在自己电脑上启动后端程序&a…...

Unity WebGL:本机部署,运行到手机
Unity WebGL 简单介绍一下Unity WebGL的技术方案,在WebGL平台出包后,Unity的运行时C/C代码是通过Emscripten编译成了WebAssembly/Wasm;游戏逻辑部分的C#代码是先通过il2cpp转成C再编译转成的Wasm,Unity程序就就可以在支持WebAsse…...

【线性代数】行列式的概念
d e t ( A ) ∑ i 1 , i 2 , ⋯ , i n ( − 1 ) σ ( i 1 , ⋯ , i n ) a 1 , i 1 a 2 , i 2 , ⋯ , a n , i n det(A) \sum_{i_1,i_2,\cdots,i_n } (-1)^{\sigma(i_1,\cdots,i_n)} a_{1,i_1}a_{2,i_2},\cdots, a_{n,i_n} det(A)i1,i2,⋯,in∑(−1)σ(i1,⋯,in)a1…...

Android SystemUI——StatusBar视图创建(六)
上一篇文章我们介绍了 StatusBar 的构建过程,在 makeStatusBarView() 中获得 FragmentHostManager,用来管理 StatusBar 的窗口。 一、状态栏视图 在得到 FragmentHostManager 实例对象之后,还会继续调用 addTagListener() 方法设置监听对象,然后获取 FragmentManager 并开…...

Picocli 命令行框架
官方文档 https://picocli.info/ 官方提供的快速入门教程 https://picocli.info/quick-guide.html 使用 Picocli 创建命令行应用程序 Picocli 是一个用于构建 Java 命令行应用的强大框架,它简化了参数解析和帮助消息生成的过程。 下面是如何使用 Picocli 构建简单命…...

spring-cloud-starter-gateway 使用中 KafkaAppender的问题
公司需要将应用日志上报到kafka,以供分析与查看。 结合logback可以完成此功能,大致配置如下: <appender name"KafkaAppender" class"com.github.danielwegener.logback.kafka.KafkaAppender"><encoder class&…...

【全套】基于分类算法的学业警示预测信息管理系统
【全套】基于分类算法的学业警示预测信息管理系统 【摘 要】 随着网络技术的发展基于分类算法的学业警示预测信息管理系统是一种新的管理方式,同时也是现代学业预测信息管理的基础,利用互联网的时代与实际情况相结合来改变过去传统的学业预测信息管理中…...

Elasticsearch Python 客户端是否与自由线程 Python 兼容?
作者:来自 Elastic Quentin_Pradet 在这篇文章中,我们将进行一些实验,看看 Python Elasticsearch 客户端是否与新的 Python 3.13 自由线程(free-threading)版本兼容,其中 GIL 已被删除。 介绍 但首先&…...

基于大语言模型的组合优化
摘要:组合优化(Combinatorial Optimization, CO)对于提高工程应用的效率和性能至关重要。随着问题规模的增大和依赖关系的复杂化,找到最优解变得极具挑战性。在处理现实世界的工程问题时,基于纯数学推理的算法存在局限…...

#CSS混合模式:解决渐变背景下的文字可见性问题
在现代网页设计中,渐变背景的使用越来越普遍。然而,当我们在渐变背景上放置文字时,常常会遇到一个问题:文字在某些背景颜色下可能变得难以阅读。今天,我们将探讨一个优雅的解决方案:使用CSS混合模式。 问题…...

Vue2+OpenLayers给标点Feature添加信息窗体(提供Gitee源码)
目录 一、案例截图 二、安装OpenLayers库 三、代码实现 3.1、信息窗体DOM元素 3.2、创建Overlay 3.3、创建一个点 3.4、给点初始化点击事件 3.5、完整代码 四、Gitee源码 一、案例截图 二、安装OpenLayers库 npm install ol 三、代码实现 初始化变量: d…...

实战threeJS数字孪生开源 数字工厂
threeJS数字孪生 数字工厂 设备定位 基于three.js的数字工厂开源项目介绍 一、项目概述 本项目是一款基于three.js的数字工厂项目,旨在通过3D可视化技术,为工业制造领域提供一个直观、高效、智能的生产监控与管理平台。该项目结合了现代前端技术栈&…...

【Python基础篇】——第3篇:从入门到精通:掌握Python数据类型与数据结构
第3篇:数据类型与数据结构 目录 Python中的数据类型概述列表(List) 创建列表列表的基本操作列表方法列表推导式 元组(Tuple) 创建元组元组的基本操作元组的不可变性 字典(Dictionary) 创建字典…...

算法3(力扣83)-删除链表中的重复元素
1、题目:给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。 2、实现( 因为已排序,所以元素若重复,必然在其下一位)(这里为在vscod…...

HarmonyOS 鸿蒙 ArkTs(5.0.1 13)实现Scroll下拉到顶刷新/上拉触底加载,Scroll滚动到顶部
HarmonyOS 鸿蒙 ArkTs(5.0.1 13)实现Scroll下拉到顶刷新/上拉触底加载 效果展示 使用方法 import LoadingText from "../components/LoadingText" import PageToRefresh from "../components/PageToRefresh" import FooterBar from "../components/…...

.NET8.0多线程编码结合异步编码示例
1、创建一个.NET8.0控制台项目来演示多线程的应用 2、快速创建一个线程 3、多次运行程序,可以得到输出结果 这就是多线程的特点 - 当多个线程并行执行时,它们的具体执行顺序是不确定的,除非我们使用同步机制(如 lock、信号量等&am…...

SpringBoot项目中解决CORS跨域资源共享问题
在Spring Boot项目中解决CORS(跨域资源共享)问题,可以通过以下几种方法: 1. 使用CrossOrigin注解 这是最简单的方法,适用于单个控制器或控制器方法级别的跨域配置。你可以在控制器类或具体的方法上使用CrossOrigin注…...

Android string.xml中特殊字符转义
项目中要在string.xml 中显示特殊符号 空格: (普通的英文半角空格但不换行) 窄空格: (中文全角空格 (一个中文宽度)) (半个中文宽度,但两个空格比一个中文…...

解析传统Workflow、AI Workflow与AI Agent概念,并通过Coze案例探讨利用AI工作流构建应用的实践流程
传统工作流 工作流入门这篇就够了 BPMN.JS中文教程 BPMN 工作流引擎解析 定义:工作流是在计算机支持下业务流程的自动或半自动化,其通过对流程进行描述以及按一定规则执行以完成相应工作。 应用:随着计算机技术的发展以及工业生产、办公自动…...

光谱相机的光谱分辨率可以达到多少?
多光谱相机 多光谱相机的光谱分辨率相对较低,波段数一般在 10 到 20 个左右,光谱分辨率通常在几十纳米到几百纳米之间,如常见的多光谱相机光谱分辨率为 100nm 左右。 高光谱相机 一般的高光谱相机光谱分辨率可达 2.5nm 到 10nm 左右&#x…...