一、CMake概述
CMake 是一个项目构建工具,并且是跨平台的。关于项目构建我们所熟知的还有Makefile(通过 make 命令进行项目的构建),大多的IDE软件都集成了make,比如:VS 的 nmake、linux 下的 GNU make、Qt 的 qmake等,如果自己动手写 makefile,会发现makefile 通常依赖于当前的编译平台,而且编写 makefile 的工作量比较大,解决依赖关系时也容易出错。
而 CMake 恰好能解决上述问题, 其允许开发者指定整个工程的编译流程,在根据编译平台,自动生成本地化的Makefile和工程文件,最后用户只需make编译即可,所以可以把CMake看成一款自动生成 Makefile的工具,其编译流程如下图:
- 蓝色虚线表示使用makefile构建项目的过程;
- 红色实线表示使用cmake构建项目的过程。
介绍完CMake的作用之后,再来总结一下它的优点:
- 跨平台;
- 能够管理大型项目;
- 简化编译构建过程和编译过程;
- 可扩展:可以为 cmake 编写特定功能的模块,扩充 cmake 功能。
注意事项:
首先我们要创建一个CMakeLists.txt文件,名称大小写一定不能错。
这里要注意:我测试用的CMakeLists.txt文件写错了,写成了CmakeLists.txt文件;但在Windows下没有报错,CmakeLists.txt文件和CMakeLists.txt文件名称在Windows下都可以。但是在linux下必须是大写的CMakeLists.txt文件,大小写不能错。
二、Cmake的使用
1、cmake版本
1.1、查看cmake版本
1)终端输入:
cmake --version
2)或在有CMakeLists.txt的文件目录下敲击tree,树状图里会有cmake版本
结果如下:
1.2、设置cmake最小版本
在CMakeLists.txt文件中设置如下:
cmake_minimum_required(VERSION 3.10) #设置cmake最小版本
cmake_minimum_required:指定使用的 cmake 的最低版本
注:此版本设置高些较好,但不能超过电脑当前安装的cmake版本;此版本设置的较低,有些功能或函数可能不支持。
2、注释
2.1、行注释
CMake 使用 # 进行行注释,可以放在任何位置;如下图红框所示:
2.2、块注释
CMake 使用 #[[ ]] 形式进行块注释;如下图红框所示:
3、指定头文件
在编译项目源文件的时候,我们需要知道头文件的路径才能编译通过;cmake指定头文件路径的命令为:include_directories
include_directories(HEADPATH)
HEADPATH:表示头文件所在的路径
如下图,PROJECT_SOURCE_DIR是cmake定义好的宏,表示项目的根目录,也可以理解为CmakeLists.txt文件所在的目录;那么下图命令表示该项目的头文件在项目的根目录的inc文件夹下。
eg:
include_directories(${PROJECT_SOURCE_DIR}/inc)
当需要添加多个头文件路径时也可以按下图换行,每个路径间用空格分隔开;
4、搜索文件
在一个项目中,头文件与源文件和其他文件都很多,一个个列举出来会很麻烦,这时候我们就需要简便快速的搜索到它们;cmake提供了以下两个命令:
4.1、查找源文件
aux_source_directory 命令是cmake设定的查找指定路径下的所有源文件的命令
aux_source_directory(< dir > < variable >)
dir:要搜索的目录
variable:将从dir目录下搜索到的源文件列表存储到该变量中
如下图便是指搜索项目的根目录下的src文件夹的所有源文件给到变量SRCS
eg:
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
当我们需要引用变量时只需要${SRCS},便可以引用变量SRCS存储的内容
4.2、查找指定文件类型
file命令是cmake提供的能够搜索指定文件类型的命令,不再像aux_source_directory 命令局限于查找源文件。
file(GLOB/GLOB_RECURSE 变量名 要搜索的文件路径和文件类型)
GLOB: 将指定目录下搜索到的满足条件的所有文件名生成一个列表,并将其存储到变量中。
GLOB_RECURSE:递归搜索指定目录,将搜索到的满足条件的文件名生成一个列表,并将其存储到变量中。
如下图代表
将项目根目录下的src文件夹下(不递归)的所有源文件存储到到变量SRCS
将项目根目录下的inc文件夹下(不递归)的所有头文件存储到到变量INCS
eg:
file(GLOB SRCS ${PROJECT_SOURCE_DIR}/src/*c)
file(GLOB INCS ${PROJECT_SOURCE_DIR}/inc/*h)
5、Set的使用
Set的使用主要给宏赋值和定义变量
SET(VAR VALUE)
VAR:变量名
VALUE:变量值
5.1、设置宏
如下图所示,EXECUTABLE_OUTPUT_PATH这个宏是cmake定义好的表示可执行程序输出路径的宏;
如下图表示将${PROJECT_SOURCE_DIR}/bin存储到EXECUTABLE_OUTPUT_PATH;即表示可执行程序的输出路径设置为项目根目录下的bin文件夹下。
eg:
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
5.2、定义变量
下图表示将CmakeLists.txt文件所在的目录下的src文件夹下的hello.c yes.c main.c给到变量SRCS
注:源文件名可以是一个也可以是多个,如有多个可用空格或;间隔
以下两种方式都是对的
eg:
set(SRCS src/hello.c src/main.c src/yes.c)
set(SRCS src/hello.c;src/main.c;src/yes.c)
6、生成可执行程序
add_executable:定义工程生成一个可执行程序
如下图表示将add.c等五个源文件和指定输出的头文件生成app.exe可执行程序。
add_executable(可执行程序名 源文件名称)
这里的可执行程序名和project中的项目名没有任何关系
源文件名可以是一个也可以是多个,如有多个可用空格或;间隔
eg:
add_executable(app add.c div.c main.c mult.c sub.c)
add_executable(app add.c;div.c;main.c;mult.c;sub.c)
在生成可执行程序前还需要指定可执行程序的生成路径,如5、set的使用所示,将想要生成可执行程序的路径给到EXECUTABLE_OUTPUT_PATH宏。
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
7、编写一个简单的CmakeLists.txt文件
经过上面六个部分命令的了解,我们现在可以编写一个简单的CmakeLists.txt文件。
7.1、编写前的准备
开始之前先要确认以下:
1、安装minGW和CMake
2、在windows配置minGW和CMake的bin环境变量
首先我们要创建一个CMakeLists.txt文件,名称大小写一定不能错。
这里要注意:我测试用的CMakeLists.txt文件写错了,写成了CmakeLists.txt文件;但在Windows下没有报错,CmakeLists.txt文件和CMakeLists.txt文件在Windows下都可以。但是在linux下必须是大写的CMakeLists.txt文件,大小写不能错。
项目上创建以下几个文件夹:
src:存放源文件;
inc:存放头文件;
build:执行cmake会生成一些配置文件比较杂,放在该目录下;
bin:存放生成的可执行程序;
CmakeLists.txt文件
创建以下测试程序:
inc目录下:
sys.h
#ifndef _SYS_H_
#define _SYS_H_
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define long 2560 //宏定义分辨率
#define wide 1440
void hello();
#endif
src目录下:
main.c
#include"sys.h"
int main(int argc, char *argv[])
{
hello();
printf("读取bin文件结果:success\n");
printf("long = %d,wide = %d\n",long,wide);
system("pause");
return 0;
}
hello.c
#include"sys.h"
void hello()
{
printf("读取****文件结果:success\n");
}
生成文件如下图:
7.2、编写CmakeLists.txt文件
这时候我们开始写CmakeLists.txt文件,让他帮我们生成makefile文件;
首先要写设置cmake最小版本,电脑当前安装的cmake查找方式在1、cmake版本中;如果没有安装,需要先去cmake官网安装下cmake。
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
再设置项目名称,名称随意设置,使用project(项目名称)命令设置
#项目名称
project(easy)
然后将头文件的路径指定出来,指定头文件的命令include_directories在3、搜索头文件中提到,
如下图指定头文件的路径为项目的根目录的inc目录下。
#指定头文件的路径,PROJECT_SOURCE_DIR宏对应的值是工程的根目录
#include_directories(headpath)
include_directories(${PROJECT_SOURCE_DIR}/inc)
搜索项目根目录下的src目录下的所有源文件存储到变量SRCS,搜索源文件的命令aux_source_directory 在4.1查找源文件中提到
#[[
aux_source_directory(< dir > < variable >)
dir:要搜索的目录
variable:将从dir目录下搜索到的源文件列表存储到该变量中
]]
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
设置可执行程序的输出路径到bin文件夹下,使用add_executable命令引用存储源文件的变量SRCS生成名称为test的可执行程序,最终在bin目录下生成test.exe。
#设置可执行程序路径,EXECUTABLE_OUTPUT_PATH是可执行路径的宏
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#生成可执行程序 add_executable(可执行程序名 源文件名称)
add_executable(test ${SRCS})
到了这里,一个简单的CmakeLists.txt文件基本编写完成。
我们进入build目录下敲击以下命令表示cmake指定MinGW编译生成makefile(我是在Windows环境下)
cmake .. -G "MinGW Makefiles"
如下图所示build目录下生成了一些配置文件和makefile文件,终端上没有报错,生成成功。
在build目录下敲击以下命令运行makefile生成可执行程序;
mingw32-make
如下图所示,bin文件夹下生成了test.exe可执行程序,并运行成功。
也可以将下图下载的MinW bin目录下的mingw32-make.exe重命名为make.exe;
这样敲击如下命令即可生成可执行程序:
make
7.3、遇到的问题
调试中还遇到以下两种报错:
1)原因:因为没有在Vscode下设置gcc为默认编译器
第一种设置方式:
扩展中安装cmake工具
点击工作区点击快捷键ctrl+shift+p,选择下图所示的CMake配置;
选择下图GCC作为编译器
Cmake会自动配置生成。
第二种设置方式:
编译器路径也有对应的宏,如下图使用set命令将下载的gcc编译器路径给到对应的宏即可。
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
2) 使用cmake .. -G “MinGW Makefiles”时出现下列问题
原因是之前已经生成过错的,重新生成之前没把上一次生成的删除
解决:
1、删除build文件里面的之前生成的文件
2、重新cmake .. -G "MinGW Makefiles"
最终CmakeLists.txt文件编写如下:
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(easy)
#指定头文件的路径,PROJECT_SOURCE_DIR宏对应的值是工程的根目录 include_directories(headpath)
include_directories(${PROJECT_SOURCE_DIR}/inc)
#[[
aux_source_directory(< dir > < variable >)
dir:要搜索的目录
variable:将从dir目录下搜索到的源文件列表存储到该变量中
]]
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
#设置可执行程序路径,EXECUTABLE_OUTPUT_PATH是可执行路径的宏
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
#生成可执行程序 add_executable(可执行程序名 源文件名称)
add_executable(test ${SRCS})
#最后使用cmake .. -G "MinGW Makefiles"生成makefile
#在生成的makefie目录下敲击mingw32-make即可生成可执行文件
8、日志打印
Cmake提供了可以给我们打印我们需要的信息的命令;日志打印命令:message
message([STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR] "message to display" ...)
(无) :重要消息
STATUS :非重要消息
WARNING:CMake 警告, 会继续执行
AUTHOR_WARNING:CMake 警告 (dev), 会继续执行
SEND_ERROR:CMake 错误, 继续执行,但是会跳过生成的步骤
FATAL_ERROR:CMake 错误, 终止所有处理过程
如下图所示加入以下日志信息,敲击cmake .. -G "MinGW Makefiles"便会在终端上将加上的日志信息打印出来,也可以打印变量。
eg:
message(WARNING "3333333333333333333333333333333")
message(STATUS "22222222222222222222222222222")
message("######################################")
9、宏
Cmake中提供了一些定义好的宏以及我们也可以自己定义宏。
9.1、常用宏
一些CMake中常用的宏:
宏 功能
PROJECT_SOURCE_DIR cmake命令后紧跟的目录,一般是工程的根目录
PROJECT_BINARY_DIR 执行cmake命令的目录
CMAKE_CURRENT_SOURCE_DIR 当前处理的CMakeLists.txt所在的路径
EXECUTABLE_OUTPUT_PATH 重新定义目标二进制可执行文件的存放位置
LIBRARY_OUTPUT_PATH 重新定义目标链接库文件的存放位置
PROJECT_NAME 返回通过PROJECT指令定义的项目名称
CMAKE_BINARY_DIR 项目实际构建路径,假设在build目录进行的构建,那么得到的就是这个目
录的路径
CMAKE_CURRENT_SOURCE_DIR与PROJECT_SOURCE_DIR相当。
9.2、自定义宏
在cmake中自定义宏的命令是add_definitions
add_definitions(-D宏名称)
比如我们在main.c中加入下面这段
CmakeLists.txt文件中加入以下命令:
add_definitions(-DDEBUG)
如下所示,生成的可执行程序便将main.c中加的this is custom macro打印出来了;
10、拼接
拼接主要分为set拼接与list拼接;list的功能有许多,非常强大,这里不做过多阐述,只了解拼接这一部分。
10.1、set拼接
如果使用set进行字符串拼接,对应的命令格式如下:
set(变量名1 ${变量名1} ${变量名2} ...)
关于上面的命令其实就是将从第二个参数开始往后所有的字符串进行拼接,
最后将结果存储到第一个参数中,如果第一个参数中原来有数据会对原数据就行覆盖。
10.2、list拼接
对于list,拼接只是它的一个功能,所以它的第一个参数是表示我们该做什么操作;其中APPEND表示追加,REMOVE_ITEM表示字符串移除。
如下图所示,SET_SRCS表示将hello.c和main.c拼接在一起,main.c在前存储到变量SET_SRCS中;
第一次参数为APPEND的变量LIST_SCRS1表示与变量SET_SRCS相同;
第一次参数为REMOVE_ITEM的变量LIST_SCRS1表示与变量main相同,即main.c与hello.c拼接在一起后,又把hello.c去除,还剩余main.c;
#set设置变量
set(hello ${PROJECT_SOURCE_DIR}/src/hello.c)
set(main ${PROJECT_SOURCE_DIR}/src/main.c)
#set拼接
set(SET_SRCS ${main} ${hello})
#list拼接
list(APPEND LIST_SRCS ${main} ${hello})
list(APPEND LIST_SRCS1 ${main} ${hello})
message("######################################")
message(${hello})
message("######################################")
message(${main})
message("######################################")
message(${SET_SRCS})
message("######################################")
message(${LIST_SRCS})
message("######################################")
message(${LIST_SRCS1})
list(REMOVE_ITEM LIST_SRCS1 ${hello})
message("######################################")
message(${LIST_SRCS1})
接下来我们可以看下cmake上的日志输出如下,确认输出正确:
对于set拼接与list拼接,可以用在用多个文件夹下的源文件生成库或生成可执行程序,使用一个变量存储所有想要的源文件方便些;
对于list字符串移除,可以用在一个文件夹有多个源文件,但是有一个源文件我不想打包进来,这时候就可以使用REMOVE_ITEM移除掉
eg:
file(GLOB SRCS ${PROJECT_SOURCE_DIR}/*.c)
# 移除 main.c
list(REMOVE_ITEM SRCS ${PROJECT_SOURCE_DIR}/main.c)
11、制作库文件
有时候我们制作的源文件并不一定要生成可执行程序,也可以生成静态库或动态库的库文件提供给第三方使用;以及我们在制作一些模块化的功能,可以把每个功能制作成一个库,最终可执行程序链接到他们生成最终完整功能的可执行程序。
使用场景:
动态链接库与静态链接库
动态链接库:适用于源文件比较多的时候,使用动态库不会把源代码打包到可执行程序里;应用程序启动后调用了动态库里的数据,动态库的数据才会被加载到内存中。
静态链接库:适用于源文件比较少的时候,使用静态库会把源代码打包到可执行程序里;应用程序启动后便会把静态库的数据加载到内存中。
11.1、生成静态库
Cmake中要生成静态库,命令如下
add_library(库名称 STATIC 源文件1 [源文件2] ...)
STATIC:表示生成静态库
在Linux中,静态库名字分为三部分:lib+库名字+.a,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充
在Windows中静态库也是lib+库名字+.a。
如下图所示便是将项目根目录下的src目录下的源文件生成库名为test1的静态库,最终会在libs目录下生成libtest1.a的库。
eg:
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称,随意设
project(joint)
#指定头文件的路径,PROJECT_SOURCE_DIR宏对应的值是工程的根目录 include_directories(headpath)
include_directories(${PROJECT_SOURCE_DIR}/inc)
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
add_library(test1 STATIC ${SRCS})
11.2、生成动态库
Cmake中要生成动态库,命令如下
add_library(库名称 SHARED 源文件1 [源文件2] ...)
SHARED:表示生成动态库
在Linux中,动态库名字分为三部分:lib+库名字+.so,此处只需要指定出库的名字就可以了,另外两部分在生成该文件的时候会自动填充
在Windows中动态库会生成lib+库名字+.dll
在同一个目录下生成静态库与动态库,两者不能重名,即便文件类型不同。
如下图所示便是将项目根目录下的src目录下的源文件生成库名为test1的动态库,最终会在libs目录下生成libtest1.dll和libtest1.dll.a(这个好像没什么用)的库。
eg:
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称,随意设
project(joint)
#指定头文件的路径,PROJECT_SOURCE_DIR宏对应的值是工程的根目录 include_directories(headpath)
include_directories(${PROJECT_SOURCE_DIR}/inc)
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
add_library(test1 SHARED ${SRCS})
11.3、指定库文件输出路径
在Cmake中指定库文件输出路径有定义好的宏,LIBRARY_OUTPUT_PATH
将生成的库想要保存的路径存储到LIBRARY_OUTPUT_PATH中即可;
如下图便是将库文件的输出路径设置为当前项目根目录的libs目录下:
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
12、链接库文件
在构建项目,生成可执行程序过程中,复杂些的项目可能都需要链接库文件
12.1、链接静态库
在cmake中,链接静态库的命令如下:
link_libraries(要链接的静态库名称)
也可以使用以下命令链接静态库:
target_link_libraries(
<target>
<PRIVATE|PUBLIC|INTERFACE> <item>...
[<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
target:指定要加载动态库的文件的名字
该文件可能是一个源文件
该文件可能是一个动态库文件
该文件可能是一个可执行文件
PRIVATE|PUBLIC|INTERFACE:动态库的访问权限,默认为PUBLIC
如果各个动态库之间没有依赖关系,无需做任何设置,三者没有没有区别,一般无需指定,使用默认的 PUBLIC 即可。
动态库的链接具有传递性,如果动态库 A 链接了动态库B、C,动态库D链接了动态库A,此时动态库D相当于也链接了动态库B、C,并可以使用动态库B、C中定义的方法。
target_link_libraries(A B C)
target_link_libraries(D A)
PUBLIC:在public后面的库会被Link到前面的target中,并且里面的符号也会被导出,提供给第三方使用。
PRIVATE:在private后面的库仅被link到前面的target中,并且终结掉,第三方不能感知你调了啥库
INTERFACE:在interface后面引入的库不会被链接到前面的target中,只会导出符号。
如果该静态库不是系统提供的(自己制作或者使用第三方提供的静态库)可能出现静态库找不到的情况,此时可以将静态库的路径也指定出来:
link_directories(<lib path>)
<lib path>:要链接的库文件的路径
如下便是将src目录下的源文件生成库名为test1的静态库存放在libs目录下,根目录下的main.c生成可执行程序link_test.exe并链接libs目录下的库名为test1的静态库并输出在bin目录下。
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称,随意设
project(joint)
#指定头文件的路径,PROJECT_SOURCE_DIR宏对应的值是工程的根目录 include_directories(headpath)
include_directories(${PROJECT_SOURCE_DIR}/inc)
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
add_library(test1 STATIC ${SRCS})
set(main ${PROJECT_SOURCE_DIR}/main.c)
#链接静态库 test1是要链接的静态库名称
link_libraries(test1)
#链接静态库的地址
link_directories(${PROJECT_SOURCE_DIR}/libs)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
add_executable(link_test ${main})
message("########################success")
也可以将link_libraries(test1)命令去除,再最后一行加上命令
target_link_libraries(link_test test1)也是可以链接的
12.2、链接动态库
也可以使用以下命令链接动态库:
target_link_libraries( <target> <library>)
target:需要链接动态库的可执行程序或库的名称
library:被链接的动态库的名称
target_link_libraries:既可以链接静态库也可以链接动态库,命令通常放在生成需要链接动态库的可执行程序或库之后
link_libraries:只能链接静态库
和链接静态库相同,如果该动态库不是系统提供的(自己制作或者使用第三方提供的动态库)可能出现动态库找不到的情况,此时可以将动态库的路径也指定出来:
link_directories(<lib path>)
<lib path>:要链接的库文件的路径
如下程序便是将src目录下的源文件生成库名为test2的静态库存放在libs目录下,根目录下的main.c生成可执行程序link_test2.exe并链接libs目录下的库名为test2的静态库并输出在libs目录下。(至于生成的可执行程序为什么要和要链接的动态库放在同一目录下见12.3、问题)
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称,随意设
project(link_shared_library)
#指定头文件的路径,PROJECT_SOURCE_DIR宏对应的值是工程的根目录 include_directories(headpath)
include_directories(${PROJECT_SOURCE_DIR}/inc)
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
link_directories(${PROJECT_SOURCE_DIR}/libs)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
add_library(test2 SHARED ${SRCS})
set(main ${PROJECT_SOURCE_DIR}/main.c)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/libs)
add_executable(link_test2 ${main})
target_link_libraries(link_test2 test2)
message("########################success")
12.3、问题
在链接动态库过程中遇到了一个问题,Windows下生成的动态库通过link_directories命令将要链接的动态库指定出来了,但是运行可执行程序会报错找不到动态库。
要把生成的库和可执行程序放在同一目录下才能运行,网上查找的好像也是要在同一目录下,Windows下不知道要链接的动态库与可执行程序不在同一目录下该如何实现。
因为要链接静态库会把源代码打包到可执行程序里,而动态库不会,所以静态库不在同一目录下没有问题。
13、添加子目录
如下图,在一些模块化处理的程序中,一个CmakeLists.txt文件处理起来会很麻烦,比较复杂;有一种化繁为简的方式就是给每个源码目录都添加一个CMakeLists.txt文件(头文件目录不需要)
嵌套的 CMake 是一个树状结构,最顶层的 CMakeLists.txt 是根节点,其次都是子节点。因此,我们需要了解一些关于 CMakeLists.txt 文件变量作用域的一些信息:
根节点CMakeLists.txt中的变量全局有效
父节点CMakeLists.txt中的变量可以在子节点中使用
子节点CMakeLists.txt中的变量只能在当前节点中使用
CMake 中父子节点之间的关系是如何建立的,这里需要用到一个 CMake 命令:
add_subdirectory(source_dir)
source_dir:指定子目录的位置
接下来我们按照上一张图的目录结构做一个简单的测试程序:
根目录下CMakeLists.txt(父节点):
#[[
嵌套cmake
]]
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(multiple_nodes_program)
#头文件路径
set(HEAD_PATH ${PROJECT_SOURCE_DIR}/inc)
#库路径
set(LIBRARYS_PATH ${PROJECT_SOURCE_DIR}/libs)
#可执行程序生成的路径
set(EXECUTABLE_PATH ${PROJECT_SOURCE_DIR}/libs)
#库文件名称
set(HELLO_LIB hello)
set(YES_LIB YES)
#可执行程序名称
set(TESTNAME1 test1)
set(TESTNAME2 test2)
#给当前节点添加子目录
add_subdirectory(hello)
add_subdirectory(yes)
add_subdirectory(test1)
add_subdirectory(test2)
message("########################success")
inc目录下的sys.h:
#ifndef _SYS_H_
#define _SYS_H_
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define long 2560 //宏定义分辨率
#define wide 1440
void hello();
void yes();
#endif
hello目录下的src目录下hello.c:
#include"sys.h"
void hello()
{
printf("hello world\n");
}
hello目录下的CMakeLists.txt(子节点):
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(hello)
include_directories(${HEAD_PATH})
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
set(LIBRARY_OUTPUT_PATH ${LIBRARYS_PATH})
add_library(${HELLO_LIB} SHARED ${SRCS})
message("########################hello")
yes目录下的src目录下yes.c:
#include"sys.h"
void yes()
{
printf("yes success\n");
}
yes目录下的CMakeLists.txt: (子节点)
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(yes)
include_directories(${HEAD_PATH})
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
set(LIBRARY_OUTPUT_PATH ${LIBRARYS_PATH})
add_library(${YES_LIB} SHARED ${SRCS})
message("########################yes")
test1目录下的test1.c:
#include"sys.h"
int main(int argc, char *argv[])
{
printf("test1 success\n");
hello();
yes();
printf("long = %d,wide = %d\n",long,wide);
printf("test1 success\n");
system("pause");
return 0;
}
test1目录下的CMakeLists.txt: (子节点)
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(test1)
include_directories(${HEAD_PATH})
aux_source_directory(./ SRCS)
link_directories(${LIBRARYS_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXECUTABLE_PATH})
add_executable(${TESTNAME1} ${SRCS})
target_link_libraries(${TESTNAME1} ${HELLO_LIB} ${YES_LIB}
message("########################test1")
test2目录下的test2.c:
#include"sys.h"
int main(int argc, char *argv[])
{
printf("test2 success\n");
hello();
yes();
printf("long = %d,wide = %d\n",long,wide);
printf("test2 success\n");
system("pause");
return 0;
}
test2目录下的CMakeLists.txt: (子节点)
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(test2)
include_directories(${HEAD_PATH})
aux_source_directory(./ SRCS)
link_directories(${LIBRARYS_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXECUTABLE_PATH})
add_executable(${TESTNAME2} ${SRCS})
target_link_libraries(${TESTNAME2} ${HELLO_LIB} ${YES_LIB})
message("########################test2")
在父节点上敲击命令cmake .. -G "MinGW Makefiles"生成makefile,mingw32-make命令运行makefile;最终效果如下:
在子节点hello和yes生成libhello.dll和libYES.dll动态库并放在目录libs下,test1和test2目录下的源文件生成可执行程序并链接libhello.dll和libYES.dll这两个动态库,生成的可执行程序与动态库一起放在libs同一个目录下。
14、嵌套
在一些程序中,有的可执行程序链接的库,该库也可能再次链接其他的静态库或动态库,形成嵌套式。
14.1、静态库链接静态库
接下来来看一个简单的举例程序:
目录结构如下和添加子节点的目录结构一致,只是在inc目录下新增了yes.h头文件。
根目录下CMakeLists.txt(父节点):
#[[
嵌套cmake
]]
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(multiple_nodes_program)
#头文件路径
set(HEAD_PATH ${PROJECT_SOURCE_DIR}/inc)
#库路径
set(LIBRARYS_PATH ${PROJECT_SOURCE_DIR}/libs)
#可执行程序生成的路径
set(EXECUTABLE_PATH ${PROJECT_SOURCE_DIR}/libs)
#库文件名称
set(HELLO_LIB hello)
set(YES_LIB yes)
#可执行程序名称
set(TESTNAME1 test1)
set(TESTNAME2 test2)
#给当前节点添加子目录
add_subdirectory(yes)
add_subdirectory(hello)
add_subdirectory(test1)
add_subdirectory(test2)
message("########################success")
inc目录下的sys.h:
#ifndef _SYS_H_
#define _SYS_H_
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define long 2560 //宏定义分辨率
#define wide 1440
void hello();
#endif
inc目录下的yes.h:
#ifndef _YES_H_
#define _YES_H_
void yes();
#endif
hello目录下的src目录下hello.c:
#include"sys.h"
#include"yes.h"
void hello()
{
yes();
printf("hello world\n");
}
hello目录下的CMakeLists.txt(子节点):
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(hello)
include_directories(${HEAD_PATH})
link_directories(${LIBRARYS_PATH})
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
set(LIBRARY_OUTPUT_PATH ${LIBRARYS_PATH})
add_library(${HELLO_LIB} STATIC ${SRCS})
target_link_libraries(${HELLO_LIB} ${YES_LIB})
message("########################hello")
yes目录下的src目录下yes.c:
#include<stdio.h>
#include"yes.h"
void yes()
{
printf("yes success\n");
}
yes目录下的CMakeLists.txt: (子节点)
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(yes)
include_directories(${HEAD_PATH})
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
set(LIBRARY_OUTPUT_PATH ${LIBRARYS_PATH})
add_library(${YES_LIB} STATIC ${SRCS})
message("########################yes")
test1目录下的test1.c:
#include"sys.h"
int main(int argc, char *argv[])
{
printf("test1 success\n");
hello();
printf("long = %d,wide = %d\n",long,wide);
printf("test1 success\n");
system("pause");
return 0;
}
test1目录下的CMakeLists.txt: (子节点)
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(test1)
include_directories(${HEAD_PATH})
aux_source_directory(./ SRCS)
link_directories(${LIBRARYS_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXECUTABLE_PATH})
add_executable(${TESTNAME1} ${SRCS})
target_link_libraries(${TESTNAME1} ${HELLO_LIB})
message("########################test1")
test2目录下的test2.c:
#include"sys.h"
int main(int argc, char *argv[])
{
printf("test2 success\n");
hello();
printf("long = %d,wide = %d\n",long,wide);
printf("test2 success\n");
system("pause");
return 0;
}
test2目录下的CMakeLists.txt: (子节点)
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(test2)
include_directories(${HEAD_PATH})
aux_source_directory(./ SRCS)
link_directories(${LIBRARYS_PATH})
set(EXECUTABLE_OUTPUT_PATH ${EXECUTABLE_PATH})
add_executable(${TESTNAME2} ${SRCS})
target_link_libraries(${TESTNAME2} ${HELLO_LIB})
message("########################test2")
可以看到程序中hello.c中的函数用到了yes.c中的函数,hello.c生成的静态库要链接yes.c生成的静态库;而这时候测试程序test1和test2只需要链接hello.c生成的静态库即可。test1链接libhello.a静态库,libhello.a静态库又链接libyes.a静态库,形成嵌套式。
14.2、静态库链接动态库
这里与静态库链接静态库相同,只需要yes目录下的CMakeLists.txt文件改成生成动态库即可。
#设置camke最小版本
cmake_minimum_required(VERSION 3.27.1)
#设置编译器路径
set(CMAKE_CXX_COMPILER "D:/GCC_compiler/mingw64/bin/g++.exe")
set(CMAKE_C_COMPILER "D:/GCC_compiler/mingw64/bin/gcc.exe")
#项目名称
project(yes)
include_directories(${HEAD_PATH})
aux_source_directory(${PROJECT_SOURCE_DIR}/src SRCS)
set(LIBRARY_OUTPUT_PATH ${LIBRARYS_PATH})
add_library(${YES_LIB} SHARED ${SRCS})
message("########################yes")
参考资料:
1)B站苏老师CMake 保姆级教程【C/C++】
课程:
2)CMake 保姆级教程(上)
https://subingwen.cn/cmake/CMake-primer/
3)CMake 保姆级教程(下)
https://subingwen.cn/cmake/CMake-advanced/