自学内容网 自学内容网

CMake学习

本文是对于该视频的学习
重要参考

一、CMake是一款自动生成本地化Makefile的工具

是全自动完成代码编译、链接、打包的整个过程,同时管理不同组件和第三方库。
其编译流程如下图:
在这里插入图片描述

  • cmake就是一行行执行的,并没有跳转。

二、预处理和编译

单用g++:

  • 使用预处理器展开头文件,宏替换,去掉注释:g++ -E ??.cpp -o ??.i
  • 使用编译器对文件进行编译:g++ -S ??.i -o ??.s
  • 使用汇编器对文件进行汇编(生成机器语言):g++ -c ??.s -o ??.o
  • 调用链接器对程序需要调用的库进行链接:g++ ??.o ???.o ........ -o ?
    在这里插入图片描述
    但是实际上是在makefile中保存g++命令,并用make启用调用,而cmake就是自动化生成makefile/sln(windows)。
    在这里插入图片描述
    所以其实可以先用cmake ..命令生成makefile,再用make命令进行g++步骤/使用sln。
    如果想要平台统一命令,就cmake ..,再用cmake --build .生成可执行文件等。

1.预处理:inclue,define,if,ifdef等等

  • 其中include就是打开指定文件,并把文件的所有内容copy到包含的文件中。
  • define就是搜索,直接替换。
  • if预处理语句让我们依据特定条件包含或剔除代码,例如if 0和endif中间的代码,在处理include时,将不会被拷贝。
  • 标记解释
  • 解析
  • 创建抽象语法树(代码的表达),将代码转换成constant data/常数资料/instruction指令/机器语言。
  • 产生代码,cpu执行

2.编译

  • 首先编译器将会把每一个cpp文件编译成一个中继格式obj(一行行编译成二进制,计算机只能识别二进制),所以这些cpp也被称为translation unity/编译单元(但cpp只有在不互相include的时候与编译单元相等)。
  • 在c++中,文件不代表任何指定的东西,只要我告诉编译器,甚至可以实现将.h文件当作cpp来编译。
  • 声明:
    在这里插入图片描述

三、编写CMakeLists构建成项目

添加一个子目录并构建/跳转该子目录

  • cmake可以去找别的cmake:添加一个子目录并构建/跳转该子目录.

类似vs中项目的概念,例如项目1可以生成可执行文件,项目2可以生成静态库;这两个项目都可以处于同一个工程中。

# cmakelists.txt中
# cmake默认为可以找到当前路径的文件(cmakelists当前所在文件夹下)
cmake_minimum_required(VERSION 3.0)
project(工程名)
add_subdirectory (source_dir [binary_dir] [EXCLUDE_FROM_ALL])
。。。

原链接

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

在这里插入图片描述
同理:
在这里插入图片描述

  • 可以使用相对路径法
    ./ 代表当前路径
    …/ 代表上一级路径
#下面写法含义相同
add_subdirectory(lesson1_1)
add_subdirectory(./lesson1_1)
  • 可以使用环境变量$ENV{JAVA_HOME}
    原链接

1.写:

set( ENV{PATH} /home/martink )

2.判断:

if(NOT DEFINED ENV{JAVA_HOME})
# 没有找到JAVA_HOME环境变量
message(FATAL_ERROR "not defined environment variable:JAVA_HOME")  

#不能用if(ENV{JAVA_HOME})形式来判断是否定义 
#但可以用if($ENV{JAVA_HOME})

3.读+$

add_subdirectory(&ENV{PATH}\\source_dir [binary_dir] [EXCLUDE_FROM_ALL])

配置环境

思路还是和在vs中一样:

1.先考虑可见性,能通过编译;

  • 通用方法:
    在这里插入图片描述

  • 使用类似vs中附加包含目录的方法配置编译环境:

# 在cmakelists中
# 将指定目录添加到编译器的头文件搜索路径之下
include_directories ([AFTER|BEFORE] [SYSTEM] dir1 [dir2 ...])
//但是不推荐使用,尤其是在最上层的cmakelists中使用,可能会导致过度依赖。

在这里插入图片描述
在这里插入图片描述
推荐使用:

