CMake学习

    在网上找了很多cmake的教程,感觉都比较零散,这里也是对这一个实际CMake示例一边查一边看,费了很大劲整理了一些,但是有点零散这里只是整理别人的资料,复现一些实验和图中遇到的一些问题,内容几乎全部来自其他资料说实话,没想到cmake这么复杂......(知乎上有个大佬用cmake实现光追,震惊中带着一丝绝望)

参考内容:

强烈建议系列CMake 入门与进阶(1)】一个例子搞懂什么是CMakeLists

cmake : add_library详解

为个add_library(官方文档)

Linux下动态链接库文件的realname、soname和linkname

其余参考均在文章内容中有标注。

一、什么是cmake

    camke是用来配置并产生makefile文件的(可与以提前看一下makefile语法,不过;makefile语法挺复杂的,而且不同的平台是有差异的),但是cmake语法相对简单,并且与平台无关。

二、cmake语法

    cmake有自己的语法规则,就像c++,shell一样,有自己的语法规则,但是虽然都是语言,但是cmake应用的目标是控制cmake行为,使其按规定构建项目。

2.1 变量

    cmake中存在三种变量:一般变量缓存变量环境变量。这三个变量均可以通过set函数设定,变量可以分为字符串变量列表变量布尔变量(只有ON/OFF,不是true/false)三种类型。

2.1.1 一般变量

set(<variable> <value>... [PARENT_SCOPE])
  • variable:变量名称
  • value:变量值,可以是1个,多个甚至0个(为空表示unset)。
  • PARENT_SCOPE:设置变量的作用域(用c++中变量具有namespace一样)。

    变量名和值通过空格隔开。

2.1.1.1 字符串变量
set(a "Hello world")
2.1.1.2 列表变量
set(a 1 2 3 4 5)
也可以set(a 1;2;3;4;5)
最终a = 12345
2.1.1.3 布尔变量
set(a TRUE)
示例
set(a "hello world")
set(b 1 2 3 4 5)
set(c TRUE)

message("a is:" ${a})
message("b is:" ${b})
message("c is:" ${c})

执行命令 cmake -B build,得到结果

 注意,这里cmake输出变量是${var},不能用$(var)。

2.1.2 缓存变量

set(<variable> <value>... CACHE <type> <docstring> [FORCE]) 

    缓存变量需要加关键字CACHE,注明类型,docstring是注释,[FORCE]有其他作用,后面会给出解释。

2.1.2.1 cmake缓存

   首先从cmake的缓存现象来看看

这是连续两次运行同一个CMakeLists.txt,从结果看,这两次输出的长度不一样,在输出的build文件夹下存在/build/CMakeCache.txt文件,这个文件缓存了上次build的一些信息,删除这个文件,在运行同一个CMakeLists.txt就会输出结果和第一次运行长度一样。

    所以cmake具有缓存机制,使得下一次的build快很多,但同时,也使某些操作复杂很多。

2.1.2.2 缓存变量类型
set(<variable> <value>... CACHE <type> <docstring> [FORCE]) 

type表示缓存变量类型,有

  • STRING 字符串
  • FILEPATH 文件路径
  • PATH 目录路径
  • BOOL 布尔值:ON/OFF

docstring表示注释,变量的解释。

[FORCE]表示不论缓存是否存在,都强制更新缓存。

实例
set(a "hello world")
set(b 1 2 3 4 5)
set(c TRUE)

message("a is:" ${a})
message("b is:" ${b})
message("c is:" ${c})

set(var "hello" CACHE STRING "cache var example")
message("var is: " ${var})

在CMakeCache.txt中有

如果修改CMakeist.txt文件中的var为world但不删除CMakeCache.txt,输出结果

var is: hello

var的值没有改变,因为第二次build直接从缓存中读取数据,CMakeLists.txt中的数据相当于作为变量初始化的过程,所以,对于缓存变量,只修改CMakeLists.txt中的数据是没有用的。

    如果想要修改var的值,一种办法是:删除原来build文件或者删除CMakeCache.txt文件;另一种办法是加上[FORCE],例如

set(var "hello" CACHE STRING "cache var example")


输出结果
var is: worldCACHESTRINGcache var example[FORCE]

但是,在CMakeCache.txt中var依然没有改变。仍是

还有一种方法是在命令行修改,例如

cmake -B build -Dvar=world

输出结果
var is: world

这种方法在 CMakeCache.txt 文件中var变量的值也会发生改变。当set有[FORCE]时u,命令行修改会失效。

2.1.3 环境变量

    环境变量是从操作系统的环境中继承的,变量名需要为ENV{name}就,例如

set(ENV{HOME} value)

2.1.4 CMake中的预定义变量

    这些变量是CMake系统预定义的,不用set可以直接使用

PROJECT_SOURCE_DIR                 	工程顶层目录,也就是顶层 CMakeLists.txt 源码所在目录

PROJECT_BINARY_DIR                  工程 BINARY_DIR, 也就是顶层 CMakeLists.txt 源码的 BINARY_DIR

CMAKE_SOURCE_DIR                    与 PROJECT_SOURCE_DIR 等价

CMAKE_BINARY_DIR                    与 PROJECT_BINARY_DIR 等价

CMAKE_CURRENT_SOURCE_DIR            当前源码所在路径

CMAKE_CURRENT_BINARY_DIR            当前源码的 BINARY_DIR

CMAKE_MAJOR_VERSION                 cmake 的主版本号

CMAKE_MINOR_VERSION                 cmake 的次版本号

CMAKE_VERSION                       cmake 的版本号(主+次+修订)

