CMake实例讲解

实例目录

1、CMake helloworld 简单工程
2、CMake输出自定义信息
3、CMake项目的二进制目录和源目录
4、CMake构建静态库和动态库
5、CMake添加工程子目录
6、CMake指定目标保存目录
7、CMake定义安装
8、CMake设置生成库的属性
9、CMake调用外部库
10、CMake查询主机系统特定信息
11、CMake设置版本最低要求
12、CMake策略管理
13、CMake配置文件
14、CMake函数
15、CMake宏
16、CMake的foreach循环
17、CMake的条件判断指令
18、CMake的while循环指令
19、CMake的math指令
20、CMake列表的操作之一(读取列表)
21、CMake列表的操作之二(搜索列表)
22、CMake列表的操作之三(修改列表)
23、CMake列表的操作之四(排序列表)
24、CMake列表的操作之五(转换列表)
25、CMake的变量监测
26、CMake添加项目树之外的子目录
























一、 CMake helloworld 简单工程

  1. 新建文件 HelloCMake.cpp

#include

int main(int argc, char** argv) {

std::cout <<"Hello CMake!" << std::endl;
return 0;

}

// 编辑CMakeLists.txt文件, 这个文件是CMake构建定义文件,名字必须是这个名字,大小写也要一致,
// 如果项目有多个目录,则每个目录下都要有一个CMakeLists.txt文件。

  1. CMakeLists.txt

project(HelloCMake)
set(SRC_LIST HelloCMake.cpp)
add_executable(hello_cmake $(SRC_LIST))

project函数, 用于定义项目名称,这里定义项目名称为HelloCMake,CMake 里函数是不分大小写的,写成PROJECT也是可以的

set函数, 用于定义变量并赋值,这里定义一个SRC_LIST变量,并且赋值为HelloCMake.cpp

add_executable函数, 用于添加一个可执行的目标,第一个参数是生成的目标文件名(即可执行的程序名),
第二个参数是依赖的源文件,这里传入了SRC_LIST, 引用变量的内容要用${VAR_NAME}这样的语法

使用cmake 生成Makefile, 传入的参数为一个点(.), 这个点是指当前目录,也就是告诉cmake,
从当前目录下的CMakeLists.txt文件作为入口,如果没有CMakeLists.txt,则会报错。

执行完cmake命令后,没有报错的情况下,除了生成了Makefile, 还生成了一些其他的文件,其它的文件可以先不管它。

有了Makefile, 就可以执行make命令来生成程序了, 最终生成的程序名称为在CMakeLists.txt里指定的名称, 即hello_cmake

//---------------------------------------------------------

二、CMake输出自定义信息

CMake输出自定义信息,可以使用message函数 调用格式为:

message([SEND_ERROR | STATUS | FATAL_ERROR] “要输出的信息”)
SEND_ERROR , STATUS , FATAL_ERROR 三个参数有什么区别? 不给参数又是如何?

  1. 新建一个build 子目录,然后进入build子目录来执行cmake, 这样cmake生成的文件就会在build子目录中,
    从而不会"污染"了源文件。

  2. 修改一下 CMakeLists.txt文件, 演示message的使用
    任何参数都不给,直接输出
    message(“This is a message”)

    传递STATUS参数
    message(STATUS “This is another message”)

  3. 执行cmake, 后面跟着两个点(…) ,两个点是代表上一层目录,即告诉cmake到上一层目录去找CMakeLists.txt

    没有给message传参数的,直接就输出了给定的信息。而传递了STATUS参数的,会在输出的信息前面加–

    输出:
    This is a message
    – This is another message

    cmake 生成的文件全在build这个子目录里,可以直接全部删除也不会对源文件有影响。

  4. 再看 SEND_ERROR是怎样的?
    message(SEND_ERROR “This is SEND_ERROR message”)

    输出:
    CMake Error at CMakeLists.txt 5 (message):
    This is SEND_ERROR message

    从输出的信息来看,SEND_ERROR 发送了错误信息, 并且跳过了生成的过程

  5. 再看 FATAL_ERROR是怎样的?
    message(FATAL_ERROR “This is FATAL_ERROR message”)

    输出:
    CMake Error at CMakeLists.txt 6 (message):
    This is FATAL_ERROR message

    从输出的信息来看好像跟SEND_ERROR 没什么区别, 但它们本质上是有区别的,SEND_ERROR 只是发送错误,
    跳过生成过程,它后面的语句还会执行; 而FATAL_ERROR , 从字面可以知道,
    致命错误,会立即中止过程,它后面的语句也不再执行。

//---------------------------------------------------------

三、CMake项目的二进制目录和源目录

CMake 会为每个项目的二进制目录和源目录隐式生成两个变量:
<project_name>_BINARY_DIR
<project_name>_SOURCE_DIR

同时也存在这样的两个变量:
PROJECT_BINARY_DIR = <project_name>_BINARY_DIR
PROJECT_SOURCE_DIR = <project_name>_SOURCE_DIR

  1. 这里项目名称为HelloCMake, 因此隐式变量为:
    HelloCMake_BINARY_DIR
    HelloCMake_SOURCE_DIR

    同时查看一下:
    PROJECT_BINARY_DIR
    PROJECT_SOURCE_DIR

    cmake 代码:
    message(STATUS ${HelloCMake_BINARY_DIR})
    message(STATUS ${PROJECT_BINARY_DIR})
    message(STATUS ${HelloCMake_SOURCE_DIR})
    message(STATUS ${PROJECT_SOURCE_DIR})



    输出:
    – E:/ProjectCMake/3、CMake项目的二进制目录和源目录/build
    – E:/ProjectCMake/3、CMake项目的二进制目录和源目录/build
    – E:/ProjectCMake/3、CMake项目的二进制目录和源目录
    – E:/ProjectCMake/3、CMake项目的二进制目录和源目录



    PROJECT_BINARY_DIR 对应的是执行cmake 命令所在的目录,这里是build 这个子目录的路径,
    而PROJECT_SOURCE_DIR 则对应源文件的目录,这里是build 的上一层目录,
    即CMakeList.txt对应的目录。

    结果也显示了:
    HelloCMake_BINARY_DIR = PROJECT_BINARY_DIR
    HelloCMake_SOURCE_DIR = PROJECT_SOURCE_DIR

    实际使用时,用PROJECT_XXX这个变量比较好,因为这两个变量不会因为项目名称的改变而改变,
    <project_name>_XXX 这个变量则要随项目名称的改变而更改

//---------------------------------------------------------

四、CMake构建静态库和动态库

CMake 中要生成静态库或动态库,可以使用
add_library 指令(函数) ,该指令使用方法:

add_library(lib_name [SHARED|STATIC|MODULE] [EXCLUDE_FROM_ALL] src1 src2 … srcN)

参数说明: lib_name : 指定生成库的名称

SHARED: 生成动态库
STATIC: 生成静态库

MODULE: 使用dyld(the dynamic link editor, 苹果的动态链接器)的系统有效,如果不支持dyld,
则为SHARED

EXCLUDE_FROM_ALL: 指定此参数,则该库默认情况下不被构建,当有其他组件用到该库或手动构建时,
才被构建

src1 src2 … srcN: 用于构建库用到的源文件

  1. 新建程序文件
    先来个头文件 HelloLibrary.h, 声明一个函数

    #ifndef HELLO_LIBRARY
    #define HELLO_LIBRARY

    #include
    void hello_library();

    #endif // HELLO_LIBRARY

    头文件对应的源文件,实现头文件里声明的函数,简单的输出字符串

    #include “HelloLibrary.h”

    void hello_library(){
    std::cout << “Hello Shared Library!” << std::endl;
    }

    主程序 ,用于调用生成库里的函数

    #include “HelloLibrary.h”
    int main(){
    hello_library();
    return 0;
    }



  2. 编辑CMakeLists.txt

    cmake_minimum_required指令,用于检测CMake的版本,指定最小版本至少为3.8

    项目名称为HelloLibrary

    add_library指令,指定库名称为hello_library,由于指明生成SHARED库(动态库),
    实际生成的库文件名为libhello_library.dll ,用于生成该库的源文件为HelloLibrary.cpp

    include_directories指令,用于增加包含头文件的路径,这里是把项目源目录增加到包含头文件的路径中

    add_executable 指令,用于指定生成可执行文件,生成的可执行文件名为hello_main ,
    用到的源文件为Main.cpp

    target_link_libraries指令,指定某个目标的生成所依赖的库,这里要生成的可执行文件hello_main要
    依赖hello_library这个库,也就是add_library指令生成的那个库

    cmake_minimum_required(VERSION 3.8)
    project(HelloLibrary)
    add_library(hello_library SHARED HelloLibrary.cpp)
    include_directories(${PROJECT_SOURCE_DIR})
    add_executable(hello_main Main.cpp)
    target_link_libraries(hello_main hello_library)




    一共四个文件, 一个头文件,用于声明函数,一个源文件用于生成库,一个源文件用于生成可执行文件,
    最后一个是CMake工程文件

  3. 修改CMakeLists.txt 项目文件,add_library 指令中,把SHARED 改为STATIC
    SHARED : 用于生成动态库 win 下为 dll文件,linux 下为 so文件
    STATIC : 用于生成静态库 win 下为 lib文件,linux 下为 a 文件

