CMake構(gòu)建使用及技巧

      網(wǎng)友投稿 1707 2025-03-31

      CMake是C/C++項(xiàng)目最廣泛使用的構(gòu)建工具,同時(shí)也是一門獨(dú)立的語言,可以編寫分支、循環(huán)、字符串處理等操作。CMake的歷史非常悠久,類似C++11起稱為Modern C++,同樣的3.X版本的CMake稱為Modern CMake?,F(xiàn)代版CMake推薦基于Target進(jìn)行構(gòu)建,Target之間構(gòu)成DAG依賴,這種類似面向?qū)ο?/a>的風(fēng)格使相關(guān)的構(gòu)建選項(xiàng)更加易于控制。

      本文不詳細(xì)講解CMake這門構(gòu)建語言,只寫一些筆者在基于CMake進(jìn)行項(xiàng)目構(gòu)建實(shí)踐中的一些積累。希望讀者對CMake的基本使用有一定的認(rèn)識(shí)。

      推薦學(xué)習(xí)資料:

      cmake-examples, 這個(gè)的樣例和知識(shí)比較新, 建議只學(xué)習(xí)這個(gè)就夠了

      modern cmake, cmake3.x版本被稱為modern,在部分用法上與舊版有區(qū)別

      知識(shí)點(diǎn)

      CMake構(gòu)建使用及技巧

      構(gòu)建命令

      通常情況下, cmake應(yīng)該使用out-of-source編譯, 即創(chuàng)建單獨(dú)的目錄, 在目錄里面編譯, 并生成編譯中間文件和編譯結(jié)果.

      注:慣例是在項(xiàng)目根目錄下創(chuàng)建build目錄并在里面編譯, 也可使用cmake-build-目錄, 跟Clion官方相同.

      傳統(tǒng)cmake的核心使用一共兩個(gè)命令(cmake ..和make):

      cd cmake-build-debug # 切換到構(gòu)建目錄 rm -rf * # 刪除上次構(gòu)建的中間文件和結(jié)果, 默認(rèn)情況下, cmake會(huì)使用上次的構(gòu)建緩存 cmake .. # 生成makefile make # 構(gòu)建 ctest # 如果定義有測試, 執(zhí)行測試,或make test make install # 如果定義有安裝, 執(zhí)行安裝

      現(xiàn)代CMake, 可基于項(xiàng)目源碼目錄構(gòu)建:

      cmake -S . -B cmake-build-debug # -S 指定源碼目錄 -B 指定構(gòu)建目錄, 生成make緩存, 替換cmake .. cmake --build cmake-build-debug --config Debug -- -j8 # 構(gòu)建項(xiàng)目,替換make -j8

      在install后會(huì)在構(gòu)建目錄中生成一個(gè)install_manifest.txt文件, 每一行是安裝的內(nèi)容. 因此, 可以執(zhí)行xargs rm < install_manifest.txt來刪除所有的安裝文件.

      注:無法刪除目錄, 只能刪除文件

      如果為測試程序添加了工作目錄, 直接跑測試程序可能會(huì)有問題, 盡量使用以下方式來跑測試程序.

      ctest # 運(yùn)行所有測試 ctest -N # 列出所有的測試 ctest -R # 執(zhí)行匹配特定名稱的測試 ctest -R --verbose # 冗余模式, 輸出更多信息

      構(gòu)建選項(xiàng)

      使用方式為cmake -DCMAKE_BUILD_TYPE=Release ..

      CMAKE_BUILD_TYPE及對應(yīng)的編譯選項(xiàng):

      Debug: -g

      Release: -O3 -DNDEBUG

      RelWithDebInfo: -O2 -g -DNDEBUG

      MinSizeRel: -Os -DNDEBUG

      -DCMAKE_INSTALL_PREFIX=/path/to/install

      CMAKE_CXX_COMPILER

      CMAKE_C_COMPILER

      設(shè)定set(CMAKE_CXX_STANDARD 11), 默認(rèn)在Linux下為-std=gnu++11

      開啟編譯警告

      警告的開啟通常建議針對具體的target進(jìn)行. 在定義target的CMakeLists.txt文件中, 加入target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra)這一行, 會(huì)開啟此target編譯時(shí)的所有警告.

      開啟安全編譯

      推薦基于具體的target進(jìn)行:

      編譯時(shí):target_compile_options(${PROJECT_NAME} PRIVATE -fstack-protector-strong -fpie).

      鏈接時(shí):target_link_options(Cut2dServer PRIVATE -Wl,-z,relro,-z,now,-z,noexecstack -pie)

      注意:不同安全編譯選項(xiàng)有自己的適用范圍,有的用于靜態(tài)庫、動(dòng)態(tài)庫、可執(zhí)行程序, 有的用于編譯時(shí)、鏈接時(shí),不同場景選項(xiàng)可能不同。另外,安全編譯選項(xiàng)可以會(huì)影響性能,確保知道在做什么。

      靜態(tài)鏈接標(biāo)準(zhǔn)庫

      暫時(shí)沒測試,也不太了解具體。表面看,將標(biāo)準(zhǔn)庫靜態(tài)鏈接到執(zhí)行程序,則不再依賴標(biāo)準(zhǔn)庫

      add_library(static_libstd INTERFACE) if (STATIC_LINK_LIBSTD AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_link_libraries(static_libstd INTERFACE -static-libgcc -static-libstdc++) endif ()

      編譯可重定向靜態(tài)庫

      庫A依賴庫B,將庫B編譯為靜態(tài)庫,將庫A編譯為動(dòng)態(tài)庫,在鏈接靜態(tài)庫時(shí),經(jīng)常會(huì)出現(xiàn)這樣的錯(cuò)誤:relocation R_X86_64_PC32 against symbol xxx can not be used when making a shared object; recompile with -fPIC,根本原因是在編譯靜態(tài)庫時(shí)未加入可重定向選項(xiàng)。

      在CMAKE中有多種添加方式, 在本質(zhì)上就是添加-fpic的編譯選項(xiàng):

      target_compile_options(myLib PRIVATE -fPIC) # 目標(biāo)級 add_compile_options(-fPIC) # 全局級 set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fpic") # 全局級 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpic") set_property(TARGET lib1 PROPERTY POSITION_INDEPENDENT_CODE ON) # 獨(dú)立于編譯器,目標(biāo)級 set(CMAKE_POSITION_INDEPENDENT_CODE ON) # 獨(dú)立于編譯器,全局級,可命令行指定-DCMAKE_POSITION_INDEPENDENT_CODE=ON

      生成庫重命名

      set_target_properties(MyLibStatic PROPERTIES OUTPUT_NAME MyLib)

      適用場景:

      cmake禁止target重名,因此有時(shí)候需要不同target生成的程序名相同(位于不同目錄下)

      希望生成命名相同的動(dòng)態(tài)庫和靜態(tài)庫

      構(gòu)建測試配置

      通常測試文件在項(xiàng)目根目錄下的test目錄, test目錄與src目錄的結(jié)構(gòu)應(yīng)該是相同的, 每個(gè)測試文件與src目錄下的文件應(yīng)該是一一對應(yīng)的. 比如, src目錄下有個(gè)a/b/c.cpp文件, 則test目錄下對應(yīng)的測試文件為a/b/c_test.cpp.

      測試文件的命名: 如果源文件使用HelloWorld.cpp, 則測試文件為HelloWorldTest.cpp; 若源文件為hello_world.cpp, 則測試文件為hello_world_test.cpp

      test目錄類同于常規(guī)的源碼目錄, 假設(shè)有a_test.cpp和b_test.cpp, 每個(gè)測試文件都相當(dāng)于一個(gè)有main函數(shù)的文件, 它在編譯后會(huì)生成獨(dú)立的執(zhí)行程序. 則test/CMakeLists.txt文件內(nèi)容如下:

      function(add_test_executable test_name) add_executable(${test_name} ${test_name}.cpp) target_link_libraries(${test_name} PRIVATE Catch2::Test) # 這里可能需要鏈接主項(xiàng)目生成的動(dòng)態(tài)庫 endfunction() add_test_executable(A_test) add_test_executable(B_test)

      這里使用了cmake函數(shù)定義, 這個(gè)函數(shù)的每次調(diào)用為每個(gè)測試文件生成可執(zhí)行程序.

      項(xiàng)目根目錄下的CMakeLists.txt的內(nèi)容需要添加如下:

      add_subdirectory(test) # 將test視為包含源碼的目錄, 級聯(lián)目錄中的CMakeLists.txt enable_testing() # 開啟測試 add_test(NAME A_test COMMAND test/A_test) # 添加第1個(gè)測試, 測試名為A_test, 執(zhí)行命令為test目錄下編譯生成的A_test程序 add_test(NAME B_test COMMAND test/B_test)

      add_test的本質(zhì), 就只是執(zhí)行命令而已, 換句話說, 命令其實(shí)可以是任何命令, 可以有命令行參數(shù)等.

      當(dāng)測試文件比較多時(shí), 這樣寫可能會(huì)比較麻煩, 可以用文件遍歷和函數(shù)來簡化這一過程.

      額外工具集成使用

      LWYU (link what you use)

      以警告的形式,顯示編譯的每個(gè)target的無用鏈接庫.

      注: 在只能整包使用三方庫的情況下,但項(xiàng)目只使用了三方庫的部分功能,就會(huì)導(dǎo)致項(xiàng)目間接依賴了大量的三方庫的依賴庫。

      注: 目前如何解決,不確定

      cmake -DCMAKE_LINK_WHAT_YOU_USE=TRUE ..

      IWYU (include-what-you-use)

      github: https://github.com/include-what-you-use/include-what-you-use

      安裝: ubuntu: sudo apt install iwyu, 官網(wǎng)有更新的版本

      針對每個(gè)文件分析它的#include情況,會(huì)明確給出哪些頭文件應(yīng)該添加,哪些應(yīng)該刪除,它的完整頭文件使用情況等??梢愿鶕?jù)此來整改。

      cmake "-DCMAKE_CXX_INCLUDE_WHAT_YOU_USE=/usr/bin/iwyu;-Xiwyu;--transitive_includes_only" ..

      Clang Tidy

      基于clang的linter工具,支持非常多的檢查項(xiàng),可以配置.clang-tidy文件,clion默認(rèn)支持

      安裝: sudo apt install clang-tidy-9

      Cppcheck

      官網(wǎng): http://cppcheck.sourceforge.net/

      安裝: sudo apt install cppcheck,官網(wǎng)有更新的版本

      有專用的clion插件: https://plugins.jetbrains.com/plugin/8143-cppcheck

      doxygen文檔

      find_package(Doxygen REQUIRED dot) set(DOXYGEN_BUILTIN_STL_SUPPORT YES) # 支持STL set(DOXYGEN_UML_LOOK YES) set(DOC_SRCS docs headers sources) # set for input list(TRANSFORM DOC_SRCS PREPEND "${PROJECT_SOURCE_DIR}/") doxygen_add_docs(doxygen-docs # 重點(diǎn): 添加目標(biāo)doxygen-docs ${DOC_SRCS} COMMENT "Generate projects pages" )

      三方庫引入

      因?yàn)闅v史原因,C++中對依賴庫的管理較為混亂,在cmake中引入三方庫也較為混亂。大體上有以下幾種方式:

      find_package

      如果依賴的三方庫提供了FetchPKG.cmake, 或者xx-config.cmake文件時(shí),則在三方庫安裝后可通過find_package(PKG REQUIRED)來引入. 這些cmake文件默認(rèn)安裝在/lib/cmake或/share/cmake(具體目錄可能跟操作系統(tǒng)相關(guān)). 在為標(biāo)準(zhǔn)目錄/usr/local/或/usr/時(shí),cmake可以默認(rèn)找到,

      以安裝json-schema-validator庫為例,通過經(jīng)典的cmake .. && make -j && make install安裝后,會(huì)在/usr/local/lib/cmake下安裝cmake文件。

      find_package(nlohmann_json_schema_validator 2.1.0 REQUIRED) # 引入庫

      但如果安裝前綴在非標(biāo)準(zhǔn)目錄,則需要在項(xiàng)目中設(shè)置CMAKE_PREFIX_PATH變量。如安裝到/opt/json-schema-validator前綴,則相應(yīng)的cmake文件位置為/opt/json-schema-validator/lib/cmake中。

      set(CMAKE_PREFIX_PATH "${CMAKE_PREFIX_PATH};/opt/json-schema-validator/lib/cmake") # 設(shè)置cmake搜索路徑, 注意以;分隔 find_package(nlohmann_json_schema_validator 2.1.0 REQUIRED) # 引入庫

      通過find_package具體引入的是環(huán)境變量名,還是Targets, 需要看三方庫的使用指導(dǎo)文檔,或者看前述的.cmake文件。json-schema-validator同時(shí)引入了變量和target,這種方式并不標(biāo)準(zhǔn):

      target_include_directories(target-name PUBLIC ${NLOHMANN_JSON_SCHEMA_VALIDATOR_INCLUDE_DIRS}) target_link_libraries(target-name PUBLIC nlohmann_json_schema_validator)

      通常cmake在安裝后會(huì)自帶非常多的cmake文件,為常見的軟件基本都提供了,像find_package(Threads REQUIRED).

      如果軟件本身沒有提供cmake文件,但提供了pkg-config文件(*.pc),通常位于/lib/pkgconfig或/share/pkgconfig目錄中. pkg-config工具主要用于makefile編寫時(shí)自省已安裝庫的一些信息,如頭文件路徑、庫路徑等,cmake為其提供了通用方案。

      以安裝Clipper為例:

      find_package(PkgConfig) # 確保pkg-config工具已安裝到開發(fā)機(jī) pkg_search_module(polyclipping REQUIRED IMPORTED_TARGET polyclipping) # 通過pkg來搜索庫

      如果pkg文件未安裝在標(biāo)準(zhǔn)目錄,則需要額外設(shè)置環(huán)境變量(除環(huán)境變量外,有多種方式):

      set(ENV{PKG_CONFIG_PATH} "ENV{PKG_CONFIG_PATH}:/opt/oroas/3rds/Clipper/share/pkgconfig") # 在cmake中設(shè)置環(huán)境變量, 注意以:分隔

      如果軟件本身沒有提供cmake文件,也沒有pkg-config文件,則可以手寫cmake文件。通常位于項(xiàng)目根目錄下的cmake目錄中,下面為常用的模板,但有更高級的寫法,即導(dǎo)出為target而非變量。

      以oneTBB庫為例,其在2021版本前未提供完整的cmake使用方案,使用較為麻煩,就自己寫了如下:

      find_path(TBB_INCLUDE_DIR tbb/tbb.h # 查找頭文件路徑 HINTS /usr/ # 以下全部為提示目錄, 可以為非標(biāo)準(zhǔn)目錄 HINTS /usr/include/ HINTS /usr/local/ HINTS /usr/local/include/ ) find_library(TBB_LIBRARY # 查找libtbb.so庫 NAMES tbb libtbb libtbb.so HINTS ${TBB_INCLUDE_DIR}/../lib/ ) find_library(TBB_MALLOC_LIBRARY NAMES tbbmalloc libtbbmalloc libtbbmalloc.so HINTS ${TBB_INCLUDE_DIR}/../lib/ ) FIND_PACKAGE_HANDLE_STANDARD_ARGS(TBB DEFAULT_MSG # 檢查是否找到,找到則設(shè)置TBB_FOUND變量 TBB_INCLUDE_DIR TBB_LIBRARY TBB_MALLOC_LIBRARY ) if (TBB_FOUND) # 聚合找到的多個(gè)庫 SET(TBB_INCLUDE_DIRS ${TBB_INCLUDE_DIR}) SET(TBB_LIBRARIES "${TBB_LIBRARY};${TBB_MALLOC_LIBRARY}") # 以;分隔 endif ()

      以下面方式來引入庫:

      set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) # 設(shè)置模塊路徑 find_package(TBB REQUIRED)

      以下面方式來使用:

      target_include_directories(target-name PUBLIC ${TBB_INCLUDE_DIRS}) target_link_libraries(target-name PUBLIC ${TBB_LIBRARIES})

      add_subdirectory

      對于部分三方庫,在官方使用指導(dǎo)文檔會(huì)提出這種方式,即將整個(gè)庫放置在項(xiàng)目目錄中,再通過add_subdirectory來使用.

      以Catch2為例,如果安裝的話可以通過find_package,如果不安裝的話則通過以下引入:

      add_subdirectory(opensource/catchorg/Catch2) # 假設(shè)在opensource/catchorg目錄

      這種類型的庫大多是header-only庫,對于部署編譯型的庫(如json-schema-validator), 也可以直接用。但對于編譯型庫來說,這種方式會(huì)在編譯項(xiàng)目代碼時(shí)也編譯三方庫,會(huì)增加編譯時(shí)間, 引入額外的警告。

      以如下方式使用:

      target_link_libraries(target-name PRIVATE Catch2::Catch2)

      對于一些極其不標(biāo)準(zhǔn)的庫, 可能沒有CMakeLists.txt文件,這種時(shí)候需要手動(dòng)編寫。(不限制header-only還編譯型)

      以IMQS為例,這種個(gè)人型的庫就一個(gè)頭文件,什么也不包含。直接把頭文件復(fù)制到項(xiàng)目目錄中會(huì)混淆自有文件和庫文件。

      方案:把庫整個(gè)復(fù)制到項(xiàng)目根目錄的3rds目錄,并為其編寫CMakeLists.txt文件。

      add_library(flatbush INTERFACE) target_include_directories(flatbush INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

      以如下方式引入庫:

      add_subdirectory(3rds/IMQS/flatbush)

      以如下方式使用庫:

      target_link_libraries(target-name PRIVATE flatbush)

      FetchContent

      FetchContent為cmake提供了在"配置時(shí)(cmake ..)"自動(dòng)下載(git/svn/url并導(dǎo)入三方庫的能力。FetchContent不是全新的技術(shù),其在本質(zhì)上是通過add_subdirectory來引入庫,如果庫本身不支持這種方式,則無效。

      創(chuàng)建FetchPkgs.cmake文件, 可位于deps目錄:

      FetchContent_Declare( Catch2 GIT_REPOSITORY https://github.com/catchorg/Catch2 GIT_TAG v2.13.2 ) FetchContent_MakeAvailable(Catch2) # 方式一:簡單,自動(dòng), cmake3.14版本前 FetchContent_GetProperties(Catch2) # 方式二:更多控制 string(TOLOWER Catch2 lcName) if (NOT ${lcName}_POPULATED) FetchContent_Populate(Catch2) # 設(shè)置更多屬性 add_subdirectory(${${lcName}_SOURCE_DIR} ${${lcName}_BINARY_DIR}) endif()

      再在主CMakeLists.txt文件中,添加:

      include(FetchContent) include(deps/FetchCatch2.cmake)

      頭文件引入

      對于header-only庫,只有頭文件,不希望侵入式修改加個(gè)CMakeLists.txt文件,則可以直接引入頭文件:

      同樣以IMQS為例,將其放置在項(xiàng)目根目錄下的opensource下:

      target_include_directories(target-name PRIVATE ${CMAKE_SOURCE_DIR}/opensource/IMQS/flatbush)

      C++ EI企業(yè)智能 EI創(chuàng)新孵化Lab 運(yùn)籌優(yōu)化

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:wps如何數(shù)據(jù)驗(yàn)證(wps怎么找數(shù)據(jù)驗(yàn)證)
      下一篇:excel為什么在表格里打字顯示不出來(在表格里打字怎么顯示不出來)
      相關(guān)文章
      亚洲丝袜美腿视频| 区久久AAA片69亚洲| 亚洲国产成人久久精品动漫| 亚洲性日韩精品国产一区二区| 亚洲精品第一国产综合亚AV| 中国亚洲呦女专区| 亚洲人成网站在线播放2019| 亚洲色欲色欲www| 亚洲精品二三区伊人久久| 亚洲国产高清美女在线观看| 91嫩草亚洲精品| 亚洲免费福利视频| 亚洲av无码不卡久久| 国产精品亚洲自在线播放页码| 亚洲fuli在线观看| 四虎亚洲精品高清在线观看| 美女视频黄免费亚洲| 亚洲第一第二第三第四第五第六| 亚洲精品久久无码av片俺去也| 亚洲啪AV永久无码精品放毛片| 亚洲精品无码国产片| 国产精品亚洲精品久久精品| 另类图片亚洲校园小说区| 亚洲精品和日本精品| 久久久久国产成人精品亚洲午夜 | 亚洲另类无码专区首页| 亚洲国产精品无码久久久秋霞1 | 亚洲AV性色在线观看| 国产成人 亚洲欧洲| 亚洲精品天堂成人片?V在线播放| 亚洲日韩在线观看| 国产亚洲精品无码成人| 亚洲国产精品自在线一区二区| 91久久亚洲国产成人精品性色 | 亚洲狠狠ady亚洲精品大秀| 亚洲中文字幕日本无线码| 亚洲国产欧美国产综合一区 | 亚洲成电影在线观看青青| 亚洲综合成人婷婷五月网址| 国产成人精品日本亚洲语音| 亚洲人成网站18禁止一区|