原文链接:Modern CMake is like inheritance - Kuba Sejdak

In this article:

  1. Modern CMake = targets + properties
  2. Setting properties: include directories, preprocessor, compilation and linking flags
  3. Using (linking with) libraries behaves like inheritance
  4. Example 1: avoiding header dependencies
  5. Example 2: defining header-only libraries
  6. Summary

Modern CMake = targets + properties

Target is a fundamental concept in CMake. It generally represents one “job” of the building process. Most commonly used targets are executables and libraries. Let’s see the syntax for creating and using them:

add_executable(myExecutablemain.cpp
)add_library(libAsourceA.cpp
)add_library(libBsourceB.cppsourceB_impl.cpp
)add_library(libCsourceC.cpp
)target_link_libraries(myExecutable libA)
target_link_libraries(libA libB)
target_link_libraries(libB libC)

This is a classic way of creating targets. Here we have an executable called myExecutable and three libraries libAlibB and libC. For now we know, that in order to build myExecutable we have to link with libA, for building libA we have to link with libB and for building libB we have to link with libC. The last one has no dependencies.

Let’s keep in mind, that those are not the only possible targets that can be defined in CMake. We can create also custom targets, whose only role is to invoke some command:

add_custom_target(firmware.binCOMMAND             ${CMAKE_OBJCOPY} -O binary firmware firmware.binDEPENDS             firmwareWORKING_DIRECTORY   "${CMAKE_BINARY_DIR}/bin"
)

In the snippet above we are defining a custom target named firmware.bin, whose only objective is to create a BIN file from the original executable using GNU objcopy (if you are not familiar with objcopy, then think of this as some conversion of the binary file). Here we explicitly say, that firmware.bin depends on firmware. This is natural, because in order to convert firmware file it must be already built!

Note

We could also skip the line with DEPENDS and add add_dependencies(firmware.bin firmware) instead as a separate statement. But in my opinion, it is less elegant.

Each target can have its own set of properties, that will be used in proper contexts. Here is a list of the most popular ones:

  • compilation flags,
  • linking flags,
  • preprocessor flags,
  • C/C++ standard,
  • include directories.

All of the above properties are stored in the special CMake variables and are automatically used by the build system, when the given target appears in the certain context. For example, include directories property is automatically added to the compilation flags when the target is being compiled. Linking flags are automatically passed to the linker, when this target is being linked. You may say, that all these properties are either CFLAGS or LDFLAGS so there is no need to extract them into individual entities. But we will see in a moment, that this separation can be quite handy in Modern CMake projects.

Setting properties: include directories, preprocessor, compilation and linking flags

Target properties can be set in at least few ways. Before CMake 3.x we could either set raw CMake flags (e.g. CMAKE_C_FLAGSCMAKE_LINKER_FLAGS) or use directory-oriented commands, which set given property for all targets in the current directory and its subdirectories. Modern CMake introduced a new set of target-oriented commands, which allow us to set properties for targets individually.

Below you can find a comparison of the old directory-oriented commands and their new recommended alternatives in Modern CMake:

DIRECTORY-ORIENTED OLD COMMANDS TARGET-ORIENTED NEW COMMANDS
include_directories(<include_path>) target_include_directories(<target> [VISIBILITY] <include_path>)
add_definitions(<preprocessor_flags>) target_compile_definitions(<target> [VISIBILITY] <preprocessor_flags>)
set(CMAKE_CXX_FLAGS <compilation_flags>) target_compile_options(<target> [VISIBILITY] <compilation_flags>)
set(CMAKE_LINKER_FLAGS <linker_flags>) target_link_options(<target> [VISIBILITY] <linker_flags>)

Modern CMake introduced also new keywords, that specify visibility of the given target property: PRIVATEPUBLICINTERFACE. Their meanings are as follows:

  • PRIVATE property is only for the internal usage by the property owner,
  • PUBLIC property is for both internal usage by the property owner and for other targets that use it (link with it),
  • INTERFACE property is only for usage by other libraries.

