CMake快速入門教程

前言

Make工具因遵循不同的規範和標準,執行的Makefile的格式也是不同。主流的Make工具包括:

  • GNU Make
  • QT的qmake
  • 微軟的 MS nmake
  • BSD的 pmake

每個平臺都有自己的工具,則帶來了很大的平臺兼容性問題。CMake是一種跨平臺的編譯工具。

準備階段:

  • 安裝cmake
  • 編寫CMake配置文件CMakeLists.txt

基本流程:

  • 執行cmake PATH或者ccmake PATH命令,將CMakeLists.tx文件轉化爲所需要的Makefile文件。其中PATH爲CMAKLISTs.txt所在的目錄
  • 執行make命令,編譯原碼生成可執行程序,或者庫文件

單目錄,單文件

一個簡單的樣例:

# CMake的最低版本要求
cmake_minimum_required (VERIONS 2.8)

# 項目信息
project(Demo)

# 指定生成目標
add_executable(Demo demo.cc)

語法規則:

  • 命令、空格、註釋組成
  • 命令是不區分大小寫的
  • 符號#後面內容爲註釋
  • 參數之間使用空格分隔

單目錄,多文件

在實際項目中,一般不會只有一個demo.cc源碼文件,常爲一個目錄下多個源文件。假設目錄結構如下:

./Demo
  |-- main.cc
  |-- foo.cc
  |-- foo.h

此時的CMakeLists.txt內容可以更新爲如下:

# CMake的最低版本要求
cmake_minimum_required (VERIONS 2.8)

# 項目信息
project(Demo)

# 指定生成目標
add_executable(Demo demo.cc foo.cc)

即只需要在add_executable裏把依賴的foo.cc源文件添加進來即可。

但引入另一個問題:新增的源文件越來越多,總不能一個個手動加進來吧?

cmake中有 aux_source_directory命令,會查找指定目錄下所有源文件,並存放到指定的變量名中:

# CMake的最低版本要求
cmake_minimum_required (VERIONS 2.8)

# 項目信息
project(Demo)

# 查找當前目錄下所有源文件
aux_source_directory(. DIR_SRCS)

# 指定生成目標
add_executable(Demo ${DIR_SRCS})

多目錄,多文件

針對一個項目中包含了多了層級目錄,且每個目錄下都包含一些源文件。若目錄結構如下:

./Demo
  |-- main.cc
  |-- utils
  		|-- foo.cc
  		|-- foo.h

我們需要分別在Demoutils目錄下各自編寫一個CMakeLists.txt文件。

爲了方便,可以先將utils目錄裏的文件編譯成靜態庫,再由main函數調用。

根目錄中的CMakeLists.txt

# CMake的最低版本要求,如果不滿足則報錯
cmake_minimum_required (VERIONS 2.8 FATAL_ERROR)

# 項目信息
project(Demo)

# 添加 math 子目錄
add_subdirectory(utils)

# 指定生成目標
add_executable(Demo main.cc)

# 添加鏈接庫
target_link_libraries(Demo utils)
  • add_subdirectory表示會處理子目錄下的CMakeLists.txt和源代碼
  • target_link_libraries表示main執行文件需要鏈接一個名爲utils的鏈接庫

utils目錄中的CMakeLists.txt

# 查找當前目錄下的所有源文件、並保存到 DIR_LIB_SRCS 變量中
aux_source_directory(. DIR_LIB_SRCS)

# 生成鏈接庫
add_library(utils ${DIR_LIB_SRCS})
  • add_library會將所有源文件編譯爲靜態鏈接庫

其他編譯選項

如下是一個項目的CMakeLists.txt:

cmake_minimum_required(VERSION 2.8)

project(Demo)

# 定一個開關選項,支持cmake時通過 -DUSE_MYUTILS=OFF 指定
option(USE_MYUITLS "whether use customized math" ON)

# 自定義分支邏輯
if(USE_MYUITLS)
  include_directories("{PROJECT_SOURCE_DIR}/utils")
  add_subdirectory(utils)
  set(EXTRA_LIBS ${EXTRA_LIBS} utils)
endif(USE_MYUITLS)

# 查找目錄下所有源文件
aux_source_directory(. DIR_SRCS)
# 添加執行文件
add_executable(Demo ${DIR_SRCS})
# 鏈接靜態庫
target_link_libraries(Demo ${EXTRA_LIBS})

關於option的生效機制,這裏詳細解釋下。如main.cc中的代碼:

#include <stdio.h>
#include <stdlib.h>
#include "config.h"  // 此頭文件是cmake自動生成的

#ifdef USE_PYUTILS
  #include "utils/foo.h"
#else
  #include <foo.h>  // 假設標準庫有foo.h頭文件
#endif

爲了打通CMakeLists.txt一鍵便攜式配置,我們需要編寫一個config.h.in文件:

#cmakedefine USE_MYUTILS

這樣,在執行cmake命令時,就可以根據配置的參數,自動生成option相關的頭文件。若指定-DUSE_MYUTILS=ON時,config.h中的內容爲:

#define USE_MYUTILS

若爲OFF時,則config.h的內容爲:

/* #undef USE_MYUTILS */

安裝和測試

camke支持安裝測試,通過在生成Makefile後,使用make installmake test來執行。

接上述樣例,首先在utils/CMakeLists.txt中加上如下內容:

# 指定utils庫的安裝路徑
install(TARGETS utils DESTINATION bin)
install(FILES utils.h DESTINATION include)

Demo/CMakeLists.txt添加如下內容:

# 指定安裝路徑
install(TARGETS Demo DESTINATION bin)
install(FILES "${PROJECT_BINARY}/config.h" DESTINATION include)

原理 & 流程:

  • cmake編譯產出的Demo文件和庫libUtils.o文件將會被複制到/usr/local/bin
  • 頭文件utils.hconfig.h則會被賦值到/use/local/include
  • 可以通過CMKAE_INSTALL_PREFIX修改默認安裝的根目錄/usr/local

關於測試,CMake提供一個稱爲CTest的測試工具,通過add_test命令添加:

# 啓用測試
enable_testing()

# 測試程序是否成功運行, arg*爲函數接收的參數
add_test(test_run Demo arg1 arg2)

add_test(test_usage Demo)
set_tests_properties(test_usage PROPERTIES PASS_REGULAR_EXPRESSION "Usage: .....")

add_test(test_result Demo 10 2)
# 測試輸出的結果是否包含字符串 "is 100"
set_tests_properties(test_result PROPERTIES PASS_REGULAR_EXPRESSION "is 100")

支持gdb

CMake支持gdb的方式很簡單,只需指定Debug模式下開啓-g,一個簡單的樣例如下:

set(CMAKE_BUILD_TYPE "Debug")
# debug模式下編譯選項
set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb")
# release模式下編譯選項
set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")

小結

  • CMake的語法主要以命令、空格、參數來組成

  • 可以通過 set(<variable> <value>)設置變量的值

  • if語法

    if(<condition>)
      <commands>
    elseif(<condition>) # optional block, can be repeated
      <commands>
    else()              # optional block
      <commands>
    endif()
    
  • for語法

    # usage 1:
    foreach(<loop_var> <items>)
      <commands>
    endforeach()
    
    # usage 2:
    foreach(<loop_var> RANGE <stop>)
    
  • while語法

    while(<condition>)
      <commands>
    endwhile()
    

附錄:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章