<Linux开发> linux开发工具-之-CMake简单例程[再见]
Cmake相关文章如下:
<Linux开发> linux开发工具-之-CMake简单例程[初见]
<Linux开发> linux开发工具-之-CMake简单例程[再见]
<Linux开发> linux开发工具-之-CMake语法[细见1]
<Linux开发> linux开发工具-之-CMake语法[细见2]
<Linux开发> linux开发工具-之-CMake语法[细见3]
一、前言
本文主要是基于第一篇“ CMake简单例程[初见]”进一步将工程代码的文件结构进行分类 细化。
比如将编译输出的文件放到单独的一个文件夹中;再比如特定模块功能的.c .h文件放到单独的文件夹中。这样做的目的是方便模块化编写代码,也有利于代码间的解耦,和管理。
当一个项目功能越来越大的时候,所有的.c .h文件不会单单只放到一个文件夹里。这个时候就需要借助cmake的相关内容进行管理了。话不多说,cmake一切的目的就是为了得到对应平台的Makefile,然后编译源码,接下来我们就来一点一点走进cmake。
以下cmake的编译工具都采用的是gcc 或g++。后续有涉及到交叉编译时,我们再讲交叉编译的工具设置到cmake的方法。
在ubuntu中安装的cmake,默认采用主机的编译环境,所以在未修改编译工具的情况下,使用cmake得到的Makefile都是基于主机的。
二、默认build输出目录
新建项目目录“Cmake_test2”,并创建其子目录“build”.
1、编写main.c代码
//Cmake_test2/main.c
/***************************************************************
Copyright © OneFu Co., Ltd. 2019-2023. All rights reserved.
文件名 : main.c
作者 : waterfxw
版本 : V1.0
描述 : hello 示例代码
其他 : 主要是测试 使用cmake
日志 : 初版 V1.0 2023/03/15 waterfxw创建
***************************************************************/
#include <stdio.h>
#include <unistd.h>
int main(void)
{
while(1)
{
printf(" hello OneFu test \n");
sleep(2);
}
return 0;
}
2、编写Cmake
//Cmake_test2/CMakeLists.txt
project(WATER)
add_executable(water ./main.c)
3、编译
cmake编译
使用终端进入到build目录下,并执行命令“cmake …/”,如下图:
可以看到,所有cmake得到的文件都在build目录下。
接着使用Makefile来编译源码,
在build目录下,直接运行“make”,如下图:
至此源码编译得到 可执行文件“water”。
4、运行
前面我们提到过,cmake中我们并没有设置编译工具,采用的都是Ubuntu中默认的,那么我们来看看这个可执行文件的信息,如下:
有文件类型可知,是x86的,那就是在pc上运行的;
接下来我们运行试试。
运行效果符合,代码设计。
5、camke解析
在编写cmake的时候,我们书写了几句内容,下面一一分析。
(1)第1行: project(WATER)
project 是一个命令,命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,
并且通常需要我们提供参数,多个参数使用空格分隔而不是逗号“,”。
project 命令用于设置工程的名称,括号中的参数 WATER 便是我们要设置的工程名称;设置工程名称并不是强制性的,但是最好加上。
(2)第2行:add_executable(water ./main.c)
add_executable 同样也是一个命令,用于生成一个可执行文件,在本例中传入了两个参数,第一个参数表示生成的可执行文件对应的文件名,第二个参数表示对应的源文件;所以 add_executable(water ./main.c)表示需要生成一个名为 water 的可执行文件,所需源文件为当前目录下的 main.c。
6、小结
本小结主要是为了将编译的输出文件放到相应的目录,并于与源码文件的混淆。其实相对与前一篇文章来看,cmake并没有改动什么,主要的原因是运行cmake时是在build目录下,所以在运行cmake时默认输出到当前目录下的。
三、多源文件
通常我们在一个一个项目中会有多个.c .h文件,那么在这种情况下如何使用cmake呢?下面我们就来看看。
我们需要创建的文件有OneFu.c,OneFu.h和main.c,内容如下:
1、编写 OneFu.c OneFu.h代码
//Cmake/Cmake_test3/OneFu.h
/***************************************************************
Copyright © OneFu Co., Ltd. 2019-2023. All rights reserved.
文件名 : OneFu.h
作者 : waterfxw
版本 : V1.0
描述 : OneFu 示例代码
其他 : 主要是测试 使用cmake
日志 : 初版 V1.0 2023/03/21 waterfxw创建
***************************************************************/
#ifndef __TEST_ONEFU_
#define __TEST_ONEFU_
void OneFu(const char *name);
#endif //__TEST_ONEFU_
//Cmake/Cmake_test3/OneFu.c
/***************************************************************
Copyright © OneFu Co., Ltd. 2019-2023. All rights reserved.
文件名 : OneFu.c
作者 : waterfxw
版本 : V1.0
描述 : OneFu 示例代码
其他 : 主要是测试 使用cmake
日志 : 初版 V1.0 2023/03/21 waterfxw创建
***************************************************************/
#include <stdio.h>
#include "OneFu.h"
void OneFu(const char *name) {
printf("OneFu %s!\n", name);
}
主要是声明函数,用于主函数调用。
2、编写main.c代码
//Cmake/Cmake_test3/main.c
/***************************************************************
Copyright © OneFu Co., Ltd. 2019-2023. All rights reserved.
文件名 : main.c
作者 : waterfxw
版本 : V1.0
描述 : hello 示例代码
其他 : 主要是测试 使用cmake
日志 : 初版 V1.0 2023/03/15 waterfxw创建
***************************************************************/
#include <stdio.h>
#include <unistd.h>
#include "OneFu.h"
int main(void)
{
while(1)
{
OneFu(" hello test");
sleep(2);
}
return 0;
}
函数的具体实现,被主函数调用。
3、编写Cmake
//Cmake/Cmake_test3/CMakeLists.txt
project(WATER)
set(SRC_LIST main.c OneFu.c)
add_executable(water ${SRC_LIST})
4、编译
(1)camke编译
在build目录运行camke命令,如下:
(2)Makefile
在build目录下运行make命令,如下:
5、运行
运行可执行文件water,如下图:
6、camke解析
(1)第1行: project(WATER)
project 是一个命令,命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,
并且通常需要我们提供参数,多个参数使用空格分隔而不是逗号“,”。
project 命令用于设置工程的名称,括号中的参数 WATER 便是我们要设置的工程名称;设置工程名称并不是强制性的,但是最好加上。
(2)第2行:set(SRC_LIST main.c OneFu.c)
文件中使用到了 set 命令,set 命令用于设置变量,如果变量不存在则创建该变量并设置它;在本例中,我们定义了一个 SRC_LIST 变量,SRC_LIST 变量是一个源文件列表,记录生成可执行文件 water 所需的源文件 main.c 和 OneFu.c。
(3)第3行:add_executable(water ./main.c)
add_executable 同样也是一个命令,用于生成一个可执行文件,在本例中传入了两个参数,第一个参数表示生成的可执行文件对应的文件名,第二个参数表示对应的源文件;所以 add_executable(water ${SRC_LIST})表示需要生成一个名为 water 的可执行文件,所需源文件为SRC_LIST 变量列表中的文件。当然我们也可以不去定义 SRC_LIST 变量,直接将源文件列表写在 add_executable 命令中,如下:
add_executable(water main.c OneFu.c)
7、小结
相较于前一节的的内容,多了OneFu.c 和OneFu.h文件,并在CMakeLists.txt的内容中修改了一些。多个文件时,可引入变量的形式来组合所有的.c文件。这样有助于源文件的管理。
四、生成库文件
通常有些独立的功能模块是以库的形式提供给其它主程序调用的。例如前面多文件的形式,我们可以将OneFu.c生成库,然后给main.c调用,接下来我们来看下怎么做。
1、编写OneFu.c OneFu.h代码
参考第3节;
2、编写main.c代码
参考第3节;
3、编写Cmake
//Cmake/Cmake_test4/CMakeLists.txt
project(WATER)
add_library(libOneFu OneFu.c)
add_executable(water main.c)
target_link_libraries(water libOneFu)
4、编译
camke编译:
cd ./Cmake_test4/build
cmake ../
Makefile编译:
cd ./Cmake_test4/build
make
5、运行

