written by SJTU-XHW

Reference: CMake Tutor

本人知识浅薄,文章难免有问题或缺漏,欢迎批评指正!

本文目标:在前文(GNU Tutor)初步了解 CMake、C++ 编译过程后,继续深入学习 CMake 在 C++ 构建中的使用;


劝退警告:如果你只想用 IDE 一键编译运行,而不想了解构建和编译细节,那么这篇文章不是为你准备的!

Chapter 0. Make 介绍

对 Make 没兴趣的这章可以跳过

CMake 生成的 Makefile 究竟是什么?语法是怎样?为什么要有它?

由于现在是 21 世纪 20 年代,所以像 make 这样底层的古董就点到为止;;

  • 地位:GNU 计划的一个开源程序;

  • 作用

    1. 制定整个项目的编译规则(利用 Makefile 定义整个编译流程以及各个目标文件与源文件之间的依赖关系),自动化编译步骤,以此提高开发效率;
    2. 二次编译时,仅重新编译你的修改会影响到的部分,从而降低编译的时间;
  • 劣势:为什么上面说 “点到为止”?因为它比较底层,导致抽象层级不高,不能跨平台,每个平台有各自的 make 程序,导致编写 Makefile 较为繁琐;

    例如在 前文 “GNU Tutor” 中提到的 MinGW 编译器中的 make 和 Unix 系统下的 make 就有所差别;

    这一劣势将由 CMake 进行弥补,之后讨论;

0.1 Makefile 的规则

0.1.1 显式规则

  • 定义:显式规则说明了如何生成一个或多个目标文件。这是由 Makefile 的书写者明显指出要生成的文件、文件的依赖文件和生成的命令;

  • 基本语法:

    1
    2
    3
    4
    5
    6
    7
    <target> : <prerequisites>
    <command>

    # And <-- Makefile 的行注释是 “#” 字符

    <pTarget> :
    <command>
    • target:可以是一个object file(*.o),也可以是一个执行文件(最终目标);

    • pTarget:在一个 makefile 中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等;被称为伪目标

      pTarget 部分在运行 make 的时候不会自动调用,外界可以使用 make [pTargetName] 来手动调用,例如:make cleanmake install (如果 Makefile 中定义了的话)

    • prerequisites:生成该 target 所依赖的文件 target

    • command:该 targetmotion 要执行的命令(任意的 shell 命令)

    这指明了文件的依赖关系,即:target 这一个或多个的目标文件依赖于 prerequisites 中的文件,其生成规则定义在 command 中。

    有有同学会问,Make 是怎么做到 上面的第二条作用(仅重新编译修改的部分)的呢?

    很简单,如果 prerequisites 文件的日期要比 targets 文件的日期要新,或者 target 不存在的话,那么,make 就会执行后续定义的 command

  • 示例:这个例子可以不需要了解项目依赖关系;废话少说,上栗子🌰:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    # 假设有一个项目包含 3 个头文件、8个源文件(名称如下):

    # 最终目标 edit
    edit : main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
    cc -o edit main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o

    # 中间目标
    main.o : main.c defs.h
    cc -c main.c
    kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
    command.o : command.c defs.h command.h
    cc -c command.c
    display.o : display.c defs.h buffer.h
    cc -c display.c
    insert.o : insert.c defs.h buffer.h
    cc -c insert.c
    search.o : search.c defs.h buffer.h
    cc -c search.c
    files.o : files.c defs.h buffer.h command.h
    cc -c files.c
    utils.o : utils.c defs.h
    cc -c utils.c

    # 伪目标
    clean :
    rm edit main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
    backup :
    cp edit main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o /opt/
    install :
    # ...
    • 中间目标一般都是 *.o ;最终目标视需求而定,可以是可执行文件,也可以是动/静态链接库;

    • prerequisites 是目标的依赖,一般是 *.h/c/cpp

    • command 是获得目标 / 完成动作的操作;上面的例子中,cc 是 C 的编译器命令,cprm 是 Unix 系统的命令

      1. 必须以制表符(或者说 \t、Tab 键)开头
      2. 默认工作目录为 Makefile 所在目录;
  • (规范)伪目标声明:对于伪目标而言,应该使用 .PHONY 关键字声明,更符合规范;

    例如上面例子的规范写法应该加上:

    1
    .PHONY : clean backup install

    另外,规范来说,请将所有伪目标写在后面,以防读者误认为最终目标;

    • 作用:防止与文件名/最终目标重名,增强可读性;

    • 特点:伪目标可以有依赖,这样相当于是委托调用,例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      .PHONY : cleanall cleanobj cleandiff

      cleanall : cleanobj cleandiff
      rm program

      cleanobj :
      rm *.o

      cleandiff :
      rm *.diff
  • 指定文件查找目录:VPATH 关键字(知道就行);