//---------------------------------------------------------

五、CMake添加工程子目录

CMake 提供了ADD_SUBDIRECTORY 指令,用于向当前工程添加子目录,并且可以指定中间二进制和目标二进制文件
存放的位置,还可以将指定目录排除出编译过程(这个功能这里并没有演示)。

  1. 创建项目的源文件目录 src, 并编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.8)
    project(SubDirectory)
    add_subdirectory(src)


    指定最小版本 3.8
    项目名称为SubDirectory
    add_subdirectory 指令,指定源文件子目录为src

  2. 创建程序文件
    main.cpp 文件

    #include
    int main(){
    std::cout << “This is sub directory project!” << std::endl;
    return 0;
    }



  3. src 子目录也要有自己的CMakeLists.txt
    add_executable(sub_directory main.cpp)

  4. 为add_subdirectory 指令指定bin_dir ,为bin,即编译输出到build/bin目录下
    cmake_minimum_required(VERSION 3.8)
    project(SubDirectory)
    add_subdirectory(src bin)


//---------------------------------------------------------

六、CMake指定目标保存目录

虽然ADD_SUBDIRECTORY 指令可以指定编译输出目录,但是也可以通过SET指令来设置EXECUTABLE_OUTPUT_PATH
和LIBRARY_OUTPUT_PATH 来指定最终目标的存放目录(只是最终目标文件,编译生成的中间文件不在此列)。

EXECUTABLE_OUTPUT_PATH : 最终可执行文件存放的目录。
LIBRARY_OUTPUT_PATH : 最终的库文件(静态库、共享库) 存放的目录。

  1. 新建src目录,在源文件目录中,新增相关的源文件,用于生成库和可执行目标。

    SayHello.h 用于生成库的头文件
    #ifndef SAY_HELLO_H
    #define SAY_HELLO_H

    #include

    void say_Hello();

    #endif // SAY_HELLO_H

    SayHello.cpp 文件

    #include “SayHello.h”

    void say_Hello(){
    std::cout << “Say Hello!” << std::endl;
    }

    main.cpp 用于可执行目标
    #include “SayHello.h”

    int main(){
    std::cout << “This is outputPath project!” << std::endl;
    say_Hello(); // 调用将要生成的库里的函数

     return 0;
    

    }

  2. 编辑src/CMakeLists.txt
    设置EXECUTABLE_OUTPUT_PATH, 把可执行文件生成于项目编译目录下的bin子目录
    设置LIBRARY_OUTPUT_PATH, 把库文件生成于项目编译目录下的lib子目录

    添加生成库目标。名为SayHello, 依赖源文件为SayHello.cpp

    把源文件所在目录加入包含头文件目录中,如果不加,会找不到SayHello.h 这个头文件

    添加可执行目标,名称为OutputPath, 依赖的源文件为main.cpp

    设置可执行目标的依赖库,即OutputPath 依赖SayHello这个库

    cmakelists.txt 代码:
    set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
    set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)

    add_library(SayHello SayHello.cpp)
    include_directories(${PROJECT_SOURCE_DIR}/src)
    add_executable(OutputPath main.cpp)
    target_link_libraries(OutputPath SayHello)


  3. 编辑项目目录下CMakeLists.txt
    把src 这个源文件目录加入到项目工程中,第二个参数可以不给了,给了也没用,因为已经设置了输出目录。

    cmakelists.txt 代码:
    cmake_minimum_required(VERSION 3.8)
    project(OutputPath)
    add_subdirectory(src)


  4. cmake 之后,在编译目录下生成了bin、lib 和src子目录,其实bin和lib还是空的,src存放了中间文件
    生成目标,包括库文件和可执行文件
    bin文件夹 : 可执行文件OutputPath.exe
    lib文件夹 : 库SayHello.lib


//---------------------------------------------------------

七、CMake定义安装

CMake 中 可通过INSTALL 指令来定义安装规则,并配合CMAKE_INSTALL_PREFIX 变量来指定安装的路径。
即执行cmake命令时指定:

mac: cmake -DCMAKE_INSTALL_PREFIX= /tmp.
win: cmake -DCMAKE_INSTALL_PREFIX= D:/tmp

定义安装的规则内容可以是可执行的二进制目标、动态库(共享库)、静态库、文件、目录、脚本等。

