新建   文章列表   相册  后台  网摘
 

Kreos

Kreos 无所不能

LINUX应用编程 第三十一章 CMAKE 进阶 学习笔记(1)

CMake 入门与进阶

https://www.cnblogs.com/Kreos/p/19365968 //入门基础
https://www.cnblogs.com/Kreos/p/19413015 //语法规
https://www.cnblogs.com/Kreos/p/19413018 //进阶


#      //注释符
空格 //参数分隔


在前面两篇内容中,我们编写了很多示例程序,但这些示例程序都只有一个.c 源文件,非常简单。所以,编译这些示例代码其实都非常简单,直接使用 GCC 编译器编译即可,连 Makefile 都不需要

但是,在实际的项目中,并非如此简单,一个工程中可能包含几十、成百甚至上千个源文件,这些源文件按照其类型、功能、模块分别放置在不同的目录中;

面对这样的一个工程,通常会使用 make 工具进行管理、编译,make 工具依赖于 Makefile 文件,通过 Makefile 文件来定义整个工程的编译规则,使用 make 工具来解析 Makefile 所定义的编译规则。
Makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全按照Makefile 文件定义的编译规则进行自动编译,极大的提高了软件开发的效率。大都数的 IDE 都有这个工具,
譬如

  • Visual C++的 nmake、
  • linux 下的 GNU make、
  • Qt 的 qmake 等等,

这些 make 工具遵循着不同的规范和标准,对应的 Makefile 文件其语法、格式也不相同,这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台下编译,而如果使用上面的 make 工具,就得为每一种标准写一次 Makefile,这将是一件让人抓狂的工作。
cmake 就是针对这个问题所诞生,允许开发者编写一种与平台无关的 CMakeLists.txt 文件来制定整个工程的编译流程,再根据具体的编译平台,生成本地化的 Makefile 和工程文件,最后执行 make 编译。
因此,对于大多数项目,我们应当考虑使用更自动化一些的 cmake 或者 autotools 来生成 Makefile,而不是直接动手编写 Makefile。
本章我们便来学习 cmake,本章将会讨论如下主题内容。

  •  cmake 是什么?
  •  cmake 和 Makefile 之间的关系
  •  如何使用 cmake


31.1 cmake 简介
cmake 是一个跨平台的自动构建工具,前面导语部分也已经给大家介绍了,cmake 的诞生主要是为了解决直接使用 make+Makefile 这种方式无法实现跨平台的问题,所以 cmake 是可以实现跨平台的编译工具,
这是它最大的特点,当然除了这个之外,cmake 还包含以下优点:
 开放源代码。我们可以直接从 cmake 官网 https://cmake.org/下载到它的源代码;
 跨平台。cmake 并不直接编译、构建出最终的可执行文件或库文件,它允许开发者编写一种与平台无关的 CMakeLists.txt 文件来制定整个工程的编译流程,cmake 工具会解析 CMakeLists.txt 文件语法规则,再根据当前的编译平台,生成本地化的 Makefile 和工程文件,最后通过 make 工具来编译整个工程;所以由此可知,cmake 仅仅只是根据不同平台生成对应的 Makefile,最终还是通过 make工具来编译工程源码,但是 cmake 却是跨平台的。
 语法规则简单。Makefile 语法规则比较复杂,对于一个初学者来说,通常并不那么友好,并且Makefile 语法规则在不同平台下往往是不一样的;而 cmake 依赖的是 CMakeLists.txt 文件,该文件的语法规则与平台无关,并且语法规则简单、容易理解!cmake 工具通过解析 CMakeLists.txt 自动帮我们生成 Makefile,这样就不需要我们自己手动编写 Makefile 了。



31.2 cmake 和 和 Makefile
直观上理解,cmake 就是用来产生 Makefile 的工具,解析 CMakeLists.txt 自动生成 Makefile:

image

 图 31.2-1 cmake 与 Makefile

除了 cmake 之外,还有一些其它的自动构建工具,常用的譬如 automakeautoconf 等,有兴趣的朋友可以自己了解下。



31.3 cmake 的使用方法
cmake 就是一个工具命令,在 Ubuntu 系统下通过 apt-get 命令可以在线安装,如下所示:

sudo apt-get install cmake

这种方式下载的是当前 ubuntu 对应的最新版本,这里笔者不采用这种方式。我们可以到 cmake 的GitHub 链接地址 https://github.com/Kitware/CMake/releases 下载对应版本的 cmake,笔者下载的是 3.16.0 版本的,为了保持一致,也建议大家下载这个版本。