PROJECT_VERSION_MAJOR	            工程的主版本号

PROJECT_VERSION_MINOR               工程的次版本号

PROJECT_VERSION                     工程的版本号

CMAKE_PROJECT_NAME                  工程的名字

PROJECT_NAME	                    工程名,与 CMAKE_PROJECT_NAME 等价

PROJECT_SOURCE_DIR 指的就是工程的顶层 CMakeLists.txt 源码所在路径,而ROJECT_BINARY_DIR 指的是我们执行 cmake 命令的所在目录,也是顶层 CMakeLists.txt 源码的 BINARY_DIR。

2.1.5 变量的作用域

    这里,先看看一点作用,例如

set(VAR "gcc")

function(setVAR1)
        set(VAR "cc")
endfunction()

function(setVAR2)
        set(VAR "cc" PARENT_SCOPE)
endfunction()

message("org VAR: " ${VAR})

setVAR1()

message("call setVAR1, VAR: " ${VAR})

setVAR2()

message("call setVAR2, VAR: " ${VAR})

输出结果

org VAR: gcc
call setVAR1, VAR: gcc
call setVAR2, VAR: cc

关于变量更详细的例子可以看Cmake命令之set介绍(一定要看),但是foreach语句不会建立新的作用域。

2.2 函数

    函数这一节主要通过示例,一边讲解函数一边看看CMake是如何生成makefile目录以及最终目标文件,并且所有示例和一些内容来自CMake 入门与进阶(1)】系列其其 踏实学Cmake侵删!!!

2.2.1 示例一

// 文件路径安排
├── CMakeLists.txt
└── main.cpp


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


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


// cmake 命令
cmake -B build

输出的结果:

├── CMakeLists.txt
└── main.cpp
└── build
    └── CMakeFiles
    └── CMakeCache.txt
    └── cmake_install.cmake
    └── Makefile(文件夹包含很多东西和子文件夹)

    最终包括makefile文件,可以直接生成可执行文件和一些link.txt等等,后面会分析。在build文件夹下运行make会生成可执行文件hello。

2.2.1.1 project函数

    命令原型

形式1:
project(<PROJECT-NAME> [<language-name>...])

形式2:
project(<PROJECT-NAME>
        [VERSION <major>[.<minor>[.<patch>[.<tweak>]]]]
        [DESCRIPTION <project-description-string>]
        [HOMEPAGE_URL <url-string>]
        [LANGUAGES <language-name>...])
  • PROJECT_NAME : 必选——用来设置工程名,设置后,会把设置的值存储在CMAKE_PROJECT_NAME变量中。
  • VERSION:可选,工程版本号,有主版本号、次版本号、补丁版本号。
  • DESCRIPTION:工程简单的的描述。
  • HOMEPAGE_URL:工程主页url。
  • LANGUAGES:工程使用的语言,默认为C或CXX。

project指定工程名字的同时,camke内部还会为下边的一些变量赋值:

- PROJECT_NAME: 将名称赋值给PROJECT_NAME,即${PROJECT_NAME} = cmaketest
- PROJECT_SOURCE_DIR: 当前工程的源码路径
- PROJECT_BINARY_DIR:当前工程的二进制路径
- CMAKE_PROJECT_NAME:顶层工程的名称。cmake命令首次调用那个CMakeLists.txt对应工程的名字
示例
project(HELLO)
add_executable(hello main.cpp)

message("PROJECT_NAME: " ${PROJECT_NAME})
message("PEOJECT_SOURCE_DIR: " ${PROJECT_SOURCE_DIR})
message("PROJECT_BINARY_DIR: " ${PROJECT_BINARY_DIR})
message("CMAKE_PROJECT_NAME: " ${CMAKE_PROJECT_NAME})

输出结果

PROJECT_NAME: HELLO
PEOJECT_SOURCE_DIR: /media/ubuntu/F0A28823A287EC82/mycpp/CMakeLearning/ex1
PROJECT_BINARY_DIR: /media/ubuntu/F0A28823A287EC82/mycpp/CMakeLearning/ex1/build
CMAKE_PROJECT_NAME: HELLO
2.2.1.2 add_executable函数

    命令原型

add_executable([WIN32][MACOSX_BUNDLE][EXCLUDE_FROM_ALL][source1][source2...])
add_executable(IMPORTED [GLOBAL])
add_executable(ALIAS)

    目标原型分为三类:普通可执行文件、导入可执行文件、别名可执行文件。

2.2.1.2.1 普通可执行文件
add_executable([WIN32][MACOSX_BUNDLE][EXCLUDE_FROM_ALL][source1][source2...])
  • name:生成可执行文件的名字,必须在工程内全局唯一
  • WIN32:有此参数时,WIN32_EXECUTABLE属性会被置为true,此时在windows环境下创建的可执行文件将以WinMain函数代替main函数作为程序入口,构建而成的可执行文件为GUI应用程序而不是控制台应用程序。
  • MACOSX_BUNDLE: 有此参数时,MACOSX_BUNDLE属性会被置为true,此时在macOS或者iOS上构建可执行文件目标时,目标会成为一个从Finder启动的GUI可执行程序。
  • EXCLUDE_FROM_ALL:有此参数时,此目标就会被排除在all target列表之外,即在执行默认的make时,不会构造此目标,需要构造此目标的时候,需要手动构建,如:
add_executable(test EXCLUDE_FROM_ALL test.cpp)
// test加了EXCLUDE_FROM_ALL属性,在默认编译的时候,
//不会被编译,如果要编译它,需要手动编译, 
//比如make test指定编译名为test
make test

