CMAKE学习笔记

1. 定义

CMAKE是一个开源、跨平台的编译、测试和打包工具,它使用比较简单的语言描述编译、安装的过程,输出Makefile或者project文件,再去执行构建。

当多人协同开发一个较大的项目时,会产生较多的源代码文,因此需要说明编译的顺序,例如需要先编译什么 ,再编译什么,这个过程称之为构建(Build),在构建的过程中使用的工具是make,对应的定义构建过程的文件称为Makefile;但是编写Makefile文件的语法较为复杂,尤其是对于大型的复杂项目,编写Makefile的过程则更为困难。

而Cmake为我们提供了一套简洁的语法去定义构建的流程,CMake定义构建过程的文件称为CMakeLists.txt。在使用IDE进行开发的过程中,这个流程一般是由IDE自动完成的,开发者基本不需要干预,但是如果开发者需要控制构建的细节,则需要自己定义构建过程。

2. gcc,make,cmake之间的关系

  • gcc能够将源代码文件编译成可执行文件或者共享库
  • 当编译的源文件较多的时候,需要指定编译的先后次序,这个过程称为构建(Build),使用的工具是make,定义构建过程的文件称为Makefile
  • 对于大型项目,编写Makefile的过程较为复杂,通过CMake,使用更加简洁的语法,就可以定义构建流程(也即生成Makefile),CMake定义构建流程的文件为CMakeLists.txt

3. CMake介绍以及使用

3.1 介绍

CMake提供了cmake,ctest,cpack三个命令行工具,分别用于项目的构建,测试,打包。

1. PROJECT关键字

用于指定工程名,此外,还可以指定项目版本号和使用的语言

PROJECT(demon1)
PROJECT(demon1 VERSION 1.0.0 LANGUAGES C CXX)

与此同时,这条语句还隐式的定义了两个变量

PROJECT_BINARY_DIR   // 二进制文件目录
PROJECT_SOURCE_DIR   // 源代码目录

如果在CMakeList.txt的同级目录下创建了build目录,且调用了cmake -B 指定build目录,则PROJECT_BINARY_DIR表示的就是build目录。如果没有创建,则上述两个变量指定的是同一个目录,即源代码目录。

2. SET关键字

用于显式地定义一个变量,从而实现用变量代替一个或者多个值,例如:

# 定义变量
set(SRCLIST main.cpp)
set(SRCLIST main.cpp other.cpp)  

3. MESSAGE关键字

主要用于向终端输出用户自定义的信息,输出信息可分为三类:

  • SEND_ERROR          表示产生错误,生成过程直接被跳过
  • STATUS                     常用于查看变量值,类似于 DEBUG 级别信息。信息的前缀带 --
  • FATAL_ERROR          立即终止所有cake过程
MESSAGE(STATUS "Binary dir is: ${PROJECT_BINARY_DIR}")

4. LIST关键字

用于对列表进行一些列的操作,List命令的格式如下:

list (subcommand  [args...])
  • subcommand      子命令
  •                    需要操作的列表
  • agrs                     参数

子命令的类型如下所示:

LENGTH            // 获取list的长度
 
GET              // 返回list中下标为index的element到value中
 
APPEND            // 添加新元素element到list中
 
FIND             // 返回list中element的index,没有找到返回-1
 
INSERT           // 将新element插入到list中index的位置
 
REMOVE_ITEM        // 从list中删除某个element
 
REMOVE_AT         // 从list中删除指定index的element
 
REMOVE_DUPLICATES       // 从list中删除重复的element
 
REVERSE           // 将list的元素的次序进行反转
 
SORT             // 将list按字母顺序进行排序

LIST使用实例:

# test list

SET(alist a b c d)

LIST(LENGTH alist len)

MESSAGE(STATUS "List length is ${len}")

LIST(GET alist 1 var)

MESSAGE(STATUS "The 1st element is ${var}")

LIST(APPEND alist hh)

MESSAGE(STATUS "The list is ${alist}")

LIST(FIND alist hh index)

MESSAGE(STATUS "The index of hh is ${index}")

LIST(FIND alist k index)

MESSAGE(STATUS "The index of k is ${index}")

输出结果:

5. ADD_EXECUTABLE关键字

通过指定的源文件列表构建出可执行目标文件,格式如下所示:

add_executable ( [WIN32] [MACOSX_BUNDLE]
      [EXCLUDE_FROM_ALL]
      [source1] [source2 ...])
  • name      可执行文件的名称
  • source    生成可执行文件的源代码文件

例如:

add_executable(main test1.cpp test2.cpp main.cpp)

使用实例:简单的hello world程序 main.cpp和CMakeLists.txt在同一目录

main.cpp文件

#include 
#include 

/*
   unistd.h为Linux/Unix系统中内置头文件,包含了许多系统服务的函数原型,例如read函数、write函数和getpid函数等。
   其作用相当于windows操作系统的"windows.h",是操作系统为用户提供的统一API接口,方便调用系统提供的一些服务。
*/

int gval = 0;

int main(int argc, char** argv)
{
	printf("Hello world\n");

	return 0;
}

CMakeLists.txt文件

CMAKE_MINIMUM_REQUIRED(VERSION 3.1)

PROJECT(forkDemon VERSION 1.0.0 LANGUAGES CXX)    # projectName version languages

AUX_SOURCE_DIRECTORY(. DIR_SRCS)

ADD_EXECUTABLE(main ${DIR_SRCS})

# prinf some info
message(STATUS "This is binary dir: " ${PROJECT_BINARY_DIR})
message(STATUS "This is dource dir: " ${PROJECT_SOURCE_DIR})

message(NOTICE, "Finished build the project.")

执行如下命令:

mkdir build     // 创建build目录,进行外部编译,所有生成的内容都存放在build目录下,不影响源代码目录
cmake -S . -B ./build     // 构建 -S指定源代码目录,且需要有一个CMakeLists.txt  -B指定构建的目录

cmake --build ./build     // 执行构建

6. ADD_SUBDIRECTORY关键字

用于向构建添加一个子目录,格式如下所示:

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
  • source_dir   用于指定源CMakeLists.txt以及源代码文件所在的路径  (通常是相对路径)
  • binary_dir    用于指定输出文件所在的路径   (通常是相对路径)

注:在这个添加的SUBDIRECTORY中,必须有CMakeLists.txt文件以及源代码文件

7. ADD_LIBRARY 关键字

用于生成动态库或者静态库,默认是生成静态库,格式如下所示:

add_library( [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [source1] [source2 ...])
  • name            生成库的名字,必须全局唯一
  • STATIC         指定生成的是静态库
  • SHARED      指定生成的是动态库
  • MODULE      指定生成模块库 (暂时没有用过)
  • source           指定生成库所需要的代码源文件

注:生成的library名会根据STATIC或SHARED成为动态库或者静态库,这里的STATIC和SHARED可不设置,通过全局的BUILD_SHARED_LIBS的FALSE或TRUE来指定,windows下,如果dll没有export任何信息,则不能使用SHARED,要标识为MODULE

使用实例:

新建代码目录以及代码源文件(Linux下)

CMAKE学习笔记_第1张图片

 main.cpp文件

#include 
#include "math/mathfunc.h"      // 这里路径不全会导致报错,找不到相应的文件

int main(int agrc, char** argv)
{
    add(3,5);

    sub(9,3);

    mul(2,6);

    sub(12,4);

    return 0;
}

CMakeLsts.txt

CMAKE_MINIMUM_REQUIRED(VERSION 3.1)      # cmake version

# 设置工程名
PROJECT(calcluator VERSION 1.0.0 LANGUAGES CXX)   # projectName version languages

# 添加源文件 
AUX_SOURCE_DIRECTORY(. DIR_SRCS)

# 构建子目录
ADD_SUBDIRECTORY(./math PROJECT_BINARY_DIR)

ADD_EXECUTABLE(cal ${DIR_SRCS})

# 需要连接的库
target_link_libraries(cal mathfuncs)

MESSAGE(STATUS, "Finished build the project.")

math目录下的文件
mathfunc.h文件:

#ifndef MATHFUNC_H
#define MATHFUNC_H

extern int add(int x, int y);
extern int sub(int x, int y);
extern int mul(int x, int y);
extern int div(int x, int y);

#endif

mathfunc.cpp文件:

#include "mathfunc.h"
#include 
#include 

int add(int x, int y)
{
    int result = x + y;

    printf("%d + %d = %d\n", x, y, result);
    
    return result;
}

int sub(int x, int y)
{
    int result = x - y;

    printf("%d - %d = %d\n", x, y, result);
    
    return result;
}

int mul(int x, int y)
{
    int result = x * y;

    printf("%d * %d = %d\n", x, y, result);
    
    return result;
}

int div(int x, int y)
{
    if (y == 0)
    {
        printf("The devisor can not be zero.\n");
        return INT_MIN;
    }

    int result = x / y;

    printf("%d / %d = %d\n", x, y, result);
    
    return result;
}

CMakeLists.txt

# mathfuncs 的makeLists

AUX_SOURCE_DIRECTORY(. MATH_LIB_SRC)

# 生成指定的连接库
ADD_LIBRARY(mathfuncs SHARED ${MATH_LIB_SRC})

MESSAGE(STATUS "Generationg mathfunc lib")

开始进行构架以及编译:

在源代码更目录新建builld文件夹:

mkdir build 

cmake -S . -B ./build

cd build && make -j && cd -

编译以及运行结果:

CMAKE学习笔记_第2张图片 ​​​​CMAKE学习笔记_第3张图片

 8. INCLUDE_DIRECTORIES关键字

将指定的文件添加到编译器的头文件搜索路径之下,为编译器提供搜索头文件的路径,例如上面的cal实例中,在main.cpp中需要#include "math/mathfunc.h",显得比较繁琐,如果在CMakeLists.txt文件中为编译器提供了此路径作为搜索路径,则在源代码中直接#include "mathfunc.h"即可。

代码修改如下:

main.cpp

#include 
#include "mathfunc.h"      

int main(int agrc, char** argv)
{
    add(3,5);

    sub(9,3);

    mul(2,6);

    sub(12,4);

    return 0;
}

CMakeLists.txt

CMAKE_MINIMUM_REQUIRED(VERSION 3.1)      # cmake version

# 设置工程名
PROJECT(calcluator VERSION 1.0.0 LANGUAGES CXX)   # projectName version languages

# 添加源文件 
AUX_SOURCE_DIRECTORY(. DIR_SRCS)

# 为编译器指定头文件搜索路径
INCLUDE_DIRECTORIES(./math)

# 构建子目录
ADD_SUBDIRECTORY(./math PROJECT_BINARY_DIR)

ADD_EXECUTABLE(cal ${DIR_SRCS})

# 需要连接的库
target_link_libraries(cal mathfuncs)

MESSAGE(STATUS, "Finished build the project.")

9. CMake向程序中添加调试信息,可供GDB进行调试

需要向CMakeList.txt文件中添加如下信息:

SET(CMAKE_BUILD_TYPE "Debug")  
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")  
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")  

9.1  CMAKE_BUILD_TYPE:   为CMake的内建变量,可以取值为Debug,Release,RelWithDebInfo 和 MinSizeRel

9.2  当CMAKE_BUILD_TYPE的值设置为Debug的时候,CMake会使用内建变量 CMAKE_CXX_FLAGS_DEBUG 和 CMAKE_C_FLAGS_DEBUG 中的字符串作为编译选项来生成 Makefile。

9.3  当CMAKE_BUILD_TYPE的值设置为Release的时候,CMake会使用内建变量 CMAKE_CXX_FLAGS_RELEASE 和 CMAKE_C_FLAGS_RELEASE 中的字符串作为编译选项来生成 Makefile。

CXXFLAGS: C++编译器的选项,无默认值

CFLAGS: C编译器的选项,无默认值

在设置编译选项的时候,还可以使用add_compile_options来对编译选项进行设置,区别是add_compile_options是针对所有编译器的(c和c++),而CXXFLAGS选项只针对c++编译器,CFLAGS选项指针会c编译器。

9.4  $ENV{}的作用是用于获取环境变量且设置环境变量的值

set($ENV{变量名} 变量值)

9.5  编译选项设置

gcc的四个优化等级选项O0, O1, O2, O3,Os

gcc为了满足不同用户对于代码编译时候的优化需求,提供了近百种的优化选项供用户选择,从编译时间,目标文件长度,程序执行效率等多个方面进行优化。使用户可以通过不同的选择项组合,实现在多个维度对程序编译的优化进行取舍和平衡。由于编译优化选项过于多,使用不便且难度较大,因此gcc又为用户提供了O0, O1, O2, O3,Os等多个不同的优化等级,降低优化选项的使用难度。

gcc优化选项
优化等级 描述
O0 编译器默认的选项,对程序编译时不做任何优化
O1 对程序做部分优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化流程。它主要对代码的分支,常量以及表达式等进行优化。
O2 比O1更高级的选项,在打开O1的所有选项的基础上,进行更多的优化,执行更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间,在增加了编译时间的基础上,提高了生成代码的执行效率。
O3 在打开了O2所有的优化的基础上进行进一步的优化,如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化,但是O3优化会使得程序调试变得更加的不可能,因为O3优化中对寄存器进行了优化,变量不再存放于原本的寄存器中。
Os 主要是对程序的尺寸进行优化,对程序代码的大小做更深层次的优化

通常各种优化都会打乱程序的结构,让调试工作变得无从着手,并且会打乱执行顺序,依赖内存操作顺序的程序需要做相关处理才能确保程序运行结果的正确性。

-ggdb : 用于生成使编译器生成符合GDB专用的更为丰富的调试信息 

-std=c++11 : 设置使用c++11标准

-Wall : 告知编译器编译完成后显示所有警告信息,与-W选项功能类似。与之相反的是-w选项,表示关闭警告信息显示

4. CMake为项目传入版本号,进行版本管理

1. 在源代码根目录中新建config.h.in文件,文件内容如下:

#ifndef CONFIG_H_IN
#define CONFIG_H_IN

// VERSION 1.x.x 

#define PROJECT_NAME "@PROJECT_NAME@"
#define PROJECT_VER  "@PROJECT_VERSION@"
#define PROJECT_VER_MAJOR "@PROJECT_VERSION_MAJOR@"
#define PROJECT_VER_MINOR "@PROJECT_VERSION_MINOR@"
#define PTOJECT_VER_PATCH "@PROJECT_VERSION_PATCH@"

#endif 

2. 在CMakeLists.txt里面设置项目名称,以及版本等信息,并指定配置文件

PROJECT("demon" VERSION 1.1.2)
CONFIGURE_FILE(config.h.in config.h)

3. 在编译完成之后,就会在PROJECT_BINARY_DIR里面生成对应的config.h文件,然后在CMakeLists.txt中加上引用目录,再在程序中引用即可。

INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR})

