# CMake ## References + 🎬 [**奇乐编程学院 - CMake软件构建实战**](https://youtu.be/dIs7TFBDbIw) + 🔗 [**Wikipedia 教科書 - CMake**](https://zh.wikibooks.org/wiki/CMake_%E5%85%A5%E9%96%80) + 🔗 [**HackMD - CMake 專案建置**](https://hackmd.io/@kogiokka/ntou-cse-cpp-tutorial-02) ## Note |📘 <span class="note">NOTE</span>| |:---| |case insensitive| |📘 <span class="note">NOTE</span>| |:---| |CMake 所產生出來的 Makefile 無法獨立運作,建置過程仍然必須依賴 CMake,因此無法直接將 makefile 搬移到沒有 CMake 的環境中使用。事實上,只要更換環境就應該重新執行 CMake,針對目前環境配置產生 Makefile。| ## VScode Setting ```json= { "cmake.configureOnEdit": false, // CMake Tools 禁止 CMakeLists.txt 存檔自動構建 } ``` ## Configure 配置 CMake。\ 配置完後,你可以在 CMakeCache.txt 可找到一些配置的相關資訊。\ 一般而言,<mark>編譯器、Makefile 產生器會自動選擇全域可見的那些</mark>。 ### GUI 在 VScode 按 F7 自動構建。 ### CLI ```c cmake -S <專案目錄> -B <建置目錄> -G <Makefile 產生器> -D <變數>=<值> ``` ``` cmake -S . -B build -G "Unix Makefiles" ``` + `-S`:指定原始碼目錄(通常是專案的跟目錄) + `-B`:指定建置目錄(產生 Makefile 與執行檔的目錄) + `-G`:指定 Makefile 產生器 + `"Ninja"`:ninja + `"Unix Makefiles"`:make + `"NMake Makefiles"`:nmake + `"MinGW Makefiles"`:mingw32-make + `"Visual Studio 17 2022"`:MSVC (🚨 預設) + `-D`:設定變數 可於 CMakeLists.txt 中使用 `set` 指定。 + `CMAKE_C_COMPILER=gcc`:C 編譯器(gcc) + `CMAKE_CXX_COMPILER=g++`:C++ 編譯器(g++) + `CMAKE_BUILD_TYPE=Release`:建構型態(Release, Debug...) + `CMAKE_TOOLCHAIN_FILE=.../vcpkg.cmake`:套件管理器(vcpkg...) ```c cmake -P <腳本名稱> ``` + `-P`:將 CMake 檔案當成腳本執行 > 可單純作為試驗邏輯使用 ## Build 執行檔應位於 build 目錄頂層 ### CLI ``` cmake --build build ``` ## Example ### 範例 0 [單檔案](https://hackmd.io/@kogiokka/ntou-cse-cpp-tutorial-02#%E7%AF%84%E4%BE%8B%E3%80%87%E3%80%81%E7%B0%A1%E5%96%AE%E7%9A%84CMake%E5%B0%88%E6%A1%88) ### 範例 1 [多檔案](https://hackmd.io/@kogiokka/ntou-cse-cpp-tutorial-02#%E7%AF%84%E4%BE%8B%E4%B8%80%E3%80%81%E5%A4%9A%E5%80%8B%E5%8E%9F%E5%A7%8B%E7%A2%BC%E6%AA%94%E6%A1%88%E7%9A%84CMake%E5%B0%88%E6%A1%88) ### 範例 2 [靜態鏈結函式庫](https://hackmd.io/@kogiokka/ntou-cse-cpp-tutorial-02#%E7%AF%84%E4%BE%8B%E4%BA%8C%E3%80%81%E5%BB%BA%E7%AB%8B%E5%92%8C%E4%BD%BF%E7%94%A8%E9%9D%9C%E6%85%8B%E5%87%BD%E5%BC%8F%E5%BA%AB) ### 範例 3 [動態鏈結函式庫](https://jasonkayzk.github.io/2021/01/27/cmake%E7%94%9F%E6%88%90%E5%8A%A8%E6%80%81%E9%93%BE%E6%8E%A5%E5%BA%93dll/) 專案目錄結構 ``` Project3 ├── CMakeLists.txt ├── lib │ ├── CMakeLists.txt │ ├── my_dll.cpp │ └── my_dll.h └── main.cpp ``` CMakeLists.txt ```= cmake_minimum_required(VERSION 3.16) project(Project3) add_subdirectory(lib) ``` lib/CMakeLists.txt ```= set(LIBHELLO_SRC ./my_dll.h ./my_dll.cpp) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ../lib_out) # 動態鏈結函式庫 add_library(my_dll SHARED ${LIBHELLO_SRC}) install(TARGETS my_dll) set_target_properties(my_dll PROPERTIES LINKER_LANGUAGE C RUNTIME_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} OUTPUT_NAME "my_dll" PREFIX "" ) ``` lib/my_dll.h ```cpp= #ifndef CPP_LEARN_MY_DLL_H #define CPP_LEARN_MY_DLL_H #define EXPORT_DLL __declspec(dllexport) extern "C" EXPORT_DLL int add(int a, int b); #endif // CPP_LEARN_MY_DLL_H ``` lib/my_dll.cpp ```cpp= #include "my_dll.h" int add(int a, int b) { return (a + b); } ``` run_dll.cpp ```cpp= #include <windows.h> #include <iostream> typedef int (*add)(int, int); int main() { HINSTANCE handle = LoadLibrary("./my_dll.dll"); auto f = (add) GetProcAddress(handle, "add"); std::cout << f(1, 32) << std::endl; FreeLibrary(handle); return 0; } ``` ## Commands ### basic + `set`:變數 ```= set(var hello) ``` ✅:foo 變數為 string list ```= set(foo this is a list) set(foo this;is;a;list) ``` + `messages`:標準輸出 ```= message(${var}) ``` + `math`:數學計算 ```= math(EXPR var "1 + 2 * 3") message("var = ${var}") # var = 7 ``` + `if` / `elseif` / `else` / `endif`:條件邏輯 ```= if(expr) command1(arg) elseif() command2(arg) else() command3(arg) endif() ``` + `foreach` / `endforeach`:迴圈 ```= set(V alpha beta gamma) message(${V}) # alphabetagamma foreach(i ${V}) message(${i}) endforeach() # alpha # beta # gamma ``` + `macro` / `endmacro` / function / `endfunction`:函式、巨集 ✅:function 會建立 local 變數,而 macro 則會影響 global 變數 ```= set(k 5) macro(mprint MESSAGE) set(k ${MESSAGE}) message(${MESSAGE}) endmacro(mprint) function(fprint MESSAGE) set(k ${MESSAGE}) message(${MESSAGE}) endfunction(fprint) message("k=${k}") # k=5 mprint("from mprint") # from mprint fprint("from fprint") # from fprint message("k=${k}") # k=from mprint ``` ### build + `add_subdirectory`:將指定目錄的 CMake 專案(內含 CMakeLists.txt)一起加入建置 ```= add_subdirectory(libstatic1/) ``` + `add_executable`:新增一個 CMake 目標,其為<mark>執行檔</mark> ```= add_executable(project-01) ``` + `add_library`:新增一個 CMake 目標,其為<mark>鏈結函式庫</mark> 靜態鏈結函式庫 ```= add_library(libstatic1 STATIC) ``` 動態鏈結函式庫 ```= add_library(my_dll SHARED ./my_dll.h ./my_dll.cpp) ``` + `add_definitions`:構建時,加入 preprocessor 定義 ```= add_definitions("-DFOO -DBAR") ``` + `target_sources`:構建 CMake 目標時,需要哪些源代碼(無須標頭檔) ```= target_sources(project-01 PRIVATE main.cpp f1.cpp f2.cpp ) ``` + `include_directories` / `target_include_directories`:添加<mark>標頭檔</mark>目錄 target 是針對特定 CMake 目標,現代較推薦後者。 ```= include_directories(${PROJECT_SOURCE_DIR}/external/headers) # 2. 再搜索一般情況 include_directories(BEFORE ${PROJECT_SOURCE_DIR}/include) # 1. 先搜索 BEFORE include_directories(AFTER /usr/local/include) # 3. 再搜索 AFTER ``` + `link_libraries` / `target_link_libraries`:添加<mark>鏈結函式庫</mark>目錄 target 是針對特定 CMake 目標,現代較推薦後者。\ 通常來自 `find_package` 或 `add_subdirectory`。 ```= target_link_libraries(project-02 PRIVATE libstatic1 ) ``` + `set_target_properties`:屬性控制編譯選項(若無給定,則採用全域預設值) ```= set_target_properties(my_dll PROPERTIES LINKER_LANGUAGE C RUNTIME_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} LIBRARY_OUTPUT_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY} OUTPUT_NAME "my_dll" PREFIX "" ) ``` + `include`:執行另一個 CMake 腳本 ``` include($ENV{IDF_PATH}/tools/cmake/project.cmake) ``` + [`find_package`](https://blog.csdn.net/zhanghm1995/article/details/105466372) ...