定义安装规则包括以下几方面:
1. 二进制目标文件(可执行、动态库、静态库)的安装
2. 普通文件的安装
3. 非目标可执行程序(如 脚本)的安装。
4. 目录的安装。
5. 安装时执行cmake脚本




  1. 二进制目标文件(可执行、动态库、静态库)的安装
    INSTALL(TARGETS targets… [[ARCHIVE|LIBRARY|RUNTIME]
    [DESTINATION

    ][PERMISSIONS permissions…]
    [CONFIGURATIONS [Debug|Release|…]]
    [COMPONENT ][OPTIONAL]][…])

    TARGETS: 后面的targets就是add_executable或add_library定义的目标,二进制目标或库
    ARCHIVE|LIBRARY|RUNTIME: 分别指静态库、动态库、可执行二进制目标
    DESTINATION

    : 定义要安装到哪个路径,结合CMAKE_INSTALL_PREFIX,
    路径为${CMAKE_INSTALL_PREFIX}/DESTINATION, 如果以/开头,则从根目录开始,这时CMAKE_INSTALL_PREFIX就不起作用了,所以一般写相对路径。

    PERMISSIONS: 指定权限
    Debug|Release:指定版本

  2. 举个例子:
    INSTALL(TARGETS exe sharedlib staticlib
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION static_lib
    )




    exe 安装到 C M A K E I N S T A L L P R E F I X / b i n 下 s h a r e d l i b 安 装 到 {CMAKE_INSTALL_PREFIX}/bin 下 sharedlib 安装到 CMAKEINSTALLPREFIX/binsharedlib{CMAKE_INSTALL_PREFIX}/lib 下
    staticlib 安装到${CMAKE_INSTALL_PREFIX}/static_lib 下

  3. 普通文件的安装
    INSTALL(FILES files… DESTINATION


    [PERMISSIONS permissions…]
    [CONFIGURATIONS [Debug|Release|…]]
    [COMPONENT]
    [RENAME][OPTIONAL]
    )
    用于安装一般文件,可指定访问权限,文件名是此指令所在路径下的相对路径。如果不定义权限PERMISSIONS,安装后的权限为
    : OWNER_WRITE OWNER_READ|GROUP_READ|WORLD_READ ,即644 权限






  4. 非目标可执行程序(如脚本)的安装
    INSTALL(PROGRAMS files… DESTINATION


    [PERMISSIONS permissions…]
    [CONFIGURATIONS [Debug|Release|…]]
    [COMPONENT]
    [RENAME][OPTIONAL]
    )




    与普通文件安装差不多,只是默认权限不一样:
    OWNER_EXECUTE OWNER_WRITE OWNER_READ|GROUP_EXECUTE GROUP_READ|WORLD_EXECUTE WORLD_READ, 即 755权限

  5. 目录的安装
    INSTALL(DIRECTORY dirs… DESTINATION


    [FILE_PERMISSIONS permissions…]
    [DIRECTORY_PERMISSIONS permissions…]
    [USE_SOURCE_PERMISSIONS]
    [CONFIGURATIONS [Debug|Release|…]]
    [COMPONENT]
    [[PATTERN | REGEX]
    [EXCLUDE] [PERMISSIONS permissions…]] […]
    )







    DIRECTORY: 后面接的是所在源目录的相对路径,目录后有没有/,区别很大,如dir 与dir/是不一样的, dir是将dir这个目录安装到目标路径下的dir, 而dir/是将这个目录下的内容安装到目标目录,但不包括这个目录本身。

    PATTERN:使用正则表达式进行过滤
    PERMISSIONS: 用于指定PATTERN 过滤后的文件权限

  6. 举个例子:
    INSTALL(DIRECTORY samples modules/ DESTINATION share
    PATTERN “TXT” EXCLUDE
    PATTERN “modules/*”
    PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ|GROUP_EXECUTE GROUP_READ
    )




    将samples目录安装到 C M A K E I N S T A L L P R E F I X / s h a r e 目 录 下 , 将 m o d u l e s / 中 的 内 容 安 装 到 {CMAKE_INSTALL_PREFIX}/share目录下,将modules/中的内容安装到 CMAKEINSTALLPREFIX/sharemodules/{CMAKE_INSTALL_PREFIX}/share目录下。
    不包含目录名为TXT的目录,对modules/目录下的文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ|GROUP_EXECUTE
    GROUP_READ

  7. 安装时执行cmake 脚本
    INSTALL([[SCRIPT ][CODE ]][…])

    SCRIPT: 用于在安装时调用cmake脚本文件,即 xxxx.cmake文件
    CODE: 行cmake指令, 要用双引号,如:INSTALL(CODE “MESSAGE(“Sample install message.”)”)

  8. 创建示例工程
    新建src doc 文件夹 ,copyright, readme 两个文件

    src/main.cpp 主程序文件
    #include

    int main(){
    std::cout << “customize install!” << std::endl;

     return 0;
    

    }

    src/cmakelists.txt

    set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
    add_executable(customize_install main.cpp)
    install(TARGETS customize_install DESTINATION CustomizeInstall/bin)

    设置可执行目标输出的路径
    生成可执行二进制
    指定这个可执行二进制要安装到的目录,这里为 /${CMAKE_INSTALL_PREFIX}/CustomizeInstall/bin

    项目主目录下的CMakeLists,txt文件

    cmake_minimum_required(VERSION 3.8)
    project(CustomizeInstall)
    add_subdirectory(src bin)
    install(FILES copyright.txt readme.txt DESTINATION CustomizeInstall)
    install(PROGRAMS run_customize_install.sh DESTINATION CustomizeInstall/bin)
    install(DIRECTORY doc/ DESTINATION CustomizeInstall/share/doc)




    指定最小的CMake版本
    指定项目名称
    把src源文件子目录加入到项目中
    把copyright, readme 这两个文件安装到目录: / C M A K E I N S T A L L P R E F I X / C u s t o m i z e I n s t a l l 下 把 r u n c u s t o m i z e i n s t a l l . s h ( 还 没 有 创 建 ) 文 件 安 装 到 目 录 : / {CMAKE_INSTALL_PREFIX}/CustomizeInstall 下 把run_customize_install.sh (还没有创建) 文件安装到目录:/ CMAKEINSTALLPREFIX/CustomizeInstallruncustomizeinstall.sh()/{CMAKE_INSTALL_PREFIX}/CustomizeInstall/bin下
    把doc/目录下的文件(不包含这个目录本身) 安装到目录: /${CMAKE_INSTALL_PREFIX}/CustomizeInstall/share/doc下



    //创建run_customize_install.sh , 简单执行 ./customize_install
    //进入doc 子目录,简单来个文件代表为文档文件

    //执行cmake命令 , 指定CMAKE_INSTALL_PREFIX为 E:/tmp
    // cmake -DCMAKE_INSTALL_PREFIX=E:/tmp …

    //执行make install 来安装项目,提示信息看到所有的文件都按指定的目录进行安装了

    // 指定安装 本质上时拷贝一些文件到一个指定目录下, 这个功能可以用在生成库时,拷贝头文件和库文件到指定目录

//---------------------------------------------------------

八、CMake设置生成库的属性
指令SET_TARGET_PROPERTIES 可设置目标的属性

SET_TARGET_PROPERTIES(target1 target2 ...
	PROPERTIES prop1 value1 prop2 value2 ...)

target1 target2: 指定要设置属性的目标
prop1 value1: 指定属性名和属性值,这里将演示 OUTPUT_NAME、VERSION、SOVERSION 三个属性

有设置属性的指令,自然也有获取属性的指令:
GET_TARGET_PROPERTY(VAR target prop)

VAR: 获取的属性存放的变量
target: 目标
prop: 要获取的属性名
  1. 编辑cmakelists.txt

    cmake_minimum_required(VERSION 3.8)
    project(LibraryProperty)
    add_subdirectory(src)

    指定最小的CMake版本
    项目名称为LibraryProperty
    添加src子目录

  2. 添加src 子目录 并添加要生成库的头文件和源文件

    linear.h

    #ifndef LINEAR_H
    #define LINEAR_H

    void linear_fit();

    #endif

    linear.cpp

    #include
    #include “linear.h”

    void linear_fit(){
    std::cout << “linear_fir called” << std::endl;
    }

    src/CMakeLists.txt

    set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
    add_library(linear SHARED linear.cpp)
    add_library(static_linear STATIC linear.cpp)

    设置生成库的存放目录, 为编译目录下的lib 子目录
    添加生成动态库目标,目标名为linear
    再添加生成静态库目标,目标名也是linear

  3. 上面生成两个库文件,但更期望生成的动态库和静态库分别为 liblinear.dll 和 liblinear.lib , 这样才统一,而不是像现在这样。

    修改 src/CMakeLists.txt

    set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
    add_library(linear SHARED linear.cpp)
    add_library(static_linear STATIC linear.cpp)

    set_target_properties(static_linear PROPERTIES OUTPUT_NAME “linear”)

    get_target_property(OUTPUT_VALUE static_linear OUTPUT_NAME)
    message(STATUS “OUTPUT_NAME = ${OUTPUT_VALUE}”)

    get_target_property(UNDEFINE_VALUE static_linear UNDEFINE_NAME)
    message(STATUS “UNDEFINE_NAME = ${UNDEFINE_VALUE}”)

    设置生成库的存放目录, 为编译目录下的lib 子目录
    添加生成动态库目标,目标名为linear
    再添加生成静态库目标,目标名也是linear
    设置静态库目标的输出名称为 linear
    同时,获取一下这个属性值看看
    如果试图获取未定义的属性,会发生什么?
    (运行cmake 可以看到,如果属性没设置,得到的值为NOTFOUND)
    (最后运行的结果 输出的两个库文件名称为期待的结果)






  4. 对于动态库,一般都有版本号,VERSION 为版本号, SOVERSION 为API版本号。

    修改 src/CMakeLists.txt

    set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
    add_library(linear SHARED linear.cpp)
    add_library(static_linear STATIC linear.cpp)

    set_target_properties(static_linear PROPERTIES OUTPUT_NAME “linear”)

    get_target_property(OUTPUT_VALUE static_linear OUTPUT_NAME)
    message(STATUS “OUTPUT_NAME = ${OUTPUT_VALUE}”)

    get_target_property(UNDEFINE_VALUE static_linear UNDEFINE_NAME)
    message(STATUS “UNDEFINE_NAME = ${UNDEFINE_VALUE}”)

    set_target_properties(linear PROPERTIES VERSION 1.0 SOVERSION 1)

    install(TARGETS linear static_linear LIBRARY DESTINATION lib ARCHIVE DESTINATION lib/ P R O J E C T N A M E ) i n s t a l l ( F I L E S l i n e a r . h D E S T I N A T I O N i n c l u d e / {PROJECT_NAME}) install(FILES linear.h DESTINATION include/ PROJECTNAME)install(FILESlinear.hDESTINATIONinclude/{PROJECT_NAME})

    设置生成库的存放目录, 为编译目录下的lib 子目录
    添加生成动态库目标,目标名为linear
    再添加生成静态库目标,目标名也是linear
    设置静态库目标的输出名称为 linear
    同时,获取一下这个属性值看看
    如果试图获取未定义的属性,会发生什么?
    (运行cmake 可以看到,如果属性没设置,得到的值为NOTFOUND)
    (最后运行的结果 输出的两个库文件名称为期待的结果)






    生成的动态库带有版本号, 并生成两个链接文件

    设置安装规则,动态库安装到/lib下,静态库安装到/lib/LibraryProperty下
    把头文件安装到/include/LibraryProperty下

    // 执行cmake 时 指定 CMAKE_INSTALL_PREFIX 为 E:/tmp
    // cmake -DCMAKE_INSTALL_PREFIX=E:/tmp …

//---------------------------------------------------------

九、CMake调用外部库

INCLUDE_DIRECTORIES([AFTER|BEFORE]
[SYSTEM] dir1 dir2 …)

添加头文件搜索路径,多个路径用空格分开,如果路径有空格,路径用双引号引起来。
默认是添加到搜索路径的后面,可以通过AFTER和BEFORE来控制是添加到前面还是后面。
也可以把CMake变量 CMAKE_INCLUDE_DIRECTORIES_BEFORE设置为on来把路径添加到前面。

LINK_DIRECTORIES(dir1 dir2 …)

添加非系统(非标准)的库搜索路径。此指令要放在目标之前,比如要放在add_executable或add_library之前。

LINK_LIBRARIES([item1 [item2 […]]]
[[debug|optimized|general]] ]…)

为目标添加需要链接的库。此指令要放在目标之前,比如要放在add_executable或add_library之前。指定的库名称要全路径。

对于不是系统内部的库,CMake如何调用?

CMake主要提供了以下几个指令来实现:
INCLUDE_DIRECTORIES([AFTER|BEFORE]
[SYSTEM] dir1 dir2 …)

LINK_DIRECTORIES(dir1 dir2 …)

LINK_LIBRARIES([item1 [item2 […]]]
[[debug|optimized|general]] ]…)

TARGETS_LINK_LIBRARIES(target lib1
<debug|optimized> lib2 …)

  1. 编辑cmakelists.txt

    cmake_minimum_required(VERSION 3.8)
    project(ExternalProperty)
    add_subdirectory(src)

    指定最小的CMake版本
    项目名称为ExternalProperty
    添加src子目录

  2. 新建src 目录 添加main.cpp CMakeLists.txt ,这里链接的是静态库

    main.cpp

    #include
    #include “linear.h”

    int main(){
    std::cout << “Main called!” << std::endl;
    linear_fit();

     return 0;
    

    }

    CMakeLists.txt

    set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
    include_directories(E:/tmp/include/LibraryProperty)
    link_libraries(E:/tmp/lib/LibraryProperty/linear.lib)
    add_executable(ExternalLibrary main.cpp)


    设置二进制目标文件的输出目录
    把对应头文件的目录加进来 (使用 八、CMake设置生成库的属性 )
    添加链接库,注意,这是放在add_executable前面的,用了全路径
    添加二进制目标,依赖Main.cpp 这个源文件


  3. 修改 CMakeLists.txt,另一种链接库方式

    CMakeLists.txt

    set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
    link_directories(E:/tmp/lib/LibraryProperty)
    add_executable(ExternalLibrary main.cpp)
    include_directories(E:/tmp/include/LibraryProperty)
    target_link_libraries(ExternalLibrary linear.dll)



    使用link_directories 指定库对应的目录
    使用target_link_libraries ,指定目标和库名称

//---------------------------------------------------------

十、CMake查询主机系统特定信息
CMake 提供下面的指令用于查询主机系统特定信息:
cmake_host_system_information(RESULT QUERY …)

: 用于保存查询到的信息的变量
: 查询信息的关键词,多个关键词之间用空格分开,返回到中多个信息之间用分号分隔

可查询的信息关键词有很多,如:INSTALL
NUMBER_OF_LOGICAL_CORES,
NUMBER_OF_PHYSICAL_CORES,
HOSTNAME,
FQDN,
TOTAL_VIRTUAL_MEMORY,
AVAILABLE_VIRTUAL_MEMORY,
TOTAL_PHYSICAL_MEMORY,
AVAILABLE_PHYSICAL_MEMORY…
等等








  1. 编辑cmakelists.txt 演示一下各个关键词查询的信息

    cmake_minimum_required(VERSION 3.8)
    project(HostSystemInfo)
    add_subdirectory(src)

  2. 新建src目录 添加main.cpp CMakeLists.txt文件
    main.cpp

    #include

    int main(){
    std::cout << “Host System Info Output!” << std::endl;

     return 0;
    

    }

    CMakeLists.txt

    cmake_host_system_information(RESULT NumberOfLogicalCores QUERY NUMBER_OF_LOGICAL_CORES)
    message(STATUS “NUMBER_OF_LOGICAL_CORES = ${NumberOfLogicalCores}”)

    cmake_host_system_information(RESULT NumberOfPhysicalCores QUERY NUMBER_OF_PHYSICAL_CORES)
    message(STATUS “NUMBER_OF_PHYSICAL_CORES = ${NumberOfPhysicalCores}”)

    cmake_host_system_information(RESULT HostName QUERY HOSTNAME FQDN)
    message(STATUS “HOSTNAME,FQDN = ${HostName}”)

    cmake_host_system_information(RESULT VirtualMemory QUERY TOTAL_VIRTUAL_MEMORY AVAILABLE_VIRTUAL_MEMORY)
    message(STATUS “TOTAL_VIRTUAL_MEMORY,AVAILABLE_VIRTUAL_MEMORY = ${VirtualMemory}”)

    cmake_host_system_information(RESULT PhysicalMemory QUERY TOTAL_PHYSICAL_MEMORY AVAILABLE_PHYSICAL_MEMORY)
    message(STATUS “TOTAL_PHYSICAL_MEMORY,AVAILABLE_PHYSICAL_MEMORY = ${PhysicalMemory}”)

    cmake_host_system_information(RESULT Is64Bit QUERY IS_64BIT)
    message(STATUS “IS_64BIT = ${Is64Bit}”)

    cmake_host_system_information(RESULT HasFPU QUERY HAS_FPU)
    message(STATUS “HAS_FPU = ${HasFPU}”)

    cmake_host_system_information(RESULT processor QUERY PROCESSOR_NAME PROCESSOR_DESCRIPTION)
    message(STATUS “PROCESSOR_NAME, PROCESSOR_DESCRIPTION = ${processor}”)

    cmake_host_system_information(RESULT os QUERY OS_NAME OS_RELEASE OS_VERSION OS_PLATFORM)
    message(STATUS “OS_NAME,OS_RELEASE,OS_VERSION,OS_PLATFORM = ${os}”)

    set(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
    add_executable(HostSystemInfo Main.cpp)

    查询NUMBER_OF_LOGICAL_CORES, 逻辑核心数
    查询NUMBER_OF_PHYSICAL_CORES, 物理核心数
    查询HOSTNAME和FQDN, 主机名和主机域名全称
    查询TOTAL_VIRTUAL_MEMORY 和 AVAILABLE_VIRTUAL_MEMORY,总虚拟内存和可用虚拟内存
    查询TOTAL_PHYSICAL_MEMORY 和 AVAILABLE_PHYSICAL_MEMORY,总物理内存和可用物理内存
    查询IS_64BIT ,是否为64位
    查询HAS_FPU, 处理器是否有浮点单元
    查询PROCESSOR_NAME 和 PROCESSOR_DESCRIPTION , 处理器名称和处理器全描述






    查询OS_NAME、OS_RELEASE、 OS_VERSION 和 OS_PLATFORM,操作系统相关的信息
    设置可执行二进制输出目录
    添加可执行二进制生成目标

    // cmake 之后,即可看到输出的相关信息

//---------------------------------------------------------
十一、CMake设置版本最低要求

cmake_minimum_required 指令: 

cmake_minimum_required(VERSION <min> [...<max>][FATAL_ERROR])

<min> 和 <max> 指定CMake的版本号,版本号的格式为: major.minor[.patch[.tweak]]

[...<max>]说明: 这里的...就真的是三个点,直接写三个点

如果安装的CMake版本低于指定的<min>, 则停止项目的处理并报错。

如果指定了...<max> ,则会影响cmake_policy(VERSION <min>[...<max>]) 指定的策略

在CMake3.12之前的版本,...三个点被认为是版本号组成的各部分分隔符。因此,如果CMake的版本小于3.12, ... <max>
部分会被忽略,于是对应的策略行为会保持3.12之前那种基于<min>的形式。

FATAL_ERROR: 在CMake2.6 或更高版本中,此参数会被忽略,因此传不传都是一样的。
  1. cmake_minimum_required() 使用注意:
    a. 在顶层的CMakeLists.txt最开始调用,甚至先于project()调用,这样可以一开始建立版本策略,从而对于一些受策略影响
    的指令比较友好。
    b. 如果在function()里调用, 则不要期望能在全局调用的效果,毕竟会受到函数域的限制。


  2. 编辑CMakeLists.txt

    cmake_minimum_required(VERSION 3.18.5)
    #cmake_minimum_required(VERSION 3.8)
    #cmake_minimum_required(VERSION 3.8…3.17)

    指定最小版本为3.18.5 ,运行会报错
    指定最小版本为3.8 ,运行正确
    指定最小版本为3.8, 最大版本为 3.17 ,运行正确

//---------------------------------------------------------

十二、CMake策略管理

CMake 的策略管理用于维护版本的向后兼容,从版本2.6开始引入。

每个新发布版本一般都会引入一些新的策略,每个策略都有一个标识号,格式为CMP,对应四个0到9的整数。
每个策略都在文档中描述了OLD和NEW的行为,以及引入的原因。

CMake的策略是一种弃用机制,并不是可靠特性的切换,因此,策略的OLD行为一般不可取,因为在未来版本中可能被抛弃。

  1. CMake 可以通过版本来设置策略,指令为
    cmake_policy(VERSION […])

    : 最小版本,至少为2.4, 至多为当前安装的版本号
    : 最大版本,至少为, 可以大于当前安装版本号
    如果当前安装版本小于3.12,…被认为是版本组成的分隔符,从而会忽略…,因此会按3.12之前的版本基于
    的策略来执行。


    调用cmake_minimum_required 会隐式调用cmake_policy, 因此,如果没有特别的策略要指定,可以直接通过调用
    cmake_minimum_required就够了。

  2. CMake 可以显示设置策略的行为,为OLD或为New
    cmake_policy(SET CMP NEW)
    cmake_policy(SET CMP OLD)

    OLD 行为为弃用定义,在未来的版本中可能被移除。

  3. CMake获取策略的行为
    cmake_policy(SET CMP )
    获取指定的策略行为是OLD还是NEW, 的值设为OLD 或NEW, 如果指定的策略没有设置,则为空。

  4. CMake的策略栈
    cmake_policy(PUSH)
    cmake_policy(POP)
    CMake 用栈来保存策略设置,cmake_policy的操作只影响栈顶。对每一个子目录,策略栈自动管理新入口,从而保证孩子
    目录的父目录和兄弟目录不被破坏。
    对于include()和find_package()指令的脚本,也有新的入口,除非指定了NO_POLICY_SCOPE选项。




  5. 编辑CMakeLists.txt

    cmake_minimum_required(VERSION 3.8)
    #cmake_policy(VERSION 2.3)
    cmake_policy(VERSION 2.4)
    project(policy)


    指定最小版本为 3.8
    策略最小版本为 2.3, 运行会报错
    策略最小版本为 2.4, 运行正常

//---------------------------------------------------------

十三、CMake配置文件

CMake 通过以下指令来复制一个文件到指定地方,并可修改这个文件的内容,也就是配置文件。修改的内容为输入文件中
以@VAR@或${VAR} 指定的CMake变量占位符, 占位符被相应变量的内容替换:

configure_file(
[COPYONLY][ESCAPE_QUOTES][@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF]])

: 要复制的文件,如果用的是相对路径,则基于CMAKE_CURRENT_SOURCE_DIR目录

: 要复制到的目标,可以指定文件名或目录,如果是已经存在的目录,则用的文件名。
如果用相对路径来指定,则基于CMAKE_CURRENT_BINARY_DIR目录
COPYONLY:原封不动复制文件,不作任何的变量替换
ESCAPE_QUOTES: 回避反斜杠转义,即C分格对的转义
@ONLY: 只替换@VAR@的变量,$(VAR)的不作替换
NEWLINE_STYLE: 输出文件中换行的方式。UNIX和LF是以\n换行,而DOS, WIN32和CRLF是以\r\n换行。
因为输出文件与输入文件内容有变动,所以不能与COPYONLY一起使用。





  1. 几种替换说明:

    a. 输入文件中的内容为:
    #cmakedefine VAR …
    如果VAR 被定义,替换的内容为:
    #define VAR …
    如果VAR未被定义,则替换为:
    /* #undef VAR */




    b. 输入文件中的内容为:
    #cmakedefine01 VAR
    如果VAR被定义,替换的内容为:
    #define VAR 1
    否则替换的内容为:
    #define VAR 0




    c. ESCAPE_QUOTES的使用
    输入文件中的内容为:
    “@QUOTE@”
    CMakeLists.txt 有定义:
    set(QUOTE ““bb””)
    如果不使用ESCAPE_QUOTES,则输入文件中的占位符被替换为 ““bb””, 如果使用了 ESCAPE_QUOTES,则替换为"“bb”"




  2. 编辑input.h 文件

    #cmakedefine VAR 3.14
    #cmakedefine VAR1 “Good”
    #cmakedefine01 VAR3
    #cmakedefine01 VAR4
    #cmakedefine NAME “@NAME@”
    #cmakedefine NEW_NAME “${NEW_NAME}”
    #cmakedefine QUOTE “@QUOTE@”





    #include

    void $(my_print)(){
    std::cout << “print something” <<std::endl;
    }

    //#cmakedefine 的替换
    //#cmakedefine01 的替换
    //#cmakedefine NAME “@NAME@” 变量替换@类型
    //#cmakedefine NEW_NAME " N E W N A M E " 变 量 替 换 {NEW_NAME}" 变量替换 NEWNAME"{VAR}类型
    //#cmakedefine QUOTE “@QUOTE@” 用于演示 ESCAPE_QUOTES 的替换



    // void $(my_print)() 随意位置的替换

  3. 编辑cmakelists.txt 文件
    cmake_minimum_required(VERSION 3.8)
    project(ConfigureFile)

    set(VAR1 123)
    set(VAR3 “abc”)

    set(NAME “haha”)
    set(NEW_NAME “new haha”)

    set(QUIET ““bb””)
    set(my_print “print_something”)

    configure_file(input.h input_default.h)
    configure_file(input.h input_copyonly.h COPYONLY)
    configure_file(input.h input_escape_quotes.h ESCAPE_QUOTES)
    configure_file(input.h input_at_only.h @ONLY)
    configure_file(input.h input_newline_style_dos.h NEWLINE_STYLE DOS)
    configure_file(input.h input_newline_style_unix.h NEWLINE_STYLE LF)




    定义VAR1, 注意没有定义VAR
    定义NAME, NEW_NAME
    定义QUOTE, my_print

    按可用的参数分别输出不同的配置文件,包括默认的、COPYONLY、ESCAPE_QUOTES、@ONLY、 NEWLINE_STYLE

  4. 执行cmake
    生成文件 input_at_only.h input_copyonly.h input_default.h 等等…其文件中都有相应的替换

//---------------------------------------------------------

十四、CMake函数

CMake 中用一下指令定义函数:
function(<name> [<arg1> ...])
	<commands>
endfunction()

function指令, 指定函数的名称,参数名称
endfunction指令,用于结束函数的定义,可以传入与function指令中同样的函数名称作为参数,也可以不传。

这两个指令中间就是函数要执行的命令。
  1. 函数的调用:
    如果定义的函数为:
    function(func)

    endfunction



    则可以这样调用:
    func()
    FUNC()
    不区分大小写


  2. 函数中变量的作用域:
    a. 在函数中调用set(var xxxx)
    则var在函数的作用域内的值为xxxx,如果var在函数的父层作用域有定义,值为yyyy,则出了函数作用域,
    var的值仍然为yyyy,并不会变为xxxx。


    b. 在函数中调用set(var xxxx PARENT_SCOPE)
    在函数的父层作用域中设置var的值为xxxx,如果在函数调用之前,var存在,值为yyyy,则在函数中var的值为yyyy,
    并不会变成xxxx,调用函数之后,var的值就变成xxxx。如果调用之前var不存在,则在函数内var也是不存在。

  3. 函数的参数:
    a. ARGC : 参数的个数
    b. ARGV0,ARGV1,ARGV2, …,ARGV#:第#个参数的值
    c. ARGV: 所有传入的参数值的列表
    d. ARGN: 如果在定义函数时,显式的指定了两个参数,而在调用时,传入了五个参数,
    则后面的三个参数的值就存在在这个变量里




  4. 编辑cmakelists.txt

    cmake_minimum_required(VERSION 3.8)
    project(Function)

    function(func1)
    message(STATUS “func1 called”)
    endfunction(func1)

    message(STATUS “======================”)
    func1()
    FUNC1()

    set(Var1 “Var1: init”)

    function(func2)
    set(Var1 “Var1: value in func2”)
    message(STATUS ${Var1})
    endfunction()


    message(STATUS “======================”)
    message(STATUS “Var1 before call func2: ${var1}”)
    func2()
    message(STATUS “Var1 after call func2: ${var1}”)


    function(func3)
    set(Var1 “Var1 : Value in func3” PARENT_SCOPE)
    message(STATUS ${Var1})
    set(Var2 “Var2 : init in func3” PARENT_SCOPE)
    message(STATUS ${Var2})
    endfunction()




    message(STATUS “======================”)
    message(STATUS “Var1 before call func3: ${var1}”)
    message(STATUS “Var2 before call func3: ${var2}”)
    func3()
    message(STATUS “Var1 after call func3: ${var1}”)
    message(STATUS “Var2 after call func3: ${var2}”)




    function(func4 arg1 arg2)
    message(STATUS “arg1 = ${arg1}”)
    message(STATUS “arg2 = ${arg2}”)

    message(STATUS "ARGC = ${ARGC}")
    message(STATUS "${ARGV0} - ${ARGV1}")
    message(STATUS "ARGV = ${ARGV}")
    message(STATUS "ARGN = ${ARGN}")
    message(STATUS "ARGV2 = ${ARGV2}")
    

    endfunction()

    message(STATUS “======================”)

    func4(“aaa” “bbb” “ccc” “ddd”)

    定义一个函数

    调用函数,函数名称不区分大小写
    设置变量Var1

    设置变量var1 在函数中的值
    查看Var1在调用func2之前的值
    调用函数func2
    查看Var1在调用func2之后的值


    在func3中设置Var1在函数的上一层作用域的值

    查看Var1在func3中的值
    在func3中设置Var2在父作用域中的值
    查看Var2在func3中的值

    调用func3之前,看一下Var1和Var2都是什么值
    调用func3
    再查看Var1 和Var2的值

    定义func4 ,带两个显示参数
    查看两个显示参数的值
    查看传入的参数个数
    通过ARGV#查看前两个参数值
    查看所有参数值得列表
    查看未在函数定义中声明的参数值列表
    通过ARGV2查看第三个参数值





    调用func4, 传入4个参数

    // cmake 一下 查看输出结果

    // 变量Var1在 func2中改变了,但在func2的父作用域不变

    // 在调用func3前, Var1有值, var2 不存在,在func3中,设置了Var1、Var2在父作用域中的值,因此,在func3中,
    // Var1、Var2 的值就是调用func3之前的值;调用func3后,Var1、Var2的值就是在func3中设置的值

//---------------------------------------------------------

十五、CMake宏

CMake 中的宏与函数很相似,但也有区别
a. 相同: 调用方式相同, 同样是不区分名称大小写,参数的引用同样可用,如${ARGC}, ${ARGV#}, ${ARGV#}, ${ARGN}。
b. 区别: CMake 的宏跟C语言的宏相似,原地替换。宏的参数相关的引用,并不是真实的变量,而函数中对参数的引用,则
是真实的变量。函数有自身的域,而宏没有。


  1. 编辑cmakelists.txt

    cmake_minimum_required(VERSION 3.8)
    project(Macro)

    function(print_line)
    message(STATUS “=======================”)
    endfunction()

    macro(macro1)
    message(STATUS “macro1 called”)
    endmacro(macro1)

    print_line()
    macro1()
    MACRO1()

    set(Var1 “Var1 : init”)

    macro(macro2)
    set(Var1 “Var1: set Var1 in macro2”)
    message(STATUS “Var1 in macro2 = ${Var1}”)
    endmacro(macro2)


    print_line()
    message(STATUS “before call macro2: Var1 = ${Var1}”)
    macro2()
    message(STATUS “after call macro2: Var1 = ${Var1}”)


    macro(macro3 arg1 arg2)
    message(STATUS “arg1 = ${arg1}”)
    message(STATUS “arg2 = ${arg2}”)
    message(STATUS “number of arguments = ${ARGC}”)
    message(STATUS “arg0 = ${ARGV0}, arg1 = ${ARGV1}”)
    message(STATUS “arguments list = ${ARGV}”)
    message(STATUS “ARGN = ${ARGN}”)
    message(STATUS “ARGV2 = ${ARGV2}”)
    endmacro(macro3)







    print_line()

    macro3(“aaa”, “bbb”, “ccc”, “ddd”)

    function(func4)
    if(ARGV1)
    message(STATUS “ARGV1 = ${ARGV1}”)
    endif()
    if(DEFINED ARGV2)
    message(STATUS “ARGV2 = ${ARGV2}”)
    endif()





     if(ARGC GREATER 2)
     	message(STATUS "ARGC GREATER 2")
     else()
     	message(STATUS "ARGC NOT GREATER 2")
     endif()
     
     foreach(var IN LISTS ARGN)
     	message(STATUS "var = ${var}")
     endforeach()
    

    endfunction(func4)

    print_line()

    func4(“aaa”, “bbb”, “ccc”, “ddd”)

    macro(macro4)
    if(ARGV1)
    message(STATUS “ARGV1 = ${ARGV1}”)
    endif()
    if(DEFINED ARGV2)
    message(STATUS “ARGV2 = ${ARGV2}”)
    endif()





     if(ARGC GREATER 2)
     	message(STATUS "ARGC GREATER 2")
     else()
     	message(STATUS "ARGC NOT GREATER 2")
     endif()
     
     foreach(var IN LISTS ARGN)
     	message(STATUS "var = ${var}")
     endforeach()
     
     set(list_var "${ARGN}") #这里把ARGN转成列表,再用foreach 
     foreach(var IN LISTS list_var) 
     	message(STATUS "var in list_var = ${var}")
     endforeach()
    

    endmacro(macro4)

    print_line()

    macro4(“aaa”, “bbb”, “ccc”, “ddd”)

    macro(macro5)
    foreach(arg IN LISTS ARGN)
    message(STATUS “arg = ${arg}”)
    endforeach()
    endmacro(macro5)



    function(func5)
    macro5(xx yy zz)
    endfunction(func5)

    print_line()

    func5(“aa”, “bb”, “cc”)

    宏的定义用macro 和 endmacro 两个指令
    定义一个函数输出分割线

    调用宏,名称不区别大小写

    宏对变量的操作,先定义一个变量
    定义一个宏,并在宏里修改Var1的值

    调用宏前Var1的值
    调用宏

    // cmake 一下 查看输出结果

    //调用宏后Var1 的值,可以看到,Var1的值在宏里修改了,修改后的值在调用宏后保留下来了, 这一点与函数时不一样的,
    //因为宏是原地替换的,没有自身的域,这是比较容易理解的。
    //相关参数的引用,与函数对参数的引用是一样的

    //定义一个宏对参数相关的引用

    函数中的参数是真实的变量,ARGV1 是变量,为True
    ARGV2 也是变量,DEFINED 的,也为True
    ARGC 也是真实变量,可用于与2比较
    ARGN 是列表变量,可以直接进行foreach循环


    函数中的同样操作用于宏,看下结果
    这里把ARGN转成列表,再用foreach

    在函数宏调用宏,宏传入了三个未定义的参数

    调用函数,传入了三个参数

    // cmake 一下 查看输出结果
    // 函数里, 参数的引用都是真实的变量,而在宏中,并不是变量,因此,在宏中,ARGN、ARGC、ARGV、ARGV#这些都是
    // 未定义的。

//---------------------------------------------------------

十六、CMake的foreach循环
CMake的foreach 指令用于循环列表中的每一项:
foreach(<loop_var> )

endforeach()



loop_var : 用于存放每一轮循环时列表中该项的值
items: 列表的项
commands: 第一轮循环执行的指令
endforeach(): 是foreach循环结束指令,为了兼容,可以把loop_var作为参数传入


foreach 指令的几种使用方法:
foreach(<loop_var> RANGE <stop>)

foreach(<loop_var> RANGE <start> <stop> [<step>])

foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS[<items>]])

