CMake是1999年推出的開源自由軟體計劃,目的是提供不同平臺之間共同的編譯環境。他的特點有:
CMake的執行流程簡單來說是
另外一點值得注意的是cmake本身沒有提供uninstall功能。
CMake語法的格式為命令字串(參數)
,而相關規範可以分為「list和字串」、「變數」、「流程控制」、「Quotation」。分別討論如下:
CMake的基本單位是字串,而多個字串可以透過空白或是;
組合成字串list。
有興趣的可以直接剪貼下面程式存成CMakeLists.txt後下cmake .看看結果。
set(xxx a b c d)
message(${xxx})
set(xxx e;f;g;h)
message(${xxx})
變數使用set
命令設定,格式為set(變數名稱 指定的值)
。使用${變數名稱}
取值。
有興趣的可以直接剪貼下面程式存成CMakeLists.txt後下cmake .看看結果。
set(xxx a b c d)
message(${xxx})
set(xxx e;f;g;h)
message(${xxx})
另外這邊也列出了CMake內建好用的變數。
流程控制又可以分成條件執行、迴圈、和巨集等情況討論:
直接看範例,這個範例單純從command line吃變數值,做字串比對。
if ( NOT DEFINED test)
message("Use: cmake -Dtest:STRING=val to test")
elseif(${test} STREQUAL yes)
message("if: ${test}")
elseif(${test} STREQUAL test1)
message("else if: ${test}")
else()
message("else: ${test}")
endif()
有幾點需要說明:
DEFINED
判斷是否該變數有被定義。-D變數名稱:變數型態=變數值
來設定CMakeList.txt內部的變數。我們第一次 不從command 帶參數 執行結果:
$ cmake .
-- The C compiler identification is GNU
...
Use: cmake -Dtest:STRING=val to test
接下來我們從command 帶參數 重新執行一次:
$ cmake . -Dtest:STRING=yes
if: yes
然後 不從command 帶參數 再執行會發現test變數變成yes了
$ cmake .
if: yes
看一下目前目錄會發現新的檔案CMakeCache.txt
,找一下裡面的字串test
會看到
$ grep test CMakeCache.txt
...
test:STRING=yes
...
**結論就是CMake的確有cache,而且不小心cache會影響到執行的結果。**另外其實set(..)
裡面也可以cache行為的相關參數,這邊就先跳過不談。
兩種為主,foreach
和while
,直接看範例。
foreach
為何有cmake_minimum_required(VERSION 2.8)
呢?因為不打執行cmake .
會產生警告。有興趣的可以打cmake --help-policy CMP0000
看說明。cmake_minimum_required(VERSION 2.8)
set(xxx e;f;g;h)
foreach(i ${xxx})
message(${i})
endforeach()
執行結果如下:
$ cmake .
e
f
g
h
cmake_minimum_required(VERSION 2.8)
set(i 0)
while(i LESS 10)
message(${i})
math(EXPR i "${i} + 1")
endwhile()
這兩個差別是**在函數內產生的變數scope只存在函數內,而巨集是全域的。**直接看範例,範例中的巨集和函數都是在內部產生變數並且印出傳進來的參數。可以仔細看輸出結果的確函數內的變數呼叫完後就消失了。
cmake_minimum_required(VERSION 2.8)
macro(mac_print msg)
set(mac "macro")
message("${mac}: ${msg}")
endmacro(mac_print)
function(func_print msg)
set (func "func")
message("${func}: ${msg}")
endfunction(func_print)
mac_print("test macro")
message("check var in macro: ${mac}")
func_print("test function")
message("check var in function: ${func}")
執行結果如下
$ cmake .
macro: test macro
check var in macro: macro
func: test function
check var in function:
"
表示\n``\t
\${var}
印出來就是${VAR}
簡單的語法如下,詳細資料請參考這邊
install(TARGETS 執行檔名稱 DESTINATION 安裝目錄路徑)
install(TARGETS 函式庫名稱 LIBRARY DESTINATION 安裝目錄路徑)
install(FILES Header檔名稱 DESTINATION 安裝目錄路徑)
這些安裝描述都是允許多個檔案。另外你可以在執行cmake帶-DCMAKE_INSTALL_PREFIX=安裝目錄
指定安裝的top目錄,或是make DESTDIR=安裝目錄
也有同樣效果。
前面有提到in-place和out-place的編譯方式。他們方式的差別是:
cmake . && make
mkdir build && cd build && cmake ../ && make
範例程式細節在這邊,檔案各別分配到src
, include
, libs
這三個目錄。不想看code只要知道每個檔案都有參考到某個自訂的header file就好了。
├── include
│ ├── liba.h
│ └── libb.h
├── libs
│ ├── liba.c
│ └── libb.c
└── src
└── test.c
先暖身一下,只要在project最上層放一個CMakeLists.txt就好了。
這版本CMakeLists.txt不難理解,就做
top 目錄的CMakeLists.txt如下:
CMake top 目錄的CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
# Project data
project(testcmake)
# Directories
set(SRC_DIR src)
set(LIB_DIR libs)
set(INC_DIR include)
# Release mode
set(CMAKE_BUILD_TYPE Debug)
# Compile flags
set(CMAKE_C_FLAGS "-Wall -Werror")
# I like verbose, must after project, do not know why
set(CMAKE_VERBOSE_MAKEFILE true)
# Where to include?
include_directories(${INC_DIR})
# Files to compile
set(test_SRCS ${SRC_DIR}/test.c ${LIB_DIR}/liba.c ${LIB_DIR}/libb.c)
add_executable(${PROJECT_NAME} ${test_SRCS})
這邊可以看到和原本的差別只有多了CMakeLists.txt檔而已。
├── CMakeLists.txt
├── include
│ ├── liba.h
│ └── libb.h
├── libs
│ ├── liba.c
│ └── libb.c
└── src
└── test.c
要編譯函式庫,要在top目錄下的CMakeLists.txt做以下的修改
add_library(檔案名稱 函式庫名稱)
告訴CMake要搬把哪些檔案編譯函式庫
add_library(檔案名稱 SHARED 函式庫名稱)
就變成shared library了。target_link_libraries(執行檔名稱 函式庫名稱)
top 目錄的CMakeLists.txt如下:
CMake top 目錄的CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
# Project data
project(testcmake)
# Directories
set(SRC_DIR src)
set(LIB_DIR libs)
set(INC_DIR include)
# Release mode
set(CMAKE_BUILD_TYPE Debug)
# Compile flags
set(CMAKE_C_FLAGS "-Wall -Werror")
# I like verbose, must after project, do not know why
set(CMAKE_VERBOSE_MAKEFILE true)
# Where to include?
include_directories(${INC_DIR})
# Build libraries
set(liba_SRCS ${LIB_DIR}/liba.c)
set(libb_SRCS ${LIB_DIR}/libb.c)
add_library(a SHARED ${liba_SRCS})
add_library(b SHARED ${libb_SRCS})
# Build binary
set(test_SRCS ${SRC_DIR}/test.c)
add_executable(${PROJECT_NAME} ${test_SRCS})
target_link_libraries(${PROJECT_NAME} a b)
要做的事情很簡單,就是
src
和libs
下面加入CMakeLists.txt,描述編譯行為add_subdirectory(子目錄名稱)
把要編譯的子目錄加進去所以我們現在目錄樹狀結構會變成src和lib目錄都有CMakeLists.txt
├── CMakeLists.txt
├── include
│ ├── liba.h
│ └── libb.h
├── libs
│ ├── CMakeLists.txt
│ ├── liba.c
│ └── libb.c
├── readme.txt
└── src
├── CMakeLists.txt
└── test.c
每個目錄的CMakeLists.txt列出如下
CMake CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
# Project data
project(testcmake)
# Directories
set(SRC_DIR src)
set(LIB_DIR libs)
set(INC_DIR include)
# Release mode
set(CMAKE_BUILD_TYPE Debug)
# Compile flags
set(CMAKE_C_FLAGS "-Wall -Werror")
# I like verbose, must after project, do not know why
set(CMAKE_VERBOSE_MAKEFILE true)
# Where to include?
include_directories(${INC_DIR})
# Build library in libs directory or not?
# Dive into libs directory
add_subdirectory(${SRC_DIR})
add_subdirectory(${LIB_DIR})
CMake libs/CMakeLists.txt
# Build binary, inherit setting from parent
set(liba_SRCS liba.c)
set(libb_SRCS libb.c)
add_library(a ${liba_SRCS})
add_library(b ${libb_SRCS})
CMake src/CMakeLists.txt
# Build binary
set(test_SRCS test.c)
add_executable(${PROJECT_NAME} ${test_SRCS})
target_link_libraries(${PROJECT_NAME} a b)
這邊就是單純把前面的install()命令套用到每一個目錄下的CMakeLists.txt。由於我們也要安裝header檔,所以在include目錄下面會新增CMakeLists.txt描述安裝header的細節。
所以我們現在目錄樹狀結構會變成每個目錄都有CMakeLists.txt
├── CMakeLists.txt
├── include
│ ├── CMakeLists.txt
│ ├── liba.h
│ └── libb.h
├── libs
│ ├── CMakeLists.txt
│ ├── liba.c
│ └── libb.c
├── readme.txt
└── src
├── CMakeLists.txt
└── test.c
而各CMakeLists.txt新增的描述為
CMake CMakeLists.txt
add_subdirectory(${INC_DIR})
CMake libs/CMakeLists.txt
install(TARGETS a b LIBRARY DESTINATION lib)
CMake src/CMakeLists.txt
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
CMake include/CMakeLists.txt
install(FILES liba.h libb.h DESTINATION include)
$ cmake ../ -DCMAKE_INSTALL_PREFIX=`pwd`/test && make && make install
...
$ tree test
test/
├── bin
│ └── testcmake
├── include
│ ├── liba.h
│ └── libb.h
└── lib
├── liba.so
└── libb.so
本篇文章簡單介紹了CMake的語法,以及示範用CMake產生執行檔和函式庫。但是CMake還有太多東西值得去注意,例如把字串代換到程式碼,config.h的建立,搜尋depend 套件等。這部份以後有緣份會用到再跟各位分享。