不一樣Make工具,如GNU Make、QT的qmake、微軟的MS nmake、BSD Make(pmake)等,遵循着不一樣的規範和標準,所執行的Makefile格式也不一樣。若是軟件想跨平臺,必需要保證可以在不一樣平臺編譯。而若是使用Make工具,必須爲不一樣的Make工具編寫不一樣的Makefile。
CMake是一個比Make工具更高級的編譯配置工具,是一個跨平臺的、開源的構建系統(BuildSystem)。CMake容許開發者編寫一種平臺無關的CMakeList.txt文件來定製整個編譯流程,而後再根據目標用戶的平臺進一步生成所需的本地化Makefile和工程文件,如:爲Unix平臺生成Makefile文件(使用GCC編譯),爲Windows MSVC生成projects/workspaces(使用VS IDE編譯)或Makefile文件(使用nmake編譯)。使用CMake做爲項目架構系統的知名開源項目有VTK、ITK、KDE、OpenCV、OSG等。數組
在Linux平臺下使用CMake生成Makefile並編譯的流程以下:
A、編寫CMake配置文件CMakeLists.txt
B、執行命令cmake PATH生成Makefile,PATH是CMakeLists.txt所在的目錄。
C、使用make命令進行編譯。架構
假設項目test中只有一個main.cpp源文件,程序用途是計算一個數的指數冪。ide
#include <stdio.h> #include <stdlib.h> /** * 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; } int main(int argc, char *argv[]) { if(argc < 3) { printf("Usage: %s base exponent \n", argv[0]); 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); return 0; }
在main.cpp源文件目錄test下編寫CMakeLists.txt文件。函數
#CMake最低版本號要求 cmake_minimum_required (VERSION 2.8) #項目信息 project (demo) #指定生成目標 add_executable(demomain.cpp)
CMakeLists.txt由命令、註釋和空格組成,其中命令是不區分大小寫。符號#後的內容被認爲是註釋。命令由命令名稱、小括號和參數組成,參數之間使用空格進行間隔。
本例中CMakeLists.txt文件的命令以下:
cmake_minimum_required:指定運行本配置文件所需的CMake的最低版本;
project:參數值是demo,表示項目的名稱是demo。
add_executable:將名爲main.cpp的源文件編譯成一個名稱爲demo的可執行文件。工具
在源碼根目錄下建立一個build目錄,進入build目錄,執行cmake ..,生成Makefile,再使用make命令編譯獲得demo可執行文件。
一般,建議在源碼根目錄下建立一個獨立的build構建編譯目錄,將構建過程產生的臨時文件等文件與源碼隔離,避免源碼被污染。開發工具
假如把power函數單獨寫進一個名爲MathFunctions.cpp的源文件裏,使得這個工程變成以下的形式:
MathFunctions.h文件:測試
/** * 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);
MathFunctions.cpp文件:ui
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.cpp文件:this
#include <stdio.h> #include <stdlib.h> #include "MathFunctions.h" int main(int argc, char *argv[]) { if(argc < 3) { printf("Usage: %s base exponent \n", argv[0]); 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); return 0; }
#CMake最低版本號要求 cmake_minimum_required(VERSION 2.8) #項目信息 project(demo) #指定生成目標 add_executable(demomain.cpp MathFunctions.cpp)
add_executable命令中增長了一個MathFunctions.cpp源文件,但若是源文件不少,可使用aux_source_directory命令,aux_source_directory命令會查找指定目錄下的全部源文件,而後將結果存進指定變量名。其語法以下:aux_source_directory(dir variable)
修改後CMakeLists.txt以下:spa
#CMake最低版本號要求 cmake_minimum_required(VERSION 2.8) # 項目信息 project(demo) #查找當前目錄下的全部源文件 #並將名稱保存到DIR_SRCS變量 aux_source_directory(. DIR_SRCS) #指定生成目標 add_executable(demo${DIR_SRCS})
CMake會將當前目錄全部源文件的文件名賦值給變量DIR_SRCS ,再指示變量DIR_SRCS中的源文件須要編譯成一個名稱爲demo的可執行文件。
建立一個math目錄,將MathFunctions.h和MathFunctions.cpp文件移動到math目錄下。在工程目錄根目錄test和子目錄math裏各編寫一個CMakeLists.txt文件,能夠先將math目錄裏的文件編譯成靜態庫再由main函數調用。
math子目錄:
MathFunctions.h文件:
/** * 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);
MathFunctions.cpp文件:
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; }
根目錄源文件:
#include <stdio.h> #include <stdlib.h> #include "math/MathFunctions.h" int main(int argc, char *argv[]) { if(argc < 3) { printf("Usage: %s base exponent \n", argv[0]); 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); return 0; }
根目錄的CMakeLists.txt文件:
# CMake最低版本號要求 cmake_minimum_required(VERSION 2.8) # 項目信息 project(demo) #查找當前目錄下的全部源文件 #並將名稱保存到DIR_SRCS變量 aux_source_directory(. DIR_SRCS) #添加math子目錄 add_subdirectory(math) #指定生成目標 add_executable(demo${DIR_SRCS}) # 添加連接庫 target_link_libraries(demoMathFunctions)
add_subdirectory命令指明本工程包含一個子目錄math,math目錄下的 CMakeLists.txt文件和源代碼也會被處理 。target_link_libraries命令指明可執行文件demo須要鏈接一個名爲MathFunctions的連接庫 。
math子目錄的CMakeLists.txt文件:
#查找當前目錄下的全部源文件 #並將名稱保存到DIR_LIB_SRCS變量 aux_source_directory(. DIR_LIB_SRCS) #生成連接庫 add_library(MathFunctions ${DIR_LIB_SRCS})
add_library命令將math目錄中的源文件編譯爲靜態連接庫。
CMake容許爲工程增長編譯選項,從而能夠根據用戶的環境和需求選擇最合適的編譯方案。
例如,能夠將MathFunctions庫設爲一個可選的庫,若是該選項爲ON ,就使用MathFunctions庫定義的數學函數來進行運算,不然就調用標準庫中的數學函數庫。
在根目錄的CMakeLists.txt文件指定自定義編譯選項:
# CMake 最低版本號要求 cmake_minimum_required (VERSION 2.8) # 項目信息 project (demo) set(CMAKE_INCLUDE_CURRENT_DIR ON) # 加入一個配置頭文件,用於處理 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") add_subdirectory (math) 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})
configure_file命令用於加入一個配置頭文件config.h,config.h文件由CMake從config.h.in生成,經過預約義一些參數和變量來控制代碼的生成。
option命令添加了一個USE_MYMATH選項,而且默認值爲ON。
根據USE_MYMATH變量的值來決定是否使用本身編寫的MathFunctions庫。
修改main.cpp文件,讓其根據USE_MYMATH的預約義值來決定是否調用標準庫仍是MathFunctions庫。
#include <cstdio> #include <cstdlib> #include <config.h> #ifdef USE_MYMATH #include <MathFunctions.h> #else #include <cmath> #endif int main(int argc, char *argv[]) { if (argc < 3) { printf("Usage: %s base exponent \n", argv[0]); return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); #ifdef USE_MYMATH printf("Now we use our own Math library. \n"); double result = power(base, exponent); #else printf("Now we use the standard library. \n"); double result = pow(base, exponent); #endif printf("%g ^ %d is %g\n", base, exponent, result); return 0; }
main.cpp文件包含了一個config.h文件,config.h文件預約義了USE_MYMATH 的值。但不會直接編寫config.h文件,爲了方便從CMakeLists.txt中導入配置,一般編寫一個config.h.in文件,內容以下:#cmakedefine USE_MYMATH
CMake會自動根據CMakeLists.txt配置文件中的設置自動生成config.h文件。
修改CMakeLists.txt文件,USE_MYMATH爲OFF,使用標準庫。
# 是否使用本身的MathFunctions庫 option (USE_MYMATH "Use provided math implementation" OFF)
在build目錄下cmake ..,make,執行程序:
在math/CMakeLists.txt文件指定MathFunctions庫的安裝規則:
#指定MathFunctions庫的安裝路徑 install(TARGETS MathFunctions DESTINATION bin) install(FILES MathFunctions.h DESTINATION include)
修改根目錄的CMakeLists.txt文件指定目標文件的安裝規則:
#指定安裝路徑 install(TARGETS test DESTINATION bin) install(FILES "${PROJECT_BINARY_DIR}/config.h" DESTINATION include)
經過對安裝規則的定製,生成的目標文件和MathFunctions函數庫 libMathFunctions.o文件將會被拷貝到/usr/local/bin中,而MathFunctions.h和生成的config.h文件則會被複制到/usr/local/include中。
/usr/local是默認安裝到的根目錄,能夠經過修改 CMAKE_INSTALL_PREFIX 變量的值來指定文件應該拷貝到哪一個根目錄。
CMake提供了一個CTest測試工具。在項目根目錄的CMakeLists.txt文件中調用一系列的add_test 命令。
#啓用測試 enable_testing() #測試程序是否成功運行 add_test(test_run demo 5 2) #測試幫助信息是否能夠正常提示 add_test(test_usage demo) set_tests_properties(test_usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent") #測試5的平方 add_test(test_5_2 demo 5 2) set_tests_properties(test_5_2 PROPERTIES PASS_REGULAR_EXPRESSION "is 25") #測試10的5次方 add_test(test_10_5 demo 10 5) set_tests_properties(test_10_5 PROPERTIES PASS_REGULAR_EXPRESSION "is 100000") #測試2的10次方 add_test(test_2_10 demo 2 10) set_tests_properties(test_2_10 PROPERTIES PASS_REGULAR_EXPRESSION "is 1024")
第一個測試test_run用來測試程序是否成功運行並返回0值。剩下的三個測試分別用來測試 5 的 平方、10 的 5 次方、2 的 10 次方是否都能獲得正確的結果。其中PASS_REGULAR_EXPRESSION用來測試輸出是否包含後面跟着的字符串。
若是要測試更多的輸入數據,能夠經過編寫宏來實現:
# 啓用測試 enable_testing() # 測試程序是否成功運行 add_test (test_run demo 5 2) # 測試幫助信息是否能夠正常提示 add_test (test_usage demo) set_tests_properties (test_usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent") # 測試 5 的平方 # add_test (test_5_2 Demo 5 2) # set_tests_properties (test_5_2 # PROPERTIES PASS_REGULAR_EXPRESSION "is 25") # 測試 10 的 5 次方 # add_test (test_10_5 Demo 10 5) # set_tests_properties (test_10_5 # PROPERTIES PASS_REGULAR_EXPRESSION "is 100000") # 測試 2 的 10 次方 # add_test (test_2_10 Demo 2 10) # set_tests_properties (test_2_10 # PROPERTIES PASS_REGULAR_EXPRESSION "is 1024") # 定義一個宏,用來簡化測試工做 macro (do_test arg1 arg2 result) add_test (test_${arg1}_${arg2} demo ${arg1} ${arg2}) set_tests_properties (test_${arg1}_${arg2} PROPERTIES PASS_REGULAR_EXPRESSION ${result}) endmacro (do_test) # 利用 do_test 宏,測試一系列數據 do_test (5 2 "is 25") do_test (10 5 "is 100000") do_test (2 10 "is 1024")
讓CMake支持gdb的設置只須要指定Debug模式下開啓-g選項:
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")
生成的程序能夠直接使用gdb來調試。
使用平臺相關的特性時,須要對系統環境作檢查。檢查系統是否自帶pow函數,若是有pow函數,就使用;不然使用自定義的power函數。
首先在頂層CMakeLists.txt文件中添加CheckFunctionExists.cmake 宏,並調用check_function_exists命令測試連接器是否可以在連接階段找到 pow函數。
#檢查系統是否支持 pow 函數 include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake) check_function_exists (pow HAVE_POW) check_function_exists須要放在configure_file命令前。
修改 config.h.in 文件,預約義相關的宏變量。
// does the platform provide pow function? #cmakedefine HAVE_POW
修改 main.cpp文件 ,在代碼中使用宏和函數。
#include <stdio.h> #include <stdlib.h> #include <config.h> #ifdef HAVE_POW #include <math.h> #else #include <MathFunctions.h> #endif int main(int argc, char *argv[]) { if (argc < 3) { printf("Usage: %s base exponent \n", argv[0]); return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); #ifdef HAVE_POW printf("Now we use the standard library. \n"); double result = pow(base, exponent); #else printf("Now we use our own Math library. \n"); double result = power(base, exponent); #endif printf("%g ^ %d is %g\n", base, exponent, result); return 0; }
修改頂層CMakeLists.txt文件,在project命令後分別指定當前的項目的主版本號和副版本號。
# CMake 最低版本號要求 cmake_minimum_required (VERSION 2.8) # 項目信息 project (demo) set (Demo_VERSION_MAJOR 1) set (Demo_VERSION_MINOR 0) set(CMAKE_INCLUDE_CURRENT_DIR ON) #檢查系統是否支持 pow 函數 include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake) check_function_exists (pow HAVE_POW) # 加入一個配置頭文件,用於處理 CMake 對源碼的設置 configure_file ( "${PROJECT_SOURCE_DIR}/config.h.in" "${PROJECT_BINARY_DIR}/config.h" ) # 是否加入 MathFunctions 庫 if (NOT HAVE_POW) include_directories ("${PROJECT_SOURCE_DIR}/math") add_subdirectory (math) set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions) endif (NOT HAVE_POW) # 查找當前目錄下的全部源文件 # 並將名稱保存到 DIR_SRCS 變量 aux_source_directory(. DIR_SRCS) # 指定生成目標 add_executable (demo ${DIR_SRCS}) target_link_libraries (demo ${EXTRA_LIBS}) #指定安裝路徑 install(TARGETS demo DESTINATION bin) install(FILES "${PROJECT_BINARY_DIR}/config.h" DESTINATION include) # 啓用測試 enable_testing() # 測試程序是否成功運行 add_test (test_run demo 5 2) # 測試幫助信息是否能夠正常提示 add_test (test_usage demo) set_tests_properties (test_usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .* base exponent") # 測試 5 的平方 # add_test (test_5_2 Demo 5 2) # set_tests_properties (test_5_2 # PROPERTIES PASS_REGULAR_EXPRESSION "is 25") # 測試 10 的 5 次方 # add_test (test_10_5 Demo 10 5) # set_tests_properties (test_10_5 # PROPERTIES PASS_REGULAR_EXPRESSION "is 100000") # 測試 2 的 10 次方 # add_test (test_2_10 Demo 2 10) # set_tests_properties (test_2_10 # PROPERTIES PASS_REGULAR_EXPRESSION "is 1024") # 定義一個宏,用來簡化測試工做 macro (do_test arg1 arg2 result) add_test (test_${arg1}_${arg2} demo ${arg1} ${arg2}) set_tests_properties (test_${arg1}_${arg2} PROPERTIES PASS_REGULAR_EXPRESSION ${result}) endmacro (do_test) # 利用 do_test 宏,測試一系列數據 do_test (5 2 "is 25") do_test (10 5 "is 100000") do_test (2 10 "is 1024")
分別指定當前的項目的主版本號和副版本號。
爲了在代碼中獲取版本信息,能夠修改 config.h.in 文件,添加兩個預約義變量:
// the configured options and settings for Tutorial #define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@ #define Demo_VERSION_MINOR @Demo_VERSION_MINOR@ // does the platform provide pow function? #cmakedefine HAVE_POW
直接在源碼中使用:
#include <stdio.h> #include <stdlib.h> #include <config.h> #ifdef HAVE_POW #include <math.h> #else #include <MathFunctions.h> #endif int main(int argc, char *argv[]) { if (argc < 3) { // print version info printf("%s Version %d.%d\n", argv[0], Demo_VERSION_MAJOR, Demo_VERSION_MINOR); printf("Usage: %s base exponent \n", argv[0]); return 1; } double base = atof(argv[1]); int exponent = atoi(argv[2]); #ifdef HAVE_POW printf("Now we use the standard library. \n"); double result = pow(base, exponent); #else printf("Now we use our own Math library. \n"); double result = power(base, exponent); #endif printf("%g ^ %d is %g\n", base, exponent, result); return 0; }
CMake提供了一個專門用於打包的工具CPack,用於配置生成各類平臺上的安裝包,包括二進制安裝包和源碼安裝包。
首先在頂層的CMakeLists.txt文件尾部添加下面幾行:
# 構建一個 CPack 安裝包 include (InstallRequiredSystemLibraries) set (CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/License.txt") set (CPACK_PACKAGE_VERSION_MAJOR "${Demo_VERSION_MAJOR}") set (CPACK_PACKAGE_VERSION_MINOR "${Demo_VERSION_MINOR}") include (CPack)
導入InstallRequiredSystemLibraries模塊,便於導入CPack模塊;
設置一些CPack相關變量,包括版權信息和版本信息
導入CPack模塊。
在頂層目錄下建立License.txt文件內如以下:
The MIT License (MIT) Copyright (c) 2018 Scorpio Studio Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
生成二進制安裝包:cpack -C CPackConfig.cmake
生成源碼安裝包:cpack -C CPackSourceConfig.cmake
上述兩個命令都會在目錄下建立3個不一樣格式的二進制包文件:demo-1.0.1-Linux.tar.gzdemo-1.0.1-Linux.tar.Zdemo-1.0.1-Linux.sh3個二進制包文件所包含的內容是徹底相同的。