当前位置: 首页 > 工具软件 > Vala_CMake > 使用案例 >

CMake基本语法(一)

方弘
2023-12-01

1.什么是CMake?

  • 是一种跨平台Make工具。
  • 写一个CMakeList.txt与平台无关的文件来定制整个编译流程。
  • 然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。
  • CMake是Qt6的默认构建系统,之前是qmake。

2.流程

  1. 写CMake配置文件CMakeList.txt。
  2. 执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile(ccmake 和 cmake 的区别在于前者提供了一个交互式的界面)。其中, PATH 是 CMakeLists.txt 所在的目录。
  3. 使用make命令进行编译。

2.1 写好源文件

2.2 编写CMakeList.txt文件

  • 需要将CMakeList.txt保存在与源文件同个目录下
# 写明版本号
cmake_minimum_required(VERSION 3.10)

# 设置项目名和版本号
project(Tutorial)

# 生成可执行文件
add_executable(Demo main.c)

语法由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。

  1. cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本;
  2. project:参数值是 Demo1,该命令表示项目的名称是 Demo1 。
  3. add_executable:将名为 main.cc 的源文件编译成一个名称为 Demo 的可执行文件。

2.3 编译

  • 执行cmake .,得到Makefile。
  • 执行make命令编译得到源码的可执行文件。

2.4 同一个文件夹下多个源文件的编译

比如一个文件夹下有main.c,function.c,和function.h可以这样写

# 写明版本号
cmake_minimum_required(VERSION 3.10)

# 设置项目名
project(Tutorial)

# 生成可执行文件
add_executable(Tutorial main.c function.c)

当一个文件夹下的源文件很多时,使用aux_source_directory 命令,将指定目录下的所有源文件名存到指定的变量名。

# <dir> 文件夹路径;<variable>变量名
aux_source_directory(<dir> <variable>)
# 写明版本号
cmake_minimum_required(VERSION 3.10)

# 设置项目名
project(Tutorial)

# 查找当前目录下所有源文件名,并将其存储到	DIR_CURR中
aux_source_directory(. DIR_CURR)

# 生成可执行文件
add_executable(Tutorial ${DIR_CURR})

2.5 多个文件夹下有多个源文件

如当前目录下为main.c,当前目录下有目录fun,fun目录有文件function.c,function.h。

这种情况下需要将fun文件夹下的源文件编译成为静态库,再由main.c调用。

# 写明版本号
cmake_minimum_required(VERSION 3.10)

# 设置项目名
project(Tutorial)

# 查找当前目录下所有源文件名,并将其存储到	DIR_CURR中
aux_source_directory(. DIR_CURR)

# 添加子目录fun,同时将子目录下的CMakeLists.txt文件执行
add_subdirectory(fun)

# 生成可执行文件
add_executable(Tutorial ${DIR_CURR})

# 添加链接库,指明可执行文件 main 需要连接一个名为 function 的链接库
target_link_libraries(Tutorial function)

子目录fun下的CMakeLists.txt文件内容:

# 查找当前目录下所有源文件名,并将其存储到	DIR_CURR中
aux_source_directory(. DIR_FUN)

# 生成链接库,将 fun 目录中的源文件编译为静态链接库。
add_library(function ${DIR_FUN})

3.cmake:configure_file指令

configure_file 指令通过读取输入文件中的内容,将 CMakeLists.txt 文件中的变量转变为 C/C++ 中可识别的宏定义,然后存入另一个文件中。

configure_file(<input> <output>
               [NO_SOURCE_PERMISSIONS | USE_SOURCE_PERMISSIONS |
                FILE_PERMISSIONS <permissions>...]
               [COPYONLY] [ESCAPE_QUOTES] [@ONLY]
               [NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])

输入文件input为xxx-config.h.in,输出文件output为xxx-config.h,@ONLY:在 文件中只使用 @VAR@ 的方式获取变量值,不适用 ${VAR} 的方式;

3.1 xxx-config.in语法

1. 判断变量VAR是否定义

#cmakedefine VAR

如果 CMakeLists.txt 文件中,定义了变量 VAR,那么在转化出来的文件中就会存在 #define VAR 的语句。否则,在文件中就会显示 /*undef VAR*/。然后,在源码中使用 #ifdef 语句进行使用——#ifdef VAR。

