Linux下CMake总结

CMake是开源、跨平台的构建工具,可以让我们通过编写简单的配置文件去生成本地的Makefile,这个配置文件是独立于运行平台和编译器的,这样就不用亲自去编写Makefile了,而且配置文件可以直接拿到其它平台上使用,无需修改,非常方便。

bg_cmake

本文主要讲述在Linux下如何使用CMake来编译我们的程序。

安装CMake

实验机器用的是Ubuntu16.04

我们安装完CMake后,看一下cmake版本为3.24

简单样例

文件准备

我们新建一个文件夹,在里面创建一个hello.cpp

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(void)
{
printf("Hello World\n");

return 0;
}

再编写一个CMakeLists.txt,内容如下:

1
2
3
4
5
cmake_minimum_required (VERSION 2.8)

project (demo)

add_executable(main main.c)

第一行意思是表示cmake的版本最低要求是2.8

第二行意思是表示本工程信息,即工程名叫demo

第三行,表示要生成的elf文件的名字叫main,使用的源文件是main.c

操作

  • 切到main.c所在目录下,执行cmake .命令,构建系统是需要指定CMakeFLists.txt所在路径的,因为此时正好在本目录下,只需要 .即可,有些会创建一个build文件夹在里面cmake,那么路径就是..

    生成了Makefile以及其他自动生成的文件

  • 输入make,回车

    成功生成了main

同一目录下多个源文件

接下来进入稍微复杂的例子:在同一个目录下有多个源文件。
在之前的目录下添加2个文件,testFunc.c和testFunc.h。

testFunc.c内容如下,

1
2
3
4
5
6
7
8
9
10
11
/*
** testFunc.c
*/

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

void func(int data)
{
printf("data is %d\n", data);
}

testFunc.h内容如下,

1
2
3
4
5
6
7
8
9
10
/*
** testFunc.h
*/

#ifndef _TEST_FUNC_H_
#define _TEST_FUNC_H_

void func(int data);

#endif

修改main.c,调用testFunc.h里声明的函数func(),

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

#include "testFunc.h"

int main(void)
{
func(100);

return 0;

}

修改CMakeLists.txt,在add_executable的参数里把testFunc.c加进来

1
2
3
4
5
cmake_minimum_required (VERSION 2.8)

project (demo)

add_executable(main main.c testFunc.c)

操作还是一样,成功生成了main

将指定目录下所有源文件存储在一个变量中

在上文基础上,再加上一个头文件和一个源文件:

1
2
3
4
5
6
7
8
9
10
11
/*
** testFunc1.c
*/

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

void func1(int data)
{
printf("data is %d\n", data);
}
1
2
3
4
5
6
7
8
9
10
/*
** testFunc1.h
*/

#ifndef _TEST_FUNC1_H_
#define _TEST_FUNC1_H_

void func1(int data);

#endif

修改CMakeLists.txt

1
2
3
4
5
6
7
cmake_minimum_required (VERSION 2.8)

project (demo)

aux_source_directory(. SRC_LIST)

add_executable(main ${SRC_LIST})

aux_source_directory也有弊端,可能会把不需要的源文件也加进来,所以也可以使用set

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required (VERSION 2.8)

project (demo)

set( SRC_LIST
./main.c
./testFunc1.c
./testFunc.c)

add_executable(main ${SRC_LIST})

不同目录下多个源文件

20180901105703600

考虑如图所示的代码结构,我们修改CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
cmake_minimum_required (VERSION 2.8)

project (demo)

include_directories (test_func test_func1)

aux_source_directory (test_func SRC_LIST)
aux_source_directory (test_func1 SRC_LIST1)

add_executable (main main.c ${SRC_LIST} ${SRC_LIST1})

除了第六行都比较好理解,第六行是为了main.c的头文件,因为main.c包含了两个头文件,所以要指定头文件所在位置,不然找不到

正规一点的组织结构