下面的用法要3.17或以上版本:
foreach(<loop_var> ... IN ZIP_LISTS <lists>)

foreach(<loop_var> RANGE <stop>)
循环值从0 到指定的stop, 包含stop,stop 不能为负值

foreach(<loop_var> RANGE <start> <stop> [<step>])
循环从start 到 stop 的值,步长为step,如果step不指定则为1,三个参数都不能为负数,而且stop不能比start小。


foreach(<loop_var> IN [LISTS [<lists>]] [ITEMS[<items>]])
循环列表lists中的项,或直接指定项 items。


foreach(<loop_var> ... IN ZIP_LISTS <lists>)
这种用法至少要3.17版本,是组合多个列表一起循环,如果指定的多个列表项数不相等,项数少的,
在loop_var中的值为空值。
  1. 编辑cmakelists.txt
    第一种用法: 直接指定stop值
    第二种用法: 指定start, stop, step
    第三种用法: 指定列表的变量,多个变量可以空格分开(第三种用法,也可以只指定一个列表变量)


    第三种用法, 通过ITEMS来指定列表的项,这里用${B}, 即等于把B变量的项在这里展开
    第四种用法: 多个列表组合,只用一个变量取多个列表的项,则通过"变量名_#"来取各个列表的项值
    第四种用法, 多个列表组合,也可以每个列表都用一个变量来取项值