图【略】

图 31.3-1 cmake 3.16.0 版本

cmake-3.16.0.tar.gz 这个是 cmake 的源码工程,下载完拷贝到 ubuntu 下,可以自己创建一个文件夹放置,
将其解压。

tar -xf cmake-3.16.0.tar.gz

图 31.3-2 下载后解压 cmake 源码包

 

进入到解压后的 cmake-3.16.0 文件夹中,执行下面命令对源码进行编译和安装执行 bootstrap 脚本,生成编译所需要的各种配置文件

./bootstrap

image

 图 31.3-3 运行 bootstrap 脚本

 

提示 CMake has bootstrapped. Now run make. 就表示配置完成,可以对 cmake 源码进行编译了。

image

 图 31.3-4 bootstrap 配置完成

 

执行 make -j$(nproc) 来编译 cmake 源码,-j 表示启用多线程编译,$(nproc)会自动获取当前系统可用的CPU 核心数。

make -j$(nproc)

 

image

 图 31.3-5 make 进行编译


编译完源码后,就可用 make install 进行安装了。

sudo make install

image

 图 31.3-6 安装 cmake

 

安装完成后,可以执行 cmake --version 命令查看 cmake 版本号,如下所示:

image

 图 31.3-7 查看 cmake 版本号

 

由上图可知,当前系统安装的 cmake 对应的版本号为 3.16.0,cmake 工具版本更新也是比较快的,从官网 https://cmake.org/可知,cmake 最新版本为 4.0.0。
安装完 cmake 工具之后,接着我们就来学习如何去使用 cmake。cmake 官方也给大家提供相应教程,链接地址如下所示:
https://cmake.org/documentation/ //文档总链接地址
https://cmake.org/cmake/help/latest/guide/tutorial/index.html //培训教程

 

图【略】

图 31.3-8 cmake 官方文档


如果大家自学能力强,完全可以参考官方提供的培训教程学习 cmake;对于 cmake 的学习,笔者给大家两个建议:
 从简单开始、再到复杂!
 重点是自己动手多练习。


本小节我们将从一个非常简单的示例开始向大家介绍如何使用 cmake,再从这个示例进一步扩展、提出更多需求,来看看 cmake 如何去满足这些需求。

 


示例一:单个源文件


单个源文件的程序通常是最简单的,一个经典的 C 程序“Hello World”,如何用 cmake 来进行构建呢?

//main.c
#include <stdio.h>
int main()
{
  printf("Hello World!\n");
  return 0;
}

 

现在我们需要新建一个CMakeLists.txt文件,CMakeLists.txt文件会被cmake工具解析,就好比是Makefile文件会被 make 工具解析一样;

CMakeLists.txt 创建完成之后,在文件中写入如下内容:

project(HELLO)
add_executable(hello ./main.c)

 写入完成之后,保存退出,当前工程目录结构如下所示:

├── CMakeLists.txt
└── main.c

 在我们的工程目录下有两个文件,源文件 main.c 和 CMakeLists.txt,接着我们在工程目录下直接执行cmake 命令,如下所示:

cmake ./

cmake 后面携带的路径指定了 CMakeLists.txt 文件的所在路径,执行结果如下所示:

image

 图 31.3-9 执行 cmake


执行完 cmake 之后,除了源文件 main.c 和 CMakeLists.txt 之外,可以看到当前目录下生成了很多其它的文件或文件夹,包括:

CMakeCache.txt、
CmakeFiles、
cmake_install.cmake、
Makefile,

重点是生成了这个Makefile 文件,有了 Makefile 之后,接着我们使用 make 工具编译我们的工程,如下所示:

image

 图 31.3-10 make 编译工程


通过 make 编译之后得到了一个可执行文件 hello,这个名字是在 CMakeLists.txt 文件中指定的,稍后向大家介绍。通过 file 命令可以查看到 hello 是一个 x86-64 架构下的可执行文件,所以只能在我们的 UbuntuPC 上运行:
 image

图 31.3-11 file 查看可执行文件


为了验证 hello 可执行文件运行结果是否与源代码相同,我们直接在 Ubuntu 下运行即可,如下所示:
image

图 31.3-12 执行 hello


 

 


CMakeLists.txt 文件

多个参数使用空格分隔而不是逗号“,”。


上面我们通过了一个非常简单例子向大家演示了如何使用 cmake,重点在于去编写一个 CMakeLists.txt文件,现在来看看 CMakeLists.txt 文件中写的都是什么意思。

