CMake簡要教程

CMake是我非常喜歡且一直使用的工具。它不但能幫助我跨平臺、跨編譯器,而且最酷的是,它幫我節(jié)約了太多的存儲空間。特別是與水銀結(jié)合起來使用,其友好的體驗(yàn),足以給我們這些苦逼碼農(nóng)一絲慰藉。

以下內(nèi)容翻譯自官網(wǎng)教程

CMake Tutorial

A Basic Starting Point (Step 1)

最基本的就是將一個源代碼文件編譯成一個exe可執(zhí)行程序。對于一個簡單的工程來說,兩行的CMakeLists.txt文件就足夠了。這將是我們教程的開始。CMakeLists.txt文件看起來會像這樣:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable(Tutorial tutorial.cxx)

注意,在這個例子中,CMakeLists.txt都是使用的小寫字母。事實(shí)上,CMake命令是大小寫不敏感的,你可以用大寫,也可以用小寫,也可以混寫。tutorial.cxx源碼會計(jì)算出一個數(shù)的平方根。它的第一個版本看起來非常簡單,如下:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}

Adding a Version Number and Configured Header File

我們第一個要加入的特性是,在工程和可執(zhí)行程序上加一個版本號。雖然你可以直接在源代碼里面這么做,然而如果用CMakeLists文件來做的話會提供更多的靈活性。為了增加版本號,我們可以如此更改CMakeLists文件:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
 
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )
 
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories("${PROJECT_BINARY_DIR}")
 
# add the executable
add_executable(Tutorial tutorial.cxx) 

由于配置文件必須寫到binary tree中,因此我們必須將這個目錄添加到頭文件搜索目錄中。我們接下來在源碼目錄中創(chuàng)建了TutorialConfig.h.in文件,其內(nèi)容如下:

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@

當(dāng)CMake配置了這個頭文件, @Tutorial_VERSION_MAJOR@ 和 @Tutorial_VERSION_MINOR@ 的值將會被改變。接下來,我們修改了tutorial.cxx來包含配置的頭文件并且使用版本號。最終的源代碼如下所示:

// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
 
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"%s Version %d.%d\n",
            argv[0],
            Tutorial_VERSION_MAJOR,
            Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
  double inputValue = atof(argv[1]);
  double outputValue = sqrt(inputValue);
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}

最主要的變更是包含了TutorialConfig.h頭文件,并輸出了版本號。

Adding a Library (Step 2)

現(xiàn)在我們給工程添加一個庫。這個庫會包含我們自己的平方根實(shí)現(xiàn)。如此,應(yīng)用程序就可以使用這個庫而非編譯器提供的庫了。在這個教程中,我們將庫放入一個叫MathFunctions的子文件夾中。它會使用如下的一行CMakeLists文件:

add_library(MathFunctions mysqrt.cxx)

原文件mysqrt.cxx有一個叫做mysqrt的函數(shù)可以提供與編譯器的sqrt相似的功能。為了使用新的庫,我們需要在頂層的CMakeLists 文件中添加add_subdirectory的調(diào)用。我們也要添加一個另外的頭文件搜索目錄,使得MathFunctions/mysqrt.h可以被搜索到。最后的改變就是將新的庫加到可執(zhí)行程序中。頂層的CMakeLists 文件現(xiàn)在看起來是這樣:

include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions) 
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial MathFunctions) 

現(xiàn)在我們來考慮如何使得MathFunctions庫成為可選的。雖然在這個教程當(dāng)中沒有什么理由這么做,然而如果使用更大的庫或者當(dāng)依賴于第三方的庫時,你或許希望這么做。第一步是要在頂層的CMakeLists文件中加上一個選擇項(xiàng)。

# should we use our own math functions?
option (USE_MYMATH 
        "Use tutorial provided math implementation" ON) 

這個選項(xiàng)會顯示在CMake的GUI,并且其默認(rèn)值為ON。當(dāng)用戶選擇了之后,這個值會被保存在CACHE中,這樣就不需要每次CMAKE都進(jìn)行更改了。下面一步條件構(gòu)建和鏈接MathFunctions庫。為了達(dá)到這個目的,我們可以改變頂層的CMakeLists文件,使得其看起來像這樣:

# add the MathFunctions library?
#
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})

這里使用了USE_MYMATH來決定MathFunctions是否會被編譯和使用。注意這里變量EXTRA_LIBS的使用方法。這是保持一個大的項(xiàng)目看起來比較簡潔的一個方法。源代碼中相應(yīng)的變化就比較簡單了:


// A simple program that computes the square root of a number
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "TutorialConfig.h"
#ifdef USE_MYMATH
#include "MathFunctions.h"
#endif
 