//---------------------------------------------------------

十七、 CMake的条件判断指令
CMake 的条件判断指令如下:

if(<条件>)
<命令>
elseif(<条件>)
<命令>
else()
<命令>
endif()





其中 elseif可按实际情况有或者无,也可以有多个,else则按实际情况可有可无。

多个条件组合时, 顺序优先级为:
a. 有括号的,先检测括号内
b. 然后是一元检测,如EXISTS, COMMAND, DEFINED
c. 接着是二元检测,如EQUAL, LESS, LESS_EQUAL, GREATER, GREATER_EQUAL, STREQUAL, STRLESS,
d. 最后是布尔操作,首先是NOT,然后是AND, 最后才到OR



  1. 条件的类型:
    a. if(<常量>)
    真的常量有: 1, ON, YES, TRUE, Y, 非零数
    假的常量有:0, OFF, NO, FALSE, N, IGNORE, NOTFOUND,空字符串等


    b. if(<变量|字符串>)
    检测变量或字符串的值是否为真,这里要注意: 宏的参数不是变量

    c. if(NOT <条件>), if(<条件1> AND <条件2>), if(<条件1> OR <条件2>)
    分别为取反,与操作,或操作

    d. if(COMMAND 命令名)
    判断给的命令名是否为可调用的命令、宏、函数

    e. if(POLICY 策略ID)
    给定的策略是否存在

    f. if(TARGET <目标名>)
    检测目标是否存在,这个目标是由add_executable, add_library 或add_custom_target创建

    g. if(TEST <测试名>)
    检测测试名是否存在,由 add_test创建

    h. if(EXISTS 文件或目录)
    文件或目录存在则为真,要用全路径

    i. if(文件1 IS_NEWER_THAN 文件2)
    判断文件1是否比文件2新,要用全路径

    g. if(IS_DIRECTORY <目录>)
    给定的全路径目录是否为目录

    k. if(IS_SYMLINK <文件名>)
    给定的全路径文件名是否为符号连接文件

    l. if(IS_ABSOLUTE 路径)
    给定的路径是否为绝对路径

    m. if(<变量|字符串> MATCHES 正则表达式)
    是否匹配给定的正则表达式

    n. if(<变量|字符串> LESS<变量|字符串>)
    数字比较,LESS 为小于,GREATER为大于, EQUAL为等于,

  2. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.17) # 本地安装的是3.18
    project(IfElse)
    add_executable(main main.cpp)


    if(ON)
    message(“ON”)
    endif()

    if(NOT IGNORE)
    message(“NOT IGNORE”)
    endif()

    if(ON AND NO)
    message(“ON AND NO”)
    elseif(OFF OR 0)
    message(“OFF OR 0”)
    else()
    message(“else()”)
    endif()





    if(NOT “var”)
    message(“NOT var”)
    endif()

    set(var1 “var1”)
    if(var1)
    message(“var1”)
    endif()


    function(func1)
    message(“func1”)
    endfunction()

    if(COMMAND func1)
    message(“COMMAND func1”)
    endif()

    if(NOT COMMAND func2)
    message(“NOT COMMAND func2”)
    endif()

    if(POLICY CMP0054)
    message(“POLICY CMP0054”)
    endif()

    if(TARGET main)
    message(“TARGET main”)
    endif()

    if(EXISTS E:/tmp)
    message(“EXISTS E:/tmp”)
    endif()

    判断常量
    常量取反
    与操作
    elseif 分支,或操作
    else 分支
    字符串常量,会认为是假,取反则为真




    变量判断,已定义一个值,为真
    定义函数
    判断函数是否存在

    指定策略是否存在

    目标是否存在

    指定路径(目录或文件)是否存在