这里如果看过makefile会容易理解一点。

    也可以使用target_sources()继续为构建可执行文件目标添加源文件,但是target_sources()指令必须在add_executableadd_library之后调用。

2.2.1.2.2 导入的可执行文件
add_executable(<name> IMPORTED [GLOBAL])
  •  name:导入可执行文件目标的名字。
  • IMPORTED:导入的目标文件需指定IMPORTED属性,IMPORTED属性指定后,目标文件的属性IMPORTED被置为true,在工程内构建生成的可执行文件的IMPORTED属性会被置为false
  • GLOBAL:指定GLOBAL则会将范围扩大到整个工程,否则目标文件的范围为文件创建的目录及子目录。
2.2.1.2.2 别名可执行文件

    创建一个别名目标,使得后续命令中可以使用来引用。

2.2.2 示例二

    实例一构建了单个文件,这里多加入几个源文件

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


// hello.h    
#ifndef __TEST_HELLO_
#define __TEST_HELLO_
     
void hello(const char *name);
     
#endif //__TEST_HELLO_



// hello.cpp
#include <stdio.h>
#include "hello.h"
 
void hello(const char *name){
 printf("Hello %s!\n", name);
}



// main.cpp
#include "hello.h"
 
int main(void){
 hello("World");
 return 0;
}



// CMakeLists.txt
project(HELLO)
set(SRC_LIST main.cpp hello.cpp)
add_executable(hello ${SRC_LIST})

执行命令

cmake -B build

在build文件夹下执行make命令即可得道可执行文件hello。set命令在前面已经看到了。

生成库文件
// CMakeLists.txt
project(HELLO)
add_library(libhello hello.cpp)
add_executable(hello main.cpp)
target_link_libraries(hello libhello)

 执行命令

cmake -B build_with_lib

在文件夹build_with_lib下执行make,在build_with_lib得到两个文件:hello和liblibhello.a。

2.2.2.1 add_library

    命令原型

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [source1] [source2 ...])

    添加名为name的库,库的源文件可指定,也在target_sources后指定。库的类型是

  • STATIC(静态库):
  • SHARED(动态库):
  • MODULE(模块库):不一定会被链接的插件,但是可能通过dlopen类似的操作加载。

EXCLUDE_FROM_ALL:排除在外,通过make主动调用生成库。name必须全局唯一,最终的名字会根据库的类型变成libxxx.a或者libxxx.so文件(自动加上lib前缀)。既然可以自动加上,那么示例中执行的命令是add_library(libhello hello.cpp),所以最终生成的是liblibhello文件,如果想生成libhello文件,就要改成add_library(hello hello.cpp),但是这样名称hello就不是全局唯一的了,解决这个问题需要用到set_target_properties函数。

2.2.2.2 set_target_properties

    命令原型

set_target_properties(目标文件1 目标文件2 ...
                      PROPERTIES 
                      属性1 属性值1 属性2 属性值2 ...)                 

    属性既可以为内置属性,也可以为自定义属性

内置属性有

  • OUTPUT_NAME:更改目标文件的输出名称。

  • VERSION和SOVERSION:详见name和soname(Linux下动态链接库文件的realname、soname和linkname)

  • RUNTIME_OUTPUT_DIRECTORY(二进制执行文件)/LIBRARY_OUTPUT_DIRECTORY(动态库)/ARCHIVE_OUTPUT_DIRECTORY(静态库)

  • DEBUG_POSTFIX:指定Debug模式下的目标文件名后缀为 _d,以用于区分 release 模式下生成的目标文件。

自定义属性,就比较随便,可以随意指定名称和值

2.2.2.3 get_target_properties

    命令原型

get_target_property(<variable> <target> <target_property>)

可以获取到某个目标已有的属性对应的值,并保存到指定变量中。这个属性可以是内置的,也可以是自己创建的。

    然后看看如何解决上面库命名的问题

// CMakeLists.txt
project(HELLO)
add_library(libhello hello.cpp)
set_target_properties(libhello PROPERTIES OUTPUT_NAME hello)
add_executable(hello main.cpp)
target_link_libraries(hello libhello)

按上述步骤重新make最后生成的库名字为libhello.a。

2.2.2.4 target_link_libraries和link_libraries

    link_libraries这和好像是指定全局范围的链接,总之好像建议使用target_link_libraries

target_ling_libraries命令原型

target_link_libraries(<target> ... <item>... ...)
target_link_libraries(<target>
                      <PRIVATE|PUBLIC|INTERFACE> <item>...
                     [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • PUBLIC: 在public后面的库会被Link到你的target中,并且里面的符号也会被导出,提供给第三方使用。
  • PRIVATE 在private后面的库仅被link到你的target中,并且终结掉,第三方不能感知你调了啥库
  • INTERFACE 在interface后面引入的库不会被链接到你的target中,只会导出符号。

详见:cmake:target_** 中的 PUBLIC,PRIVATE,INTERFACE

link_libraries用在add_executable之前,target_link_libraries用在add_executable之后。

2.2.3 常用函数

上面这张图来自【CMake 入门与进阶(3)】 CMakeLists.txt 语法规则基础及部分常用指令(附使用代码)名s

命令include_directories相当于-I(大写的i)

2.2.3.1 add_subdirectory

    命令原型

add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])