0.1.2 隐晦规则

作用:make 有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写 Makefile;

  1. 生成 *.o 时,默认将同名的 *.c/cpp 加入依赖中,同时省去相应的编译命令
  2. 当 make 在 Makefile 中找不到与目标同名的 *.c/cpp,那么 make 认为这个目标为伪目标;

因此,利用隐晦规则,最开始的例子可以简化为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
-rm edit $(objects)

注:上面的 “-rm edit” 前面有个 “-”,表示中间如果出现错误,也请继续进行的意思(忽略错误);

这里正好补充一下 Makefile 的变量使用:

  • 定义:name=value;调用:${variableName}
  • 类似 C++ 中的宏替换(后面介绍赋值运算符);

0.1.3 文件指示 和 编译设置

文件指示一共包含 3 个部分

  1. 在一个 Makefile 中引用另一个 Makefile,就像 C 语言中的 #include 一样;
  2. 根据某些情况指定 Makefile 中的有效部分,就像 C 语言中的预编译 #if 一样;
  3. 还有就是定义一个多行的命令,不会深入介绍,感兴趣请查看 教程

这里就介绍第一个:

1
include foo.make *.mk ${bar}

上面这行代码的含义是:包含 foo.make,所有后缀为 mk 的,和在变量 ${bar} 中的文件作为 Makefile;

此外,以 UNIX 为例,make 自动包含了 /usr/local/bin/usr/include 中的文件;

还可以加上 “-” 表示读取错误全部忽略:

1
-include foo.make *.mk ${bar}

编译设置 最常用的是设置 C++ 编译器,可以由修改内置变量完成:

1
2
CC := clang
CXX := clang++

上面的代码含义是:将 C 编译器改为 clang,将 C++ 编译器改为 clang++

0.1.4 赋值运算符

看到上面的例子,大家可能有些疑惑,:== 赋值有区别吗?答案是,有的。

  • =保留计算式的赋值;

  • :=立刻计算结果并覆盖原来的值;

  • ?= 是如果没有被赋值过就赋予等号后面的值;

  • += 是添加等号后面的值;

举个例子体会一下:

1
2
3
4
5
x = foo
y = $(x) bar
x = xyz
all:
@echo "$(y)"
1
2
3
4
5
x := foo
y := $(x) bar
x := xyz
all:
@echo "$(y)"

上面一段 Makefile 运行 make all 会输出:xyz bar

而下面一段会输出:foo bar

0.1.5 小结:Make 的工作方式

  1. 读入所有的 Makefile;

  2. 读入被 include 的其它 Makefile;

  3. 初始化文件中的变量;

  4. 推导隐晦规则,并分析所有规则;

  5. 为所有的目标文件创建依赖关系链;

  6. 根据依赖关系,决定哪些目标要重新生成;

  7. 执行生成命令;

0.2 Make 命令的使用

  • Windows 上如果安装 MinGW 编译器套件,那么应该使用 mingw32-make 来进行;不讨论 MSVC 编译器套件 ~ 用它的大多数是用了微软的 Visual Studio 的套件;
  • Linux 本身就是 GNU 产物,直接安装 make 就能用了;