第一行 project(HELLO)    //project 命令:用于设置工程的名称; 

 project 命令:命令的使用方式有点类似于 C 语言中的函数,因为命令后面需要提供一对括号,并且通常需要我们提供参数。

 括号中的参数 HELLO 便是我们要设置的工程名称;  设置工程名称并不是强制性的,但是最好加上。

第二行 add_executable(hello ./main.c)     //add_executable 命令:用于生成一个可执行文件;

在本例中传入了两个参数,

  • 第一个参数表示生成的可执行文件对应的文件名,
  • 第二个参数表示对应的源文件; 

 所以 add_executable(hello ./main.c)表示需要生成一个名为 hello 的可执行文件,所需源文件为当前目录下的 main.c。


 

 

 


 使用 out-of-source 方式构建

将构建过程生成的文件与源文件分离开来,就是out-of-source构建方式。    //

这样 cmake 生成的中间文件以及 make 编译生成的可执行文件就全部在 build 目录下了,如果要清理工程,直接删除 build 目录即可,这样就方便多了。 
在上面的例子中,cmake 生成的文件以及最终的可执行文件 hello 与工程的源码文件 main.c 混在了一起,这使得工程看起来非常乱,当我们需要清理 cmake 产生的文件时将变得非常麻烦,这不是我们想看到的;

 

将 cmake 编译生成的文件清理下,然后在工程目录下创建一个 build 目录,如下所示:

├── build
├── CMakeLists.txt
└── main.c

 然后进入到 build 目录下执行 cmake

cd build/
cmake ../
make

 

image

 图 31.3-13 在 build 目录下编译

 


 

 

 

 


示例二:多个源文件


一个源文件的例子似乎没什么意思,我们在 main.c 源文件中将会调用头文件函数文件 加入头文件hello.h和 hello.c 源文件。在 hello.c 文件中定义了一个函数 hello:

 

 

  CMakeLists.txt 文件

project(HELLO)
set(SRC_LIST main.c hello.c) //set 命令:set 命令用于设置变量如果变量不存在则创建该变量并设置它; add_executable(hello ${SRC_LIST})

 在本例中,我们定义了一个 SRC_LIST 变量,SRC_LIST 变量是一个源文件列表,记录生成可执行文件 hello 所需的源文件 main.c 和 hello.c,而在 add_executable 命令引用了该变量;

当然我们也可以不去定义 SRC_LIST 变量,直接将源文件列表写在 add_executable 命令中,如下:

add_executable(hello main.c hello.c)

 

工程目录结构如下所示:

├── build //文件夹
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c
 

  同样,进入到 build 目录下,执行 cmake、再执行 make 编译工程,最终就会得到可执行文件 hello。



 hello.h 文件内容

#ifndef __TEST_HELLO_
#define __TEST_HELLO_
void hello(const char *name);

#endif //__TEST_HELLO_

  hello.c 文件内容

#include <stdio.h>
#include "hello.h"

void hello(const char *name) {   printf("Hello %s!\n", name); }

  main.c 文件内容

#include "hello.h"
int main(void)

{
  hello("World");
  return 0;
}

 



 

 


示例三:生成库文件


将 hello.c 编译为静态库文件或者动态库文件,在示例二的基础上对 CMakeLists.txt 文件进行修改,如下所示:

project(HELLO)
add_library(libhello hello.c)         #用于生成库文件,  //SHARED //STATIC 
add_executable(hello main.c) 
target_link_libraries(hello libhello) #为目标指定依赖库,

 

add_library 命令:用于生成库文件

在本例中我们传入了两个参数,

第一个参数表示库文件的名字,

需要注意的是,这个名字是不包含前缀和后缀的名字;在 Linux 系统中,库文件的前缀是 lib

  • 动态库文件的后缀是.so
  • 而静态库文件的后缀是.a

所以,意味着最终生成的库文件对应的名字会自动添加上前缀和后缀

第二个参数表示库文件对应的源文件。
本例中,add_library 命令生成了一个静态库文件 liblibhello.a,如果要生成动态库文件,可以这样做:

add_library(libhello SHARED hello.c) #生成动态库文件
add_library(libhello STATIC hello.c) #生成静态库文件

 

target_link_libraries 命令:为目标指定依赖库,

在本例中,hello.c 被编译为库文件,并将其链接进 hello 程序

 

进入到 build 目录下,执行 cmake、再执行 make 编译工程,编译完成之后,在 build 目录下就会生成 库文件  和 可执行文件 hello ,如下所示:

image

 图 31.3-14 生成可执行文件和库文件

 

目录结构如下所示:

├── build
│ ├── hello
│ └── liblibhello.a
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c

 



