CMake 軟體打包準則
32 位 – 安全 – CLR – CMake – DKMS – Eclipse – Electron – Free Pascal – GNOME – Go – Haskell – Java – 交叉編譯工具 – KDE – Lisp – Meson – MinGW – 內核模塊 – Node.js – Nonfree – OCaml – Perl – PHP – Python – R – Ruby – Rust – Shell – VCS – Web – Wine – 字體
本文描述了為使用 cmake包 的軟體編寫 PKGBUILD 時需遵循的標準與指引。
摘自 CMake 網站:
- CMake is an open-source, cross-platform family of tools designed to build, test and package software. CMake is used to control the software compilation process using simple platform and compiler independent configuration files, and generate native makefiles and workspaces that can be used in the compiler environment of your choice.
典型用法
典型用法是運行 cmake 命令,然後執行構建命令。cmake 命令通常會設置一些參數,檢查所需的依賴關係並創建構建文件,從而使該軟體可以通過諸如 make 和 ninja 的其他工具進行構建。
CMake 的不良行為
由於其內部的生成構建文件的特性,有時 CMake 可能會以不良方式運行。因此,在為基於 CMake 的軟體編寫 PKGBUILD 時,應注意一些步驟。
CMake 可能會自動覆蓋默認的編譯器優化標誌
使用 -DCMAKE_BUILD_TYPE=Release 選項運行 CMake 是很常見的。一些上游項目甚至無意中在其構建指令中包含此選項,但這會產生不希望的行為。
每個構建類型都會導致 CMake 自動將一組標誌附加到 CFLAGS 和 CXXFLAGS。當使用通用 Release 構建類型時,它會自動附加 -O3[1] 編譯器優化標誌,這將覆蓋當前為 -O2(在 makepkg 配置文件中定義)的默認 Arch Linux 標誌。這是不可取的,因為它偏離了 Arch Linux 的目標優化級別。
關於 -O3 的注意事項
使用 -O3 不能保證軟體的性能會更好,有時甚至會減慢程序的速度。在某些情況下,它也會破壞軟體。Arch Linux 開發者選擇 -O2 作為目標優化級別是經斟酌後的結果,不應進行改動。除非你確切知道自己在做什麼,或者如果上游明確告訴或暗示需要 -O3,否則應該避免在我們的軟體包中使用它。
修復自動優化標誌覆蓋
由於 CMake 的靈活性,以 100% 完全解決這個問題並不簡單。請注意,不存在可適用於所有情況的標準解決方案。本節將討論可能的解決方案和應遵守的一些要點。
默認的 CMake 構建類型是 None,默認情況下它不會向 CFLAGS 和 CXXFLAGS 附加任何標誌,因此只要省略 CMAKE_BUILD_TYPE 就默認使用 None 類型。但請注意,忽略此選項並不能保證解決問題,因為如果命令行中未設置 CMAKE_BUILD_TYPE,許多軟體項目會自動將 CMake 文件中的生成類型設置為 Release 或其他類型。另外請注意,由於 None 構建類型中缺少 NDEBUG 定義,軟體包結果中可能會有對源文件的引用,並導致 makepkg 輸出 WARNING: Package contains reference to $srcdir 警告。
由於默認的 None 構建類型在默認情況下不會向 CFLAGS 和 CXXFLAGS 追加任何標誌,因此也可以使用 -DCMAKE_BUILD_TYPE=None 選項。一般來說,使用 -DCMAKE_BUILD_TYPE=None 選項比省略 CMAKE_BUILD_TYPE 要好,它默認不會附加任何標誌,也很少有軟體會為 None 構建類型設置不需要的標誌。
不幸的是,該問題不是簡單使用 -DCMAKE_BUILD_TYPE=None 就能解決的。當使用 None 構建類型修復 -O3 問題時,可能會遇到另一個問題。很多軟體項目會在 CMake 文件中為 Release 構建類型定義一些必需的編譯器標誌(例如 CMAKE_C_FLAGS_RELEASE 和 CMAKE_CXX_FLAGS_RELEASE CMake 變量)。如果使用 None 構建類型,在沒有這些上游定義標誌的情況下編譯時,此類軟體可能會損壞或出現錯誤行為。為了確定是否缺少某些標誌,需要檢查 CMake 文件,或者可以比較 make VERBOSE=1 對於 None Release 構建類型的輸出。如果 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 中(比如 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 選項將抑制某些警告的輸出,這些警告僅適用於編寫 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 模塊,可以在構建時下載額外的資源和子項目。但在理想情況下,所有資源都應在 sources 數組中指定,並由 makepkg 在構建之前獲取。可以通過 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() 功能時可用。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。