CMake簡介
原文:http://blog.gclxry.com/use-cmake-on-windows/
你或許聽過好幾種 Make 工具,例如 GNU Make ,QT 的 qmake ,微軟的 MS nmake,BSD Make(pmake),Makepp,等等。這些 Make 工具遵循着不同的規范和標准,所執行的 Makefile 格式也千差萬別。這樣就帶來了一個嚴峻的問題:如果軟件想跨平台,必須要保證能夠在不同平台編譯。而如果使用上面的 Make 工具,就得為每一種標准寫一次 Makefile ,這將是一件讓人抓狂的工作。
CMake就是針對上面問題所設計的工具:它首先允許開發者編寫一種平台無關的 CMakeList.txt 文件來定制整個編譯流程,然后再根據目標用戶的平台進一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。從而做到“Write once, run everywhere”。
Windows上使用CMake
Windows上使用CMake也很方便,除了傳統的命令行方式使用CMake,還有一個簡單的GUI程序cmake-gui.exe來使用CMake。
安裝CMake
Windows上安裝CMake很簡單,去https://cmake.org/ 上面下載最新的CMake安裝包就可以了。安裝的時候還可以選擇是否把CMake加到系統的PATH中,如下圖所示:
為了方便起見,可以把CMake加到系統PATH中。
CMake的GUI用法
安裝好CMake,會創建一個快捷方式,點擊運行就會運行CMake-gui.exe,這個是CMake的GUI程序,以下圖所示:
source code編輯框就是輸入代碼的所在的路徑,這個路徑能夠找到一個CMakeLists.txt文件。
build the binaries編輯框就是編譯輸出的中間文件和最終的二進制文件的目錄。
因為CMake最終通過CMakeLists.txt文件生成Windows上對應的vs工程文件,不同的vs版本也會影響到最終生成vs工程文件,所以configure對話框就是選擇代碼編譯工具的,如圖所示:
下面以google test工程的代碼為例來使用CMake-gui,輸入google test對應的路徑,點擊Generate按鈕就會在E:/googletest/googletest/build目錄生成vs編譯工程文件:
用vs打開gtest.sln文件,就可以編譯google test代碼了。
CMake的命令行用法
CMake的命令行用法比GUI的用法復雜,但是功能更加強大,值得一學。以下是CMake命令行調用的方法:
1
2
3
4
5
|
cmake [<options>] (<path-to-source> | <path-to-existing-build>)
cmake [(-D<var>=<value>)...] -P <cmake-script-file>
cmake --build <dir> [<options>] [-- <build-tool-options>...]
cmake -E <command> [<options>...]
cmake --find-package <options>...
|
生成編譯工程文件
cmake <CMakeLists.txt_Path>就是生成可以編譯工程文件。當時運行的目錄在哪里,生成的可編譯工程文件就在哪個目錄。比如CMakeLists.txt文件在f:cmake目錄,而當時在f:cmakebuild目錄運行cmake ..,則生成的編譯工程文件在f:cmakebuild目錄。
也可以再生成編譯工程文件的時候通過-D來添加變量值,比如CMakeLists.txt內容如下:
1
2
3
4
|
cmake_minimum_required (VERSION 2.6)
project (a)
message(${EXECUTABLE_OUTPUT_PATH})
add_executable(b b.cpp)
|
我們可以通過-D選擇來設置EXECUTABLE_OUTPUT_PATH的值,也是編譯的文件的輸出目錄:
1
|
cmake -D EXECUTABLE_OUTPUT_PATH="another_output" ..
|
這樣,我們就給CMakeLists.txt編譯腳本傳遞了新的EXECUTABLE_OUTPUT_PATH值。
編譯工程
CMake除了生成編譯工程文件,它也可以調用系統的編譯工程來編譯工程,如:
1
|
cmake --build .
|
默認是編譯debug模式,也可以傳遞在–后面傳遞MSBUILD參數來控制:
1
|
cmake --build . -- /p:Configuration=Release
|
命令行工具模式
CMake有一個-E的命令行工具模式,提供了一些常用的功能,比復制文件、創建目錄、讀寫注冊表、讀寫環境變量、計算md5值等等。腳本可以調用這些功能。
編寫CMakeLists.txt
創建可以執行程序工程
首先從創建一個最簡單的可執行程序開始,CMakeLists.txt內容如下:
1
2
3
4
5
|
cmake_minimum_required (VERSION 2.6)
project (LearnCMake)
message(${LearnCMake_SOURCE_DIR})
message(${LearnCMake_BINARY_DIR})
add_executable(FirstExecutable hello_world.cpp)
|
第1行是cmake需要的最低版本,目前這個是VERSION 2.6,一般不用修改。
第2~4行表示我們創建了一個名為LearnCMake工程,對應生成一個LearnCMake.sln。project函數表示創建一個工程。同時,也生成了4個變量:
- PROJECT_SOURCE_DIR, <PROJECT-NAME>_SOURCE_DIR。工程的源代碼目錄。
- PROJECT_BINARY_DIR, <PROJECT-NAME>_BINARY_DIR。工程的二進制文件目錄。
第5行表示添加一個名為FirstExecutable的可執行程序項目,它的源代碼為hello_world.cpp。下面是add_executable的完整用法:
1
2
3
|
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
|
默認的是創建控制台工程,加上WIN32表示創建的是win32工程,如下:
1
|
add_executable(FirstExecutable WIN32 hello_world.cpp)
|
后面是項目的代碼,可以添加多個代碼文件,用空格分開。
創建庫工程
創建庫工程跟創建可執行程序工程類似,創建庫工程使用add_library函數,如下例子:
1
2
3
4
5
6
|
cmake_minimum_required (VERSION 3.0)
project (LearnCMake)
add_library(FirstLibrary first_library.cpp)
add_library(SecondLibrary second_library.cpp)
add_executable(FirstExecutable hello_world.cpp)
target_link_libraries(FirstExecutable FirstLibrary)
|
add_library的用法如下:
1
2
3
|
add_library(<name> [STATIC | SHARED | MODULE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
|
默認的是靜態庫,也可以顯式的設置庫是否為靜態庫、動態庫或者是模塊。另外BUILD_SHARED_LIBS也可控制編譯成哪種庫。
target_link_libraries用來鏈接庫,用法如下:
1
2
|
target_link_libraries(<target> [item1 [item2 [...]]]
[[debug|optimized|general] <item>] ...)
|
set設置變量
add_library、add_executable都可以添加多個源文件,如下:
1
2
3
4
|
cmake_minimum_required (VERSION 3.0)
project (LearnCMake)
add_executable(FirstExecutable main.cpp app_util.h app_util.cpp)
add_library(FirstLibrary app_util.h app_util.cpp)
|
我們可以通過定義一個AppUtilSrc變量來代替app_util.h app_util.cpp,如下:
1
2
3
4
5
|
cmake_minimum_required (VERSION 3.0)
project (LearnCMake)
set(AppUtilSrcs app_util.h app_util.cpp)
add_executable(FirstExecutable main.cpp ${AppUtilSrcs})
add_library(FirstLibrary ${AppUtilSrcs})
|
效果是跟上面等價的。還可以累積值:
1
2
|
set(AppUtilSrcs app_util.h app_util.cpp)
set(AppUtilSrcs ${AppUtilSrcs} b.cpp)
|
這樣AppUtilSrcs就代表着3個文件了。
設置編譯模式
設置mt編譯模式:
1
2
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
|
設置md編譯模式:
1
2
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MD")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MDd")
|
默認是多字節模式,設置成unicode模式:
1
|
add_definitions(-D_UNICODE)
|
另外add_definitions還可以設置其他的選項。
添加其他CMakeLists.txt
一個CMakeLists.txt里面的target如果要鏈接其他CMakeLists.txt中的target,可以使用add_subdirectory,我們以使用googletest庫為例:
1
2
3
4
5
6
7
8
|
add_subdirectory("../thirdparty/googletest/googletest/" gtest)
file(GLOB_RECURSE gtest_lib_head_files "../thirdparty/googletest/googletest/*.h")
source_group("gtest" FILES ${gtest_lib_head_files})
include_directories("../thirdparty/googletest/googletest/include")
aux_source_directory("./pbase_unittest/src" pbase_unittest_src_files)
file(GLOB_RECURSE pbase_unittest_include_files "./pbase_unittest/include/*.h")
add_executable(pbase_unittest ${pbase_unittest_src_files} ${pbase_unittest_include_files} ${gtest_lib_head_files})
target_link_libraries(pbase_unittest gtest)
|
代碼控制
如果想把./pbase/src目錄下的所有源文件加入到工程,可以用aux_source_directory把某個目錄下的源文件加入到某個變量中,稍后就可以使用這個變量代表的代碼了,如:
1
2
|
aux_source_directory("./pbase/src" pbase_lib_src_files)
add_library(pbase ${pbase_lib_src_files})
|
添加頭文件包含目錄是:
1
|
include_directories("../thirdparty/googletest/googletest/include")
|
但是include_directories中的文件不會體現先visual studio工程中,而aux_source_directory只會添加源文件,會忽略頭文件,如果想生存的visual studio工程里面也包含頭文件,可以這樣:
1
2
3
|
# add head files
file(GLOB_RECURSE pbase_lib_head_files "./pbase/include/*.h")
add_library(pbase ${pbase_lib_head_files})
|
如果想生存visual studio中的filter,可以使用source_group:
1
2
|
file(GLOB_RECURSE gtest_lib_head_files "../thirdparty/googletest/googletest/*.h")
source_group("gtest" FILES ${gtest_lib_head_files})
|
最終添加頭文件到工程里標准模板是:
1
2
3
4
|
file(GLOB_RECURSE gtest_lib_head_files "../thirdparty/googletest/googletest/*.h")
source_group("gtest" FILES ${gtest_lib_head_files})
include_directories("../thirdparty/googletest/googletest/include")
add_executable(pbase_unittest ${gtest_lib_head_files})
|