小記錄
參考:
https://www.zhihu.com/search?type=content&q=premake
https://www.zhihu.com/question/58949190/answer/999701073
口訣:
- Declare a target
- Declare target's traits
- It's all about targets
add_definitions 已經被 add_compile_definitions 取代
Windows下是 .dll 和 .lib,Linux下是 .so 和 .a
cmake區分大小寫,但是cmake指令不分(比如set和SET)還有函數啥的也不分
cmake中永遠用正斜杠 / ,cmake會自動將反斜杠轉成正斜杠
學習資料
cmake-examples中文版:https://sfumecjf.github.io/cmake-examples-Chinese/
cmake-examples:https://github.com/ttroy50/cmake-examples
CMake “菜譜”:https://www.bookstack.cn/read/CMake-Cookbook/README.md
CMake 官方文檔:https://cmake.org/cmake/help/latest/
CMake 中文文檔:https://runebook.dev/zh-CN/docs/cmake/-index-
高性能並行編程與優化 - 課件:https://github.com/parallel101/course
More Modern CMake:
https://hsf-training.github.io/hsf-training-cmake-webpage/aio/index.html
https://github.com/hsf-training/hsf-training-cmake-webpage/
Modern CMake:https://cliutils.gitlab.io/modern-cmake/chapters/basics/structure.html
configuration stage / build stage
https://cmake.org/cmake/help/v3.25/manual/cmake.1.html#generate-a-project-buildsystem
cmake-examples
https://sfumecjf.github.io/cmake-examples-Chinese/
https://github.com/ttroy50/cmake-examples
1. basic
1.1 hello-cmake
最基礎的當然是
cmake_minimum_required(VERSION 3.5) #設置CMake最小版本
project (hello_cmake) #設置工程名
add_executable(hello_cmake main.cpp) #生成可執行文件
細節就是,在這一行語句中project (hello_cmake) #設置工程名
CMake構建包含一個項目名稱,上面的命令會自動生成一些變量,在使用多個項目時引用某些變量會更加容易。
比如生成了: PROJECT_NAME 這個變量。PROJECT_NAME是變量名,${PROJECT_NAME}是變量值,值為hello_cmake
因此上面的代碼和下面的一樣:
cmake_minimum_required(VERSION 3.5) #設置CMake最小版本
project (hello_cmake) #設置工程名
add_executable(${PROJECT_NAME} main.cpp) #生成可執行文件
1.2 頭文件
對於頭文件的情況,我們這樣寫:
cmake_minimum_required(VERSION 3.5)#最低CMake版本
project (hello_headers)# 工程名
set(SOURCES
src/Hello.cpp
src/main.cpp
)#創建一個變量,名字叫SOURCE。它包含了所有的cpp文件。
add_executable(hello_headers ${SOURCES})#用所有的源文件生成一個可執行文件,因為這里定義了SOURCE變量,所以就不需要羅列cpp文件了
#等價於命令: add_executable(hello_headers src/Hello.cpp src/main.cpp)
target_include_directories(hello_headers
PRIVATE
${PROJECT_SOURCE_DIR}/include
)#設置這個可執行文件hello_headers需要包含的庫的路徑
#PROJECT_SOURCE_DIR指工程頂層目錄
#PROJECT_Binary_DIR指編譯目錄
#PRIVATE指定了庫的范圍,下一節講
我們可以用 message 函數來查看,比如我在上面的最后一行加一句:message(${CMAKE_SOURCE_DIR})
我們在 cmake .. 的時候會打印輸出:
我們在 make 的時候可以這樣來詳細列出(否則僅顯示構建狀態):make VERBOSE=1
1.3 Static Library
將源文件直接傳遞給add_library調用,這是modern CMake的建議。(而不是先把Hello.cpp賦給一個變量)
cmake_minimum_required(VERSION 3.5)
project(hello_library)
############################################################
# Create a library
############################################################
#庫的源文件Hello.cpp生成靜態庫hello_library
add_library(hello_library STATIC
src/Hello.cpp
)
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
# target_include_directories為一個目標(可能是一個庫library也可能是可執行文件)添加頭文件路徑。
############################################################
# Create an executable
############################################################
# Add an executable with the above sources
#指定用哪個源文件生成可執行文件
add_executable(hello_binary
src/main.cpp
)
#鏈接可執行文件和靜態庫
target_link_libraries( hello_binary
PRIVATE
hello_library
)
#鏈接庫和包含頭文件都有關於scope這三個關鍵字的用法。
關於 private pubic interface 的范圍
官方文檔講解:https://cmake.org/cmake/help/v3.0/command/target_include_directories.html
- PRIVATE - 目錄被添加到目標(庫)的包含路徑中。
- INTERFACE - 目錄沒有被添加到目標(庫)的包含路徑中,而是鏈接了這個庫的其他目標(庫或者可執行程序)包含路徑中
- PUBLIC - 目錄既被添加到目標(庫)的包含路徑中,同時添加到了鏈接了這個庫的其他目標(庫或者可執行程序)的包含路徑中
也就是說,根據庫是否包含這個路徑,以及調用了這個庫的其他目標是否包含這個路徑,可以分為三種scope。
拿剛才的項目為例,結構如下:
那么對於我們的 CMake 命令:
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
我們這里 Hello.cpp 和 main.cpp 都是這樣包含頭文件的:#include "static/Hello.h"
而這里我們要加入 ${PROJECT_SOURCE_DIR}/include,為 PUBLIC 的時候,就會是正確的;為 PRIVATE 的時候,那么此時只有靜態庫能找到這個路徑,對於鏈接這個靜態庫的最終可執行文件而言是沒有添加這個路徑的,也就是 main.cpp 沒有添加這個路徑,所以在 main.cpp 中會報錯
而若是換成 INTERFACE ,那么就只有要鏈接這個靜態庫的東西添加了這個路徑,因此會在 Hello.cpp 中報錯:
同樣,在代碼:
target_link_libraries( hello_binary
PRIVATE
hello_library
)
中,若是把 PRIVATE 換成 INTERFACE 也會在 main.cpp 報錯,因為 INTERFACE 會讓 main.cpp 找不到這個路徑。(這告訴CMake在鏈接期間將hello_library鏈接到hello_binary可執行文件。 同時,這個被鏈接的庫如果有INTERFACE或者PUBLIC屬性的包含目錄,那么,這個包含目錄也會被傳遞( propagate )給這個可執行文件。)
這里對於hello_binary,它不是庫,所以不會被鏈接。直接private自己用這個庫就行。
建議:
對於公共的頭文件,最好在include文件夾下建立子目錄。
傳遞給函數target_include_directories()的目錄,應該是所有包含目錄的根目錄,然后在這個根目錄下建立不同的文件夾,分別寫頭文件。
這樣使用的時候,不需要寫${PROJECT_SOURCE_DIR}/include,而是直接選擇對應的文件夾里對應頭文件。下面是例子:#include "static/Hello.h"而不是#include "Hello.h"使用此方法意味着在項目中使用多個庫時,頭文件名沖突的可能性較小。
1.4 Shared Library
CMakeLists:
cmake_minimum_required(VERSION 3.5)
project(hello_library)
############################################################
# Create a library
############################################################
#根據Hello.cpp生成動態庫
add_library(hello_library SHARED
src/Hello.cpp
)
#給動態庫hello_library起一個別的名字hello::library
add_library(hello::library ALIAS hello_library)
#為這個庫目標,添加頭文件路徑,PUBLIC表示包含了這個庫的目標也會包含這個路徑
target_include_directories(hello_library
PUBLIC
${PROJECT_SOURCE_DIR}/include
)
############################################################
# Create an executable
############################################################
#根據main.cpp生成可執行文件
add_executable(hello_binary
src/main.cpp
)
#鏈接庫和可執行文件,使用的是這個庫的別名。PRIVATE 表示
target_link_libraries( hello_binary
PRIVATE
hello::library
)
1.5 build-type、set、CACHE變量
強烈建議看:https://sfumecjf.github.io/cmake-examples-Chinese/01-basic/1.5 build-type.html
cmake_minimum_required(VERSION 3.5)
#如果沒有指定則設置默認編譯方式
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
#在命令行中輸出message里的信息
message("Setting build type to 'RelWithDebInfo' as none was specified.")
#不管CACHE里有沒有設置過CMAKE_BUILD_TYPE這個變量,都強制賦值這個值為RelWithDebInfo
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
# 當使用cmake-gui的時候,設置構建級別的四個可選項
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release"
"MinSizeRel" "RelWithDebInfo")
endif()
project (build_type)
add_executable(cmake_examples_build_type main.cpp)
關於編譯器參數:
參考:http://cn.voidcc.com/question/p-fvbzitto-bhq.html
-DNDEBUG 由兩部分組成,標記 -D 和參數 NDEBUG 。該標志被用於創建預處理器定義,因此這將創建一個新的預處理器 #define 稱為 NDEBUG
-g 是一個編譯器開關,以產生調試信息。它與 -D 完全分開創建定義。
在命令行運行CMake的時候, 使用cmake命令行的-D選項配置編譯類型:
cmake .. -DCMAKE_BUILD_TYPE=Release
1.6 Compile Flags
cmake_minimum_required(VERSION 3.5)
#強制設置默認C++編譯標志變量為緩存變量,如CMake(五) build type所說,該緩存變量被定義在文件中,相當於全局變量,源文件中也可以使用這個變量
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DEX2" CACHE STRING "Set C++ Compiler Flags" FORCE)
project (compile_flags)
add_executable(cmake_examples_compile_flags main.cpp)
#為可執行文件添加私有編譯定義
target_compile_definitions(cmake_examples_compile_flags
PRIVATE EX3
)
在現代CMake中設置C ++標志的推薦方法是專門針對某個目標(target)設置標志,可以通過target_compile_definitions()函數設置某個目標的編譯標志。
target_compile_definitions(cmake_examples_compile_flags
PRIVATE EX3
)
如果這個目標是一個庫(cmake_examples_compile_flags),編譯器在編譯目標時添加定義-DEX3 ,並且選擇了范圍PUBLIC或INTERFACE,該定義-DEX3也將包含在鏈接此目標(cmake_examples_compile_flags)的所有可執行文件中。 注意,本語句使用了PRIVATE,所以編譯選項不會傳遞。
1.7 Including Third Party Library
https://sfumecjf.github.io/cmake-examples-Chinese/01-basic/1.7 Including Third Party Library.html
https://www.cnblogs.com/hebohang/p/15990146.html
1.8 設置 C++ 標准
# Set the minimum version of CMake that can be used
# To find the cmake version run
# $ cmake --version
cmake_minimum_required(VERSION 3.1)
# Set the project name
project (hello_cpp11)
# set the C++ standard to C++ 11
set(CMAKE_CXX_STANDARD 11)
# Add an executable
add_executable(hello_cpp11 main.cpp)
1.9 installing
https://github.com/ttroy50/cmake-examples/tree/master/01-basic/E-installing
2. sub projects
https://sfumecjf.github.io/cmake-examples-Chinese/02-sub-projects/A-basic/
小彭老師 CMake 教學
1
https://www.bilibili.com/video/BV1fa411r7zp/?spm_id_from=333.788
關於構建系統、CMake
首先是為什么出現了構建系統(比如 Makefile(GNU Make)、Ninja 之類的)
參考
https://sfumecjf.github.io/cmake-examples-Chinese/01-basic/1.9 J-building-with-ninja.html
https://github.com/ttroy50/cmake-examples/tree/master/01-basic/J-building-with-ninja
比如老師這個例子:
有 hello.cpp main.cpp Makefile,我們make一下,會編譯出 hello.o 和 main.o,要是我們改變 hello.cpp,我們就知道 main.o 不需要改變,只要再編譯一次 hello.cpp 到 hello.o 即可,這便是增量編譯。我想,檢測可以由操作系統打時間戳,GNU Make 再去檢測。
為了應對不同平台的差異,於是出現了 CMake,一個構建系統的構建系統,生成的 makefile 之類的再有對應的編譯器編譯成可執行文件。
想切換成 clang++ 去編譯,可以這樣寫:
cmake -Bbuild -DCMAKE_CXX_COMPILER=clang++
測試:
這里的 -B 就是指定編譯的位置,我們這里相當於指定為文件夾 build 了。-D 則是 define 的意思,在前面有講過。
還能直接指定C++版本:
cmake -Bbuild -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_STANDARD=17
關於 library(動態庫、靜態庫)
比如之前寫的一個 printf 的代碼,被優化成了 puts 函數,用 objdump 查看(反匯編了)如下:
這里的
就是一個插樁函數,會去 libc.so 找到對應的位置。
Linux是沒有與 .so 配套的 .a 的,它會自動生成插樁。可以用 ldd a.out
查看可執行文件 a.out 鏈接了誰(.so)。
CMake 的一些指令
上圖的 -DMY_MACRO=1 只是為了符合某些人的習慣,其實還是相當於 #define MY_MACRO 1
沒有 target 的下面的指令就像是全局的,不推薦使用。
解決菱形依賴的問題:
安裝第三方庫-包管理器:
2
https://www.bilibili.com/video/BV16P4y1g7MH/?spm_id_from=333.788
現代 CMake 命令行調用:
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel 4
cmake --build build --target install
這里的 --build 在Linux中就會調用make,在 Windows 中就會調用 visual studio 的那個 msbuild 去構建。--parallel 4 就代表用 4 個進程並行地構建,並且可以用 --target 來指定要構建的目標,和這個 make install 是一樣的。
還有就是現代 CMake 中 public 的應用:

一般比較常用的是 Ninja 這個后端,比較快:
Ninja 還是跨平台的,Windows和Linux上都能用:
但是像 CUDA 在Windows上強制用 MSBuild 而不能用 Ninja。
可以使用 GLOB 來直接加所有的 .cpp 和 .h,但是可能存在刪掉或者新增cpp卻不被識別的問題,小彭老師推薦加關鍵字 CONFIGUE_DEPENDS:
不過前面說的現代 CMake 推薦都寫下來。
可以用 aux_source_directory 來自動根據當前語言搜索所需要的文件:
同時還有遞歸地去搜索的關鍵詞 GLOB_RECURSE(能自動包含所有子文件夾下的文件):
但是會把 CMake 構建時build中的測試的臨時 cpp 也被加進來:
注:在CMake看來編譯和鏈接是一起的,
默認是 Debug 構建模式:
https://cmake.org/cmake/help/latest/command/project.html
一般會把 CMAKE_CXX_EXTENSIONS 設置為 OFF,表示不需要有那些GCC的特性(夾帶私貨!否則MSVC可能會通不過)。
這些變量最好設置在 project 之前,這樣編譯器在對project的時候啟用這個語言的時候就會檢測一下,就不容易出錯。
CMAKE常見變量:https://blog.csdn.net/fuyajun01/article/details/8891749
小彭老師建議卸載Ubuntu,使用Arch Linux.
一個標准的 CMakeLists.txt 模板
cmake_minimum_required(VERSION 3.15)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
project(MyProjName LANGUAGES C CXX)
if (PROJECT_BINARY_DIR STREQUAL PROJECT_SOURCE_DIR)
message(WARNING "The binary directory of CMake cannot be the same as source directory!")
endif()
if (NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
if (WIN32)
add_definitions(-DNOMINMAX -D_USE_MATH_DEFINES)
endif()
if (NOT MSVC)
find_program(CCACHE_PROGRAM ccache)
if (CCACHE_PROGRAM)
message(STATUS "Found CCache: ${CCACHE_PROGRAM}")
set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${CCACHE_PROGRAM})
set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ${CCACHE_PROGRAM})
endif()
endif()
鏈接庫文件
小彭老師推薦用靜態庫,因為動態庫在Windows上有很多坑點。
對象庫:https://www.scivision.dev/cmake-object-libraries/
在自己的項目里可以都用對象庫來組織。對象庫的一個優點是,如上面的 mylib 作為一個對象庫可以指定不同的編譯選項,有時候就會做到。還有一點就是可以繞開編譯器和操作系統的各種規則,保證跨平台統一性。
常見坑點:
原因是動態庫在內存中的地址是會變化的,他在編譯時會指定一個 -fPIC 選項,但是靜態庫卻沒有這個選項。靜態庫不想變換地址而動態庫卻需要變換地址,因此把一個靜態庫鏈到動態庫上就會報錯:cannot relocate x86_ to loc 之類的錯誤。
解決方法:要么把靜態庫變為一個對象庫,要么讓靜態庫編譯時也生成位置無關的代碼(PIC)
不過這樣就導致那些本來不需要為 PIC 的那些庫也變為 PIC 了。
前面的set(CMAKE_POSITION_INDEPENDENT_CODE ON)
會把那個文件后面所有的靜態庫都生成 PIC ,因此我們可以用 set_property 來只針對某一個庫:
對象的屬性
注:經嘗試,set_property(TARGET TinyRaytracer PROPERTY WIN32_EXECUTABLE TRUE)
必須要 WinMain 才可以,main 入口是不可以的,會報錯。
手動拷貝 dll 太麻煩了,有如下幾個解決方法:
鏈接第三方庫
輸出與變量
set(myvar hello world)
相當於 set(myvar "hello;world")
,即視作列表。
所以如果不確定的時候最好打上引號。
結論:除非確實需要列表,建議始終在你不確定的地方加上引號,例如: set(sources “main.cpp” “mylib.cpp” “C:/Program Files/a.cpp”) message(“${sources}”)
變量與緩存
CMake 的最大坑點就是緩存了。
清除緩存,其實只需刪除 build/CMakeCache.txt 就可以了:rm build/CMakeCache.txt
這個和參數的語法是一樣的,就是前面加個 -D 而已。
bool 值還有 TRUE 和 FALSE,但他們是由於歷史原因保留的,目前普遍用的是 ON 和 OFF。
如上圖,設置 WITH_TBB 是一個 BOOL 類型的緩存變量,那么用戶使用的時候就可以這樣來設置是否開啟:
cmake -B build -DWITH_TBB:BOOL=OFF
CMake 對 BOOL 類型緩存的 set 指令提供了一個簡寫:option
但是上圖的問題是,比如 ccmake(或者Windows的CMake GUI?)會查緩存,如果是不帶 CACHE 的這樣 set 就不進緩存,於是 ccmake 就看不到。
跨平台與編譯器
相當於 gcc -DMY_MACRO=233
雖然名字叫 WIN32,實際上對 32 位 Windows 和 64 位 Windows 都適用。
生成器表達式
https://cmake.org/cmake/help/latest/manual/cmake-generator-expressions.7.html#genex:PLATFORM_ID
這個生成器表達式就相當於,當滿足 PLATFORM_ID:Windows 的時候才會定義后面的 MY_NAME="Bill Gates",如果不滿足就會定義為一個空字符串。總之就相當於剛才的那三個指令,且自動做了if判斷。
分支與判斷
建議同學們始終使用 ON/OFF 避免混淆
變量與作用域
小彭老師小建議
3
推薦的目錄組織方式
多套一層名字,防止頭文件名重復:
.cmake 文件
find_package
科普:語義版本號(semantic versioning)系統
其他記錄
怎么判斷64位平台和32位平台
CMAKE_SIZEOF_VOID_P 表示 void* 的大小(例如為 4 或者 8),可以使用其來判斷當前構建為 32 位還是 64 位
if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "TinyRaytracer only supports 64-bit platforms")
endif ()
經測試,這一行應該包含在 project 指定語言之后,否則不知道 CMAKE_SIZEOF_VOID_P 是多少上面那個代碼就始終會報錯。
add_library是純頭文件或者沒有源文件
此時不能用 STATIC 啥的,發現會報錯:no sources given to target
https://stackoverflow.com/questions/65415872/issue-regarding-cmake-error-no-source-given-to-target
得換成 INTERFACE
CMake 輸出路徑
https://blog.csdn.net/q610098308/article/details/121157418
CMake warining管理
有時候引入第三方庫我不想讓它報warning,很煩人,學習如下鏈接:
https://www.foonathan.net/2018/10/cmake-warnings/
target_compile_options(my_library PRIVATE
$<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
-Wall>
$<$<CXX_COMPILER_ID:MSVC>:
/W4>)
add_compile_definitions 似乎和變量一樣遵從“子不影響父”的關系
add_compile_definitions 似乎和變量一樣遵從“子不影響父”的關系?