正规一点来说,一般会把源文件放到src目录下,把头文件放入到include文件下,生成的对象文件放入到build目录下,最终输出的elf文件会放到bin目录下,这样整个结构更加清晰。让我们把前面的文件再次重新组织下,代码结构如图所示:

20180826221632533

方法一

写两个MakeLists.txt

  • 最外层

    1
    2
    3
    4
    5
    cmake_minimum_required (VERSION 2.8)

    project (demo)

    add_subdirectory (src)

    这里出现一个新的命令add_subdirectory(),这个命令可以向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制的存放位置,具体用法可以百度。

    这里指定src目录下存放了源文件,当执行cmake时,就会进入src目录下去找src目录下的CMakeLists.txt,所以在src目录下也建立一个CMakeLists.txt

  • src层

    1
    2
    3
    4
    5
    6
    7
    aux_source_directory (. SRC_LIST)

    include_directories (../include)

    add_executable (main ${SRC_LIST})

    set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

    这里又出现一个新的命令set,是用于定义变量的,EXECUTABLE_OUT_PATH和PROJECT_SOURCE_DIR是CMake自带的预定义变量,其意义如下,

    EXECUTABLE_OUTPUT_PATH :目标二进制可执行文件的存放位置
    PROJECT_SOURCE_DIR:工程的根目录
    所以,这里set的意思是把存放elf文件的位置设置为工程根目录下的bin目录。(cmake有很多预定义变量,详细的可以网上搜索一下)

20180826222905565

在build目录下,输入cmake ..,再make,切到bin目录下main成功生成

方法二

只写一个CMakeLists.txt

1
2
3
4
5
6
7
8
9
10
11
cmake_minimum_required (VERSION 2.8)

project (demo)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

aux_source_directory (src SRC_LIST)

include_directories (include)

add_executable (main ${SRC_LIST})

动态库静态库

后面用到的时候再补充

添加编译选项

有时编译程序时想添加一些编译选项,如-Wall,-std=c++11等,就可以使用add_compile_options来进行操作。
这里以一个简单程序来做演示,main.cpp如下

1
2
3
4
5
6
7
8
#include <iostream>

int main(void)
{
auto data = 100;
std::cout << "data: " << data << "\n";
return 0;
}

添加控制选项

有时希望在编译代码时只编译一些指定的源码,可以使用cmake的option命令,主要遇到的情况分为2种:

  1. 本来要生成多个bin或库文件,现在只想生成部分指定的bin或库文件

  2. 对于同一个bin文件,只想编译其中部分代码(使用宏来控制)

  • 假设我们现在的工程会生成2个bin文件,main1和main2,现在整体结构体如下,

20190811114036435

外层的CMakeLists.txt内容,如下

1
2
3
4
5
6
7
8
9
cmake_minimum_required(VERSION 3.5)

project(demo)

option(MYDEBUG "enable debug compilation" OFF)

set (EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

add_subdirectory(src)

这里使用了option命令,其第一个参数是这个option的名字,第二个参数是字符串,用来描述这个option是来干嘛的,第三个是option的值,ON或OFF,也可以不写,不写就是默认OFF。

然后编写src目录下的CMakeLists.txt,如下:

1
2
3
4
5
6
7
8
9
cmake_minimum_required (VERSION 3.5)

add_executable(main1 main1.c)

if (MYDEBUG)
add_executable(main2 main2.c)
else()
message(STATUS "Currently is not in debug mode")
endif()

然后cd到build目录下输入cmake .. && make就可以只编译出main1,如果想编译出main2,就把MYDEBUG设置为ON,再次输入cmake .. && make重新编译。

每次想改变MYDEBUG时都需要去修改CMakeLists.txt,有点麻烦,其实可以通过cmake的命令行去操作

我们先把MYDEBUG设置为OFF,先cd到build目录,然后输入cmake .. -DMYDEBUG=ON