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   // 源代码目录
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 <list> [args...])
  • subcommand    子命令
  • <list>        需要操作的列表
  • 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 (&lt;name&gt; [WIN32] [MACOSX_BUNDLE]
      [EXCLUDE_FROM_ALL]
      [source1] [source2 ...])
  • name      可执行文件的名称
  • source    生成可执行文件的源代码文件

例如:

add_executable(main test1.cpp test2.cpp main.cpp)
6. AUX_SOURCE_DIRECTORY
aux_source_directory(<dir> <variable>)

查找指定目录下所有源文件,并将名称保存到变量中,当同一目录下有多个文件的时候,较为方便。使用set一个一个添加也可以达到相同的目的.但是aux_source_directory只能检测到目录下的.cpp文件,无法检测到.h文件

set(COMMON_KERNEL_HEADERS
	src/kernel/CommRequest.h
	src/kernel/CommScheduler.h
	src/kernel/Communicator.h
	src/kernel/SleepRequest.h
	src/kernel/ExecRequest.h
	src/kernel/IORequest.h
	src/kernel/Executor.h
	src/kernel/list.h
	src/kernel/mpoller.h
	src/kernel/poller.h
	src/kernel/msgqueue.h
	src/kernel/rbtree.h
	src/kernel/SubTask.h
	src/kernel/thrdpool.h
)

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

main.cpp文件

#include <stdio.h>
#include <unistd.h>