修改生成的库文件名字

本例中有一点非常不爽,生成的库为 liblibhello.a,名字非常不好看;如果想生成 libhello.a 该怎么办?
直接修改 add_library 命令的参数,像下面这样可以吗?

add_library(hello hello.c)

 答案是不行的,因为 hello 这个目标已经存在了(add_executable(hello main.c)),目标名对于整个工程来说是唯一的,不可出现相同名字的目标,所以这种方法肯定是不行的,实际上我们只需要在 CMakeLists.txt

文件中添加下面这条命令即可:

set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

 set_target_properties :用于设置目标的属性,这里通过 set_target_properties 命令对 libhello 目标的OUTPUT_NAME 属性进行了设置,将其设置为 hello。

我们进行实验,此时 CMakeLists.txt 文件中的内容如下所示:

cmake_minimum_required(VERSION 3.16)
project(HELLO)
add_library(libhello SHARED hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
add_executable(hello main.c)
target_link_libraries(hello libhello)

 除了添加 set_target_properties 命令之外,我们还加入了

cmake_minimum_required 命令:设置当前工程的 cmake 最低版本号要求,当然这个并不是强制性的,但是最好还是加上。进入到 build 目录下,

使用 cmake+make 编译整个工程,编译完成之后会发现,生成的库文件为 libhello.a,而不是 liblibhello.a。

├── build
│ ├── hello
│ └── libhello.so
├── CMakeLists.txt
├── hello.c
├── hello.h
└── main.c

 


 

 

 

 


示例四:将源文件组织到不同的目录

 

创建目录 src 和 libhello ,所有文件源目录创建CMakeLists.txt 文件。并移入文件。

  • libhello 目录下 hello.c 和 hello.h  。
  •  src 目录下main.c 。


将文件按照类型、功能、模块给它们放置到不同的目录下。目录结构如下所示:

├── build #build 目录
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c

CMakeLists.txt 文件的数量从 1 个一下变成了 3 个,顿时感觉到有点触不及防!还好每一个都不复杂!我们来看看每一个 CMakeLists.txt 文件的内容。

顶层目录

CMakeLists.txt 

cmake_minimum_required(VERSION 3.16)  //版本
project(HELLO)
add_subdirectory(libhello)
add_subdirectory(src)

 

add_subdirectory 命令:添加子CMakeLists.txt 文件目录  并解析它;

 src 目录

CMakeLists.txt

include_directories(${PROJECT_SOURCE_DIR}/libhello)
add_executable(hello main.c)
target_link_libraries(hello libhello)

 

include_directories 命令: 添加头文件路径,并且使用到了 

PROJECT_SOURCE_DIR 变量:指向路径,从命名上可知,该变量表示工程源码的目录。

libhello 目录

CMakeLists.txt

add_library(libhello hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
 

和前面一样,进入到 build 目录下进行构建、编译,

最终会得到可执行文件 hello(build/src/hello)和库文件 libhello.a(build/libhello/libhello.a)

├── build
│ ├── libhello
│ │ └── libhello.a
│ └── src
│ └── hello
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c

 


 

 

 

 

 


示例五:将生成的可执行文件和库文件放置到单独的目录下

 

 通过set变量进行设置完成;

bin 目录下,可执行文件单独放置。

lib  目录下,库文件单独放置在。

像这样:


── build
    ├── lib     
     │ └── libhello.a
    └── bin
       └── hello

 

src 目录下的 CMakeList.txt 文件进行修改,如下所示:

include_directories( ${PROJECT_SOURCE_DIR}/libhello )
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)    #EXECUTABLE_OUTPUT_PATH 变量控制可执行文件的输出路径   
add_executable(hello main.c)
target_link_libraries(hello libhello)

然后再对

libhello 目录下的 CMakeList.txt 文件进行修改,如下所示:

set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)      #LIBRARY_OUTPUT_PATH 变量控制库文件的输出路径
add_library(libhello hello.c) set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")

 修改完成之后,再次按照步骤对工程进行构建、编译,此时便会按照我们的要求将

生成的可执行文件hello 放置在 build/bin 目录下、库文件 libhello.a 放置在 build/lib 目录下。

最终的目录结构就如下所示:

├── build
│ ├── bin
│ │ └── hello
│ └── lib
│ └── libhello.a
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c

 

 


 

 

posted on 2025-12-18 11:38  前沿风暴  阅读(70)  评论(0)    收藏  举报

导航


校内网 © 2004-2026

京公网安备 33010602011771号 京ICP备2021040463号-3