6、camke库拓展
其实在第4点编译后我们可以看到生成的库文件是“liblibOneFu.a”,可知执行后,其内部自动添加前缀“lib”,有两种解决方法:
方法1:所需生成的库名无其它模块重复名时可直接修改名字;
如此例中第2行修改为:add_library(OneFu OneFu.c)
第4行改为:target_link_libraries(water OneFu)
project(WATER)
add_library(OneFu OneFu.c)
add_executable(water main.c)
target_link_libraries(water OneFu)
重新编译后如下图:
方法2:如果编译的库模块与其它模块命名重复了怎么办?
主程序文件也为OneFu
project(WATER)
add_library(OneFu OneFu.c)
add_executable(OneFu main.c)
target_link_libraries(OneFu OneFu)
上述cmake内容存在 库名 和执行文件名重复现象,这种肯定是不行的,camke中文件名是唯一的,否则无法编译。
基于此类情况可修改如下:
cmake_minimum_required(VERSION 3.5)
project(WATER)
add_library(libOneFu SHARED OneFu.c)
set_target_properties(libOneFu PROPERTIES OUTPUT_NAME "OneFu")
add_executable(OneFu main.c)
target_link_libraries(OneFu libOneFu)
这里生成了动态库.so 和执行文件water。
7、camke解析
camke语法解析基于第6点的方法2来,因为其内容较全一些。
(1)第1句:cmake_minimum_required(VERSION 3.5)
设置cmake的最低要求版本为 3.5;
(2)第2句:project(WATER)
project 是一个命令,命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,
并且通常需要我们提供参数,多个参数使用空格分隔而不是逗号“,”。
project 命令用于设置工程的名称,括号中的参数 WATER 便是我们要设置的工程名称;设置工程名称并不是强制性的,但是最好加上。
(3)第3句:add_library(libOneFu SHARED OneFu.c)
编译生成库,库名为libOneFu,SHARED:表示是动态库 ,OneFu.c:编译库所需的源文件;
不加SHARED默认生成.a静态库。
(4)第4句:set_target_properties(libOneFu PROPERTIES OUTPUT_NAME “OneFu”)
set_target_properties 用于设置目标的属性,这里通过 set_target_properties 命令对 libOneFu目标的
OUTPUT_NAME 属性进行了设置,将其设置为 OneFu。
参数PROPERTIES OUTPUT_NAME表示文件输出名,实际文件名会加上前缀lib。其它属性自行查阅cmake手册。
(5)第5句:add_executable(OneFu main.c)
add_executable 同样也是一个命令,用于生成一个可执行文件,在本例中传入了两个参数,第一个参数表示生成的可执行文件对应的文件名,第二个参数表示对应的源文件;所以 add_executable(OneFu ./main.c)表示需要生成一个名为 OneFu 的可执行文件,所需源文件为当前目录下的 main.c。
(6)第6句:target_link_libraries(OneFu libOneFu)
目标OneFu所依赖链接的库有哪些。
8、小结
通过上述我们进一步知道,Makefile有的功能cmake都有对应的实现对应。可重命名、可编译静态库、动态库、等等。借助这些功能能实现对大工程的管理。后面我们继续深入。
五、不同目录多源文件
通在实际开发中,我们常常把对应功能模块的代码放到一个单独的文件夹中这有助于文件管理分类。前面我们分析的都是将所有源码放到一起,接下来我们来看下不同文件夹下的模块如何编译在一起。
本次例程还是以前面的文件为例,只是源文件所处位置不同。
1、编写OneFu.c OneFu.h代码
参考第3节;
将OneFu.c放到"Cmake/Cmake_test5/lib/OneFu/OneFu.c"目录下
将OneFu.h放到"Cmake/Cmake_test5/lib/OneFu/OneFu.h"目录下
2、编写main.c 代码
参考第3节;
将main.c放到"Cmake/Cmake_test5/src/main.c"目录下
3、编写Cmake
(1)顶级目录Cmake_test5下新建CMakeLists.txt,填充以下内容:
//Cmake/Cmake_test5/CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(WATER)
add_subdirectory(lib)
add_subdirectory(src)
(2)子目录src目录下新建CMakeLists.txt,填充以下内容:
//Cmake/Cmake_test5/src/CMakeLists.txt
include_directories(${PROJECT_SOURCE_DIR}/lib/OneFu)
add_executable(OneFu main.c)
target_link_libraries(OneFu libOneFu)
(3)子目录lib/OneFu目录下新建CMakeLists.txt,填充以下内容:
//Cmake/Cmake_test5/lib/OneFu/CMakeLists.txt
add_subdirectory(OneFu)
(4)子目录lib/目录下新建CMakeLists.txt,填充以下内容:
//Cmake/Cmake_test5/lib/CMakeLists.txt
add_library(libOneFu SHARED OneFu.c)
set_target_properties(libOneFu PROPERTIES OUTPUT_NAME "OneFu")
上述文件新增完成后具体如下图:
4、编译
在build目录下编译
编译cmake:
cd build
cmake ../
编译Makefile:
cd build
make
通过cmake后,在build目录的目录结构和源码的目录结构是一致的。
5、运行
生成的可执行文件在 build/src/OneFu;
生成的可执行文件在 build/lib/OneFu/libOneFu.so;
运行可执行文件:
./src/OneFu
6、camke解析
(1)Cmake/Cmake_test5/CMakeLists.txt
第1句:cmake_minimum_required(VERSION 3.5)
设置cmake的最低要求版本为 3.5;
第2句:project(WATER)
project 是一个命令,命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,
并且通常需要我们提供参数,多个参数使用空格分隔而不是逗号“,”。
project 命令用于设置工程的名称,括号中的参数 WATER 便是我们要设置的工程名称;设置工程名称并不是强制性的,但是最好加上。
第3句:add_subdirectory(lib)
该命令告诉 cmake 去lib子目录中寻找新的CMakeLists.txt 文件并解析它
第4句:add_subdirectory(src)
该命令告诉 cmake 去src子目录中寻找新的CMakeLists.txt 文件并解析它
(2)Cmake/Cmake_test5/src/CMakeLists.txt
第1句:include_directories(${PROJECT_SOURCE_DIR}/lib/OneFu)
include_directories 命令用来指明头文件所在的路径,并且使用到了 PROJECT_SOURCE_DIR 变量,该变量指向了一个路径,从命名上可知,该变量表示工程源码的目录。
第2句:add_executable(OneFu main.c)
add_executable 同样也是一个命令,用于生成一个可执行文件,在本例中传入了两个参数,第一个参数表示生成的可执行文件对应的文件名,第二个参数表示对应的源文件;所以 add_executable(OneFu ./main.c)表示需要生成一个名为 OneFu 的可执行文件,所需源文件为当前目录下的 main.c。
第3句:target_link_libraries(OneFu libOneFu)
目标OneFu所依赖链接的库有哪些。
(3)Cmake/Cmake_test5/lib/CMakeLists.txt
第1句:add_subdirectory(OneFu)
该命令告诉 cmake 去OneFu子目录中寻找新的CMakeLists.txt 文件并解析它
(4)Cmake/Cmake_test5/lib/OneFu/CMakeLists.txt
第1句:add_library(libOneFu SHARED OneFu.c)
编译生成库,库名为libOneFu,SHARED:表示是动态库 ,OneFu.c:编译库所需的源文件;
不加SHARED默认生成.a静态库。
第2句:set_target_properties(libOneFu PROPERTIES OUTPUT_NAME “OneFu”)
set_target_properties 用于设置目标的属性,这里通过 set_target_properties 命令对 libOneFu目标的
OUTPUT_NAME 属性进行了设置,将其设置为 OneFu。
参数PROPERTIES OUTPUT_NAME表示文件输出名,实际文件名会加上前缀lib。其它属性自行查阅cmake手册。
7、小结
上述例程将源码文件分层管理,这也是大部分项目的常用形式。以后新增库模块即可在lib目录新建文件夹,加入新的源码文件和CMakeLists.txt之后,即可在主程序中添加引用。
学会文件分层管理也是进一步对大项目工程的开发有很大帮助。
六、指定目标文件位置
在第五节中,我们可知生成的两个目标文件“build/lib/OneFu/libOneFu.so”和“build/src/OneFu”分别和对应目录的cmake一些生成文件放在一起,这样不利于目标文件的使用。那么我们需要指定目标文件到单独的目录,这样后续使用目标文件也方便找。
1、编写OneFu.c OneFu.h代码
参考第五节。
2、编写main.c 代码
参考第五节。
3、编写Cmake
参考第五节。
修改:
(1)子目录src目录下新建CMakeLists.txt,填充以下内容:
//Cmake/Cmake_test5/src/CMakeLists.txt
include_directories(${PROJECT_SOURCE_DIR}/lib/OneFu)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(OneFu main.c)
target_link_libraries(OneFu libOneFu)
(2)子目录lib/目录下新建CMakeLists.txt,填充以下内容:
//Cmake/Cmake_test5/lib/CMakeLists.txt
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/libbin/OneFu)
add_library(libOneFu SHARED OneFu.c)
set_target_properties(libOneFu PROPERTIES OUTPUT_NAME "OneFu")
4、编译
在build目录下编译
编译cmake:
cd build
cmake ../
编译Makefile:
cd build
make
有输出文件位置可知,我们指定的目标文件的输出位置。这样方便我们查找到目标文件和使用。
5、运行
生成的可执行文件在 build/bin/OneFu;
生成的可执行文件在 build/libbin/OneFu/libOneFu.so;
运行可执行文件:
./bin/OneFu
6、camke解析
结合第五节的内容,在本节中只新增了两句;
第1句:set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/libbin/OneFu)
其作用是指定目标库的输出路径。
第2句:set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
其作用是指定可执行文件目标的输出路径。
其中PROJECT_BINARY_DIR是系统变量,表示当前执行的cmake命令的路径,对于本文,其值为:
PROJECT_BINARY_DIR = Cmake/Cmake_test6/build/
7、小结
本小节主要是为了执行目标文件的输出位置。
七、本文总结
本文虽然还是以一个小小的printf输出功能为例,但主要涵盖了常用的几种功能。
1、不同目录下的源文件cmake文件编写;
2、多级文件camke编译包含方式;
3、动态库、静态库编译;
4、目标文件重命名;
5、指定目标文件的生成存放路径;
在常规的一般开发中上述讲解的结构方法基本能满足了,剩下的就是具体的一些cmake语法相关的知识了。
读者想深入了解cmake可到 官网查阅资料。
后续将继续退出cmake的有关文章。
本文配套源码:Cmake文章配套源码资源文件