Chapter 1. CMake 命令使用

众所周知,CMake 可以完成 2 步:① 将 CMakeLists.txt 翻译生成 Makefile;② 代替 make 完成编译构建;

下面我们特指 ① 为 生成,② 为 编译/构建

1.1 生成指令

总体语法:cmake [options] <projectDir>projectDir 需含有 CMakeLists.txt);

下面介绍 [options]

  • 指定生成 Makefile 等中间文件的目录(生成时会将所有文件放入该目录):-B <dirName>dirName 默认当前目录,下同);

  • 指定生成的 Makefile 种类-G <Makefile-Type>

    这里 Unix 类系统(macOS 和 Linux)会默认 "Unix Makefiles"大多数情况下无需更改,因为使用 Unix 内置的 GNU/Make 程序和 GNU/GCC 编译器

    这里对于 Windows 用户很重要,因为 Windows 的 make 工具种类很多,针对您所安装的编译器对应的 make,需要进行合理选择,取值有(有空格,命令行里记得加双引号):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    Visual Studio 17 2022        <--- 使用 VS IDE 的同学不需要指定,IDE 会自动设置
    Visual Studio 16 2019
    Visual Studio 15 2017 [arch]
    Visual Studio 14 2015 [arch]
    Visual Studio 12 2013 [arch]
    Visual Studio 11 2012 [arch]
    Visual Studio 9 2008 [arch]
    Borland Makefiles
    NMake Makefiles <--- 使用 nmake 的同学选这个
    NMake Makefiles JOM
    MSYS Makefiles
    MinGW Makefiles <--- 使用正宗 MinGW 编译器的同学选这个
    Green Hills MULTI
    Unix Makefiles <--- Unix 系统的同学不需要指定,系统会自动设置
    Ninja <--- 使用 Ninja 的同学选这个
    Ninja Multi-Config
    Watcom WMake
    CodeBlocks - MinGW Makefiles <--- 你的 codeblocks 装了什么编译器?
    CodeBlocks - NMake Makefiles
    CodeBlocks - NMake Makefiles JOM
    CodeBlocks - Ninja
    CodeBlocks - Unix Makefiles
    CodeLite - MinGW Makefiles
    CodeLite - NMake Makefiles
    CodeLite - Ninja
    CodeLite - Unix Makefiles
    Eclipse CDT4 - NMake Makefiles
    Eclipse CDT4 - MinGW Makefiles
    Eclipse CDT4 - Ninja
    Eclipse CDT4 - Unix Makefiles
    Kate - MinGW Makefiles
    Kate - NMake Makefiles
    Kate - Ninja
    Kate - Ninja Multi-Config
    Kate - Unix Makefiles
    Sublime Text 2 - MinGW Makefiles
    Sublime Text 2 - NMake Makefiles
    Sublime Text 2 - Ninja
    Sublime Text 2 - Unix Makefiles

1.2 编译构建指令

  • 构建编译(可代替 make):cmake --build <dirName>

    和 Make 一样,可以仅重新编译你的修改会影响到的部分,从而降低编译的时间;

Chapter 2. CMakeList 常见函数

注:CMake 的函数名不区分大小写;

以下函数分为 3 个等级:optional 可选、recommended 推荐书写、necessary 必要;