This is very similar to the access specifiers in a C++ class! Each of the new commands allow visibility specification. If none is provided, then PUBLIC is assumed. Note, that each command can have multiple properties set at different visibility level:

target_include_directories(<target>INTERFACE <include_path_1> <include_path_2> <include_path_3>PUBLIC <include_path_4> <include_path_5> <include_path_6>PRIVATE <include_path_7> <include_path_8> <include_path_9>
)

Note

Targets that don’t produce any binaries (e.g. header-only libraries) can have only INTERFACE properties and can only use INTERFACE linking. This is quite understandable, because there is no “internal” part in such targets, so PRIVATE keyword doesn’t mean anything.

Using (linking with) libraries behaves like inheritance

In order to link libraries together we use the expression:

target_link_libraries(<TARGET_A> <TARGETS...>)

Modern CMake extended this command with the visibility specifier like this:

target_link_libraries(<TARGET_A> [VISIBILITY] <TARGETS...>)

Again, visibility can be one of PRIVATEPUBLIC and INTERFACE. If none is provided then PUBLIC is used by default. But what does it mean in terms of linking?

CMake 3.x introduced a very important “side effect” of linking with targets: linked target is passing all its PUBLIC and INTERFACE properties to the library that it links with. So for example, if libA is linking with libB then libA gets all PUBLIC and INTERFACE properties of libBPRIVATE properties are still not accessible. Another question is: what is the visibility of the newly obtained set of properties (by libA)? Answer is simple: it’s the same as the specifier used in target_link_libraries() for that target. So if libA links as PRIVATE with libB, then all PUBLIC and INTERFACE properties of libB become PRIVATE properties of libA. Similarly, if it links as PUBLIC, then all PUBLIC and INTERFACE properties of libB become PUBLIC in libA. The same goes for INTERFACE linking.

Can you see now, that this looks almost identical as inheritance in C++? Private inheritance makes all public and protected members private in the derived class and public inheritance keeps the visibility unchanged.

In order to understand it better, let’s see some use cases.

Example 1: avoiding header dependencies

Let’s assume the following directory structure:

libA/- include/- libA/- sourceA.h- privateHeaderA1.h- privateHeaderA2.h- sourceA.cpp
libB/- include/- libB/- sourceB.h- submodule/- submodule.h- submodule.cpp- privateHeaderB1.h- privateHeaderB2.h- sourceB.cpp- sourceB_impl.h- sourceB_impl.cpp
libC/- include/- libC/- sourceC.h- privateHeaderC1.h- privateHeaderC2.h- sourceC.cpp
main.cpp

and the following include dependencies in code:

// sourceB.cpp#include "libC/sourceC.h"
#include "submodule.h"// ...
// sourceA.h#include "libB/sourceB.h”// ...
// main.cpp#include "libA/sourceA.h"
#include "libC/sourceC.h"// ...

Also let’s define a rule, that we don’t want any library to be able to use “private” headers of the other libraries: e.g. the code below shouldn’t compile:

// main.cpp#include "privateHeaderC2.h” // should fail as "no such file or directory"

This restriction is a good architectural practice, that can keep the code clean from unwanted dependencies. How to make that compile without a messy config?

First we have to check each target and determine the include paths that it is “creating”. By this I mean which include paths belong to this particular target. Then for each path in a given target we have to decide, if it should be accessible by others (PUBLIC) or not (PRIVATE). Finally we will use new target-oriented commands to set the include properties for each library.

add_executable(myExecutablemain.cpp
)
add_library(libAsourceA.cpp
)
target_include_directories(libAPUBLIC includePRIVATE .                 # "dot" is redundant, because local headers are always available in C/C++.
)
add_library(libBsourceB.cppsubmodule/submodule.cpp
)target_include_directories(libBPUBLIC includePRIVATE . submodule/      # "dot" is redundant, because local headers are always available in C/C++.
)
add_library(libCsourceC.cpp
)target_include_directories(libCPUBLIC includePRIVATE .                 # "dot" is redundant, because local headers are always available in C/C++.
)

