什么是cmake?
cmake 工具通过解析 CMakeLists.txt 自动帮我们生成 Makefile,可以实现跨平台的编译。cmake 就是用来产生 Makefile 的工具,解析 CMakeLists.txt 自动生成 Makefile:
cmake 的使用方法
cmake 就是一个工具命令,在 Ubuntu 系统下通过 apt-get 命令可以在线安装,如下所示
sudo apt-get install cmake
利用cmake --version命令检查:
使用实例1.单个源文件:
一个经典的 C 程序“Hello World”,如何用 cmake 来进行构建呢?
首先利用touch命令创建俩文件(mkdir是创建文件目录),后利用vi工具将代码写入。
touch main.c
touch CMakeLists.txt
//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 ./
可见
成功生成了文件。有了 Makefile 之后,接着我们使用 make 工具编译我们的工程
make
利用file hello 可见:
其中有arm,需要在arm开发板上运行,利用scp指令将文件传至开发板
开发板执行./hello,可见:
是不是有疑问,为什么是在开发板上运行而不是Ubuntu上?
这是因为电脑安装了交叉编译链,所以生成了arm环境下的编译文件。需要在ubuntu上运行也很简单;
我们先来看看CMakeLists.txt文件里的语句的意思:
project(HELLO)
这条指令定义了一个名为 HELLO
的项目。它会告诉 CMake 这是一个新项目,并且所有接下来的指令都将在这个项目的上下文中执行。多个参数使用空格分隔而不是逗号“,” 。
add_executable(hello ./main.c)
这条指令用于定义一个可执行目标文件。它告诉 CMake 要创建一个名为 hello
的可执行文件,并且这个可执行文件是由 main.c
源文件编译而来的。第一个参数表示生成的可执行文件对应的文件名,第二个参数表示对应的源文件; 所以 add_executable(hello ./main.c)表示需要生成一个名为 hello 的可执行文件,所需源文件为当前目录下的 main.c
因为我们之前设置的环境默认为交叉编译链的 ,所以只要在里面声明就行
将之前编译生成的文件删除。
后修改 CMakeLists.txt文件,改为
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")project(HELLO)
add_executable(hello ./main.c)
第一句指定c语言的利用gcc,c++的利用g++编译器。
后make,执行file hello可见:
环境为x86-64.成功
在ubuntu执行
./hello
但是各位是否有没有发现,当我们改编译器重新编译的时候,需要把生成文件删除特别麻烦,所以我们可以用一个更加简便的方法:
使用 out-of-source 方式构建
我们需要将构建过程生成的文件与源文件分离开来, 不让它们混杂在一起,也就是使用 out-of-source 方式构建。
将 cmake 编译生成的文件清理下,然后在工程目录下创建一个 build 目录
然后我们进入build文件夹进行编译:
利用
cmake ../
可以编译上级文件夹
日后如果要清除,直接返回上级目录 执行:
rm -rf build/*
这样子编译文件就删光啦!
使用示例二:多个源文件
我们再加入一个 hello.h 头文件和 hello.c 源文件。在 hello.c 文件中定义了一个函数 hello,然后在 main.c 源文件中将会调用该函数
touch hello. c hello.h生成俩文件
hello.h 文件内容
#ifndef __TEST_HELLO_
#define __TEST_HELLO_
void hello(const char *name);
#endif //__TEST_HELLO_
hello.c 文件内容(注意,%s指的是将name换成字符串,!在后面)
#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;
}
然后准备好 CMakeLists.txt 文件
(如果你默认环境是gcc,想要在开发板运行,你只要把gcc换成你的编译工具链就行,比如set(CMAKE_C_COMPILER " arm-linux-gnueabihf-gcc"))
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")
project(HELLO)
set(SRC_LIST main.c hello.c)
add_executable(hello ${SRC_LIST})
set(SRC_LIST main.c hello.c)
是 CMakeLists.txt 文件中的一条命令,用于定义一个变量并给它赋值。在这个例子中,SRC_LIST
变量被设置为包含两个源文件 main.c
和 hello.c
。
SRC_LIST
SRC_LIST
是变量名。你可以使用任何合适的名称来代表你的变量。- 这个变量通常用来存储源文件列表,以便在后续命令中引用。
add_executable(hello ${SRC_LIST})
:这行代码定义了一个名为 hello
的可执行文件,并使用 SRC_LIST
变量中的源文件来构建该可执行文件。add_executable
函数将 main.c
和 hello.c
编译并链接为一个名为 hello
的可执行文件。
cmake ../
make,file hello查看文件
编译成功。
使用示例三:生成库文件
在本例中,除了生成可执行文件 hello 之外,我们还需要将 hello.c 编译为静态库文件或者动态库文件,在示例二的基础上对 CMakeLists.txt 文件进行修改,如下所示:
set(CMAKE_C_COMPILER "gcc")
set(CMAKE_CXX_COMPILER "g++")project(HELLO)
add_library(libhello hello.c)
add_executable(hello main.c)
target_link_libraries(hello libhello)
进入到 build 目录下,执行 cmake、再执行 make 编译工程,编译完成之后,在 build 目录下就会生成可执行文件 hello 和库文件,如下所示:
本例中我们使用到了 add_library 命令和 target_link_libraries 命令。
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 程序
总结
-
add_library(libhello hello.c)
:定义一个名为libhello
的库,使用hello.c
源文件。 add_executable(hello main.c)
:定义一个名为hello
的可执行文件,使用main.c
源文件。target_link_libraries(hello libhello)
:将libhello
库链接到hello
可执行文件,使得可执行文件可以调用库中的函数。
作用是什么呢?
代码复用
- 静态库(Static Library):将常用的函数和逻辑封装在一个静态库中,可以在多个项目中复用这些函数,而不需要每次都复制代码。
- 动态库(Dynamic Library):动态库在不同的程序之间共享库中的代码,节省内存空间,并且可以在程序运行时加载和更新。
如果在CMakeLists.txt文件中添加下面这条命令:
set_target_properties(libhello PROPERTIES OUTPUT_
可以将生成的库为 liblibhello.a改成libhello.a。
使用示例四:将源文件组织到不同的目录
上面的示例中,我们已经加入了多个源文件,但是这些源文件都是放在同一个目录下,这样还是不太正规,我们应该将这些源文件按照类型、功能、模块给它们放置到不同的目录下,于是笔者将工程源码进行了整理,当前目录结构如下所示:
├── build #build 目录
├── CMakeLists.txt
├── libhello
│ ├── CMakeLists.txt
│ ├── hello.c
│ └── hello.h
└── src
├── CMakeLists.txt
└── main.c
在工程目录下,我们创建了 src 和 libhello 目录,并将 hello.c 和 hello.h 文件移动到 libhello 目录下,将main.c 文件移动到 src 目录下,并且在顶层目录、 libhello 目录以及 src 目录下都有一个 CMakeLists.txt 文件。CMakeLists.txt 文件的数量从 1 个一下变成了 3 个,我们来看看每一个 CMakeLists.txt 文件的内容。
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(HELLO)
add_subdirectory(libhello)
add_subdirectory(src)
src 目录下的 CMakeLists.txt
include_directories(${PROJECT_SOURCE_DIR}/libhello)
add_executable(hello main.c)
target_link_libraries(hello libhello)
libhello 目录下的 CMakeLists.txt
add_library(libhello hello.c)
set_target_properties(libhello PROPERTIES OUTPUT_NAME "hello")
顶层 CMakeLists.txt 中使用了 add_subdirectory 命令, 该命令告诉 cmake 去子目录中寻找新的CMakeLists.txt 文件并解析它;而在 src 的 CMakeList.txt 文件中,新增加了 include_directories 命令用来指明头文件所在的路径,并且使用到了 PROJECT_SOURCE_DIR 变量,该变量指向了一个路径,从命名上可知,该变量表示工程源码的目录。
和前面一样,进入到 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
使用实例5:将生成的可执行文件和库文件放置到单独的目录下
在默认情况下, make 编译生成的可执行文件和库文件会与 cmake 命令产生的中间文件(CMakeCache.txt、 CmakeFiles、 cmake_install.cmake 以及 Makefile 等)混在一起,也就是它们在同一个目录下; 如果我想让可执行文件单独放置在 bin 目录下,而库文件单独放置在 lib 目录下,就像下面这样:
├── build
├── lib
│ └── libhello.a
└── bin
└── hello
将库文件存放在 build 目录下的 lib 目录中,而将可执行文件存放在 build 目录下的 bin 目录中,这个时候又该怎么做呢?这个时候我们可以通过两个变量来实现,将 src 目录下的 CMakeList.txt 文件进行修改,如下所示:
include_directories(${PROJECT_SOURCE_DIR}/libhello)
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
add_executable(hello main.c)
target_link_libraries(hello libhello)
然后再对 libhello 目录下的 CMakeList.txt 文件进行修改,如下所示:
set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
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
其实实现这个需求非常简单,通过对 LIBRARY_OUTPUT_PATH 和 EXECUTABLE_OUTPUT_PATH变 量 进 行 设 置 即 可 完 成 ; EXECUTABLE_OUTPUT_PATH 变 量 控 制 可 执 行 文 件 的 输 出 路 径 , 而LIBRARY_OUTPUT_PATH 变量控制库文件的输出路径。
CMakeLists.txt 语法规则
篇幅较长,建议看手册