add_subdirectory 命令告诉 cmake 去指定的目录中寻找源码并执行它。

  1. source_dir必选参数。该参数指定一个子目录,子目录下应该包含CMakeLists.txt文件和代码文件。子目录可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前目录的一个相对路径。
  2. binary_dir可选参数。该参数指定一个目录,用于存放输出文件。可以是相对路径也可以是绝对路径,如果是相对路径,则是相对当前输出目录的一个相对路径。如果该参数没有指定,则默认的输出目录使用source_dir。
  3. EXCLUDE_FROM_ALL可选参数。当指定了该参数,则子目录下的目标不会被父目录下的目标文件包含进去,父目录的CMakeLists.txt不会构建子目录的目标文件,必须在子目录下显式去构建。例外情况:当父目录的目标依赖于子目录的目标,则子目录的目标仍然会被构建出来以满足依赖关系(例如使用了target_link_libraries)。

参数具体示例详见CMake常用命令(四)add_subdirectory命令 添加子目录

2.2.3.1 aux_source_directory

命令原型

aux_source_directory(<dir> <variable>)

   收集dir目录下所有的源文件,包括,cpp文件和.c文件,存入变量variable中。但是只会收集src目录下的源文件,而不会便俩src文件夹下所有的文件夹,不像pyhton中walk方法,进入每个子目录。

2.2.3.2 include_directories
include_directories ([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])

  用于设置头文件的搜索路径,相当于 gcc 的-I 选项,

  • AFTER或BEFORE:可以选择让添加的路径位于搜索列表的开头或结尾。缺省时,默认是AFTER。
  • SYSTEM: 可选参数,指示指定的目录包含系统头文件。当使用SYSTEM修饰符时,编译器会将这些目录视为系统级别的头文件目录,这意味着编译器不会产生关于这些目录的警告信息。
  • dir:路径

    include_directories命令应该在add_executable或add_library之前使用,以确保编译器在构建过程中能够找到所需的头文件。
    include_directories命令仅仅告诉编译器在哪些目录中查找头文件,并不会自动包含这些头文件。要包含头文件,需要使用#include预处理指令在代码中显式包含。
    尽量避免使用include_directories命令的绝对路径。推荐使用相对于CMakeLists.txt文件的相对路径,以增加项目的可移植性。
    在使用include_directories命令时,应确保指定的目录中包含正确的头文件。否则,编译器可能无法找到所需的头文件,导致编译错误。

    include_directories命令是全局性的,它将全局添加的目录路径应用于整个项目。这可能导致潜在的命名冲突或不必要的搜索路径。

2.2.3.3 target_include_directories
target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]  <INTERFACE|PUBLIC|PRIVATE> [items1...]  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

    为指定目标(target)添加搜索路径,指定目标是指通过如add_executable(),add_library()这样的命令生成的,并且决不能是alias target(引用目标,别名目标)。关于PUBLIC等参数见上面。

推荐使用target_include_directories函数

  • 更明确的作用域:include_directories命令是全局性的,它将全局添加的目录路径应用于整个项目。这可能导致潜在的命名冲突或不必要的搜索路径。而target_include_directories命令是针对特定目标(例如可执行文件或库)的,它可以将目录路径限定在特定的目标范围内,提供了更明确的作用域控制。
  • 更好的可维护性:通过使用target_include_directories命令,可以将目录路径与特定的目标关联起来。这样,在添加、修改或删除目标时,与之相关的目录路径也会自动调整,保持代码的一致性和可维护性。而使用include_directories命令时,需要手动确保目录路径的一致性,增加了维护工作的复杂性。
  • 更好的依赖管理:target_include_directories命令可以与target_link_libraries命令结合使用,以确保正确的头文件搜索路径传递给依赖的目标。这样,在构建库时,库的用户不需要手动添加依赖的头文件路径,而是通过依赖关系自动传递。这简化了项目的依赖管理。
  • 跨平台兼容性:在某些情况下,使用target_include_directories命令可以提供更好的跨平台兼容性。例如,在使用一些编译器或构建工具链时,target_include_directories命令可以更好地处理特定的平台或编译器标志,以确保正确的头文件搜索路径。

上面来自CMake系列讲解(入门篇)1.6 基础命令CMake-include_directories()

2.2.3.4 link_directories和target_link_directories
link_directories([AFTER|BEFORE] directory1 [directory2 ...])

 命令用于设置库文件的搜索路径,相当于 gcc 编译器的-L 选项;

target_link_directories(<target> [BEFORE] <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])

同上。

至此,已经乱了....稍微总结一下

  • add_executable:添加可执行文件
  • add_liberary:添加库
  • add_subdirectory:添加子目录的CMakeLists.txt
  • include_directories:添加库路径(相当于gcc -I)
  • link_directories:添加链接路径(相当于gcc -L)
  • link_libaries:添加链接库名称(相当于gcc -l)
  • target_include_directories:给目标添加库路径
  • target_link_directories:给目标添加库路径
  • target_link_libaries:给目标添加库名称
2.2.3.5 list

    list 命令是一个关于列表操作的命令,譬如获取列表的长度、从列表中返回由索引值指定的元素、将元素追加到列表中等等。命令定义如下:

list(LENGTH <list> <output variable>)
list(GET <list> <element index> [<element index> ...]
      <output variable>)