/*
   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     // 执行构建
8. 输出路径设置
变量 含义
CMAKE_ARCHIVE_OUTPUT_DIRECTORY 指定存放静态库文件的位置
这一变量用来初始化ARCHIVE_OUTPUT_DIRECTORY
会自动创建debug或者release目录
CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG 指定debug模式下存放静态库文件的位置
CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE 指定release模式下存放静态库文件的位置
CMAKE_LIBRARY_OUTPUT_DIRECTORY 指定存放动态库文件的位置
这一变量用来初始化LIBRARY_OUTPUT_DIRECTORY
会自动创建debug或者release目录
CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG 指定debug存放动态库文件的位置
CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE 指定release存放动态库文件的位置
CMAKE_RUNTIME_OUTPUT_DIRECTORY 指定存放可执行文件的位置
这一变量用来初始化RUNTIME_OUTPUT_DIRECTORY
会自动创建debug或者release目录
CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG 指定debug模式下可执行文件的存放位置
CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE 指定release模式下可执行文件的存放位置
LIBRARY_OUTPUT_PATH 默认存放库文件的位置,如果没有指定CMAKE_ARCHIVE_OUTPUT_DIRECTORYCMAKE_LIBRARY_OUTPUT_DIRECTORY,生成的动态库文件和静态库文件将默认保存在此位置
EXECUTABLE_OUTPUT_PATH 指定可执行文件输出路径, 但是RUNTIME_OUTPUT_DIRECTORY的优先级更高
如果没有初始化RUNTIME_OUTPUT_DIRECTORY,则会保存在EXECUTABLE_OUTPUT_PATH所指定的目录中
CMAKE_DEBUG_POSTFIX 指定debug模式下生成的库文件的后缀
CMAKE_RELEASE_POSTFIX 指定release模式下生成的库文件的后缀名

使用例子:

# 编译产物的输出路径
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BIN_DIR}) 
9. ADD_SUBDIRECTORY关键字

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

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

注:在这个添加的SUBDIRECTORY中,必须有CMakeLists.txt文件以及源代码文件。在使用add_subdirectory的时候,如果source_dir不是当前CMakeLists.txt所在目录的子目录,则需要显示的指定binary_dir,用于存储source_dir相关文件。

10. ADD_LIBRARY 关键字

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

add_library(&lt;name&gt; [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下)

 main.cpp文件

#include <stdio.h>
#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 <limits.h>
#include <stdio.h>

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 -

编译以及运行结果:

 ​​​​

11. INCLUDE_DIRECTORIES关键字

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

代码修改如下:
main.cpp

#include <stdio.h>
#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.")
12. TARGET_INCLUDE_DIRECTORIES

  指定目标包含的头文件的路径;与include_directories的区别为:include_directories(dir)是一个全局包含,且向下传递,即如果某个目录的CMakeLists.txt中使用了该指令,其下所有的子目录默认也包含dir目录。在实际项目中,推荐使用target_include_directories。因为如果在顶层的CMakeLists.txt文件中,加入了include_directories(dir)命令,则会导致整个工程的源文件在编译时都包含dir目录。

  每个模块都是相对独立,因此模块内部应该将对内和对外的头文件进行区分,由模块决定其是否被传递。

与此类似的命令有以下几种:

  • target_include_directories 指定目标包含的头文件路径
  • target_link_libraries 指定目标链接的库
  • target_compile_options 制定目标的编译选项

PRIVATE | INTERFACE | PUBLIC参数的含义:

   假设一个项目,需要编写一个app.exe程序,还有动态库bar和动态库foo,其中的依赖关系:

app.exe <---bar <---- foo

其中,app.exe依赖动态库bar, 动态库bar依赖动态库foo。

  • PRIVATE: bar.h头文件中没有包含foo.h,只有在bar.cpp文件中包含foo.h,此时app.cpp中没有包含foo.h,因此app.c中不能使用foo.h中定义的符号。即app.cpp只知道bar动态库的存在,而不知道foo动态库的存在;

  • INTERFACE:bar.h中包含了foo.h头文件,但是bar.c中没有用到foo中定义的符号。但是app.cpp中包含了foo.h文件,因此可以引用foo中定义的符号。因此,bar只是作为一个接口,将foo传递给了app。

  • PUBLIC:PRIVATE + INTERFACE,bar.h头文件包含了foo.h头文件,且bar.cpp中引用了foo.h中定义的符号。此时,app.h头文件中包含了foo.h头文件,且可以引用foo.h中定义的符号。

13. 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")
  • CMAKE_BUILD_TYPE:   为CMake的内建变量,可以取值为Debug,Release,RelWithDebInfo 和 MinSizeRel
  • CMAKE_BUILD_TYPE的值设置为Debug的时候,CMake会使用内建变量CMAKE_CXX_FLAGS_DEBUGCMAKE_C_FLAGS_DEBUG 中的字符串作为编译选项来生成 Makefile。
  • CMAKE_BUILD_TYPE的值设置为Release的时候,CMake会使用内建变量CMAKE_CXX_FLAGS_RELEASECMAKE_C_FLAGS_RELEASE中的字符串作为编译选项来生成 Makefile。

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

14. CMake中的find_Package

  为了方便的在项目中引人外部依赖包(第三方库),CMake官方提供了许多预定义的寻找依赖包的Module,这些Module所在的路径为/usr/share/cmake-3.16/Modules,如下所示:

find_package可以通过用户指定的库名称和版本,找到库的头文件路径,链接库路径,版本号等信息:

引入cmake官方提供的库,例如需要加载opencv库:

可通过如下命令查看cmake官方提供的库有哪些:

cmake --help-module-list | grep -E ^Find

Modules模式参数:

find_package(<package> [version] [EXACT] [QUIET] [MODULE]
             [REQUIRED] [[COMPONENTS] [components...]]
             [OPTIONAL_COMPONENTS components...]
             [NO_POLICY_SCOPE])

参数含义:

  • package 库名,区分大小写
  • version 指定库版本,如果指定,就需要检测找到的库和指定的version是否兼容
  • EXACT 找到的库版本必须和指定的version完全匹配
  • QUIET 查找失败,不会输出错误信息,指定REQUIRED的时候失效
  • MODULE cmake的默认工作模式是module,如果Module模式下失败,则会切换到config模式下,如果制定了Module,则只在Module模式下进行
  • REQUIRED 必须找到指定的库,如果找不到就会停止整个cmake流程,默认不会停止
  • COMPONENTS 表示查找到的库中必须包含的组件,如果没有,则查找失败
cmake_minimum_required(VERSION 2.8)
project(find_package_learning)
find_package(OpenCV 3 REQUIRED)

if(OpenCv_FOUND)
    message(STATUS "OpenCV_DIR = ${OpenCV_DIR}")
    message(STATUS "OpenCV_INCLUDE_DIRS = ${OpenCV_INCLUDE_DIRS}")
    message(STATUS "OpenCV_LIBS = ${OpenCV_LIBS}")

    include_directories(${OPENCV_INCLUDE_DIRS})     # 包含头文件
    add_executable(opencv_test opencv_test.cpp)     # 可执行文件
    target_link_libraries(opencv_test ${OpenCV_LIBS})  # 链接库
else(OpenCv_FOUND)
    message(FATAL_ERROR "OpenCv Not Found!")
endif(OpenCv_FOUND)

对于系统预定义的Find<LibName>.cmake模块,在执行find_package之后,每个模块都会定义以下几个变量:

  • <LibName>_FOUND 表示对应的库是否找到
  • <LibName>_INCLUDE_DIR or <LibName>_INCLUDES 表示对应的第三方库的头文件路径
  • <LibName>_LIBRARY or <LibName>_LIBRARIES表示第三方库的链接路径

输出:

-- The C compiler identification is GNU 7.5.0
-- The CXX compiler identification is GNU 7.5.0
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenCV: /usr/local (found suitable version "3.4.4", minimum required is "3") 
-- OpenCV_DIR = /usr/local/share/OpenCV
-- OpenCV_INCLUDE_DIRS = /usr/local/include;/usr/local/include/opencv
-- OpenCV_LIBS = opencv_calib3d;opencv_core;opencv_dnn;opencv_features2d;opencv_flann;opencv_highgui;opencv_imgcodecs;opencv_imgproc;opencv_ml;opencv_objdetect;opencv_photo;opencv_shape;opencv_stitching;opencv_superres;opencv_video;opencv_videoio;opencv_videostab;opencv_viz;opencv_aruco;opencv_bgsegm;opencv_bioinspired;opencv_ccalib;opencv_cvv;opencv_datasets;opencv_dnn_objdetect;opencv_dpm;opencv_face;opencv_freetype;opencv_fuzzy;opencv_hdf;opencv_hfs;opencv_img_hash;opencv_line_descriptor;opencv_optflow;opencv_phase_unwrapping;opencv_plot;opencv_reg;opencv_rgbd;opencv_saliency;opencv_stereo;opencv_structured_light;opencv_surface_matching;opencv_text;opencv_tracking;opencv_xfeatures2d;opencv_ximgproc;opencv_xobjdetect;opencv_xphoto
-- Configuring done
-- Generating done
-- Build files have been written to: /home/zhangj/Programming/programming-learning-examples/cmake_learning/learn_cmake_easily/find_package_learning/build

引入cmake非官方库:(只对支持cmake编译安装的库有效)

Modules路径/usr/share/cmake-3.16/Modules下未定义的模块,需要用户先下载源码,然后通过Cmake编译安装,之后可通过与OpenCv一样的方式引入该库。

find_package有两种工作模式,一种是Modules模式,即上述引入OpenCv库的方式,另一种是Config模式。Config模式是指引入Module路径/usr/share/cmake-3.16/Modules下未定义的模块。此时需要用户下载对应第三方库的源码,进行编译,安装,再通过上述的方式进行引入;

如何为自己编写的库编写FindLibName.cmake文件

可参考如下教程:

https://blog.csdn.net/zhanghm1995/article/details/105466372
https://zhuanlan.zhihu.com/p/97369704

15. file命令

  CMake中的file命令主要用于对文件进行操作;包括读,写,文件系统,路径转换,传输,锁,归档等七个子命令;可参考内容(https://zhuanlan.zhihu.com/p/645350929)

  • 读文件子命令
# 将文件中的内容原封不动读取到指定的变量中
file(READ <filename> <variable> [OFFSET <offset>] [LIMIT <max-in>] [HEX])

参数含义:
filename: 需要读取的文件名
variable: 读取的文件内容存储到指定变量中
OFFSET: 可选项,从指定的偏移位置开始读取
LIMIT: 可选项,限定最多读取的长度max-in
HEX: 可选项,将读取的文件内容转换为16进制

# 将文件内容读取为一串ASCII字符变量存储在文件中,其中二进制文件会被忽略。回车和/r也会被
# 忽略
file(STRINGS <filename> <variable> [LIMIT_COUNT num]
     [LIMIT_INPUT numBytes] [LIMIT_OUTPUT numBytes]
     [LENGTH_MINIMUM numBytes] [LENGTH_MAXIMUM numBytes]
     [NEWLINE_CONSUME] [REGEX regex]
     [NO_HEX_CONVERSION])

参数含义:

LIMIT_COUNT:设置可返回最大数量的字符串
LIMIT_INPUT:从输入文件中可读取的最大字节数
LIMIT_OUTPUT:存储在输出变量中的最大字节数
LENGTH_MINIMUM:返回的字符串的最小长度,小于设定值的字符串将被忽略
LENGTH_MAXIMUM:返回的字符串的最大长度,大于设定值的字符串将被分割为子字符串
NEWLINE_CONSUME:允许换行符包含进字符串中,而不是截断

file(<HASH> <filename> <variable>)

计算文件内容对应的加密散列

参数含义:

HASH: 可选值有:MD5,SHA1,SHA224,SHA256,SHA384,SHA512
filename: 文件路径
variable: 计算结果

file(TIMESTAMP <filename> <variable>)

获取文件的修改时间

  • 写文件子命令
file(WRITE <filename> <data>)

将指定的数据写入文件,如果文件存在,则会覆盖文件内的内容,如果不存在,则会新建文件

file(APPEND <filename> <data>)

将数据写入文件,区别在于将数据追加到文件的末尾,而不是覆盖文件内容;

file({TOUCH | TOUCH_NOCREATE} [<file> ...])

如果文件不存在则创建新的空文件,如果文件存在,则文件的访问或修改时间会更新为执行该命令时的时间。文件存在两者作用一样,且文件内容不会被修改。但是文件不存在两者有着区别。
文件不存在两者的区别是:如果文件不存在,TOUCH模式会创建文件,而TOUCH_NOCREATE不会创建文件。

  • 文件系统
# 匹配文件
file(GLOB <variable> [LIST_DIRECTORIES true|false] 
    [RELATIVE <path>] [CONFIGURE_DEPENDS] 
    [<globbing-expression>...])

# 匹配文件
file(GLOB_RECURSIVE <variable> [FOLLOW_SYMLINKS] 
     [LIST_DIRECTORIES true|false] [RELATIVE <path>] 
     [CONFIGURE_DEPENDS] [<globbing-expression>...])

生成一个与通配表达式匹配的文件列表,且存储在指定文件中;

参数含义:
LIST_DIRECTORIES:是否在结果中列出匹配的文件所在的目录。默认为false,即返回的文件列表中不包含目录信息

RELATIVE:指定相对于哪一个路径进行文件搜索,如果不指定,则默认相对于当前CmakeLists.txt文件所在的目录进行搜索;

CONFIGURE_DEPENDS:
在生成的 Makefile 中添加规则,以便在重新配置时重新执行 file(GLOB) 命令以更新匹配的文件列表。这可以确保在文件发生变化时重新构建相关的目标。

<globbing-expression>: 统配表达式;如果没有指定,则默认匹配所有的文件

例子:

file(GLOB SRCS     ${CMAKE_CURRENT_SOURCE_DIR}/api/*.cpp     
${CMAKE_CURRENT_SOURCE_DIR}/impl/*.cpp     
${CMAKE_CURRENT_SOURCE_DIR}/model/*.cpp     
${CMAKE_CURRENT_SOURCE_DIR}/impl/userManager/*.cpp     
${CMAKE_CURRENT_SOURCE_DIR}/impl/userManager/config/*.cpp     
${CMAKE_CURRENT_SOURCE_DIR}/*.cpp )  
add_executable(${PROJECT_NAME} ${SRCS})

也可以通过AUX_SOURCE_DIRECTORY一个一个目录进行添加,但是相对于AUX_SOURCE_DIRECTOR命令一个目录一个目录的添加方便很多;

GLOB_RECURSIVE:除了匹配当前目录,还会去递归的匹配当前目录子目录内的文件;

file(MAKE_DIRECTORY [<dir>...])

创建指定的目录,可创建多个,用都好分隔;该命令会在CMAKE构建期间执行,如果目录不存在,则创建对应的目录;

file({REMOVE | REMOVE_RECURSIVE} [<filename>...])

删除指定的文件;REMOVE只能删除文件,不能删除目录;REMOVE_RECURSIVE能删除给定的文件和目录,如果存在子目录,也能递归删除;

file(RENAME <old_name> <new_name> [RESULT <result>] [NO_REPLACE]) 

重命名文件或者目录

参数含义:

RESULT: 如果成功,则result的值被置为0
NO_REPLACE: 如果路径已存在,则不需替换

file(COPY_FILE <oldname> <newname> [RESULT <result>] 
     [ONLY_IF_DIFFERENT] [INPUT_MAY_BE_RECENT])

拷贝指定的文件;

参数含义:

RESULT: 如果成功将result的值置为0,否则将显示错误消息;如果未指定RESULT,则失败时会触发错误;

ONLY_IF_DIFFERENT:如果newname已经存在,且文件的内容与oldname相同,则不需要更新文件,这样可避免newname的时间戳被刷新;

INPUT_MAY_BE_RECENT:只适用于Windows平台,在Windows上文件创建后,短时间内可能会无法访问,如果指定了INPUT_MAY_BE_RECENT参数,则会重试几次;

file(SIZE <filename> <variable>)

读取文件的大小

总结

  在Modules模式下,cmake需要找到一个叫做Find<LibName>.cmake的文件。这个文件负责找到对应的库所在路径,为我们的项目引入头文件路径和库文件路径。cmake搜索这个文件的路径有两个,一个是上文提到的cmake安装目录下的usr/share/cmake-<version>/Modules目录,另一个是用户指定的CMAKE_MODULE_PATH的所在目录(默认为空,用户可通过set来指定它的值),如果cmake没有找到对应的Find<LibName>.cmake文件,则会转入Config模式下进行工作。

  Config模式下主要通过<LibName>Config.cmake 或者 <lower-case-package-name>-config.cmake这两个文件来引入用户需要的第三方库;在安装库(支持cmake编译安装)之后,会自动在/usr/local/lib/cmake/<LibName>/目录下生成了LibName-config.cmake文件,而/usr/local/lib/cmake/<LibName>/目录正是find_package函数在Config模式下的搜索路径之一,这就保证了后续安装的库也能够通过find_package正确引入;

tips
Cmake在Config模式下,搜索包的路径依次为:

  • <LibName>_DIR 默认为空
  • CMAKE_PREFIX_PATHCMAKE_FRAMEWORK_PATHCMAKE_APPBUNDLE_PATH 默认为空
  • PATH 环境变量路径

如果确知道想要查找的库Config.cmake或-config.cmake文件所在路径,为了能够准确定位到这个包,可以直接设置变量_DIR为具体路径,如:

set(OpenCV_DIR "/home/zhanghm/Softwares/enviroment_config/opencv3_4_4/opencv/build")

此时就可以明确找到需要的库

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

set($ENV{变量名} 变量值)
4.1编译选项设置

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选项,表示关闭警告信息显示

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

5.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 
5.2 在CMakeLists.txt里面设置项目名称,以及版本等信息,并指定配置文件
PROJECT("demon" VERSION 1.1.2)
CONFIGURE_FILE(config.h.in config.h)
5.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 <stdio.h>;
#include <unistd.h>;
#include <config.h>;

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

6. 安装

cmake也可以指定安装规则,可以在产生MakeFile之后,通过make install来执行,它可以用来安装包括目标二进制,动态库,静态库,目录以及脚本等内容。

6.1 install指令
install(TARGETS <target>... [...])
install({FILES | PROGRAMS} <file>... [...])
install(DIRECTORY <dir>... [...])
install(SCRIPT <file> [...])
install(CODE <code> [...])
install(EXPORT <export-name> [...])
6.1.1 安装目标文件
 install(TARGETS targets... [EXPORT <export-name>]
        [[ARCHIVE|LIBRARY|RUNTIME|OBJECTS|FRAMEWORK|BUNDLE|
          PRIVATE_HEADER|PUBLIC_HEADER|RESOURCE]
         [DESTINATION <dir>]
         [PERMISSIONS permissions...]
         [CONFIGURATIONS [Debug|Release|...]]
         [COMPONENT <component>]
         [NAMELINK_COMPONENT <component>]
         [OPTIONAL] [EXCLUDE_FROM_ALL]
         [NAMELINK_ONLY|NAMELINK_SKIP]
        ] [...]
        [INCLUDES DESTINATION [<dir> ...]]
        )

参数中的Target可以是多种目标文件,如二进制可执行文件,动态库,静态库

目标文件(Targets) 安装目录变量 安装目录
ARCHIVE ${CMAKE_INSTALL_LIBDIR} /usr/local/lib
LIBRARY ${CMAKE_INSTALL_LIBDIR} /usr/local/lib
RUNTIME ${CMAKE_INSTALL_BINDIR} /usr/local/bin
PRIVATE_HEADER ${CMAKE_INSTALL_INCLUDEDIR} /usr/local/include
PUBLIC_HEADER ${CMAKE_INSTALL_INCLUDEDIR} /usr/local/include

其他参数的含义:

  • DESTINATION : 指定要安装的目录
  • PERMISSIONS : 指定要安装文件的权限,有效的权限分别是:
    • OWNER_READ
    • OWNER_WRITE
    • OWNER_EXECUTE
    • GROUP_READ
    • GROUP_WRITE
    • GROUP_EXECUTE
    • WORLD_READ
    • WORLD_WRITE
    • WORLD_EXECUTE
  • CONFIGURATIONS : 指定安装规则适用的构建配置列表 (debug / release)
  • EXCLUDE_FROM_ALL : 指定该文件从完整安装中排除
  • OPTIONAL : 如果安装的文件不存在,则指定不作为错误

使用实例:

INSTALL(TARGETS myRun
        RUNTIME DESTIONATION ${CMAKE_INSTALL_BINDIR}
        PERMISSION OWNER_READ OWNER_WRITE OWNER_EXECUTE
        OPTIONAL)
6.1.2 安装普通文件
install(<FILES|PROGRAMS> files...
        TYPE <type> | DESTINATION <dir>
        [PERMISSIONS permissions...]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>]
        [RENAME <name>] [OPTIONAL] [EXCLUDE_FROM_ALL])

安装普通文件的时候,如果 FILE|PORGRAMS为相对路径,则会相对于当前源目录给出解释,推荐使用GUN标准安装变量进行安装。

  • FILE 普通文件
  • PROGRAMS 非目标文件的可执行程序(如脚本)

其中参数TYPE可选的类型如下所示:

文件类型(Type) 安装目录变量 安装目录
BIN ${CMAKE_INSTALL_BINDIR} /usr/local/bin
SBIN ${CMAKE_INSTALL_SBINDIR} /usr/local/sbin
LIB ${CMAKE_INSTALL_LIBDIR} /usr/local/lib
INCLUDE ${CMAKE_INSTALL_INCLUDEDIR /usr/local/include
SYSCONF ${CMAKE_INSTALL_SYSCONFDIR /usr/local/etc
SHAREDSTATE ${CMAKE_INSTALL_SHARESTATEDIR} /usr/local/com
LOCALSTATE ${CMAKE_INSTALL_LOCALSTATEDIR} /usr/local/var
RUNSTATE ${CMAKE_INSTALL_RUNSTATEDIR} /usr/local/run
INFO ${CMAKE_INSTALL_INFODIR} /usr/local/info
LOCALE ${CMAKE_INSTALL_LOCALEDIR} /usr/local/locale
MAN ${CMAKE_INSTALL_MANDIR} /usr/local/man
DOC ${CMAKE_INSTALL_DOCDIR} /usr/local/etc/doc

其他参数含义:

  • COMPONENT: 含义?
  • 其他参数含义与上述相同
6.1.3 目录的安装
install(DIRECTORY dirs...
        TYPE <type> | DESTINATION <dir>
        [FILE_PERMISSIONS permissions...]
        [DIRECTORY_PERMISSIONS permissions...]
        [USE_SOURCE_PERMISSIONS] [OPTIONAL] [MESSAGE_NEVER]
        [CONFIGURATIONS [Debug|Release|...]]
        [COMPONENT <component>] [EXCLUDE_FROM_ALL]
        [FILES_MATCHING]
        [[PATTERN <pattern> | REGEX <regex>]
         [EXCLUDE] [PERMISSIONS permissions...]] [...])

该命令可以将一个或多个目录的内容安装到指定的位置,需要注意的是,如果目录不以/结尾,则这个目录将被安装到目标路径下;如果目录以/结尾,则代表这个目录下的全部内容被安装到目录路径下,但是不包括这个目录

例如,DIRECTORY test,表示将test目录安装到目标路径,DIRECTORY test/ 表示将test目录下所有的内容安装到目标路径,但是不包括test目录。

权限:

  • FILE_PERMISSIONS 指定文件的权限
  • DIRECTORY_PERMISSIONS 指定目录的权限

可以通过PATTERNREGEX 选项以精细的力度控制目录和文件的安装,可指定一个通配模式或者正则表达式以匹配安装的文件或者目录。PATTERN 仅匹配完整的文件名,而REGEX 可匹配文件名或目录的任何部分。

常用的两个跟随PATTERNREGEX的参数:

  • EXCLUDE 跳过匹配的文件或者目录
  • PERMISSION 指定匹配的文件或者目录的权限

例子:

install(DIRECTORY icons scripts/ DESTINATION share/myproj
        PATTERN "CVS" EXCLUDE
        PATTERN "scripts/*"
        PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
                    GROUP_EXECUTE GROUP_READ)

命令的含义: 将目录icons和script目录下的所有内容安装到指定目录share/myproj下,安装时候跳过名为CSV的子目录,且设置scripts目录下所有文件的权限为OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ。

6.2 在GUN/Linux中使用 GNUInstallDirs 优化 cmake 安装路径

  在使用cmake指定安装路径的时候,应该使用变量来指定安装路径,而不是将安装完全写死,以便于在不完全符合FHS的系统上进行安装。GUN提出了适用于unix系统的GUN标准安装目录,GUN/Linux使用的是这套标准的变体。cmake 官方提供了 GNUInstallDirs 模块,定义了一组标准的变量,用于安装不同类型文件到规范指定的目录中。

  要使用此模块,仅需要在CMakeLists.txt文件中添加如下语句:

include(GNUInstallDirs)

如果在编写CMakeLists.txt过程中,发现CMAKE_INSTALL_XXXX变量为空,则应该是缺少该include语句。

cmake在GUNInstallDirs模块中,定义了一些列变量用于表示GUN/Linux标准安装目录,这些值可以传递给install()命令的DESTINATION选项,来指定相应文件的安装路径。

变量名 变量含义
CMAKE_INSTALL_PREFIX 一个特殊的变量,在包含了GUNInstallDirs模块之后,这个变量的
值会被初始化,后续安装过程中,所有相对路径都会将这个变量的
值作为前缀,进行拼接,形成安装的绝对路径,按照GUN的标准,
其默认值为 /usr/local
CMAKE_INSTALL_BINDIR 可执行程序目录 CMAKE_INSTALL_PREFIX/bin
CMAKE_INSTALL_SBINDIR 系统管理员可执行程序目录 CMAKE_INSTALL_PREFIX/sbin
CMAKE_INSTALL_LIBDIR 目标代码库 CMAKE_INSTALL_PREFIX/lib
CMAKE_INSTALL_INCLUDEDIR c语言头文件 CMAKE_INSTALL_PREFIX/include
CMAKE_INSTALL_MANDIR man文档目录 CMAKE_INSTALL_PREFIX/man
CMAKE_INSTALL_SYSCONFDIR 配置文件目录 CMAKE_INSTALL_PREFIX/etc
(单机只读数据,read-only single-machine data)
CMAKE_INSTALL_DATAROOTDIR 与架构无关的只读数据根目录 CMAKE_INSTALL_PREFIX/share
CMAKE_INSTALL_DOCDIR 文档根目录 CMAKE_INSTALL_DATAROOTDIR/doc

7. 测试

posted @ 2023-01-03 20:54  Alpha205  阅读(295)  评论(0编辑  收藏  举报