CMake is great. don't waste time on other C++ build tools, seriously.html
CMake
是一款工程構建工具,相似的工具還有autotool
,qmake
,Scons
等等。ios
具體見:
爲何選用cmakec++
隨着功能的不斷增長和複雜,咱們寫的C++程序也不可能再像Helloworld.cpp
那樣只有一個源文件了,整個程序工程中,會有不少的源文件與庫文件,如何將他們組合,編譯成可執行的程序,就是CMake做爲工程構建工具的做用:它須要告訴計算機,整個複雜工程的文件之間有怎麼樣的關係。git
這個過程經過一個叫CMakeList.txt
的文件來進行。github
cmake_minimum_required(VERSION 2.6) project(itest) # C++標準 set(CMAKE_CXX_STANDARD 11) # 指定參與編譯的源文件 add_executable(itest src/main.cpp src/cal/Calculator.cpp src/cal/Calculator.h) # 指定安裝路徑,make install 時運用 install (TARGETS itest DESTINATION bin) install(DIRECTORY src/ DESTINATION include/itest FILES_MATCHING PATTERN "*.h") # 設置不一樣build類別時的編譯參數 #set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb") set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
根據CMake 入門實戰 - Hahack的實例來學習記錄。編程
原文C語言Demo源碼出自《CMake入門實戰》源碼 - github, wzpan,本文改寫爲C++,代碼地址 - github。bash
Linux
平臺使用CMake
生成Makefile
並編譯的流程:CMake
配置文件CMakeList.txt
cmake ${CMAKELIST_PATH}
生成Makefile
,${CMAKELIST_PATH}
是CMakeList.txt
所在的目錄。make
進行編譯。如下面的Demo_1
- github的實例來學習程序爲例子,項目中的一個源文件main.cpp
以下:ide
/* * power - Calculate the power of number. * @param base: Base value. * @param exponent: Exponent value. * * @return base raised to the power exponent. */ #include <iostream> using namespace std; double power(double base, int exponent) { int result = base; int i; if (exponent == 0) { return 1; } for(i = 1; i < exponent; ++i){ result = result * base; } return result; } int main(int argc, char *argv[]) { if (argc < 3){ // printf("Usage: %s base exponent \n", argv[0]); cout << "Usage: " << argv[0] << "base exponent" <<endl; return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); double result = power(base, exponent); // printf("%g ^ %d is %g\n", base, exponent, result); cout << base << "^" << exponent << " is " << result <<endl; return 0; }
首先編寫CMakeList.txt
,並保存在與main.cpp
文件同一個目錄下:函數
~/Demo_1 | +--main.cpp +--CMakeList.txt
一個最基本的CMakeList.txt
是這樣的,在這個例子中,咱們只須要告訴計算機,咱們要使用main.cpp
文件,來編譯一個名爲Demo
的可執行文件:工具
# CMake 最低版本號要求 cmake_minimum_required (VERSION 2.8) # 項目信息 project (Demo_1) # 指定生成目標 add_executable(Demo main.cpp)
CMake
中,#
後面的內容爲註釋,命令不區分大小寫,參數之間用空格分隔,基本格式爲:
commandName(arg1 arg2 ...)
上面的CMakeList.txt
中的三個命令:
cmake_minmum_required()
用來指定所需的CMake
的最低版本。project()
中的參數表示項目名稱。add_excutable()
中有兩個參數Demo
和main.cc
,意思是將main.cc
源文件編譯成一個名爲Demo
的可執行文件。在編寫完CMakeList.txt
以後,執行cmake ${CMAKELIST_PATH}
:
shi@shi-Z370M-S01:~/Demo_1$ cmake . -- Configuring done -- Generating done -- Build files have been written to: /home/shi/Demo_1
會在按照文件中的配置和參數,在當前目錄生成編譯所須要的一系列文件,其中包含Makefile
文件。
cmake
命令是能夠附帶各類參數的,在命令後用空格隔開使用,--build
,--target
等。~/Demo_1 | +--CMakeFiles +--CMakeCache.txt +--cmake_install.cmake +--CMakeList.txt +--main.cpp +--Makefile
生成Makefile
後再執行make ${MAKEFILE_PATH}
命令便可編譯獲得Demo
可執行文件。
shi@shi-Z370M-S01:~/Demo_1$ make Scanning dependencies of target Demo [ 50%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o [100%] Linking CXX executable Demo [100%] Built target Demo
運行編譯後的可執行文件:
shi@shi-Z370M-S01:~/Demo_1$ ./Demo 2 10 2^10 is 1024
在實際編程過程當中,若是有多個源文件,好比將Demo_1.cpp
中的power函數單獨寫進一個mathfunctions.cpp
源文件中:
/* * power - Calculate the power of number. * @param base: Base value. * @param exponent: Exponent value. * * @return base raised to the power exponent. */ double power(double base, int exponent) { int result = base; int i; if (exponent == 0) { return 1; } for(i = 1; i < exponent; ++i){ result = result * base; } return result; }
而後在main函數中引用MathFunctions.cpp
的power
函數來進行計算:
#ifndef POWER_H #define POWER_H extern double power(double base, int exponent); #endif
#include <iostream> // import power function #include "MathFunctions.h" int main(int argc, char *argv[]) { if (argc < 3){ // printf("Usage: %s base exponent \n", argv[0]); cout << "Usage: " << argv[0] << "base exponent" <<endl; return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); double result = power(base, exponent); //printf("%g ^ %d is %g\n", base, exponent, result); cout << base << "^" << exponent << " is " << result <<endl; return 0; }
令工程變成以下結構:
~/Demo_2 | +--CMakeList.txt +--main.cpp +--MathFunctions.cpp +--MathFunctions.h
這時咱們只須要在CMakeList.txt
中的add_executable()
命令上添加新參數,變成這樣就能夠了:
# CMake 最低版本號要求 cmake_minimum_required (VERSION 2.8) # 項目信息 project (Demo_1) # 指定生成目標 add_executable(Demo "main.cpp" "MathFunctions.cpp")
這裏咱們經過CMake
告訴了計算機,咱們要使用主文件main.cpp
和函數文件MathFunctions.cpp
來生成可執行程序Demo
。
可是在源文件數量較多的時候,在add_executable()
命令上添加新參數就會變得很麻煩,這時候咱們可使用aux_source_directory()
命令:
aux_source_directory(<dir> <variable>)
這個命令會查找<dir>
參數的目錄下全部的源文件,而後將結果存進<variable>
變量中,所以能夠修改CMakeList.txt
以下:
# CMake 最低版本號要求 cmake_minimum_required (VERSION 2.8) # 項目信息 project (Demo_1) # 查找當前目錄下的全部源文件 # 並將名稱保存到 DIR_SRCS 變量 aux_source_directory(. DIR_SRCS) # 指定生成目標 add_executable(Demo ${DIR_SRCS})
編譯運行一下:
shi@shi-Z370M-S01:~/Demo_2$ cmake . -- The C compiler identification is GNU 7.4.0 -- The CXX compiler identification is GNU 7.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/shi/Demo_2 shi@shi-Z370M-S01:~/Demo_2$ make Scanning dependencies of target Demo [ 33%] Building CXX object CMakeFiles/Demo.dir/MathFunctions.cpp.o [ 66%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o [100%] Linking CXX executable Demo [100%] Built target Demo shi@shi-Z370M-S01:~/Demo_2$ ./Demo 2 10 2^10 is 1024
接下來咱們將MathFunctions.cpp
和MathFunctions.h
文件移動到math目錄下:
~/Demo_3 | +--CMakeList.txt +--main.cpp +--math/ | +--CMakeList.txt +--MathFunctions.cpp +--MathFunctions.h
這時候項目工程分紅了主程序和庫文件兩個部分,這時候須要咱們分別在根目錄~/Demo_3
和庫目錄~/Demo_3/math/
各編寫一個CMakeList.txt
文件,將庫目錄~/Demo_3/math/
裏的文件編譯成靜態庫再由根目錄主程序main.cpp
中的main()
函數調用。
庫目錄~/Demo_3/math/
下的CMakeList.txt
:
# 查找當前目錄下的全部源文件 # 並將名稱保存到 DIR_LIB_SRCS變量 aux_source_directory(. DIR_LIB_SRCS) add_library(MathFunctions ${DIR_LIB_SRCS})
以前咱們使用add_executable()
命令,編譯生成可執行文件,在這個庫目錄的CMakeList.txt
文件中,咱們使用了add_library()
函數,來將庫目錄中的文件編譯爲靜態鏈接庫。
這樣咱們先告訴了計算機,咱們須要用庫文件math
目錄下的源文件,組成一個名爲MathFunctions
的庫(library)。
以後咱們回到根目錄~/Demo_3/
中,修改根目錄的CMakeList.txt
文件以下:
# CMake 最低版本號要求 cmake_minimum_required(VERSION 2.8) # 項目信息 project(Demo_3) # 查找當前目錄下的全部源文件 # 並將名稱保存到 DIR_SRCS 變量 # aux_source_directory(. DIR_SRCS) # 添加 math 子目錄 add_subdirectory(math) # 指定生成目標 add_executable(Demo main.cpp) # 添加連接庫 target_link_libraries(Demo MathFunctions)
在根目錄新的CMakeList.txt
中,咱們使用add_subdirectory()
命令,將庫目錄/math
添加爲一個輔助目錄(subdirectory),告訴計算機咱們將要用到這個目錄下的文件或者庫。
以後咱們使用target_link_libraries()
命令,將庫目錄/math
下,已經在上一個CMakeList.txt
中定義好的庫MathFunctions
與咱們想要生成的可執行文件Demo
相鏈接。
main.cpp
的頭文件引用:// #include "MathFunctions.h" old #include "math/MathFunctions.h" // new
編譯並運行一下:
shi@shi-Z370M-S01:~/Demo_3$ cmake . -- The C compiler identification is GNU 7.4.0 -- The CXX compiler identification is GNU 7.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/shi/Demo_3 shi@shi-Z370M-S01:~/Demo_3$ make [ 50%] Built target MathFunctions Scanning dependencies of target Demo [ 75%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o [100%] Linking CXX executable Demo [100%] Built target Demo shi@shi-Z370M-S01:~/Demo_3$ ./Demo 2 10 2^10 is 1024
CMake
能夠自定義編譯選項,從而能夠容許咱們根據用戶的環境和需求,選擇最合適的編譯方案,好比能夠將上一節的咱們本身寫的MathFunctions
庫設置爲一個可選的庫,從而能夠自由選擇編譯時是使用咱們本身建立的庫,仍是調用標準庫中的數學函數。
爲此咱們須要在頂層的CMakeLists.txt
中添加選項。
首先須要在其中添加:
# 加入一個配置頭文件,用於處理 CMake 對源碼的設置 configure_file ( "${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/config.h" )
configure_file()
命令用來加入一個配置頭文件config.h
,這個文件將由CMake
從config.h.in
生成,經過這個機制,咱們能夠預約義一些參數和變量來控制代碼生成。
而後再添加:
# 是否使用本身的 MathFunctions 庫 option (USE_MYMATH "Use provided math implementation" ON) # 是否加入 MathFunctions 庫 if (USE_MYMATH) include_directories ("${PROJECT_SOURCE_DIR}/math") add_subdirectory (math) set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) endif (USE_MYMATH)
option()
命令中,咱們定義了一個名爲USE_MYMATH
變量,並設置其默認值爲ON
。
隨後的if()
根據USE_MYMATH
變量的值來決定咱們是否使用本身編寫的MathFunctions
庫。若是是,則添加頭文件目錄${PROJECT_SOURCE_DIR}/math
,子目錄math
,並將可選庫地址存於EXTRA_LIBS
。
完整的CMakeList.txt
以下:
# CMake最低版本號要求 cmake_minimum_required(VERSION 2.8) # 項目信息 project(Demo_4) # 加入一個配置頭文件,用於處理CMake對源碼的設置 configure_file( "${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/config.h" ) # 是否使用本身的MathFunctions庫 option(USE_MYMATH "Use provided math implementation" ON ) # 是否加入MathFunctions庫 if (USE_MYMATH) # 添加頭文件路徑 include_directories("${PROJECT_SOURCE_DIR}/math") # 添加math子目錄 add_subdirectory(math) # 收集可選庫地址存於EXTRA_LIBS set(EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) endif (USE_MYMATH) # 查找當前目錄下的全部源文件,並將名稱保存到 DIR_SRCS 變量 aux_source_directory(. DIR_SRCS) # 指定生成目標 add_executable(Demo ${DIR_SRCS}) # 添加連接庫 target_link_libraries(Demo ${EXTRA_LIBS})
這時咱們須要生成的可執行文件Demo
是由DIR_SRCS
目錄中的源文件生成,並與EXTRA_LIBS
目錄下的庫進行鏈接。
配置文件config.h
並不須要咱們直接編寫,咱們須要編寫一個config.h.in
文件,來讓CMake
自動生成config,h
,內容以下:
#cmakedefine USE_MYMATH
工程目錄結構以下:
~/Demo_4 | +--CMakeList.txt +--main.cpp +--config.h.in +--math/ | +--CMakeList.txt +--MathFunctions.cpp +--MathFunctions.h
以後咱們須要修改咱們的源文件main.cpp
,咱們須要引用新的配置頭文件config.h
,讓其根據USE_MYMATH
的預約義值來選擇調用MathFunctions
庫仍是標準庫。
修改後的main.cpp
文件:
#include <iostream> // #include "math/MathFunctions.h" #include "config.h" //判斷函數的調用 #ifdef USE_MYMATH #include "math/MathFunctions.h" #else #include<math.h> #endif using namespace std; int main(int argc, char *argv[]) { if (argc < 3){ cout << "Usage: " << argv[0] << "base exponent" <<endl; return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); #ifdef USE_MYMATH // 調用自制庫 cout << "Using our own math lib. "<<endl; double result = power(base, exponent); #else // 調用標準庫 cout << "Using standard math lib. "<<endl; double result = pow(base, exponent); #endif cout << base << "^" << exponent << " is " << result <<endl; return 0; }
編譯一下:
shi@shi-Z370M-S01:~/Demo_4$ cmake . -- The C compiler identification is GNU 7.4.0 -- The CXX compiler identification is GNU 7.4.0 -- Check for working C compiler: /usr/bin/cc -- Check for working C compiler: /usr/bin/cc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Detecting C compile features -- Detecting C compile features - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /home/shi/Demo_4 shi@shi-Z370M-S01:~/Demo_4$ make Scanning dependencies of target MathFunctions [ 25%] Building CXX object math/CMakeFiles/MathFunctions.dir/MathFunctions.cpp.o [ 50%] Linking CXX static library libMathFunctions.a [ 50%] Built target MathFunctions Scanning dependencies of target Demo [ 75%] Building CXX object CMakeFiles/Demo.dir/main.cpp.o [100%] Linking CXX executable Demo [100%] Built target Demo shi@shi-Z370M-S01:~/Demo_4$ ./Demo 2 10 Using standard math lib. 2^10 is 1024
這裏出現了一個問題,用默認的cmake
命令編譯以後,執行可執行文件Demo
時發現,並無引用咱們本身編寫的庫,而是引用了標準庫。
以後檢查全部文件沒有發現問題,安裝帶gui的CMake
後,執行ccmake
命令,在gui中顯示USE_MYMATH
正常的被配置爲ON
,執行編譯後運行:
shi@shi-Z370M-S01:~/Demo_4$ ./Demo 2 10 Using our own math lib. 2^10 is 1024
此次則正常調用了本身編寫的庫,目前緣由未知。(2019.10.21)