list(APPEND <list> [<element> ...])
list(FIND <list> <value> <output variable>)
list(INSERT <list> <element_index> <element> [<element> ...])
list(REMOVE_ITEM <list> <value> [<value> ...])
list(REMOVE_AT <list> <index> [<index> ...])
list(REMOVE_DUPLICATES <list>)
list(REVERSE <list>)
list(SORT <list>)
  • LENGTH 选项用于返回列表长度;
  • GET 选项从列表中返回由索引值指定的元素;
  • APPEND 选项将元素追加到列表后面;
  • FIND 选项将返回列表中指定元素的索引值,如果未找到,则返回-1。
  • INSERT 选项将向列表中的指定位置插入元素。
  • REMOVE_AT 和 REMOVE_ITEM 选项将从列表中删除元素,不同之处在于 REMOVE_ITEM 将删除给定的元素,而 REMOVE_AT 将删除给定索引值的元素。
  • REMOVE_DUPLICATES 选项将删除列表中的重复元素。
  • REVERSE 选项就地反转列表的内容。
  • SORT 选项按字母顺序对列表进行排序。
2.2.3.6 message
message( [STATUS|WARNING|AUTHOR_WARNING|FATAL_ERROR|SEND_ERROR]
  "message to display" ...)
  • 无:重要信息。
  • STATUS:非常重要的信息。
  • WARNING:CMake警告。会继续执行。
  • AUTHOR_WARNING:CMake dev警告i,但会继续执行。
  • SEND_ERROR:CMake错误,继续执行,但会跳过所有生成的步骤。
  • FATAL_ERROR:CMake错误,终止所有处理过程。
2.3.3.7 改变行为的内置变量
BUILD_SHARED_LIBS	                    控制 cmake 是否生成动态库

CMAKE_BUILD_TYPE	                    指定工程的构建类型,release 或 debug

CMAKE_SYSROOT	                        对应编译器的在--sysroot 选项

CMAKE_IGNORE_PATH	                    设置被 find_xxx 命令忽略的目录列表

CMAKE_INCLUDE_PATH	                    为find_file()和 find_path()命令指定搜索路径的目录列表

CMAKE_INCLUDE_DIRECTORIES_BEFORE	    用于控制 include_directories()命令的行为

CMAKE_LIBRARY_PATH	                    指定 find_library()命令的搜索路径的目录列表

CMAKE_MODULE_PATH	                    指定要由 include()或 find_package()命令加载的 CMake 模块的搜索路径的目录列表

CMAKE_PROGRAM_PATH	                    指定 find_program()命令的搜索路径的目录列表
  • BUILD_SHARED_LIB:对于 add_library()命令,当没有显式指定生成动态库时(SHARED 选项),默认生成的是静态库;其实 们可以通过 BUILD_SHARED_LIBS 变量来控制 add_library()命令的行为,当将变量设置为 on 时表示使能动态库,则 add_library()默认生成的便是动态库文件;当变量设置为 off 或未设置时,add_library()默认生成的便是静态库文件。
  • CMAKE_BUILD_TYPE:设置编译类型 Debug 或者 Release。debug 版会生成相关调试信息,可以使用 GDB 进行调试;release 不会生成调试信息。

  • CMAKE_SYSROOT:cmake 会将该变量传递给编译器--sysroot 选项,通常在设置交叉编译时会使用到。

  • CMAKE_INCLUDE_PATH、CMAKE_LIBRARY_PATH:针对find_file、find_path和find_library函数来说的,find_file、find_path会从CMAKE_INCLUDE_PATH变量中寻找,而find_library会从CMAKE_LIBRARY_PATH中寻找(但是主动message这两个变量什么也不会得到,即使setmessage也不会输出,但是find方法就是可以使用,不知道为啥)。

  • CMAKE_INCLUDE_DIRECTORIES_BEFORE:这个变量是用于设置include_directories中AFTER和BEFORE行为的,设置为ON,则会默认加到最前面。

  • CMAKE_IGNORE_PATH:这个变量是设置find_program()、find_library()、find_file()和 find_path()忽略路径的列表。

2.3.3.7 描述系统的变量
CMAKE_HOST_SYSTEM_NAME	                运行 cmake 的操作系统的名称(其实就是 uname -s)

CMAKE_HOST_SYSTEM_PROCESSOR	            运行 cmake 的操作系统的处理器名称(uname -p)

CMAKE_HOST_SYSTEM	                    运行 cmake 的操作系统(复合信息)

CMAKE_HOST_SYSTEM_VERSION	            运行 cmake 的操作系统的版本号(uname -r)

CMAKE_HOST_UNIX	                        如果运行 cmake 的操作系统是 UNIX 和类 UNIX,则该变量为 true,否则是空值

CMAKE_HOST_WIN32	                    如果运行 cmake 的操作系统是 Windows,则该变量为 true,否则是空值

CMAKE_SYSTEM_NAME	                    目标主机操作系统的名称

CMAKE_SYSTEM_PROCESSOR	                目标主机的处理器名称

CMAKE_SYSTEM	                        目标主机的操作系统(复合信息)

CMAKE_SYSTEM_VERSION	                目标主机操作系统的版本号

ENV	                                    用于访问环境变量

UNIX	                                与 CMAKE_HOST_UNIX 等价

WIN32	                                与 CMAKE_HOST_WIN32 等价

我的电脑这四个变量的值

  • CMAKE_HOST_SYSTEM_NAME:Linux
  • CMAKE_HOST_SYSTEM_PROCESSOR :x86_64
  • CMAKE_HOST_SYSTEM :Linux-5.15.0-102-generic
  • CMAKE_HOST_SYSTEM_VERSION:5.15.0-102-generic

对比没有HOST的四个变量:

  • CMAKE_SYSTEM_NAME:Linux
  • CMAKE_SYSTEM_PROCESSOR :x86_64
  • CMAKE_SYSTEM :Linux-5.15.0-102-generic
  • CMAKET_SYSTEM_VERSION:5.15.0-102-generic

具体差异可以在网上搜。