//---------------------------------------------------------

十八、 CMake的while循环指令

CMake 的while 循环指令使用方法:

while(<条件>)
<命令>
endwhile()

当<条件>为真时, 执行<命令>。<条件>的语法形式,跟if条件判断指令的形式一样

  1. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.17)
    project(While)

    set(var 10)
    while(var GREATER 0)
    message(“var = v a r " ) m a t h ( E X P R v a r " {var}") math(EXPR var " var")math(EXPRvar"{var} - 1”)

     if(var LESS 3)
     	message("break()")
     	break()
     endif()
     
     if(var EQUAL 5)
     		message("continue()")
     	continue()
     endif()
    

    endwhile()

    设置变量var 为10
    while 指令,只要var大于0就执行循环区间里的命令(while 到 endwhile之间的命令)
    输出var当前的值
    var的值减1
    如果var的值小于3, 则退出循环,通过调用break指令退出



    如果var的值等于5, 则跳过此轮循环,去执行新的一轮循环,通过continus指令实现

    // 执行cmake 查看输出结果

//---------------------------------------------------------

十九、CMake的math指令

CMake的math 指令的使用方法:
math(EXPR “” [OUTPUT_FORMAT ])

计算expression 表达式的值, 表达式中如要使用16进制的数,此数要以0x开头,得到值后把它赋给变量
variable。结果必须为64位的有符号整数。