All these targets have one thing in common: the only PUBLIC include path is the include directory. This means, that if other libraries call only target_link_libraries() to both get include paths and link with library, then no private header will ever leak unintentionally outside the containing library.

Now its time to properly link the libraries:

target_link_libraries(myExecutablePRIVATE libA libC
)target_link_libraries(libAPUBLIC libB
)target_link_libraries(libBPRIVATE libC
)

Observe the following things:

  1. Executable doesn’t need to specify linking type (because nothing can link with exec), but we define it for consistency.
  2. libA links publicly with libB, because it is using header from libB in its own public header. So it has to provide this path to its clients.
  3. libB link privately with libC, because it is using header from libC only in its internal implementation and its clients shouldn’t even be aware of this.
  4. target_link_libraries() means in Modern CMake two things: use library (get its properties) at compilation stage and link with it at linking stage. Hence maybe a bit better name for it would be target_use_libraries() but it would break the backward compatibility.

Example 2: defining header-only libraries

Sometimes we have to deal with libraries, that don’t produce any binaries. For example, they are just a set of headers that your application needs to include. In such a case they are called a header-only libraries.

An excellent example would be the Catch2 library, which implements the popular C++ testing framework. It consists of only one file catch.hpp which is stored in catch2 directory. First, it would be convenient for us, to still have a CMake target that provides path to that file once someone links with it. Secondly, Catch2 allows some behavior customization via the define directives. For example, we can disable usage of POSIX signals and exceptions in favor of a call to std::terminate(). This is particularly crucial on embedded systems, where we can’t use any of them. So our target should also be able to detect the environment and provide the proper defines accordingly.

In Modern CMake it could be expressed like this:

add_library(catch2 INTERFACE)target_include_directories(catch2INTERFACE catch2
)if (<some_condition_to_detect_embedded_platform>)target_compile_definitions(catch2INTERFACE CATCH_CONFIG_NO_POSIX_SIGNALS CATCH_CONFIG_DISABLE_EXCEPTIONS)
endif ()

Note the usage of INTERFACE keyword. When add_library() contains the INTERFACE specifier, then it tells CMake, that this target doesn’t produce any binary. In such a case it doesn’t contain any source files.

As mentioned before, all properties of the INTERFACE target also have to be marked as INTERFACE. This is understandable, because header-only libraries don’t have any private implementation. Everything is always accessible to the client. If this is still confusing for you then just remember, that INTERFACE target enforces INTERFACE properties. But later linking with such a target can be of any type:

add_library(myTestingModule source.cpp)target_link_libraries(myTestingModulePRIVATE catch2
)

Summary

CMake provides a new target-oriented way of specifying various compiler options and other properties. Once you link with a target, you immediately inherit (obtain) its INTERFACE and PUBLIC properties and make it your own with the access level specified in the linking command. This mechanism resembles C++ inheritance, thus should be easy to understand.

If you are using CMake 3.x and above, then use the this rule as your guide for creating targets:

Library designed and built with Modern CMake should provide its clients with everything they need to compile and use it, without the need to check its internal implementation.

If you find yourself checking what is the path to the missing include in some library then it means that you are doing something wrong:

  • either CMake configuration of that library is bad,
  • or you are trying to access files that are explicitly hidden from you.

透彻理解cmake(含PRIVATE,PUBLIC,INTERFACE的详细解释)相关推荐

  1. 透彻理解BN(Batch Normalization)层

    什么是BN Batch Normalization是2015年论文<Batch Normalization: Accelerating Deep Network Training by Redu ...

  2. CMake 中的 PUBLIC,PRIVATE,INTERFACE

    一.概述 CMake中经常会使用 target_**() 相关命令,target_**() 命令支持通过 PUBLIC,PRIVATE 和 INTERFACE 关键字来控制传播.本文主要介绍下这三个关 ...

  3. cmake / target_** 中的 PUBLIC,PRIVATE,INTERFACE

    一.指令说明 target_include_directories():指定目标包含的头文件路径.官方文档 target_link_libraries():指定目标链接的库.官方文档 target_c ...

  4. Springboot 注解类里面public @interface xxx 什么意思

    @interface 不是interface,是注解类  定义注解 是jdk1.5之后加入的,java没有给它新的关键字,所以就用@interface 这么个东西表示了  这个注解类,就是定义一个可用 ...

  5. c++ private、protect、public、virtual详细说明***

    今天遇到有些同事居然对这些关键字的权限等还不是特别了解,容易弄混淆,今天总结下. private: 私有控制符.这类成员只能被本类中的成员函数和类的友元函数访问. protected: 受保护控制符. ...

  6. 【设计模式自习室】透彻理解单例模式

    前言 <设计模式自习室>系列,顾名思义,本系列文章带你温习常见的设计模式.主要内容有: 该模式的介绍,包括: 引子.意图(大白话解释) 类图.时序图(理论规范) 该模式的代码示例:熟悉该模 ...

  7. C++中类的继承方式的区别以及private public protected 范围

    第一:private,public,protected方法的访问范围. private: 只能由该类中的方法访问,不能被该类的对象访问. protected: 可以被该类中的方法和其友元函数访问,但不 ...

  8. 透彻理解Spring事务设计思想之手写实现

    2019独角兽企业重金招聘Python工程师标准>>> 前言 事务,是描述一组操作的抽象,比如对数据库的一组操作,要么全部成功,要么全部失败.事务具有4个特性:Atomicity(原 ...

  9. linux下cmake命令行,深入理解CMake(1): CMake命令行参数

    初衷 CMake能用来编写跨平台(cross-platform)的构建规则,通过这些规则来调用各个平台的编译器.链接器,生成各个目标(静态库,静态库,或者可执行). 我第一个接触的大型C++项目是Op ...

  10. 【手写系列】透彻理解MyBatis设计思想之手写实现

    前言 MyBatis,曾经给我的感觉是一个很神奇的东西,我们只需要按照规范写好XXXMapper.xml以及XXXMapper.java接口.要知道我们并没有提供XXXMapper.java的实现类, ...

最新文章

  1. oracle 11g 逻辑备库,通过Oracle 11g 逻辑standby实现BI的需求
  2. Java文件操作大全(绝对的经典,值得收藏!)
  3. Winform中跨窗体设置ZedGraph的属性并刷新曲线图
  4. 南京理工大学计算机学院教师信息网,南京理工大学教师信息
  5. BATJ原来是这样玩大数据的!
  6. 雷军:明年2千元以上支持5G的手机,至少发布10款
  7. 51Nod-1010 只包含因子2 3 5的数【打表+排序+二分搜索】
  8. Javashop-B2B2C多店铺系统,Javashop B2C开源电商系统下载
  9. 一篇文章理解Ext4文件系统的目录
  10. 【kali技巧】kali更新系统
  11. 苹果cms vod.html,苹果cms
  12. Excel 6位数字与MD5对照表 100000-999999
  13. 解决vs2008安装问题 Office 2007 Microsoft Visual Studio Web 创作组件 安装失败
  14. 一加8 pro 刷入 kali Hunter
  15. jmeter 之 配置jdbc环境
  16. 云计算平台建设总体技术方案
  17. vue axios请求成功进入catch原因
  18. 高数一上集合与映射思维导图,继续冲冲冲
  19. Nexus 7二代 新Nexus 7 中文版ROOT教程
  20. 体虚分为气虚、血虚、阴虚、阳虚四种类型

热门文章

  1. Android Toast 总结
  2. 使用javascript的“委托”实现attachEvent
  3. 前端项目,css样式获取到了,没能渲染页面
  4. vue组件化开发学习笔记-1-组件化开发思想
  5. 扩展php-bcmath,centos安装PHP扩展(bcmath)
  6. nslookup java_使用JAVA实现nslookup命令
  7. 遥感原理与应用_遥感原理与应用考试题库及答案
  8. 如何通过一个字符串来实例化一个类_Spring官网阅读(一)容器及实例化
  9. python 三维矩阵乘以二维矩阵_python 二维矩阵转三维矩阵示例
  10. Introduction to Computer Networking学习笔记(十五):End to End Delay 端对端延迟