2.3.3.7 控制编译的变量
EXECUTABLE_OUTPUT_PATH	            可执行程序的输出路径
LIBRARY_OUTPUT_PATH	                库文件的输出路径

2.3 控制语句if

详见官方文档if官方文档

if(expression)
  # then section.
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
elseif(expression2)
  # elseif section.
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
else(expression)
  # else section.
  COMMAND1(ARGS ...)
  COMMAND2(ARGS ...)
  ...
endif(expression)

    ifelse语句评估顺序是:先评估一元运算,像EXISTS、COMMAND和DEFINED,然后评估二元运算,像EQUAL、LESS、GREATER、STREQUAL和MATCHES,最后进行AND、OR和NOT运算。最后,注意,如果endif写表达式,那么那个表达式是第一个if的表达式。

    表达式类型有:

  • constant:1, ON, YES, TRUE, Y和非零数字都有是TRUE,而0, OFF, NO, FALSE, N, IGNORE, NOTFOUND和空字符串都是FALSE。另外,这些不区分大小写。但是,但是,但是!!!!如果你去if(ON/YES...)这种指令,你会发现输出FALSE,并且出现这种警告

然后通过cmake --help-policy CMP0012指令有

CMP0012
-------

``if()`` recognizes numbers and boolean constants.

In CMake versions 2.6.4 and lower the ``if()`` command implicitly
dereferenced arguments corresponding to variables, even those named
like numbers or boolean constants, except for ``0`` and ``1``.  Numbers and
boolean constants such as ``true``, ``false``, ``yes``, ``no``, ``on``,
``off``, ``y``, ``n``, ``notfound``, ``ignore`` (all case insensitive)
were recognized in some cases but not all.  For example, the code ``if(TRUE)``
might have evaluated as ``false``.
Numbers such as 2 were recognized only in boolean expressions
like ``if(NOT 2)`` (leading to ``false``) but not as a single-argument like
``if(2)`` (also leading to ``false``).  Later versions of CMake prefer to
treat numbers and boolean constants literally, so they should not be
used as variable names.

The ``OLD`` behavior for this policy is to implicitly dereference
variables named like numbers and boolean constants.  The ``NEW`` behavior
for this policy is to recognize numbers and boolean constants without
dereferencing variables with such names.

This policy was introduced in CMake version 2.8.0.  CMake version
3.16.3 warns when the policy is not set and uses ``OLD`` behavior.  Use
the ``cmake_policy()`` command to set it to ``OLD`` or ``NEW`` explicitly.

.. note::
  The ``OLD`` behavior of a policy is
  ``deprecated by definition``
  and may be removed in a future version of CMake.

大致意思就是2.6.4及以下版本会按照上面那种方式解析参数但依然会出现false情况,但是新版本可以,利用cmake_policy显式设置OLD或者NEW。

在cmake文件中加入

cmake_policy(SET CMP0012 NEW)

结果就会正确。

  • variable|string:如果变量已经定义,并且它的值是一个非假常量,则条件为真;否则为假,注意宏参数不是变量。
  • NOT <expression>:取反。
  • <expr1> AND <expr2>:与操作
  • <expr1> OR <expr2> :或操作
  • COMMAND command-name:如果 command-name 是一个已经定义的命令、宏或函数时,条件判断为真;否则为假。
  • POLICY policy-id:如果给定的名字是存在的策略,就是true,比如
if(POLICY CMP0012)

在3.16版本是有的,所以是true

  • TARGET target-name:如果target-name是add_executable()、add_library()或add_custom_target()定义的目标(这些目标在整个工程中必须是唯一的,不可出现两个名字相同的目标),则判断为真;否则为假。
  • TEST test-name:如果是通过add_test()命令添加的test name就为true
  • EXISTS path-to-file-or-directory: 如果 path 指定的文件或目录存在,则条件判断为真;否则为假。需要注意的是,path 必须是文件或目录的全路径,也就是绝对路径。
  • file1 IS_NEWER_THAN file2:如果法ile1比file新,或者其中一个文件不存在,那么就为true。
  • IS_DIRECTORY path-to-directory:如果 path 指定的路径是一个目录,则条件判断为真;否则为假,同样,path 也必须是一个绝对路径。
  • IS_SYMLINK file-name:如果file是一个符号链接,就为true。
  • IS_ABSOLUTE path:如果给定的路径 path 是一个绝对路径,则条件判断为真;否则为假。
  • <variable|string> MATCHES regex:如果给定的字符串或变量的值与给定的正则表达式,则为真,否则为假(正则表达式见正则表达式 - 教程)。
  • <variable|string> LESS <variable|string>:如果左边给定的字符串或变量的值是有效数字并且小于右侧的值,则为真。否则为假。
  • <variable|string> GREATER <variable|string>:同上。
  • <variable|string> EQUAL <variable|string>:同上。
  • <variable|string> STRLESS <variable|string>:子典小于。
  • <variable|string> STRGREATER <variable|string>:同上。
  • <variable|string> STREQUAL <variable|string>:同上。
  • <variable|string> VERSION_LESS <variable|string>:版本小于,版本形式(ajor[.minor[.patch[.tweak]]])。
  • <variable|string> IN_LIST <variable>:如果左边给定的变量或字符串是右边列表中的某个元素相同,则条件判断为真;否则为假。
  • DEFINED <variable>:如果给定的变量已经定义,则条件判断为真,否则为假;只要变量已经被设置(定义),if 条件判断就是真,至于变量的值是真还是假并不重要。

2.4 循环语句

2.4.1 foreach