target_include_directories(<target> [SYSTEM] [AFTER|BEFORE]
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
  • PRIVATE 表示只应添加到 target 。
  • PUBLIC 表示应添加到 target 和任何链接到target 的对象中。
  • INTERFACE 用于不应添加到 target 但应添加到任何链接到 target 的对象。
    在这里插入图片描述

cmake中很多关于target指定的接口:
在这里插入图片描述

2.再考虑链接性之对编译单元的链接,cmake中可以使用add_executable来指定可执行文件所需要的main和cpp文件。

在这里插入图片描述

编译成可执行文件

在这里插入图片描述

  • 有main的函数才会有可执行文件。

在这里插入图片描述

此时如果选用vs的组件包将会自动生成由project(工程名)命名的工程名.sln,并且生成add_executable(可执行程序名 源文件名称)指定的可执行文件可执行程序名.exe

1.普通可执行目标文件

这里源文件只需要加入c/cpp文件,因为copy头文件是在预处理阶段就完成了,即使不在这里也会执行。

add_executable(<可执行文件名称> <源代码文件1> <源代码文件2> ...)
  • 可以通过target_sources继续为可执行文件添加源文件,要求是可执行目标target已经通过add_executable或add_library定义过了。
target_sources(<target>
  <INTERFACE|PUBLIC|PRIVATE> [items1...]
  [<INTERFACE|PUBLIC|PRIVATE> [items2...] ...])
  • 例子
add_executable(target main.cpp)
target_sources(target test.cpp)
target_sources(target 
    PRIVATE
        foo.cpp
        foo_p.cpp
        foo_p.h
    PUBLIC
        foo.h 
)
  • PRIVATE 表示这些源只应添加到 target 。
  • PUBLIC 表示这些源应添加到 target 和任何链接到target 的对象中。
  • INTERFACE 用于不应添加到 target 但应添加到任何链接到 target 的对象的源。

2.导入可执行目标文件

原链接

add_executable(<targetname> IMPORTED [GLOBAL])
  • 将工程外部的可执行目标导入进来,不会有任何构建可执行目标文件的动作。
  • 如果不指定GLOBAL,则可执行目标文件的范围为文件所创建的目录及其子目录;GLOBAL则将范围扩大到整个工程。
  • IMPORTED选项指定后为true;在工程内构建的可执行目标文件的属性IMPORTED为false。

3.别名可执行目标文件

add_executable(<name> ALIAS <target>)
# 例子
add_executable(runtest main.cpp)
add_executable(test_name ALIAS runtest )

4.给变量设置值——set

原链接
用于给下面的变量设置值:

  • 一般变量(Set Normal Variable)
set(<variable> <value>... [PARENT_SCOPE])
  • value:可以有0个,1个或多个,当value值为空时,方法同unset,用于取消设置的值。
  • PARENT_SCOPE(父作用域):不加则修改的是当前CMakeLists的变量的值;
    加了则修改的是上一级目录中CMakeLists的变量的值,而当前CMakeLists中该变量的值不变。
  • function scope(方法作用域):set只会修改作用域内的
set(MY_CUSTOM_VAL 123)
message("根目录的 MY_CUSTOM_VAL = ${MY_CUSTOM_VAL}")

function(func1)
    message("  function func1 修改变量之前 MY_CUSTOM_VAL = ${MY_CUSTOM_VAL}")
    set(MY_CUSTOM_VAL 234)
    message("  function func1 修改变量之后 MY_CUSTOM_VAL = ${MY_CUSTOM_VAL}")
endfunction(func1)

func1()
message("回到根目录")
message("根目录的 MY_CUSTOM_VAL = ${MY_CUSTOM_VAL}")


// 如果是 set(MY_CUSTOM_VAL 234 PARENT_SCOPE)
// 则修改的是上一级作用域变量的值

在这里插入图片描述

  • 缓存变量(Set Cache Entry)
  • 此Cache变量的值在第一次执行cmake …后保存在了一个名为CMakeCache.txt文件中;
  • 删除CMakeCache.txt文件可以删掉此缓存变量。
  • 而第一次执行cmake的代码中,如果需要修改/重新赋值cache变量需要指定FORCE
message("设置Cache变量之前 filePath = ${filePath}")
set(filePath ${PROJECT_SOURCE_DIR}/CMakeLists.txt CACHE FILEPATH " file path")
message("设置Cache变量之后 filePath = ${filePath}")
set(filePath ${PROJECT_SOURCE_DIR}/subsrc/CMakeLists.txt CACHE FILEPATH " file path")
message("设置Cache变量[不带FORCE]之后 filePath = ${filePath}")
set(filePath ${PROJECT_SOURCE_DIR}/cmake/custom.cmake CACHE FILEPATH " file path" FORCE)
message("设置Cache变量[带FORCE]之后 filePath = ${filePath}")

message("回到根目录")
message("根目录的 MY_CUSTOM_VAL = ${MY_CUSTOM_VAL}")

在这里插入图片描述

  • 环境变量(Set Environment Variable)
# 设置环境变量
message("环境变量修改之前 TEXT_ENV_VAL = $ENV{TEXT_ENV_VAL}")
set(ENV{TEXT_ENV_VAL} /usr/local/text_c)
message("环境变量修改之后 TEXT_ENV_VAL = $ENV{TEXT_ENV_VAL}")

message("回到根目录")
message("根目录的 MY_CUSTOM_VAL = ${MY_CUSTOM_VAL}")

在这里插入图片描述

  • 修改环境变量只在当前CMakeList.txt中有效,而没有真正改变操作系统中的环境变量的值;
  • 在子目录的CMakeLists.txt中查看此环境变量的值,也是修改后的值。

四、静态库

  • 工程的cmakelists:
cmake_minimum_required(VERSION 3.0)
project(工程名)
add_subdirectory(包含可生成可执行文件的文件夹名)
add_subdirectory(包含可生成静态库的文件夹名)
。。。。。。

1.生成静态库

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

  • STATIC(静态库)/SHARED(动态库)/MODULE(模块库)
  • 这里的STATIC和SHARED可不设置,通过全局的BUILD_SHARED_LIBS的FALSE或TRUE来指定;如果dll没有export任何信息,则不能使用SHARED,要标识为MODULE。

set_property() / get_property()参考

2.引入静态库

  • 使用静态库(假设已经生成了.lib)的cmakelists:

1.考虑编译:使用声明或者包含头文件来保证可见性
可以使用include_directories或target_include_directories,类似“附加包含目录”作用。

2.考虑链接:

  • link_directories([AFTER|BEFORE] directory1 [directory2 ...])
  • 类似“包含库目录”作用。用于添加目录使链接器能在其查找库。
  • 该命令只适用于在它被调用后创建的target,在add_executalbe之前使用。
  • 这些目录将添加到当前CMakeLists.txt文件的LINK_DIRECTORIES目录属性中。
  • 可能’产生过度依赖,推荐target_link_libraries。
  • add_library(<name> <SHARED|STATIC|MODULE|OBJECT|UNKNOWN> IMPORTED [GLOBAL])
add_library(baz STATIC IMPORTED)
set_target_properties(baz PROPERTIES
IMPORTED_LOCATION_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/libbaz.a
IMPORTED_LOCATION_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/libbazd.a)
  • 这种用法直接导入已经生成的库,cmake不会给这类library添加编译规则。
  • IMPORTED_LOCATION_<CONFIG>,其中的可以是DEBUG/RELEASE或其他。主要是用于标明library在硬盘上的位置。
  • target_link_libraries(<target> <PRIVATE|PUBLIC|INTERFACE> <item>... [<PRIVATE|PUBLIC|INTERFACE> <item>...]...)
  • 用于将一个或多个库链接到特定的目标上,如可执行文件或库。这个命令非常灵活,可以用来指定目标所需的所有依赖项,包括系统库、第三方库以及你自己的其他目标。
  • target:目标名称,可以是通过 add_executable 或 add_library 定义的可执行文件或库的名称。
  • item1, item2, …:可以是以下类型之一:
    1.库的名称(普通库),通常是系统自带的标准库,需要可见(例如使用了link_directories)。
    2.变量:指向库路径或名称的变量,一般用find_package找到的库,例如 ${ARMADILLO_LIBRARIES}。
    3.全路径:指定要链接的库的完整路径,一般是自定义库,例如 /usr/lib/libexample.lib或dll。

五、动态库

1.生成动态库

add_library(<name> [STATIC | SHARED | MODULE]
            [EXCLUDE_FROM_ALL]
            [source1] [source2 ...])
  • STATIC(静态库)/SHARED(动态库)/MODULE(模块库)
  • 这里的STATIC和SHARED可不设置,通过全局的BUILD_SHARED_LIBS的FALSE或TRUE来指定;如果dll没有export任何信息,则不能使用SHARED,要标识为MODULE。
  • 只有dll有export任何信息,即包含关键字_declspec(dllexport),在vs开发环境下才会生成与dll对应的lib文件。

2.添加预处理宏

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

add_definitions(-D<DEFINE>)
//D: 要定义的宏名称。在大多数编译器中,使用 -D 参数可以定义一个宏。
//定义全局宏,但是不推荐使用,因为这可能导致意外的依赖关系和难以调试的问题。

3.引入动态库

参考引入静态库,只不过需要将生成的dll放在程序可见位置,例如.exe目录下,或者系统环境中。

六、Find_package引入外部依赖包

find_package(<PackageName> [<version>] [REQUIRED] [COMPONENTS <components>...])

例如配置OpenCV库时,该库一般会有一个“OpenCVConfig.cmake”。

我们只需要把包含“OpenCVConfig.cmake”文件的路径放到系统变量中,再在CMakeLists.txt中使用find_package(OpenCV REQUIRED),当执行到这句,将自动寻找并且执行该PackageName+Config.cmake,然后将会根据相对路径,寻找到include和lib。
在这里插入图片描述

七、OBJECT库——减少依赖

1.静态

  • 链接普通库:
    在这里插入图片描述
    在这里插入图片描述
  • 对象库的生成:
add_library(<name> OBJECT <sources>...)
  • 使用:
add_library(... $<TARGET_OBJECTS:objlib> ...)
add_executable(... $<TARGET_OBJECTS:objlib> ...)
  • 例子
    如果add链接了common的对象库,则add.lib提供出去里面是包含common库的内容的,能够直接用的。

 在这里插入图片描述
以此类推:
在这里插入图片描述
在这里插入图片描述

  • PUBLIC 表示应添加到 target 和任何链接到target 的对象中,target_include_directories同理。

此时:在这里插入图片描述

2.动态

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
以此类推,add,sub都像common一样做,就可以最终合成calculator的动态库:
在这里插入图片描述


原文地址:https://blog.csdn.net/qq_42491346/article/details/142922105

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!