支持的运算符有:
+、-、*、/、%、|、&、^、~、<<、>>、()

format: 用于设置输出的格式,支持两种格式,DECIMAL 和 HEXADECIMAL。

DECIMAL: 十进制的输出方式。
HEXADECIMAL: 十六进制的输出方式。

  1. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.17)
    project(Math)

    math(EXPR val1 “100 * 0xA”)
    math(EXPR val2 “100 * 0xA” OUTPUT_FORMAT DECIMAL)
    math(EXPR val3 “100 * 0xA” OUTPUT_FORMAT HEXADECIMAL)

    message(“val1 = ${val1}”)
    message(“val2 = ${val2}”)
    message(“val3 = ${val3}”)

    计算100* 0xA ,默认格式输出到val1
    计算100* 0xA ,指定十进制格式输出到val2
    计算100* 0xA ,指定十六进制格式输出到val3

    分别输出val1, val2, val3

//---------------------------------------------------------

二十、CMake列表的操作之一(读取列表)

CMake 中的列表是以分号(😉 分隔的一组字符串,可以通过set指令来定义。

set(var a b c d e),这就定义了一个列表a;b;c;d;e, 注意set(var “abcde”)只是定义了一个字符串,并不是列表。

列表(属性) 读取包括以下几个方面:
a. 列表的长度
list(LENGTH <列表><输出变量>)
把列表的长度读取到输出变量


b. 读取列表中的元素
list(GET <元素索引> [<元素索引>…] 输出变量)
元素索引可正可负,当为正数时,从列表开头开始索引,0开始为第1个元素;当为负数时,从列表结尾开始索引,-1为
最后一个元素,读取的元素值放到输出变量


c. 列表练级成字符串
list(JOIN <列表> <连接符> <输出变量>)
把列表的元素连接成一个字符串,元素之间以指定的连接符分隔。

d.读取子列表
list(SUBLIST <列表> <开始索引> <子列表个数> 输出变量)
从列表的开始索引起读取指定个数作为子列表返回给输出变量。如果子列表个数为-1, 或开始索引+子列表个数大于列表的
元素数,则从开始索引起到列表结尾一起作为子列表返回 给输出变量。


  1. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.17)
    project(List)

    set(var a b c d e)
    message(“var = ${var}”)

    list(LENGTH var varlen)
    message(“varlen = ${varlen}”)

    list(GET var 1 3 -1 varElems)
    message(“varElems = ${varElems}”)

    list(JOIN var “–>” varJoin)
    message(“varJoin = ${varJoin}”)

    list(SUBLIST var 1 2 varSub1)
    list(SUBLIST var 1 -1 varSub2)
    list(SUBLIST var 1 10 varSub3)

    message(“varSub1 = ${varSub1}”)
    message(“varSub2 = ${varSub2}”)
    message(“varSub3 = ${varSub3}”)

    定义一个列表
    获取列表长度
    取得列表第2、4 和最后一个元素

    用–> 把列表元素连接成字符串
    获得子列表,从第2个元素开始,共2个元素
    获得子列表,从第2个元素开始,到列表结尾

    // 执行cmake

//---------------------------------------------------------

二十一、CMake列表的操作之二(搜索列表)

CMake 中对列表的搜索可以用下面的指令:
list(FIND<列表> <值> <输出变量>)

在列表中搜索给定的值,如果有匹配的,对应索引值存入输出变量,如果有多个匹配,只取第一个匹配的索引,如果没有匹配的,
输出变量为-1。

  1. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.16)
    project(List)

    set(var a b c d e b f)
    message(“var = ${var}”)

    list(FIND var “d” find1)
    message(“find1 = ${find1}”)

    list(FIND var b find2)
    message(“find2 = ${find2}”)

    list(FIND var g find3)
    message(“find3 = ${find3}”)

    定义列表
    查找d
    查找b, 可以不用双引号,有两个匹配,只取第一个
    查找g, 没有匹配的值


    // 执行cmake

//---------------------------------------------------------

二十二、CMake列表的操作之三(修改列表)

CMake 中对列表的修改有增、删、改:

a. 增加元素:
list(APPEND […]) —>在列表末尾添加给定元素
list(PREPEND […]) —>在列表开头添加给定元素
list(INSERT […]) —>在列表指定索引位置插入元素


b. 删除元素
list(REMOVE_ITEM […]) —> 删除指定元素
list(REMOVE_AT […]) —> 删除指定索引位置的元素

list(POP_BACK […]) —> 删除列表末尾的元素,out-var参数没有的话,只删除一个,否则根据out-var
的个数删除相应个数,并把删除的元素赋给out-var

list(POP_FRONT […]) —> 与POP_BACK相似,只是在列表开头删除

list(REMOVE_DUPLICATES ) —> 删除重复的元素,只保留第一次发现的那个

c. 按正则表达式保留或去除元素
list(FILTER {INCLUDE | EXCLUDE} REGEX ) -->INCLUDE的话,匹配regex的元素被保留,
EXCLUDE的话,匹配regex的元素被删除。

  1. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.16)
    project(List)

    set(var a b c d e b f)
    message(“var = ${var}”)

    list(APPEND var aa bb cc b f)
    message(“APPEND ${var}”)

    list(PREPEND var dd ee ff)
    message(“PREPEND ${var}”)

    list(INSERT var 5 gg hh ii)
    message(“INSERT ${var}”)

    list(REMOVE_ITEM var ee ff bb)
    message(“REMOVE_ITEM ${var}”)

    list(REMOVE_AT var 2 3)
    message(“REMOVE_AT ${var}”)

    list(REMOVE_DUPLICATES var)
    message(“REMOVE_DUPLICATES ${var}”)

    list(POP_BACK var)
    message(“POP_BACK ${var}”)

    list(POP_FRONT var var1 var2)
    message(“POP_FRONT ${var}”)
    message(“var1 = ${var1}”)
    message(“var2 = ${var2}”)


    list(FILTER var INCLUDE REGEX “[a-z][a-z]”)
    message(“FILTER INCLUDE ${var}”)

    list(FILTER var INCLUDE REGEX “[a-h][a-h]”)
    message(“FILTER INCLUDE ${var}”)

    定义列表
    列表末尾添加元素
    列表开头添加元素
    插入元素
    移除指定元素
    移除指定索引位置元素
    移除重复元素





    删除末尾一个元素
    删除开头两个元素,并赋值给var1和var2

    保留由两个字符组成的元素
    删除由两个字符组成的元素,并且两个字符是从a到h的

    // 执行cmake

