0%

exportinstall 命令都有 TARGETSEXPORT 两种参数形式,所以容易混淆:

export (TARGETS ...)
  将给定的 targets 导出到 target export file,不会创建 named export

install(TARGETS ... [EXPORT] ...)
  如果有 EXPORT,除了安装之外,还会顺便从 targets 创建 named export(有两个作用!!这个是我是检查了 cmake_install.cmake 的)

export (EXPORT  ...)
  将 named export 导出到文件,可以指定名字空间

install(EXPORT  ...)
  将 named export 安装到系统,和 export(EXPORT ...) 有相似的参数,同样可以指定名字空间

export 命令用来导出 target export file。install 命令用来安装文件到系统。在 target export file 的场景下,两者都能用于“安装”,但是前者一般是用在安装在临时路径上(参考 Exporting without installation),而后者是用来安装进系统的。

例子:unnamed export VS named export

# unnamed export
export(TARGETS calc
       FILE "${EXPORT_DIR}/CalcTargets.cmake"
       NAMESPACE Calc::
)

#[=[
named export,同时还有 install(TARGETS calc) 的副作用。因而代码相当于:
install(TARGETS calc)
export(TARGETS calc
       FILE "${EXPORT_DIR}/CalcTargets2.cmake"
       NAMESPACE Calc::
)
#]=]
install(TARGETS calc EXPORT CalcTargets)
export(EXPORT CalcTargets
       FILE "${EXPORT_DIR}/CalcTargets2.cmake"
       NAMESPACE Calc::
)

总体结构

一些文件的分析

chapter12/01-full-project/src/calc/CMakeLists.txt

add_library(calc_obj OBJECT calc.cpp)
target_include_directories(calc_obj INTERFACE
  "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
  "$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
set_target_properties(calc_obj PROPERTIES
    PUBLIC_HEADER src/calc/include/calc/calc.h
    POSITION_INDEPENDENT_CODE 1
)

add_library(calc_shared SHARED)
target_link_libraries(calc_shared calc_obj)
add_library(calc_static STATIC)
target_link_libraries(calc_static calc_obj)

一些要点:

  1. target_include_directories 使用生成器表达式为 build 和 install 之后指定了不同的包含路径。导出 target export file 的话这个细节非常重要。
  2. 然后是设置对象的属性。因为我们的库不是 SHARED 类型而是 OBJECT 类型,所以不是默认 PIC 的。需要正确设置 POSITION_INDEPENDENT_CODE 属性(我们实验室项目是在全局编译选项中加入 -fPIC 保证这一点的)。

头文件:template.hpp

#ifndef TEMPLATE_HPP
#define TEMPLATE_HPP

template <typename T>
void println(T t);

#endif

源文件:main.cpp

#include "template.hpp"

int main() {
  println(9);
  println(-7);
}

源文件:instantiation.cpp

在看 Modern CMake for C++ 的时候看到了这段代码:

void RunOutputsCorrectEquations() {
  string expected {"2 + 2 = 4\n3 * 3 = 9\n"};
  stringstream buffer;
  // redirect cout
  auto prevcoutbuf = cout.rdbuf(buffer.rdbuf());
  run();
  auto output = buffer.str();
  // restore original buffer
  cout.rdbuf(prevcoutbuf);
  if (expected != output)
    exit(1);
}

看起来 stdio 的 FILE 也能重定向(但是不能保存之前的关联,因而恢复到 stdout 不能跨平台):

https://stackoverflow.com/a/29154180/

// 重定向
freopen("output.txt", "a+", stdout);

// 恢复到终端输出
freopen("/dev/tty", "w", stdout); /*for gcc, ubuntu*/
// or
freopen("CON", "w", stdout); /*Mingw C++; Windows*/

cmake 的 CONFIG 生成器表达式有两种形式,一种是不接受参数、输出当前的 CONFIG 值,还有一种是判断当前的 CONFIG 是否属于几者之一。在判断 Debug 模式的时候一定要小心,因为使用 Makefile 作为生成器时,不传入 CMAKE_BUILD_TYPE 时使用的 CONFIG 为空。

target_compile_options(YourTarget PRIVATE 
    -Wall 
    $<$<CONFIG:,Debug>:-g3>
)

不过,也可以通过显式设置 CMAKE_BUILD_TYPE 来防范就是了:

if (NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE "Debug")
endif()

这样之后再用生成表达式创建自定义 target 打印 $<CONFIG> 的结果就是 Debug。

这篇用来记录学过但是不容易记住的写法。

CMake language

add_subdirectory(source_dir [binary_dir] [EXCLUDE_FROM_ALL] [SYSTEM])
                             ^^^^^^^^^
e.g.
add_subdirectory(src bin)

这个操作是可以给单个文件设置不同的编译选项,而不是对 target 设置。控制的粒度就更细了。

if(MSVC AND NOT CLANG_CL)
 set_source_files_properties(CompileCommands.cpp PROPERTIES COMPILE_FLAGS -wd4130) # disables C4130: logical operation on address of string constant
endif()

CMake 在 3.12 之后有 -j 选项了,无参数则表示不受限。

常用的两个 generators:

Unix Makefiles 默认使用 1 个线程,支持 -j 选项。

Ninja 也支持 -j 选项,如果不指定则使用默认线程数。可以用 ninja --help 来查看。

ninja --help 2>&1|grep -- -j

docker run 创建容器的时候用 --cpuset-cpus 0-3 可以限制 CPU 在 0-3 这四个核心上,实测 nproc 的结果已经改变了。

docker build 的时候也可以用同样的参数,比如 docker build . --cpuset-cpus 0-3,这能够被正常识别,但是 nproc 的结果没有改变,也不知道是否正常限制了。

\begin{document}
% Answer: [trim={left bottom right top},clip]
% Ex. 1: trim from left edge
\includegraphics[trim={5cm 0 0 0},clip]{example-image-a}
% Ex. 2: trim from right edge
\includegraphics[trim={0 0 5cm 0},clip]{example-image-a}
\end{document}

顺序是左下右上。和 css 中的 margin 的指定是相反的顺序。(css 中是上右下左。css 这个好记忆,就是初始位置在最上面,然后顺时针。)