int main (int argc, char *argv[])
{
  if (argc < 2)
    {
    fprintf(stdout,"%s Version %d.%d\n", argv[0],
            Tutorial_VERSION_MAJOR,
            Tutorial_VERSION_MINOR);
    fprintf(stdout,"Usage: %s number\n",argv[0]);
    return 1;
    }
 
  double inputValue = atof(argv[1]);
 
#ifdef USE_MYMATH
  double outputValue = mysqrt(inputValue);
#else
  double outputValue = sqrt(inputValue);
#endif
 
  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
} 

在源代碼中我們同樣使用了USE_MYMATH這個宏。它由CMAKE通過配置文件TutorialConfig.h.in來提供給源代碼。

#cmakedefine USE_MYMATH

Installing and Testing (Step 3)

接下來我們會為我們的工程增加安裝規(guī)則和測試支持。安裝規(guī)則是非常非常簡單的。對于MathFunctions庫我們安裝庫和頭文件只需要添加如下的語句:

install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)

對于應(yīng)用程序,我們只需要在頂層CMakeLists 文件中如此配置即可以安裝可執(zhí)行程序和配置了的頭文件:

# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"        
         DESTINATION include)

這就是所有需要做的?,F(xiàn)在你就可以編譯這個教程了,然后輸入make install(或者編譯IDE中的INSTALL目標(biāo)),則頭文件、庫和可執(zhí)行程序等就會被正確地安裝。CMake變量CMAKE_INSTALL_PREFIX被用來決定那些文件會被安裝在哪個根目錄下。添加測試也是一個相當(dāng)簡單的過程。在最頂層的CMakeLists文件的最后我們可以添加一系列的基礎(chǔ)測試來確認(rèn)這個程序是否在正確工作。

# does the application run
add_test (TutorialRuns Tutorial 25)
 
# does it sqrt of 25
add_test (TutorialComp25 Tutorial 25)
 
set_tests_properties (TutorialComp25 
  PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")
 
# does it handle negative numbers
add_test (TutorialNegative Tutorial -25)
set_tests_properties (TutorialNegative
  PROPERTIES PASS_REGULAR_EXPRESSION "-25 is 0")
 
# does it handle small numbers
add_test (TutorialSmall Tutorial 0.0001)
set_tests_properties (TutorialSmall
  PROPERTIES PASS_REGULAR_EXPRESSION "0.0001 is 0.01")
 
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
  PROPERTIES 
  PASS_REGULAR_EXPRESSION "Usage:.*number")

第一個測試簡單地確認(rèn)應(yīng)用是否運(yùn)行,沒有段錯誤或者其它的崩潰問題,并且返回0。這是CTest的最基本的形式。下面的測試都使用了PASS_REGULAR_EXPRESSION測試屬性來確認(rèn)輸出的結(jié)果中是否含有某個字符串。如果你需要添加大量的測試來判斷不同的輸入值,則你需要考慮創(chuàng)建一個類似于下面的宏:

#define a macro to simplify adding tests, then use it
macro (do_test arg result)
  add_test (TutorialComp${arg} Tutorial ${arg})
  set_tests_properties (TutorialComp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result})
endmacro (do_test)
 
# do a bunch of result based tests
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")

對do_test的任意一次調(diào)用,就有另一個測試被添加到工程中。

Adding System Introspection (Step 4)

接下來,我們來考慮添加一些有些目標(biāo)平臺可能不支持的代碼。在這個樣例中,我們將根據(jù)目標(biāo)平臺是否有l(wèi)og和exp函數(shù)來添加我們的代碼。當(dāng)然大多數(shù)平臺都是有這些函數(shù)的,只是本教程假設(shè)這兩個函數(shù)沒有被那么普遍地支持。如果平臺有l(wèi)og,那么在mysqrt中,就用它來計(jì)算平方根。我們首先使用CheckFunctionExists.cmake來測試這些函數(shù)的是否存在,在頂層的CMakeLists文件中:

# does this system provide the log and exp functions?
include (CheckFunctionExists.cmake)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)

接下來我們修改TutorialConfig.h.in來定義CMake是否找到這些函數(shù)的宏

// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP

重要的一點(diǎn)是,對tests和log的測試必須要在配置文件命令前完成。配置文件命令會使用CMake中的配置立馬配置文件。最后在mysqrt函數(shù)中我們提供了兩種實(shí)現(xiàn)方式:

// if we have both log and exp then use them
#if defined (HAVE_LOG) && defined (HAVE_EXP)
  result = exp(log(x)*0.5);
#else // otherwise use an iterative approach
  . . .

Adding a Generated File and Generator (Step 5)