2. 判断选项是否开启
#cmakedefine01 VAR

CMakelists.txt 中的 option(VAR …) 用于开关操作,并且可以使用 cmake -DVAR=ON/OFF … 修改其变量值。上述指令根据 CMakeLists.txt 中 VAR 的值为 ON 或 OFF,将其转换为 #define VAR 1 或 #define VAR 0。然后在源码中使用 #if 进行引用——#if VAR。

3. 获取变量值
#cmakedefine VAR @VAR@
或
#cmakedefine VAR ${VAR}
或
#define SELF_DEFINE_MACRO_NAME @VAR@

CMakeLists.txt 文件中,变量 VAR 多用于定义某些信息,比如版本号,作者,项目描述,调试等级等。然后在源码中输出这些值到固定位置,起到提示的作用。

如果想要在命令行中修改通过 set() 自定义变量的值,那么需要在定义变量时指定 CACHE 参数。比如:

set(DEBUG_LDEVL 1 CACHE STRING "set debug level")

便可以使用cmake -DDEBUG_LEVEL=4修改它的值。

  • 使用 configure_file() 可以省去很多的类似 add_compile_options() 指令。只需在 CMakeLists.txt 文件中定义变量,然后再 xxx-config.h.in 文件中使用 #cmakedefine 进行引用即可。

4. set命令

set命令可以设置普通变量、缓存条目、环境变量

set(<variable> <value>... [PARENT_SCOPE]) #设置普通变量
 
set(<variable> <value>... CACHE <type> <docstring> [FORCE]) #设置缓存条目
 
set(ENV{<variable>} [<value>]) #设置环境变量

…表示可以给变量设置0个或者多个值,当设置多个值时(大于2个),多个值会通过分号连接符连接成一个真实的值赋值给变量,当设置0个值时,实际上是把变量变为未设置状态,相当于调用unset命令。

4.1 设置变量为一个指定的普通值

  1. 单个值:
# 写明版本号
cmake_minimum_required(VERSION 3.10)

# 设置项目名
project(Tutorial)

set (var dd)

message (">>> value = ${var}")

输出:

>>> value = dd
  1. 指定多个值和空:
# 写明版本号
cmake_minimum_required(VERSION 3.10)

# 设置项目名
project(Tutorial)

set (var dd ee ff)

message (">>> value = ${var}")

set (var1)

message (">>> value = ${var1}")
>>> value = dd;ee;ff
>>> value =
  1. set使用选项PARENT_SCOPE,则变量的作用域只能传递到调用它的函数。
  • 调用一个使用set的函数,选项PARENT_SCOPE将变量传递到上一层调用函数
# CMakeLists.txt
cmake_minimum_required (VERSION 3.10.2)
project (set_test)
 
function (test_fn arg1)
    set (normal_var_in_fn ${arg1} PARENT_SCOPE)
    message (">>> in function, value = ${normal_var_in_fn}")
endfunction (test_fn)
 
function (test_fn_parent arg1)
    test_fn (${arg1})
    message (">>> in parent function, value = ${normal_var_in_fn}")
endfunction (test_fn_parent)
 
test_fn_parent (hello)

输出

>>> in function, value = 
>>> in parent function, value = hello
  • 函数内使用PARENT_SCOPE定义变量,函数外使用该变量,找不到变量的定义
# CMakeLists.txt
 
cmake_minimum_required (VERSION 3.10.2)
 
project (set_test)
 
function (test_fn arg1)
    set (normal_var_fn ${arg1} PARENT_SCOPE)
    message (">>> in function, value = ${normal_var_fn}")
endfunction (test_fn)
 
test_fn(hello)
message (">>> in directory, value = ${normal_var_fn}")

输出

>>> in function, value =
>>> in directory, value = 
  • 函数内先使用set,再使用带PARENT_SCOPE定义:选项PARENT_SCOPE定义的变量作用域在上一层函数,当前函数的变量必须使用不带选项PARENT_SCOPE定义。
# CMakeLists.txt
 
cmake_minimum_required (VERSION 3.10.2)
 
project (set_test)
 
function (test_fn arg1)
    set (normal_var_in_fn hello)
    set (normal_var_in_fn ${arg1} PARENT_SCOPE)
    message (">>> in function, value = ${normal_var_in_fn}")