众所周知,C++ 项目的编译和构建的目标可以是 可执行文件,也可以是静/动态链接库;以下函数如果仅用于某个特定目标,那么会标注 in EXE Projectin LIB Project

  • 【optional】指定 CMake 版本,可选低于版本产生致命错误:

    1
    2
    3
    4
    # ver 建议是当前系统中的 CMake 版本
    CMAKE_MINIMUM_REQUIRED (VERSION <ver> [FATAL_ERROR])
    # 例如我的 CMake 版本是 3.12,并且想让其他人生成时,CMake低于这个版本就报错,那么这么写:
    CMAKE_MINIMUM_REQUIRED (VERSION 3.12 FATAL_ERROR)
  • 【recommended】指定 Project 名称、使用编译器语言(不填写也可自动识别):

    1
    PROJECT (<projectName> [LANGUAGES CXX])

    注意,这里指定项目名称后,变量 ${PROJECT_NAME} 就被设定了,可以在后面使用

  • 【optional】告诉编译器从哪里寻找非标准 C++ 的头文件:

    1
    INCLUDE_DIRECTORIES (<dir>)

    可以理解为:

    Makefile 中的 include 关键字:

    1
    include <dir>/*

    或者 g++ 中的 -I 参数:

    1
    g++ [...] -I<dir> [...]
  • 【optional】向指定目标规定寻找非标准 C++ 头文件路径:

    1
    2
    3
    4
    TARGET_INCLUDE_DIRECTORIES (<targetName> [INTERFACE | PRIVATE | PUBLIC] <dir>)

    # PUBLIC 表示这个项目的外部使用者能看到
    # PRIVATE 则对外部完全隐藏,即不希望调用这个项目目标的使用者知道“这个项目引入了该头文件”

    INCLUDE_DIRECTORIESTARGET_INCLUDE_DIRECTORIES 比较:

    前者是向整个项目(包括子目录和库)添加了寻找头文件的寻找路径;

    后者是向特定目标添加了寻找头文件的寻找路径,同时可以指定暴露级别

    这就和不同范围设置 C++ 标准异曲同工

    1
    2
    3
    SET (CMAKE_CXX_STANDARD 11)        # 全局设置 C++ 规范
    # ---------------------------------------------------------------------
    TARGET_COMPILE_FEATURES (<targetName> [...] cxx_std_11) # 设置特定库的规范

    从项目规范性上说,建议使用 TARGET_INCLUDE_DIRECTORY 而非 INCLUDE_DIRECTORY

  • 【necessary in EXE Project】将指定源文件加入构建为 可执行文件 的目标中:

    1
    ADD_EXECUTABLE (<exeName> <sourceFileNames>)

    位于 TARGET_LINK_LIBRARIES 前,在其他函数之后;

    注意,和 Make 的隐晦规则恰好相反,如果这个头文件被某一源文件引入的话,可以省略对应的头文件

  • 【recommended】查找指定目录下的所有源文件,并将文件名存入变量:

    1
    AUX_SOURCE_DIRECTORY (<dir> <variableName>)

    此后就能使用变量:${<variableName>}

  • 【optional】给项目加入子目录(即读取这个目录下的 CMakeLists.txt):

    1
    ADD_SUBDIRECTORY (<dir>)

    书写这个函数后,在主 CMakeLists.txt 中就可以直接指明子目录下生成的目标名,这在导入并链接自己编写的 动/静态链接库 时用的较多

  • 【necessary in LIB Project】将指定源文件加入构建为 链接库 的目标中:

    请大家复习一下什么是 “静态链接库”、什么是 “动态链接库”:

    静态链接库是 运行前、编译时可以链接进入到目标文件中,优点是分发时文件个数少,不依赖外部文件;缺点是修改了静态链接库的内容的话程序整体需要重新编译;

    动态链接库是 运行时才链接到目标文件中,优点是只要 API 不变,修改动态链接库可以单独进行编译,并且节省内存(仅在调用库函数时才加载到内存中);缺点是文件分发数多,不便管理;

    注:C++ 的库的隐含规则——链接库文件名是 “lib” + 库名 + 后缀

    1. 目标为静态链接库(后缀名可能是 *.a*.lib和操作系统、编译器类型都有关系):

      1
      ADD_LIBRARY (<libName> [STATIC] <sourceFileNames>)    # 默认 STATIC
    2. 目标为动态链接库(后缀名可能是 *.so*.dll*.dylib):

      1
      2
      # 动态链接库一般不和主程序一起编译,因为这样还不如用静态链接库
      ADD_LIBRARY (<libName> SHARED <sourceFileNames>)
  • 【optional】告诉编译器从哪里寻找非标准 C++ 的动态链接库一般也同时指定非标准头文件查找目录(include_directories 或 target_include_directories)

    1
    LINK_DIRECTORIES (<dir>)

    可以理解为 g++ 的 -L 参数:

    1
    g++ [...] -L <dir> [...]
  • 【optional】向 可执行文件目标 或 链接库目标 链接一些库

    众所周知,想要链接库,必须要有 头文件、动/静态链接库文件,并且把它们都引入自己的项目中

    1. 要链接的库是自己编写的 / 第三方静态链接库

      1
      2
      3
      4
      5
      6
      # 如果是自己编写的静态链接库,请确保使用了 add_subdirectory 引入该库的 CMakeLists
      # 或者在当前 CMakeLists 中指定编译的库,需要 add_library

      # 如果是第三方静态链接库,则建议引入头文件,使用 include_directory

      TARGET_LINK_LIBRARIES (<targetName> <libName/libFileName>)
    2. 要链接的库是动态链接库

      1
      2
      3
      4
      5
      6
      INCLUDE_DIRECTORIES (<dir>)    # 引入非标准头文件查找目录
      LINK_DIRECTORIES (<dir>) # 引入非标准链接库查找目录

      # 如果最终目标是可执行文件,那么 add_executable 应该写在这里

      TARGET_LINK_LIBRARIES (<targetName> <libName/libFileName>)
    3. 要链接的库是标准 C++ 库(一般这种情况是这个标准 C++ 库不在标准位置,最常见的是官方的 C++ 扩充,例如 Qt 的库):

      1
      2
      3
      4
      5
      6
      # 指定标准库名,如果它在环境变量中,那么就不需要后面的 PATHS 参数
      FIND_PACKAGE (<stdLibDirName> [REQUIRED] [PATHS <dir>])

      # 指定标准库以何种形式链接到目标中
      # PUBLIC 和 PRIVATE 含义和之前的 TARGET_INCLUDE 的含义相同
      TARGET_LINK_LIBRARIES (<targetName> [PUBLIC | PRIVATE] <stdLibDirName::stdLibName>)

Chapter 3. CMakeLists 变量控制

3.1 常用内置变量

1
2
3
4
5
6
7
8
9
10
11
12
${CMAKE_CURRENT_SOURCE_DIR}        # 这是 CMakeLists.txt 所在目录
${PROJECT_NAME} # 项目名称,上面介绍了

${CMAKE_CXX_STANDARD} # 这是 C++ 标准设置,如果要使用新特性,例如 auto 出现在
# C++ 11 中,那么应该指定标准版本为 11,否则会报错
${CMAKE_C_FLAGS}
${CMAKE_CXX_FLAGS} # 这两个变量是传给 gcc/g++ 的编译器参数,
# 一些常用的参数例如 -Wall 警告、-ggdb 调试行号
# 使用的规范是在后面添加,例如:
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -ggdb")

${CMAKE_BUILD_TYPE} # 这是设置编译类型,可以是 Release | Debug

3.2 赋值和使用

CMake 变量赋值函数 SET

1
SET (<variableName> <value> [CACHE] [STRING | BOOL] [Description])

使用直接:${<variableName>}

3.3 编译时宏定义

假设程序中有一个量,不希望其他人知道,但其他代码都可以开源——那么需要实现:仅编译时将这个量传入;假设这个量在源码中以 APP_ID 表示,那么 CMakeLists.txt 应该这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ...

SET (
_APP_ID # 设置 CMakeLists 临时环境变量
CACHE STRING
<Description>
)
# 要求必须传入这个量,否则报错
IF (_APP_ID STREQUAL "")
MESSAGE (SEND_ERROR <prompt>)
ENDIF ()

# ...

# 编译时宏定义,相当于编译前加入了:#define APP_ID <值>
TARGET_COMPILE_DEFINITIONS (<targetName> PRIVATE APP_ID="${_APP_ID}")

这是传入临时值的方法是 cmake 的 -D<env=value> 参数,例如上面的生成指令应该这么写:

1
cmake -D_APP_ID="XXX" <Dir>

Chapter 4. CMakeLists 常见模板

下面模板的项目依赖极其简单,但不能照抄,需要根据项目实际依赖情况进行调整;

4.1 普通 C++ 项目

项目结构如下:

1
2
3
4
5
6
.    # C++ 标准 11
|
|--- main.cpp
|--- App.h
|--- App.cpp
|--- CMakeLists.txt

模板:

1
2
3
4
5
CMAKE_MINIMUM_REQUIRED (VERSION 3.12)
PROJECT (myApp)
SET (CMAKE_CXX_STANDARD 11)

ADD_EXECUTABLE (${PROJECT_NAME} main.cpp App.cpp)

4.2 多个目录的 C++ 项目

多个目录可以考虑使用静态链接库,下面展示一种自定义静态链接库的 CMakeLists.txt 写法

项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
.    # 这个项目需要 Debug 模式构建,并且增添编译参数 “-Wall” 和 “-ggdb”
|
|--- main.cpp
|--- App.h
|--- App.cpp
|--- CMakeLists.txt
|--- src/ # 这个目录需要编译为静态链接库并同时链接到主程序
|
|--- Manager.h
|--- Manager.cpp
|--- CMakeLists.txt

在项目根目录下的 CMakeLists.txt:

1
2
3
4
5
6
7
8
9
10
11
12
13
CMAKE_MINIMUM_REQUIRED (VERSION 3.12)
PROJECT (myApp)

SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAG} -Wall -ggdb")
SET (CMAKE_BUILD_TYPE Debug)

AUX_SOURCE_DIRECTORY (. MAIN_SRC)

ADD_SUBDIRECTORY (src/)

ADD_EXECUTABLE (${PROJECT_NAME} ${MAIN_SRC})

TARGET_LINK_LIBRARIES (${PROJECT_NAME} myLib)

src/ 目录下的 CMakeLists.txt:

1
2
3
AUX_SOURCE_DIRECTORY (. LIB_SRC)

ADD_LIBRARY (myLib ${LIB_SRC}) # 默认静态链接库

4.3 使用动态链接库的 C++ 项目

项目结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.    # 项目使用 C++ 11 规范
|
|--- main.cpp
|--- App.cpp
|--- App.h
|--- CMakeLists.txt
|--- core/ # 这个 core 目录要求链接到主程序
|
|--- lib/
| |
| |--- libManager.so
|
|--- include/
|
|--- Manager.h

项目根目录下的 CMakeLists.txt 写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CMAKE_MINIMUM_REQUIRED (VERSION 3.12)
PROJECT (myApp)
SET (CMAKE_CXX_STANDARD 11)

AUX_SOURCE_DIRECTORY (. SRC)

LINK_DIRECTORIES (${CMAKE_CURRENT_SOURCE_DIR}/core/lib/)

ADD_EXECUTABLE (${PROJECT_NAME} ${SRC})

TARGET_INCLUDE_DIRECTORIES (
${PROJECT_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/core/include/
)

TARGET_LINK_LIBRARIES (${PROJECT_NAME} libManager.so)

或者不用 LINK_DIRECTORIES,直接改为单独设置库的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CMAKE_MINIMUM_REQUIRED (VERSION 3.12)
PROJECT (myApp)
SET (CMAKE_CXX_STANDARD 11)

AUX_SOURCE_DIRECTORY (. SRC)

ADD_EXECUTABLE (${PROJECT_NAME} ${SRC})

ADD_LIBRARY (Manager STATIC IMPORTED)
SET_TARGET_PROPERTIES (Manager
PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/core/lib/libManager.so
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}/core/include/
)

TARGET_LINK_LIBRARIES (${PROJECT_NAME} Manager)