CMake 包指南

出自 ArchWiki
Arch 软件包指南

32 位CLRCMake交叉编译DKMSEclipse 插件Electron字体Free PascalGNOMEGoHaskellJavaKDE内核模块LispMesonMinGWNode.js非自由软件OCamlPerlPHPPythonRRubyRust - 安全性ShellVCSWeb 应用Wine

本文档涵盖了关于为使用 cmake 的软件编写 PKGBUILD 的标准和指南。

来自 CMake 网站

CMake 是一个开源、跨平台的工具族,旨在构建、测试和打包软件。CMake 用于通过简单的平台和编译器独立的配置文件来控制软件编译过程,并生成可在您选择的编译器环境中使用的原生 makefile 和工作区。

典型用法

典型的用法包括运行 cmake 命令,然后在之后执行构建命令。cmake 命令通常设置一些参数,检查所需的依赖项并创建构建文件,使软件准备好被其他工具(如 makeninja)构建。

CMake 不良行为

由于 CMake 生成构建文件自身的内部特性,有时 CMake 的行为可能不尽如人意。因此,在为基于 CMake 的软件编写 PKGBUILD 时,应注意以下步骤。

CMake 可以自动覆盖默认编译器优化标志

非常常见的是看到人们使用 -DCMAKE_BUILD_TYPE=Release 选项运行 CMake。一些上游项目甚至无意中在其构建说明中包含此选项,但这会产生不良行为。

每种构建类型都会导致 CMake 自动将一组标志附加到 CFLAGSCXXFLAGS。当使用常见的 Release 构建类型时,它会自动附加 -O3[1] 编译器优化标志,这会覆盖 Arch Linux 的默认标志,当前默认标志是 -O2 (在 makepkg 配置文件中定义)。这是不希望发生的,因为它偏离了 Arch Linux 目标优化级别。

关于 -O3 的说明

使用 -O3 并不能保证软件的性能会更好,有时甚至会降低程序的速度。在某些情况下,它也可能破坏软件。Arch Linux 开发者选择 -O2 作为目标优化级别是有充分理由的,我们应该坚持使用它。除非您确切知道自己在做什么,或者上游明确告知或暗示需要 -O3,否则我们应避免在软件包中使用它。

修复自动优化标志覆盖

由于 CMake 的灵活性,以 100% 保证的方式修复此问题并非易事。请注意,没有适用于所有情况的标准解决方案。本节将讨论可能的解决方案和一些应注意的点。

默认的 CMake 构建类型是 None,默认情况下它不会将任何标志附加到 CFLAGSCXXFLAGS,因此简单地省略 CMAKE_BUILD_TYPE 选项的使用可能会起作用,因为它将默认为 None。但请注意,省略此选项并不能保证解决问题,因为许多软件项目会在 CMake 文件中自动将构建类型设置为 Release(或其他类型),如果命令行中未设置 CMAKE_BUILD_TYPE。还要注意生成的软件包中可能存在的对源文件的引用,以及由于 None 构建类型中缺少 NDEBUG 定义而导致的 makepkgWARNING: Package contains reference to $srcdir 警告。

由于默认的 None 构建类型默认情况下不会将任何标志附加到 CFLAGSCXXFLAGS,因此使用 -DCMAKE_BUILD_TYPE=None 选项也可以起作用。一般来说,使用 -DCMAKE_BUILD_TYPE=None 选项比省略 CMAKE_BUILD_TYPE 的使用更好。它将涵盖上游在省略 CMAKE_BUILD_TYPE 时自动将构建类型设置为 Release 的情况,默认情况下它不会附加任何标志,并且很少见软件为 None 构建类型设置不需要的标志。

但不幸的是,事情并没有像仅使用 -DCMAKE_BUILD_TYPE=None 来解决这个问题那么简单。当使用 None 构建类型来修复 -O3 问题时,可能会陷入另一个问题。对于许多软件项目来说,在 CMake 文件中为 Release 构建类型定义一些必需的编译器标志是一种常见的做法(例如,像设置 CMAKE_C_FLAGS_RELEASECMAKE_CXX_FLAGS_RELEASE CMake 变量)。如果您使用 None 构建类型,则在没有这些上游定义的标志的情况下编译此类软件可能会崩溃或行为异常。为了确定您是否缺少某些标志,您需要查看 CMake 文件,或者您可以比较 NoneRelease 构建类型的 make VERBOSE=1 的输出。如果 None 构建类型导致遗漏一些上游定义的标志,该怎么办?在这种情况下,您可能处于两种有问题的情况之间,因为如果您使用 Release 构建类型,您可能会使用不需要的 -O3 标志,如果您使用 None 构建类型,您将错过一些必需的上游定义的标志。没有解决这种情况的标准方法,应该根据具体情况进行分析。如果上游为 Release 构建类型定义了 -O2,您可以安全地使用 -DCMAKE_BUILD_TYPE=Release(见下文)。否则,修补 CMake 文件可能是一种解决方案。

一些少数软件项目在其 CMake 文件中为 Release 构建类型硬编码了 -O2,因此如果您确定 -O2 是正在使用的优化级别,则在这种情况下可以安全地设置 -DCMAKE_BUILD_TYPE=Release

警告
  • 某些软件在使用 None 构建类型时可能会崩溃。在使用 None 构建类型时测试软件,以检查它是否会崩溃或失去功能。
  • 某些软件可能仅在 Release 构建类型下工作。您需要进行实验和测试软件。

验证修复