endfunction (test_fn)
 
test_fn (hello)
 

输出

>>> in function, value = hello

4.2. 设置缓存条目

相当于一个全局变量,在同一个CMake工程中都可以用到

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

将缓存条目variable设置为值…,除非用户进行设置或使用了选项FORCE,默认情况下缓存条目的值不会被覆盖。缓存条目可以通过CMAKE的GUI界面的add entry按钮来增加。

缓存条目的主要有以下几类:

  • BOOL:布尔值ON/OFF,CMAKE的GUI界面对此类缓存条目会提供一个复选框。
  • FILEPATH:文件路径,CMAKE的GUI界面对此类缓存条目会提供一个文件选择框。
  • PATH:目录路径,CMAKE的GUI界面对此类缓存条目会提供一个目录选择框。
  • STRING / STRINGS:文本行,CMAKE的GUI界面对此类缓存条目会提供一个文本框(对应STRING)或下拉选择框(对应STRINGS)。
  • INTERNAL:文本行,但是只用于内部,不对外呈现。主要用于运行过程中存储变量,因此使用该type意味着使用FORCE。

缓存条目的几个注意事项
1)如果变量先前未定义或者使用了FORCE选项,则缓存条目会直接被赋值。
2)可以在使用cmake构建的使用通过-D选项来给缓存条目赋值,这样CMakeLists.txt内的set命令只会为缓存条目添加类型。
3)如果变量类型是目录或者文件路径,通过-D选项传入的若只是相对路径,那么set会给这个相对路径前添加当前的工作目录以变成绝对路径(如果已经是绝对路径则不会处理)。

# CMakeLists.txt
cmake_minimum_required (VERSION 3.10.2)
project (set_test)
 
set (cache_entry_val ON OFF CACHE BOOL "choose ON to enable")
message (">>> value = ${cache_entry_val}")
 
set (cache_entry_val2 ON CACHE BOOL "choose ON to enable" FORCE)
message (">>> value2 = ${cache_entry_val2}")
 
set (cache_entry_val3 ON)
set (cache_entry_val3 OFF CACHE BOOL "choose ON to enable")
message (">>> value3 = ${cache_entry_val3}")
 
set (cache_entry_input OFF CACHE BOOL "choose ON to enable")
message (">>> value4 = ${cache_entry_input}")
 
set (mypath "test" CACHE FILEPATH "choose a file path")
message (">>> value5 = ${mypath}")

打印

>>> value = ON;OFF
>>> value2 = ON
>>> value3 = OFF
>>> value4 = OFF
>>> value5 = test

4.3. 设置环境变量

set(ENV{<variable>} [<value>])

命令含义:将环境变量设置为值(注意没有…),接着使用$ENV{}会得到新的值。cmake中的环境变量可以参考:环境变量。

环境变量设置的几个注意事项

  • 1)该命令设置的环境变量只在当前的cmake进程生效,既不会影响调用者的环境变量,也不会影响系统环境变量。
  • 2)如果值为空或者ENV{}后没有参数,则该命令会清除掉当前环境变量的值。
  • 3)后的参数会被忽略。
# CMakeLists.txt
cmake_minimum_required (VERSION 3.10.2)
project (set_test)
 
message (">>> value = $ENV{CMAKE_PREFIX_PATH}")
 
set (ENV{CMAKE_PREFIX_PATH} "/test/sub")
message (">>> value = $ENV{CMAKE_PREFIX_PATH}")
 
set (ENV{CMAKE_PREFIX_PATH})
message (">>> value = $ENV{CMAKE_PREFIX_PATH}")
 
set (ENV{CMAKE_PREFIX_PATH} "/test/top/") 
message (">>> value = $ENV{CMAKE_PREFIX_PATH}")
 
set (ENV{CMAKE_PREFIX_PATH} "") 
message (">>> value = $ENV{CMAKE_PREFIX_PATH}")

打印

>>> value = 
>>> value = /test/sub
>>> value = 
>>> value = /test/top/
>>> value = 

5. Option命令

CMake中的option用于控制编译流程,相当于C语言中的宏条件编译。

5.1 格式

option(<variable> "<help_text>" [value])
  • variable:定义选项名称
  • help_text:说明选项的含义
  • value:定义选项默认状态,一般是OFF或者ON,除去ON之外,其他所有值都为认为是OFF。