应用实例:(以hello word的)

config.h.in文件:

#ifndef CONFIG_H_IN
#define CONFIG_H_IN

// VERSION 1.x.x 

#define PROJECT_NAME "@PROJECT_NAME@"
#define PROJECT_VER  "@PROJECT_VERSION@"
#define PROJECT_VER_MAJOR "@PROJECT_VERSION_MAJOR@"
#define PROJECT_VER_MINOR "@PROJECT_VERSION_MINOR@"
#define PTOJECT_VER_PATCH "@PROJECT_VERSION_PATCH@"

#endif 

 

CMakeLists.txt文件

CMAKE_MINIMUM_REQUIRED(VERSION 3.1)      # cmake version

PROJECT(demon1 VERSION 1.2.1 LANGUAGES CXX)   # projectName version languages

# 配置文件
CONFIGURE_FILE(config.h.in config.h)

# 生成的配置文件在PROJECT_BINARY_DIR中,需要设置包含目录
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR})

AUX_SOURCE_DIRECTORY(. DIR_SRCS)

ADD_EXECUTABLE(main ${DIR_SRCS})

MESSAGE(NOTICE, "Finished build the project.")
 

main.cpp文件

#include 
#include 
#include 

int main(int argc, char** argv)
{
    printf("The program version is: %s\n", PROJECT_VER);
    printf("The Major Program is: %s\n", PROJECT_VER_MAJOR);
    printf("The Minor Program is: %s\n", PROJECT_VER_MINOR);
    printf("The Path  Program is: %s\n", PTOJECT_VER_PATCH);
    printf("Hello world\n");
    return 0;
}


运行结果:
生成的config.h文件

#ifndef CONFIG_H_IN
#define CONFIG_H_IN

// VERSION 1.x.x 

#define PROJECT_NAME "demon1"
#define PROJECT_VER  "1.2.1"
#define PROJECT_VER_MAJOR "1"
#define PROJECT_VER_MINOR "2"
#define PTOJECT_VER_PATCH "1"

#endif 

------------------------------------后续持续更新-----------------------------------------

你可能感兴趣的:(c++,常用技巧,学习,c++)