//---------------------------------------------------------

二十三、CMake列表的操作之四(排序列表)

CMake 中对列表的倒序指令为:
list(REVERSE )

CMake中对列表的排序指令为:
list(SORT [COMPARE ][CASE ][ORDER ])

compare 可以指定比较的方式: STRING —> 按字母表顺序,
FILE_BASENAME —> 列表中的元素为路径,按路径的基础名排序,
NATURAL —> 按数字的顺序排序(要3.18版本)

case 指定是否区分大小写: SENSITIVE —>区别大小写,
INSENSITIVE —> 不区别大小写

order指定升序还是降序: ASCENDING —> 升序, DESCENDING —> 降序

  1. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.16)
    project(List)

    set(var a B C d)
    message(“var = ${var}”)

    list(TRANSFORM var APPEND e)
    message(“APPEND – ${var}”)

    list(TRANSFORM var PREPEND F OUTPUT_VARIABLE outvar)
    message(“PREPEND – ${var}”)
    message(“outvar – ${outvar}”)

    list(TRANSFORM var TOUPPER)
    message(“TOUPPER – ${var}”)

    list(TRANSFORM var TOLOWER)
    message(“TOLOWER – ${var}”)

    set(var a b)
    list(APPEND var " c" "d " " e ")
    message(“var = ${var}”)

    list(TRANSFORM var STRIP)
    message(“STRIP – ${var}”)

    set(var a $BOOL:FALSE $BOOL:YES b c)
    message(“var = ${var}”)

    list(TRANSFORM var GENEX_STRIP)
    message(“GENEX_STRIP – ${var}”)

    set(var a b cc dd e f)
    message(“var = ${var}”)

    list(TRANSFORM var REPLACE “[a-z][a-z]” xx)
    message(“REPLACE – ${var}”)

    list(TRANSFORM var TOUPPER AT 0 1 3 4)
    message(“TOUPPER AT – ${var}”)

    list(TRANSFORM var TOLOWER FOR 1 3)
    message(“TOLOWER FOR – ${var}”)

    list(TRANSFORM var TOUPPER REGEX “[a-z][a-z]”)
    message(“TOUPPER REGEX – ${var}”)

    列表倒序
    按字母表顺序排, 不区分大小写,升序,
    按字母表顺序排, 不区分大小写,降序
    按字母表顺序排, 区分大小写,降序
    按字母表顺序排, 默认区分大小写,默认升序,



    按数字顺序排,区分大小写,升序(NATURAL这个选项要3.18才能使用)

    // 执行cmake

//---------------------------------------------------------

二十四、CMake列表的操作之五(转换列表)

CMake 中对列表元素进行转换的指令为:

list(TRANSFORM <列表> <操作> [<选择器>] [OUTPUT_VARIABLE <输出变量>])

所有的操作都是对元素进行操作,如果指定输出变量,则原列表不变,操作后的列表赋
给输出变量。

操作有:
a. list(TRANSFORM <列表> <APPEND|PREPEND><值>…)
在元素的后面或前面添加指定字符

b. list(TRANSFORM <列表> <TOLOWER|TOUPPER>…)
把元素变为小写或大写

c. list(TRANSFORM <列表> STRIP …)
删除元素头尾的空白

d. list(TRANSFORM <列表> GENEX_STRIP …)
如果元素有生成器表达式,则把生成器表达式删除

e. list(TRANSFORM <列表> REPLACE <正则表达式> <替换值>…)
把与指定正则表达式匹配的元素进行替换

选择器有:
a. list(TRANSFORM <列表> <操作> AT <索引> [<索引>…]…)
通过索引指定要变换的元素

b. list(TRANSFORM <列表> <操作> FOR <开始> <结束> [<步长>]…)
通过范围来指定要变换的元素,步长不指定则为1

c. list(TRANSFORM <列表> <操作> REGEX <正则表达式>…)
与正则表达式匹配的元素被变换

  1. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.16)
    project(List)

    set(var a b c d e b f)
    message(“var = ${var}”)

    list(REVERSE var)
    message(“var REVERSE ${var}”)

    set(var A b E d C f)
    list(SORT var COMPARE STRING CASE INSENSITIVE ORDER ASCENDING)
    message(“SORT STRING INSENSITIVE ASCENDING - ${var}”)

    set(var A b E d C f)
    list(SORT var COMPARE STRING CASE INSENSITIVE ORDER DESCENDING)
    message(“SORT STRING INSENSITIVE DESCENDING - ${var}”)

    set(var A b E d C f)
    list(SORT var COMPARE STRING CASE SENSITIVE ORDER DESCENDING)
    message(“SORT STRING SENSITIVE DESCENDING - ${var}”)

    set(var 10.0 1.1 2.1 8.0 2.0 3.1)
    list(SORT var COMPARE STRING)
    message(“SORT STRING - ${var}”)

    set(var 10.0 1.1 2.1 8.0 2.0 3.1)
    list(SORT var COMPARE NATURAL)
    message(“SORT NATURAL - ${var}”)

    定义变量
    每个元素后面都加个e
    每个元素前面加个F,并把变换后的列表赋给outvar
    原列表不变
    元素全变成大写
    所有元素变小写
    设置一个元素前后有空白的元素
    把元素前后的空白删除
    定义一个元素中有生成器表达式的列表
    把列表元素中的生成器表达式删除
    定义一个新列表
    把由两个字母组成的元素替换为XX










    把列表中位于0 1 3 4的元素变为大写
    把列表中从索引1到3(包含)的元素变为小写
    把由两个字母组成的元素变为大写

    // 执行cmake

//---------------------------------------------------------

二十五、CMake的变量监测

CMake 中对变量的监测指令为;
variable_watch(<变量>[<指令>])

如果变量有变化,则会输出该变量变化的相关信息,如果给出指令,则会执行给出的指令,
该指令可以是一个函数,接受的参数有变量、访问方式、变量值、当前文件列表和栈信息。

  1. 编辑cmakelists.txt
    cmake_minimum_required(VERSION 3.16)
    project(VariableWatch)

    set(var 1)
    variable_watch(var)
    set(var 3)

    function(func variable access value current_list_file stack)
    message(“variable = ${variable}”)
    message(“access = ${access}”)
    message(“value = ${value}”)
    message(“current_list_file = ${current_list_file}”)
    message(“stack = ${stack}”)
    endfunction()





    set(var1 “var1”)
    variable_watch(var1 func)
    set(var1 “var1 changed”)

    设置一个变量
    监测该变量
    改变此变量的值
    定义一个函数,参数为: 变量、访问方式、值、当前文件列表、栈
    简单的把参数都输出



    创建一个新的变量,并监测此变量,如有变化,则执行前面定义的func这个函数。
    改变变量的值

    // 执行cmake

//---------------------------------------------------------

二十六、CMake添加项目树之外的子目录

如何把不是项目目录之下的目录添加到项目中去?

  1. 建立 a 、b 两个文件夹 在b 文件夹中嵌套建立 d、e、f 文件夹

  2. 在f目录中,一个头文件,一个源文件,用于生成演示用的静态库
    在f目录中, 创建 CMakeLists.txt 用于生成库目标

    回到f目录的上一层e目录,添加CMakeLists.txt,用于添加f 目录到项目
    同样的回到e目录的上一层d目录,创建CMakeLists.txt
    继续回到d的上一层b, 创建CMakeLists.txt

    sub.h 文件
    #include

    void my_print();

    sub.cpp 文件

    #include “sub.h”

    void my_print(){

     std::cout << "my_print" << std::endl;
    

    }

    cmakelists.txt 文件

    add_library(sub STATIC sub.cpp)

  3. 回到目录a, 添加项目的源代码 main.cpp ,包含静态库的头文件
    main.cpp 文件
    #include “sub.h”

    int main(){
    std::cout << “main called” << std::endl;
    my_print();
    }


  4. 在a目录中, 创建项目的 CMakeLists.txt
    cmake_minimum_required(VERSION 3.16)
    project(a)

    set(CMAKE_CXX_FLAGS “${CMAKE_CXX_FLAGS} -std=c++11 -03”)
    add_subdirectory(…/b b.out)
    include_directories(…/b/d/e/f)
    add_executable(main main.cpp)
    target_link_libraries(main sub)



    设置CMAKE_CXX_FLAGS变量
    添加项目目录外的目录b为子目录,注意,这里第二个参数一个要指定,表示输出的目录
    添加头文件所在目录
    生成的目标
    生成目标依赖的库



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章