微吼云上線多路互動(dòng)直播服務(wù) 加速多場景互動(dò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ū)別
構(gòu)建命令
通常情況下, cmake應(yīng)該使用out-of-source編譯, 即創(chuàng)建單獨(dú)的目錄, 在目錄里面編譯, 并生成編譯中間文件和編譯結(jié)果.
注:慣例是在項(xiàng)目根目錄下創(chuàng)建build目錄并在里面編譯, 也可使用cmake-build-
傳統(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
構(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)安裝在
以安裝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),通常位于
以安裝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)容。