這是一個(gè)來(lái)自官網(wǎng)的step-by-step的CMake教程
起點(diǎn)(Step 1)
參看下面的CMakeLists.txt文件,最簡(jiǎn)單的一個(gè)工程需要有一個(gè)這樣的cmake文件,一共就這么兩行
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
add_executable (Tutorial tutorial.cxx)
注意到這里都是用的小寫的命令,在cmake文件里面大小寫不嚴(yán)格區(qū)分,都可以用。
add_executable添加一個(gè)可編譯的目標(biāo)到工程里面
add_executable(<name> [WIN32] [MACOSX_BUNDLE]
[EXCLUDE_FROM_ALL]
source1 [source2 ...])
- name: 工程所要構(gòu)建的目標(biāo)名稱
- WIN32/..: 目標(biāo)app運(yùn)行的平臺(tái)
- source1:構(gòu)建目標(biāo)App的源文件
tutorial.cxx的源代碼計(jì)算一個(gè)數(shù)的平方根。第一版的代碼很簡(jiǎn)單,參看如下:
// 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, "Uage: %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;
}
編寫完上面兩個(gè)文件以后,在根目錄下新建一個(gè)build目錄
.
├── build
├── CMakeLists.txt
└── tutorial.cxx
然后運(yùn)行如下命令
$ cd buld
$ CMake ..
$ make
- CMake會(huì)自動(dòng)加載上級(jí)目錄里面的CMakeLists.txt文件,編譯所需的文件都會(huì)生成在build目錄下
- make之后會(huì)生成可執(zhí)行文件Tutorial
添加一個(gè)版本號(hào)和配置的頭文件
修改CMakeList.txt來(lái)添加version number:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
# 版本號(hào).
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
# 配置一個(gè)頭文件來(lái)傳遞一些CMake設(shè)置到源代碼
configure_file (
"${PROJECT_SOURCE_DIR}/TutorialConfig.h.in"
"${PROJECT_BINARY_DIR}/TutorialConfig.h"
)
# 添加TutorialConfig.h的路徑到頭文件的搜索路徑
include_directories("${PROJECT_BINARY_DIR}")
# 添加目標(biāo)可執(zhí)行文件
add_executable(Tutorial tutorial.cxx)
configure_file會(huì)拷貝一個(gè)文件到另一個(gè)目錄并修改文件內(nèi)容:
configure_file(<input> <output>
[COPYONLY] [ESCAPE_QUOTES] [@ONLY]
[NEWLINE_STYLE [UNIX|DOS|WIN32|LF|CRLF] ])
cmake會(huì)自動(dòng)定義兩個(gè)變量
- ${PROJECT_SOURCE_DIR}: 當(dāng)前工程最上層的目錄
- ${PROJECT_BINARY_DIR}: 當(dāng)前工程的構(gòu)建目錄(本例中新建的build目錄)
在這個(gè)例子里,configure_file命令的源文件是TutorialConfig.h.in,手動(dòng)創(chuàng)建這個(gè)文件:
// Tutorial工程的配置選項(xiàng)和設(shè)置
#define Tutorial_VERSION_MAJOR @Tutorial_VERSION_MAJOR@
#define Tutorial_VERSION_MINOR @Tutorial_VERSION_MINOR@
調(diào)用CMake的時(shí)候會(huì)在build目錄下新的頭文件,并且使用CMakeList.txt中定義的值來(lái)替換@Tutorial_VERSION_MAJOR@和@Tutorial_VERSION_MINOR@這兩個(gè)變量。
下一步要在源文件tutorial.cxx中包含這個(gè)配置的頭文件,就能使用這些版本信息了。
// 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, "Uage: %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;
}
運(yùn)行如下命令查看結(jié)果
$ cmake ..
$ make
$ ./Tutorial
這個(gè)時(shí)候控制臺(tái)會(huì)打印出來(lái)版本號(hào)
./Tutorial Version 1.0
Uage: ./Tutorial number
添加Library(Step 2)
現(xiàn)在我們嘗試添加一個(gè)library到我們的工程。這個(gè)lib提供一個(gè)自定義的計(jì)算平方根的函數(shù),用來(lái)替換編譯器提供的函數(shù)。
lib的源文件放到一個(gè)叫MathFunctions的子目錄中,在目錄下新建CMakeList.txt文件,添加如下的一行
add_library(MathFunctions mysqrt.cxx)
源文件mysqrt.cxx包含一個(gè)函數(shù)mysqrt用于計(jì)算平方根。代碼如下
#include "MathFunctions.h"
#include <stdio.h>
// a hack square root calculation using simple operations
double mysqrt(double x)
{
if (x <= 0) {
return 0;
}
double result;
double delta;
result = x;
// do ten iterations
int i;
for (i = 0; i < 10; ++i) {
if (result <= 0) {
result = 0.1;
}
delta = x - (result * result);
result = result + 0.5 * delta / result;
fprintf(stdout, "Computing sqrt of %g to be %g\n", x, result);
}
return result;
}
還需要添加一個(gè)頭文件MathFunctions.h以提供接口給main函數(shù)調(diào)用
double mysqrt(double x);
現(xiàn)在的目錄結(jié)構(gòu)
.
├── build
├── CMakeLists.txt
├── MathFunctions
│ ├── CMakeLists.txt
│ ├── MathFunctions.h
│ └── mysqrt.cxx
├── TutorialConfig.h.in
└── tutorial.cxx
CMakeLists.txt文件需要相應(yīng)做如下改動(dòng)
- 添加一行add_subdirectory來(lái)保證新加的library在工程構(gòu)建過(guò)程中被編譯。
- 添加新的頭文件搜索路徑MathFunction/MathFunctions.h。
- 添加新的library到executable。
CMakeList.txt的最后幾行變成了這樣:
include_directories ("${PROJECT_SOURCE_DIR}/MathFunctions")
add_subdirectory (MathFunctions)
# 添加executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Turorial MathFunctions)
最后要修改tutorial.cxx文件來(lái)調(diào)用自定義的mysqrt函數(shù)
最后編譯一下試試
$ cmake ..
$ make
$ ./Tutorial
看一下編譯的log
Scanning dependencies of target MathFunctions
[ 50%] Building CXX object MathFunctions/CMakeFiles/MathFunctions.dir/mysqrt.cxx.o
Linking CXX static library libMathFunctions.a
[ 50%] Built target MathFunctions
Scanning dependencies of target Tutorial
[100%] Building CXX object CMakeFiles/Tutorial.dir/tutorial.cxx.o
Linking CXX executable Tutorial
[100%] Built target Tutorial
這里編譯生成了新的庫(kù)libMathFunctions.a
現(xiàn)在我們考慮把MathFunctions庫(kù)配置成可選的
首先在最頂層的CMakeList.txt文件添加一個(gè)option
#需要用自定義的數(shù)學(xué)函數(shù)么?
option (USE_MYMATH
"Use tutorial provided math implementation" ON)
運(yùn)行ccmake ..會(huì)跳出來(lái)配置的GUI,在GUi中會(huì)看到新添加的這個(gè)選項(xiàng),用戶可以根據(jù)需要進(jìn)行修改。
下一個(gè)改變是依據(jù)配置來(lái)判斷是否編譯和鏈接MathFunctions庫(kù)。按照如下所示的修改CMakeList.txt的末尾幾行:
# 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 executable
add_executable (Tutorial tutorial.cxx)
target_link_libraries (Turorial ${EXTRA_LIBS})
這個(gè)例子里還是用了變量(EXTRA_LIBS)來(lái)收集后面link進(jìn)可執(zhí)行文件的時(shí)候任意可選的庫(kù)。這是一個(gè)常用的方法,在工程非常大有很多optional的組件的時(shí)候,可以讓這個(gè)編譯文件保持干凈。
代碼的修改就更直接了(用宏定義隔離開(kāi)):
// 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,只要在TutorialConfig.h.in里面添加一行
#cmakedefine USE_MYMATH
安裝測(cè)試 (Step 3)
下一步我們會(huì)添加install規(guī)則和testing到工程。install規(guī)則非常直接。對(duì)于MathFunctions庫(kù),我們通過(guò)在MathFunctions的CMakeList文件中添加如下兩行來(lái)安裝庫(kù)和頭文件。
install (TARGETS MathFunctions DESTINATION bin)
install(FILES MathFunctions.h DESTINATION include)
對(duì)于應(yīng)用程序,為了安裝executable和配置頭文件,需要在最上層的CMakeList.txt文件中添加下面幾行
# add the install targets
install (TARGETS Tutorial DESTINATION bin)
install (FILES "${PROJECT_BINARY_DIR}/TutorialConfig.h" DESTINATION include)
到了這里你就可以構(gòu)建整個(gè)tutorial了,通過(guò)命令make install,系統(tǒng)會(huì)自動(dòng)安裝對(duì)應(yīng)的頭文件,庫(kù)以及可執(zhí)行文件。CMake變量CMAKE_INSTALL_PREFIX用來(lái)指定這些文件需要安裝到哪個(gè)根目錄。
添加測(cè)試用例也很直接,只要在最上層的CMakeList.txt文件添加一系列的基礎(chǔ)測(cè)試來(lái)驗(yàn)證應(yīng)用程序是否正常工作。
include(CTest)
# 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")
編譯完成后可以通過(guò)在命令行運(yùn)行ctest來(lái)執(zhí)行這些測(cè)試用例。如果你希望添加很多測(cè)試用例來(lái)測(cè)試不同的輸入值,這個(gè)時(shí)候推薦你創(chuàng)建一個(gè)宏,這樣添加新的case會(huì)更輕松:
#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")
每次調(diào)用do_test,都會(huì)添加一個(gè)新的test case到工程。
添加系統(tǒng)回顧 (Step 4)
下一步我們考慮在工程中添加一些代碼,這些代碼會(huì)依賴的某些特性在運(yùn)行的目標(biāo)平臺(tái)上可能沒(méi)有。比如說(shuō),我們添加了一些代碼,這些代碼需要用到log和exp函數(shù),但某些目標(biāo)平臺(tái)上可能沒(méi)有這些庫(kù)函數(shù)。如果平臺(tái)有l(wèi)og函數(shù)那么我們就是用log來(lái)計(jì)算平方根,我們首先通過(guò)CheckFunctionExists.cmake來(lái)測(cè)試一下是否有這些函數(shù),在最上層的CMakeList文件中添加如下內(nèi)容
# does this system provide the log and exp functions?
include (CheckFunctionExists)
check_function_exists (log HAVE_LOG)
check_function_exists (exp HAVE_EXP)
Next we modify the TutorialConfig.h.in to define those values if CMake found them on the platform as follows:
下一步,如果CMake發(fā)現(xiàn)平臺(tái)有我們需要的這些函數(shù),則需要修改TutorialConfig.h.in來(lái)定義這些值
// does the platform provide exp and log functions?
#cmakedefine HAVE_LOG
#cmakedefine HAVE_EXP
有一點(diǎn)很重要,就是log和exp的測(cè)試工作需要在配置TutorialConfig.h前完成。最后在mysqrt函數(shù)中我們可以提供一個(gè)可選的實(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
. . .
添加生成文件和生成器 (Step 5)
這一節(jié)中我們會(huì)演示一下怎么添加一個(gè)生成的源文件到引用程序的構(gòu)建過(guò)程中。例如說(shuō),我們希望在構(gòu)建過(guò)程中創(chuàng)建一個(gè)預(yù)先計(jì)算好的平方根表,然后把這個(gè)表格編譯進(jìn)我們的應(yīng)用程序。首先我們需要一個(gè)能生成這張表的程序。在MathFunctions子目錄中,定義一個(gè)新的源文件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;
}
注意到這里的需要傳遞正確的輸出文件給app,然后才會(huì)生成table。下一步是在MathFunctions的CMakeList.txt添加相應(yīng)的命令來(lái)編譯生成可執(zhí)行文件MakeTable,然后在編譯過(guò)程中運(yùn)行這個(gè)程序。如下所示的添加一些命令:
# 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。
- 然后我們添加一個(gè)用戶命令指定怎么通過(guò)允許MakeTable來(lái)生成Table.h。
- 下一步需要讓CMAKE知道m(xù)ysqrt.cxx依賴生成的Table.h。把生成的Table.h添加到MathFunctions庫(kù)的資源列表中。
- 還需要添加當(dāng)前的bin的目錄添加到include的list中,這樣mysqrt.cxx編譯時(shí)候可以找到Table.h。
- 最后編譯包含Table.h的mysqrt.cxx來(lái)生成MathFunctions庫(kù)
到這兒最上層的CMakeList.txt文件就如下面所示:
cmake_minimum_required (VERSION 2.6)
project (Tutorial)
include(CTest)
# 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.in 文件如下:
// 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.txt如下:
# 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)