可以在执行CMake时,在命令里面直接改选项值

$cmake  .. -D<variable>=OFF

CMake执行完后,选项的值会保存到CMakeCache.txt中。

5.2 与add_definitions搭配在C中使用

CMakeLists.txt

cmake_minimum_required(VERSION 3.0)
project(test)

option(USE_NEW_METHOD "option for debug" OFF)
if(USE_NEW_METHOD)
    add_definitions(-DUSE_NEW_METHOD)
endif()
 
add_executable(test main.cpp)
 
install(TARGETS test RUNTIME DESTINATION bin)

main.cpp

#include <iostream>
#include <vector>
 
//#define USE_NEW_METHOD //不用在这里再开关此行代码了
 
int main(int argc, char **argv) {
    std::cout << "Hello, world!" << std::endl;
    
#ifdef USE_NEW_METHOD
    std::cout << "USE NEW METHOD " << std::endl;
#else
    std::cout << "USE DEFAULT METHOD" << std::endl;
#endif
    
    std::cout << "END" << std::endl;
    
    return 0;
}

指令:cmake -DUSE_NEW_METHOD=ON ...

通过命令行指令,将USE_NEW_METHOD变量置为ON,使得CMakeLists.txt中USE_NEW_METHOD变量开启,从而通过add_definitions函数在C程序中添加定义USE_NEW_METHOD。

对于同一选项,子项目值遵循主项目的定义。也就是说子项目会被主项目覆盖

5.3 添加库

现在我们将向项目中添加一个库,这个库包含计算数字平方根的实现,可执行文件使用这个库,而不是编译器提供的标准平方根函数。

我们把库放在名为 MathFunctions 的子目录中。此目录包含头文件 MathFunctions.h 和源文件 mysqrt.cpp。源文件有一个名为 mysqrt 的函数,它提供了与编译器的 sqrt 函数类似的功能,MathFunctions.h 则是该函数的声明。

在 MathFunctions 目录下创建一个 CMakeLists.txt 文件,并添加以下一行:

add_library(MathFunctions mysqrt.cpp)

CMake 中的 target 有可执行文件和库文件,分别使用 add_executable 和 add_library 命令生成,除了指定生成的可执行文件名/库文件名,还需要指定相关的源文件。

此时文件结构为:

step3/
    build/
    MathFunctions/
        CMakeLists.txt
        MathFunctions.h
        mysqrt.cpp
    CMakeLists.txt
    tutorial.cpp
    TutorialConfig.h.in

为了使用 MathFunctions 这个库,我们将在根目录 CMakeLists.txt 文件中添加一个 add_subdirectory(MathFunctions) 命令指定库所在子目录,该子目录下应包含 CMakeLists.txt 文件和代码文件。

可执行文件要使用库文件,需要能够找到库文件和对应的头文件,可以分别通过 target_link_libraries 和 target_include_directories 来指定。

使用 target_link_libraries 将新的库文件添加到可执行文件中,使用 target_include_directories 将 MathFunctions 添加为头文件目录,添加到 Tutorial 目标上,以便 mysqrt.h 可以被找到。

根目录 CMakeLists.txt 的最后几行如下所示:


# add the MathFunctions library
add_subdirectory(MathFunctions)

# add the executable
add_executable(${PROJECT_NAME} tutorial.cpp)

target_link_libraries(${PROJECT_NAME} PUBLIC MathFunctions)

# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
target_include_directories(${PROJECT_NAME} PUBLIC
                           ${PROJECT_BINARY_DIR}
                           ${PROJECT_SOURCE_DIR}/MathFunctions
                           )

MathFunctions 库就算添加完成了,接下来就是在主函数使用该库中的函数,先在 tutorial.cpp 文件中添加头文件:

#include "MathFunctions.h"

然后可以使用mysqrt函数

const double outputValue = mysqrt(inputValue);

6. 自定义编译选项

样例代码

可以将标准库下的一些库定义为自己写的同名库

7.安装

7.1 定制安装规则

在子目录 fun/CMakeList.txt文件里添加:

install (TARGETS function DESTINATION bin)
install (FILES function.h DESTINATION include)

在根目录的CMakeList.txt的末尾添加:

install (TARGETS Main DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/config.h"
         DESTINATION include)

通过上面的定制,生成的 Main文件和 function 函数库 libfunction.o 文件将会被复制到 /usr/local/bin 中,而 function.h 和生成的 config.h 文件则会被复制到 /usr/local/include 中。我们可以验证一下(顺带一提的是,这里的 /usr/local/ 是默认安装到的根目录,可以通过修改 CMAKE_INSTALL_PREFIX 变量的值来指定这些文件应该拷贝到哪个根目录)。

7.2 测试

CMake 提供了一个称为 CTest 的测试工具。我们要做的只是在项目根目录的 CMakeLists 文件中调用一系列的 add_test 命令。

enable_testing()

# 测试程序是否成功运行
add_test (test_run Demo 5 2)

# 测试帮助信息是否可以正常提示
add_test (test_usage Demo)
set_tests_properties (test_usage
  PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent")

# 测试 5 的平方
add_test (test_5_2 Demo 5 2)

set_tests_properties (test_5_2
 PROPERTIES PASS_REGULAR_EXPRESSION "is 25")

# 测试 10 的 5 次方
add_test (test_10_5 Demo 10 5)

set_tests_properties (test_10_5
 PROPERTIES PASS_REGULAR_EXPRESSION "is 100000")

# 测试 2 的 10 次方
add_test (test_2_10 Demo 2 10)

set_tests_properties (test_2_10
 PROPERTIES PASS_REGULAR_EXPRESSION "is 1024")

7.3 添加版本号

一般在project命令后加上这两行

# 主版本号
set (Demo_VERSION_MAJOR 1) 
# 副版本号
set (Demo_VERSION_MINOR 0)

在config.h.in中预定义,以便在代码中获取版本信息。

#define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@
#define Demo_VERSION_MINOR @Demo_VERSION_MINOR@

在主函数中输出版本号

作者:Linux嵌入式
链接:https://zhuanlan.zhihu.com/p/534439206
来源:知乎

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#include "math/MathFunctions.h"

int main(int argc, char *argv[])
{
    if (argc < 3){
        // print version info
        printf("%s Version %d.%d\n",
            argv[0],
            Demo_VERSION_MAJOR,
            Demo_VERSION_MINOR);
        printf("Usage: %s base exponent \n", argv[0]);
        return 1;
    }
    double base = atof(argv[1]);
    int exponent = atoi(argv[2]);
    
#if defined (HAVE_POW)
    printf("Now we use the standard library. \n");
    double result = pow(base, exponent);
#else
    printf("Now we use our own Math library. \n");
    double result = power(base, exponent);
#endif
    
    printf("%g ^ %d is %g\n", base, exponent, result);
    return 0;
}

7.4 生成安装包

配置生成各种平台上的安装包,包括二进制安装包和源码安装包。为了完成这个任务,我们需要用到 CPack ,它同样也是由 CMake 提供的一个工具,专门用于打包。首先在顶层的 CMakeLists.txt 文件尾部添加下面几行:

# 构建一个 CPack 安装包
# 导入InstallRequiredSystemLibraries,以便之后导入CPack模块
include (InstallRequiredSystemLibraries)
# 设置版权
set (CPACK_RESOURCE_FILE_LICENSE
  "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
# 设置主版本号
set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}")
# 设置副版本号
set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}")
include (CPack)

执行cpack命令

# 生成二进制安装包
$cpack -C CPackConfig.cmake
# 生成源码安装包
$cpack -C CPackSourceConfig.cmake

之后生成3个不同格式的二进制文件
执行其中一个,就可以生成安装界面

8. cmake中添加引用动态链接和静态链接库

参考:知乎-Linux嵌入式 : https://zhuanlan.zhihu.com/p/534439206
set函数:https://blog.csdn.net/sinat_31608641/article/details/123101969

https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s%3F__biz%3DMzg4ODAxMzE2OA%3D%3D%26mid%3D2247484983%26idx%3D1%26sn%3Daf9760f5960492190efeb040596702ad%26chksm%3Dcf80ee28f8f7673e49c7f4cf8a71e248b283545492a1c231b4535db14d6b1738f3746c58982b%26token%3D391208598%26lang%3Dzh_CN%23rd

 类似资料: