CMake 包指南
本文档涵盖了为使用 cmake 的软件编写 PKGBUILD 的标准和指南。
来自 CMake 网页
- CMake 是一个开源的、跨平台的工具集,旨在构建、测试和打包软件。CMake 用于通过简单的平台和编译器无关的配置文件来控制软件编译过程,并生成可在您选择的编译器环境中使用的本地 makefiles 和工作区。
典型用法
典型用法包括运行 cmake 命令,然后执行构建命令。cmake 命令通常会设置一些参数,检查所需的依赖项,并创建构建文件,使软件可以被 make 和 ninja 等其他工具构建。
CMake 的不期望行为
由于其自身生成构建文件的内部特性,CMake 有时会产生不期望的行为。因此,在为基于 CMake 的软件编写 PKGBUILD 时,应注意某些步骤。
CMake 可以自动覆盖默认的编译器优化标志
人们经常会看到人们使用 -DCMAKE_BUILD_TYPE=Release 选项运行 CMake。一些上游项目甚至无意中将此选项包含在其构建说明中,但这会产生不期望的行为。
每种构建类型都会导致 CMake 自动将一组标志附加到 CFLAGS 和 CXXFLAGS。当使用常见的 Release 构建类型时,它会自动附加 -O3[1] 编译器优化标志,这会覆盖 Arch Linux 当前的默认标志 -O2(在 makepkg 配置文件中定义)。这是不期望的,因为它偏离了 Arch Linux 目标优化级别。
关于 -O3 的注释
使用 -O3 并不能保证软件的性能会更好,有时甚至会减慢程序的速度。在某些情况下,它还可能导致软件崩溃。Arch Linux 开发者选择 -O2 作为目标优化级别是有充分理由的,我们应该坚持下去。除非您确切地知道自己在做什么,或者上游明确告知或暗示需要 -O3,否则我们应该避免在我们的包中使用它。
修复自动优化标志覆盖
由于 CMake 的灵活性,100% 保证地修复这个问题并非易事。请注意,没有标准解决方案可以应用于所有情况。本节将讨论可能的解决方案以及应注意的一些事项。
默认的 CMake 构建类型是 None,它默认不向 CFLAGS 和 CXXFLAGS 附加任何标志,因此简单地省略 CMAKE_BUILD_TYPE 选项可能有效,因为它将默认为 None。但请注意,省略此选项不能保证解决问题,因为许多软件项目会在 CMake 文件中自动将构建类型设置为 Release(或其他类型),如果 CMAKE_BUILD_TYPE 未在命令行中设置。另请注意,由于 None 构建类型中缺少 NDEBUG 定义,可能导致生成的包中引用源文件以及相应的 makepkg 的 WARNING: Package contains reference to $srcdir。
由于默认的 None 构建类型默认不向 CFLAGS 和 CXXFLAGS 附加任何标志,因此使用 -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_RELEASE 和 CMAKE_CXX_FLAGS_RELEASE CMake 变量)。如果使用 None 构建类型,这类软件在没有这些上游定义的标志的情况下编译可能会崩溃或行为异常。为了确定是否缺少某些标志,您需要查看 CMake 文件,或者您可以比较 None 和 Release 构建类型的 make VERBOSE=1 输出。如果 None 构建类型导致某些上游定义的标志丢失,该怎么办?在这种情况下,您可能处于两种有问题的境地之间,因为如果使用 Release 构建类型,您可能会使用不期望的 -O3 标志,而如果使用 None 构建类型,您将缺少一些必需的上游定义的标志。没有标准的方法来解决这种情况,应逐例进行分析。如果上游为 Release 构建类型定义了 -O2,则可以使用 -DCMAKE_BUILD_TYPE=Release(见下文)。否则,修补 CMake 文件可能是一种解决方案。
一些少数软件项目在其 CMake 文件中硬编码了 Release 构建类型的 -O2,因此在这种情况下可以安全地设置 -DCMAKE_BUILD_TYPE=Release,前提是您确信 -O2 是正在使用的优化级别。
- 某些软件在使用
None构建类型时可能会崩溃。在使用None构建类型时测试该软件,以检查它是否会崩溃或失去功能。 - 有些软件可能仅在
Release构建类型下才能正常工作。您需要进行实验并测试该软件。
验证修复
您可以通过启用构建工具的详细模式来验证 CMake 是否正确使用了修复。例如,在使用 make(CMake 的默认选项)时,可以通过向其添加 VERBOSE=1 来实现(例如 make VERBOSE=1)。这将使 make 输出正在执行的编译器命令。然后,您可以运行 makepkg 并检查输出,以查看编译器是否正在使用 -D_FORTIFY_SOURCE=2 和 -O2 标志。如果在每个命令行中显示了多个优化标志,则该行中的最后一个标志将是编译器使用的标志(这意味着 -O2 需要是最后一个优化标志才能生效)。
前缀和库安装目录
可以通过 -DCMAKE_INSTALL_PREFIX=/usr CMake 选项指定标准的 Arch Linux /usr 前缀。这通常是必需的,因为许多软件默认会将文件安装到 /usr/local 前缀。
一些上游项目将其 CMake 文件设置为将库安装到 /usr64 目录。如果是这种情况,您可以使用 -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_FULLY_DISCONNECTED=ON 来跳过构建过程中的所有下载,即使您遗漏了任何 FetchContent 声明。
示例
假设一个项目会下载资源 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。