2.4.1.1 第一种形式
foreach(loop_var arg1 arg2 ...)
     command1(args ...)
     command2(args ...)
     ...
endforeach(loop_var) // 也可为endforeach()

arg1、arg2...可以换成一个list变量,如

foreach(loop_var ${list_var})
     command1(loop_var)
     command2(loop_var)
     ...
endforeach(loop_var)  // 可为endforeach()
2.4.1.2 第二种形式
foreach(loop_var RANGE stop)
foreach(loop_var RANGE start stop [step])

和python语言的for in range一样。如

# foreach 循环测试
foreach(loop_var RANGE 1 4 1)
    message("${loop_var}")
endforeach()
2.4.1.3 第三种形式
foreach(loop_var IN [LISTS [list1 [...]]]
             [ITEMS [item1 [...]]])

 例如

# foreach 循环测试
set(my_list A B C D)
 
foreach(loop_var IN LISTS my_list)
    message("${loop_var}")
endforeach()



# foreach 循环测试
foreach(loop_var IN ITEMS A B C D)
    message("${loop_var}")
endforeach()

# 这种方式只会输出my_list
foreach(loop_var IN ITEMS my_list)
    message("${loop_var}")
endforeach()

2.4.2 while循环

while(condition)
 command1(args ...)
 command1(args ...)
 ...
endwhile(condition)

例如:

# while 循环测试
set(loop_var 4)
while(loop_var GREATER 0)
    message("${loop_var}")
    math(EXPR loop_var "${loop_var} - 1")
endwhile()

太抽象了,看看math函数

math(EXPR <output variable> <math expression>)
  • EXPR:关键字,不能修改
  • output variable:输出变量位置。
  • math expression:必须是字符串形式“${loop_var} - 1”是对的,${loop_var} - 1是错的,支持的操作符有+ - * / % | & ^ ~ << >> * / %

2.4.3 break 和 continue

    break()命令用于跳出循环,continue()命令用于结束本次循环,执行下一次循环。注意,命令的使用方法是break()和continue(),有括号!!!

2.5 自定义函数

自定义函数形式如下

function(<name> [arg1 [arg2 [arg3 ...]]])
 command1(args ...)
 command2(args ...)
 ...
endfunction(<name>)


#调用方法
name(a1, a2 ...)

例如:

# function 函数测试
# 函数名: xyz
function(xyz arg1 arg2)
    message("${arg1} ${arg2}")
endfunction()
 
# 调用函数
xyz(Hello World)


# 输出 hello world

注意,函数调用是不区分大小写的!!调用Xyz也是可以的。

2.5.1 return()

   例如

# function 函数测试
# 函数名: xyz
function(xyz)
    message(Hello)
    return() # 退出函数
    message(World)
endfunction()
 
# 调用函数
xyz()


# 输出 hello

但是function不会返回值。

2.5.2 可变参数

# function 函数测试
# 函数名: xyz
function(xyz arg1)
    message(${arg1})
endfunction()
 
# 调用函数
xyz(Hello World China)

#输出 hello 

说明阿arg1并不会直接转换成列表。

2.5.3 函数的内部变量

    函数内部变量意思是在函数作用于内可以直接使用,而不用定义的

ARGVX	X 是一个数字,譬如 ARGV0、ARGV1、ARGV2、ARGV3…,这些变量表示函数的参数,ARGV0 为第一个参数、ARGV1 位第二个参数,依次类推!

ARGV	实际调用时传入的参数会存放在 ARGV 变量中(如果是多个参数,那它就是一个参数列表)

ARGN	假如定义函数时参数为 2 个,实际调用时传入了 4 个,则 ARGN 存放了剩下的 2 个参数(如果是多个参数,那它也是一个参数列表)

ARGC	调用函数时,实际传入的参数个数

例如

# function 函数测试
# 函数名: xyz
function(xyz arg1 arg2)
     message("ARGC: ${ARGC}")
     message("ARGV: ${ARGV}")
     message("ARGN: ${ARGN}")
     message("ARGV0: ${ARGV0}")
     message("ARGV1: ${ARGV1}")

endfunction()
 
# 调用函数
xyz(A B C D E F G)


# 输出:
ARGC:7
ARGV:A;B;C;D;E;F;G
ARGN:C;D;E;F;G
ARGV0:A
ARGV1:B

注意是从0开始的。

2.5.4 函数的作用域

    函数的作用域不同于变量的作用域,无论是在顶层CMakeists.txt定义的的函数,还是在子文件夹CMakeLists.txt中定义的函数,都是全局作用域,意思就是顶层CMakeists.txt可以调用子文件夹CMakeLists.txt中定义的函数,同样,子文件夹CMakeLists.txt也可以调用顶层CMakeists.txt定义的的函数。

2.6 宏

    宏形式

macro(<name> [arg1 [arg2 [arg3 ...]]])
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...
endmacro(<name>)

     endmacro 括号中的可写可不写,推荐写。在宏定义中也可以使用前面给大家介绍的 ARGVX(X 是一个数字)、ARGC、ARGV、 ARGN 这些变量。同样,宏也不区分大小写(如你问变量区分大小写吗,变量名是区分大小写的)。

    宏和函数确实差不多,但还是有区别的,譬如,宏的参数和诸如 ARGV、 ARGC、ARGN 之类的值不是通常 CMake 意义上的变量,它们是字符串替换,就像 C 语言预处理器对宏所做的一样,因此,无法使用以下命令:

if(ARGV1) # ARGV1 is not a variable
if(DEFINED ARGV2) # ARGV2 is not a variable
if(ARGC GREATER 2) # ARGC is not a variable
foreach(loop_var IN LISTS ARGN) # ARGN is not a variable

     因为在宏定义中,宏的参数和诸如 ARGC、ARGV、ARGN 等这些值并不是变量,它们是字符串替换, 也就是说,当 cmake 执行宏定义时,会先将宏的参数和 ARGC、ARGV、ARGN 等这些值进行字符串替换, 然后再去执行这段宏,其实就像是 C 语言中的预处理步骤,这是与函数不同的地方。 例如:

# macro 宏
macro(abc arg1 arg2)
    if(DEFINED ARGC)
        message(true)
    else()
        message(false)
    endif()
endmacro()
 
# function 函数
function(xyz arg1 arg2)
    if(DEFINED ARGC)
        message(true)
    else()
        message(false)
    endif()
endfunction()
 
# 调用宏
abc(A B C D)
 
# 调用函数
xyz(A B C D)



# 输出:
false
true

并且,函数有自己的作用域、而宏定义是没有作用域的概念。

宏和函数以及参数的解析详见CMake 语法】(12) CMake 宏和函数

2.8 CMake中对文件操作

    写文件

file(WRITE <filename> <content>...)
file(APPEND <filename> <content>...)

    如果文件不存在,它将被创建;如果文件已经存在,WRITE 模式将覆盖它,APPEND 模式将内容追加到文件末尾。文件可以使用绝对路径或相对路径指定。

第二种写文件形式

file(GENERATE OUTPUT output-file
    <INPUT input-file|CONTENT content>
    [CONDITION expression])
  • output-file:指定输出文件名,可以带路径(绝对路径或相对路径);
  • INPUT input-file:指定输入文件,通过输入文件的内容来生成输出文件;
  • CONTENT content:指定内容,直接指定内容来生成输出文件;
  • CONDITION expression:如果表达式expression 条件判断为真,则生成文件、否则不生成。

例如

file(WRITE wtest.txt "Hello World!") #给定内容生成 wtest.txt 文件
# 由前面生成的 wtest.txt 中的内容去生成 out1.txt 文件
file(GENERATE OUTPUT out1.txt INPUT "${PROJECT_SOURCE_DIR}/wtest.txt")
 
# 由指定的内容生成 out2.txt
file(GENERATE OUTPUT out2.txt CONTENT "This is the out2.txt file")
# 由指定的内容生成 out3.txt,加上条件控制,用户可根据实际情况
# 用表达式判断是否需要生成文件,这里只是演示,直接是 1
file(GENERATE OUTPUT out3.txt CONTENT "This is the out3.txt file" CONDITION 1)

读文件

file(READ <filename> <variable>
    [OFFSET <offset>] [LIMIT <max-in>] [HEX])

    从名为的文件中读取内容并将其存储在<variable>中。 可选择从给定的<offset>开始,最多读取<max-in>字节。HEX 选项使数据转换为十六进制表示(对二进制数据有用)。同样,指定文件既可以使用相对路径、也可使用绝对路径,相对路径被解释为相对于当前源码路径。

第二种读形式

file(STRINGS <filename> <variable> [<options>...])

    以字符串形式读取,从文件中解析 ASCII 字符串列表并将其存储在中。这个命令专用于读取字符串, 会将文件中的二进制数据将被忽略,回车符(\r, CR)字符被忽略。

    filename:指定需要读取的文2.8.1件,可使用绝对路径、也可使用相对路径,相对路径被解释为相对于当前源码路径。

    variable:存放字符串的变量。

    options:可选的参数,可选择 0 个、1 个或多个选项,这些选项包括:(1)LENGTH_MAXIMUM 读取的字符串的最大长度;(2)LENGTH_MINIMUM :读取的字符串的最小长度;(3)LIMIT_COUNT :读取的行数;(4)LIMIT_INPUT :读取的字节数;(5)LIMIT_OUTPUT :存储到变量的限制字节数;(6)NEWLINE_CONSUME:把换行符也考虑进去;(7)NEWLINE_CONSUME:把换行符也考虑进去;(8)NO_HEX_CONVERSION:除非提供此选项,否则 Intel Hex 和 Motorola S-record 文件在读取时会自动转换为二进制文件。(9)REGEX :只读取符合正则表达式的行;(10)ENCODING :指定输入文件的编码格式,目前支持的编码有:UTF-8、UTF-16LE、 UTF-16BE、UTF-32LE、UTF-32BE。如果未提供 ENCODING 选项并且文件具有字节顺序标记, 则 ENCODING 选项将默认为尊重字节顺序标记。

2.8.1 计算文件的 hash 值

file(<MD5|SHA1|SHA224|SHA256|SHA384|SHA512> <filename> <variable>)

    MD5|SHA1|SHA224|SHA256|SHA384|SHA512 表示不同的计算 hash 的算法,必须要指定其中之一, filename 指定文件(可使用绝对路径、也可使用相对路径,相对路径被解释为相对于当前源码的 BINARY_DIR),将计算结果存储在 variable 变量中。

2.8.2 文件重命名

file(RENAME <oldname> <newname>)

2.8.3 删除文件

file(REMOVE [<files>...])
file(REMOVE_RECURSE [<files>...])

     REMOVE 选项将删除给定的文件,但不可以删除目录;而 REMOVE_RECURSE 选项将删除给定的文件或目录、以及非空目录。

2.9 交叉编译

    这个似乎嵌入式会不会多一点,详见【CMake 入门与进阶(13)】 CMake如何设置交叉编译(附代码)

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值