您可以通过启用构建工具的详细模式来验证 CMake 是否正确使用了修复。例如,当使用 make(这是 CMake 默认设置)时,可以通过向其添加 VERBOSE=1 来完成此操作(例如 make VERBOSE=1)。这将使 make 输出正在执行的编译器命令。然后,您可以运行 makepkg 并检查输出,以查看编译器是否正在使用 -D_FORTIFY_SOURCE=2-O2 标志。如果在每个命令行中显示了多个优化标志,则行中的最后一个标志将是编译器使用的标志(这意味着 -O2 需要是最后一个优化标志才能生效)。

前缀和库安装目录

标准的 Arch Linux /usr 前缀可以通过 -DCMAKE_INSTALL_PREFIX=/usr CMake 选项指定。这通常是需要的,因为许多软件默认将文件安装到 /usr/local 前缀中。

一些上游项目将其 CMake 文件设置为将库安装到 /usr/lib64 目录中。如果是这种情况,您可以使用 -DCMAKE_INSTALL_LIBDIR=lib CMake 选项将库安装目录正确设置为 /usr/lib

技巧与窍门

指定目录

自 CMake 3.13 版本以来,有一个 -B 选项可以自动创建构建目录。这避免了通过单独的 mkdir(或 install)命令创建构建目录。-S 选项指定源目录(在其中搜索 CMakeLists.txt 文件),并避免了在执行 cmake 之前 cd 进入源树的需要。这两个选项结合在一起,是指定构建目录和源目录的便捷方法。

由于构建典型的 CMake 项目需要许多选项,因此将它们在构建函数中的本地数组中指定是很方便的。这避免了使用反斜杠将长命令拆分为多行的需要,并允许为每个选项分别包含注释。

PKGBUILD
build() {
  local cmake_options=(
    -B build
    -S $pkgname-$pkgver
    # Any other options required to build a project may follow
    [other_cmake_options]
  )
  cmake "${cmake_options[@]}"
  cmake --build build
}

减少可能不需要的输出

-Wno-dev CMake 选项将抑制一些警告的输出,这些警告仅供编写 CMakeLists.txt 文件的上游项目开发者使用。删除这些警告使 CMake 输出更简洁,并减轻了检查输出的负担。作为一般规则,打包者通常可以安全地忽略这些警告。

从二进制文件中移除不安全的 RPATH 引用

有时,生成的二进制文件可能在 RPATH 中包含不安全的引用。可以通过在构建的软件包上运行 Namcap 来验证这一点,并且这是一个应该修复的安全问题。通过使用 CMAKE_SKIP_INSTALL_RPATH=YES CMAKE_SKIP_RPATH=YES CMake 选项,很有可能解决此问题。您需要对两者进行实验,看看哪个适用于相关软件(不需要同时使用这两个选项)。

获取所有可用的 CMake 选项

为了获取软件项目可用的所有“可见” CMake 选项,请在源树(主 CMakeLists.txt 文件所在的位置)中执行 cmake -LAH

如果您想保存输出以供以后参考,您可以将其重定向到一个文件

$ cmake -LAH >options.txt 2>&1

避免在构建期间下载 FetchContent

CMake 提供了 FetchContent 模块,该模块允许在构建时获取其他资源或子项目。但是,理想情况下,所有源都应在构建之前由 makepkg 获取,因为它们在 sources 数组中指定。这可以通过选项 FETCHCONTENT_SOURCE_DIR_<uppercaseName> 来完成,该选项允许指定文件的路径,否则将获取这些文件。此外,即使您遗漏了任何 FetchContent 声明,也可以使用 FETCHCONTENT_FULLY_DISCONNECTED=ON 来跳过构建期间的所有下载。

示例

假设一个项目获取资源 foo

CMakeLists.txt
FetchContent_Declare(
    foo
    URL https://example.com/foo.tar.gz
    URL_HASH SHA256=cf051bf611a94884ba5e4c2d03932d14e83875c5b77f0fdf55c404cad0e4a6e6
)
FetchContent_MakeAvailable(foo)

然后,不是在构建期间下载它,而是可以将此资源添加到 sources 数组中,并在生成构建文件时声明它

PKGBUILD
sources=(
    ...
    "https://example.com/foo.tar.gz"
)

sha256sums=(
    ...
    "cf051bf611a94884ba5e4c2d03932d14e83875c5b77f0fdf55c404cad0e4a6e6"
)
$ cmake -B build -S "$pkgname-$pkgver" -DFETCHCONTENT_FULLY_DISCONNECTED=ON -DFETCHCONTENT_SOURCE_DIR_FOO="$srcdir/foo"

模板

这是一个 build() 函数的通用模板,它可以用作基于 CMake 的软件包的起点。假设该软件包是基于 C 和 C++ 的,并且它没有在 CMake 文件中为 Release 构建类型定义任何必需的编译器标志。

注意: 只有当项目在 CMakeLists.txt 中使用 enable_testing() 和/或 add_test() 功能时,check 函数才会起作用。
PKGBUILD
build() {
  local cmake_options=(
    -B build
    -S $pkgname-$pkgver
    -W no-dev
    -D CMAKE_BUILD_TYPE=None
    -D CMAKE_INSTALL_PREFIX=/usr
  )
  cmake "${cmake_options[@]}"
  cmake --build build
}

check() {
  local excluded_tests=""
  local ctest_flags=(
    --test-dir build
    # show the stdout and stderr when the test fails
    --output-on-failure
    # execute tests in parallel
    --parallel $(nproc)
    # exclude problematic tests
    --exclude-regex "$excluded_tests"
  )
  ctest "${ctest_flags[@]}"
}

package() {
  DESTDIR="$pkgdir" cmake --install build
}

不要忘记将 cmake 放在 makedepends 中。

示例软件包

参见