在這一節(jié)當(dāng)中,我們會告訴你如何將一個生成的源文件加入到應(yīng)用程序的構(gòu)建過程中。在此例中,我們會創(chuàng)建一個預(yù)先計(jì)算好的平方根的表,并將這個表編譯到應(yīng)用程序中去。為了達(dá)到這個目的,我們首先需要一個程序來生成這樣的表。在MathFunctions這個子目錄下一個新的叫做MakeTable.cxx的源文件就是用來干這個的。


// A simple program that builds a sqrt table 
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
int main (int argc, char *argv[])
{
  int i;
  double result;
 
  // make sure we have enough arguments
  if (argc < 2)
    {
    return 1;
    }
  
  // open the output file
  FILE *fout = fopen(argv[1],"w");
  if (!fout)
    {
    return 1;
    }
  
  // create a source file with a table of square roots
  fprintf(fout,"double sqrtTable[] = {\n");
  for (i = 0; i < 10; ++i)
    {
    result = sqrt(static_cast<double>(i));
    fprintf(fout,"%g,\n",result);
    }
 
  // close the table with a zero
  fprintf(fout,"0};\n");
  fclose(fout);
  return 0;
}

注意到這張表是由一個有效的C++代碼產(chǎn)生的,并且輸出文件的名字是由參數(shù)代入的。下一步就是添加合適的命令到MathFunctions的CMakeLists文件中來構(gòu)建MakeTable這個可執(zhí)行程序,并且作為構(gòu)建過程中的一部分。完成它需要一些命令,如下:

# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
 
# add the command to generate the source code
add_custom_command (
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
  )
 
# add the binary tree directory to the search path for 
# include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
 
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h  )

首先,就像其它可執(zhí)行程序一樣,MakeTable被添加為可執(zhí)行程序。然后我們添加了一個自定義命令來詳細(xì)描述如何通過運(yùn)行MakeTable來產(chǎn)生Table.h。接下來,我們需要讓CMake知道m(xù)ysqrt.cxx依賴于生成的文件Table.h。這是通過往MathFunctions這個庫里面添加生成的Table.h來實(shí)現(xiàn)的。我們也需要添加當(dāng)前的生成目錄到搜索路徑中,從而Table.h可以被mysqrt.cxx找到。

當(dāng)這個工程被構(gòu)建時,它首先會構(gòu)建MakeTable這個可執(zhí)行程序。然后運(yùn)行MakeTable從而生成Table.h。最后,它會編譯mysqrt.cxx來生成MathFunctions library。

在這一刻,我們添加了所有的特征到最頂層的CMakeLists文件,它現(xiàn)在看起來是這樣的:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)
 
# The version number.
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
 
# does this system provide the log and exp functions?
include (${CMAKE_ROOT}/Modules/CheckFunctionExists.cmake)
 
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
 
# should we use our own math functions
option(USE_MYMATH 
  "Use tutorial provided math implementation" ON)
 
# configure a header file to pass some of the CMake settings
# to the source code
configure_file (
  "${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
  "${PROJECT_BINARY_DIR}/TutorialConfig.h"
  )
 
# add the binary tree to the search path for include files
# so that we will find TutorialConfig.h
include_directories ("${PROJECT_BINARY_DIR}")
 
# add the MathFunctions library?
if (USE_MYMATH)
  include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
  add_subdirectory (MathFunctions)
  set (EXTRA_LIBS ${EXTRA_LIBS} MathFunctions)
endif (USE_MYMATH)
 
# add the executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Tutorial  ${EXTRA_LIBS})
 
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h"        
         DESTINATION include)
 
# does the application run
add_test (TutorialRuns Tutorial 25)
 
# does the usage message work?
add_test (TutorialUsage Tutorial)
set_tests_properties (TutorialUsage
  PROPERTIES 
  PASS_REGULAR_EXPRESSION "Usage:.*number"
  )
 
 
#define a macro to simplify adding tests
macro (do_test arg result)
  add_test (TutorialComp${arg} Tutorial ${arg})
  set_tests_properties (TutorialComp${arg}
    PROPERTIES PASS_REGULAR_EXPRESSION ${result}
    )
endmacro (do_test)
 
# do a bunch of result based tests
do_test (4 "4 is 2")
do_test (9 "9 is 3")
do_test (5 "5 is 2.236")
do_test (7 "7 is 2.645")
do_test (25 "25 is 5")
do_test (-25 "-25 is 0")
do_test (0.0001 "0.0001 is 0.01")

TutorialConfig.h是這樣的:

// the configured options and settings for Tutorial
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
#cmakedefine USE_MYMATH
 
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP

最后MathFunctions的CMakeLists文件看起來是這樣的:

# first we add the executable that generates the table
add_executable(MakeTable MakeTable.cxx)
# add the command to generate the source code
add_custom_command (
  OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  DEPENDS MakeTable
  COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
  )
# add the binary tree directory to the search path 
# for include files
include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
 
# add the main library
add_library(MathFunctions mysqrt.cxx ${CMAKE_CURRENT_BINARY_DIR}/Table.h)
 
install (TARGETS MathFunctions DESTINATION bin)
install (FILES MathFunctions.h DESTINATION include)

Building an Installer (Step 6)

最后假設(shè)我們想要把我們的工程發(fā)布給別人從而讓他們?nèi)ナ褂?。我們想要同時給他們不同平臺的二進(jìn)制文件和源代碼。這與步驟3中的install略有不同,install是安裝我們從源代碼中構(gòu)建的二進(jìn)制文件。而在此例中,我們將要構(gòu)建安裝包來支持二進(jìn)制安裝以及cygwin,debian,RPMs等的包管理特性。為了達(dá)到這個目的,我們會使用CPack來創(chuàng)建平臺相關(guān)的安裝包。具體地說,我們需要在頂層CMakeLists.txt文件中的底部添加數(shù)行。

# build a CPack driven installer package
include (InstallRequiredSystemLibraries)
set (CPACK_RESOURCE_FILE_LICENSE  
     "${CMAKE_CURRENT_SOURCE_DIR}/License.txt")
set (CPACK_PACKAGE_VERSION_MAJOR "${Tutorial_VERSION_MAJOR}")
set (CPACK_PACKAGE_VERSION_MINOR "${Tutorial_VERSION_MINOR}")
set (CPACK_PACKAGE_VERSION_PATCH "${Tutorial_VERSION_PATCH}")
include (CPack)

這就是所有要做的。我們首先包含了InstallRequiredSystemLibraries。這個模塊將會包含當(dāng)前平臺所需要的所有運(yùn)行時庫。接下來,我們設(shè)置了一些CPack的變量來保存license以及工程的版本信息。版本信息利用了我們在早前的教程中使用到的變量。最后我們包含了CPack這個模塊來使用這些變量和你所使用的系統(tǒng)的其它特性來設(shè)置安裝包。

接下來一步是用通常的方式構(gòu)建工程,然后在CPack上運(yùn)行它。如果要構(gòu)建一個二進(jìn)制包你需要運(yùn)行:

cpack --config CPackConfig.cmake

如果要創(chuàng)建一個關(guān)代碼包你需要輸入

cpack --config CPackSourceConfig.cmake

Adding Support for a Dashboard (Step 7)

將測試結(jié)果上傳到dashboard上是非常簡單的。我們在早前的步驟中已經(jīng)定義了一些測試。我們僅需要運(yùn)行這些例程然后提交到dashboard上。為了包含對dashboards的支持,我們需要在頂層的CMakeLists文件中包含CTest模塊。

# enable dashboard scripting
include (CTest)

我們也創(chuàng)建了一個CTestConfig.cmake文件來指定這個工程在dashobard上的的名字。

set (CTEST_PROJECT_NAME "Tutorial")

CTest會讀這個文件并且運(yùn)行它。如果要創(chuàng)建一個簡單的dashboard,你可以在你的工程上運(yùn)行CMake,改變生成路徑的目錄,然后運(yùn)行ctest -D Experimental。你的dashboard的結(jié)果會被上傳到Kitware的公共dashboard中。

最后,可以在這里下載到整個教程的源代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • CMake學(xué)習(xí) 本篇分享一下有關(guān)CMake的一些學(xué)習(xí)心得以及相關(guān)使用。 本文目錄如下: [1、CMake介紹] [...
    AlphaGL閱讀 12,452評論 11 79
  • 文章翻譯自:CMake Tutorial 第一步 | 第二步 | 第三步 | 第四步 | 第五步 | 第六步 | ...
    汪坤閱讀 14,486評論 1 23
  • 注:首發(fā)地址 1. 前言 當(dāng)在做 Android NDK 開發(fā)時,如果不熟悉用 CMake 來構(gòu)建,讀不懂 CMa...
    cfanr閱讀 24,808評論 1 53
  • 人生路上有很多畫過妝的禮物,他們常常藏在丑陋的外表之下,上天給過的痛苦體驗(yàn)有時只是想召喚人們停下匆忙的腳步,用心面...
    向陽花開開開閱讀 1,900評論 0 0
  • 兩個窮屌絲,兩張辦公桌,兩臺電腦加上N個兼職。不說創(chuàng)造多少盈利吧,好歹勉強(qiáng)維持生活,這公司在外人看來恐怕都是很不值...
    佰聞客_劉宣閱讀 411評論 0 1

友情鏈接更多精彩內(nèi)容