CMakeLists用法總結(jié)

分一下幾個(gè)方面來描述:

1. 每一個(gè)LIB要編譯成靜態(tài)庫或動(dòng)態(tài)庫如何描述野宜,每一個(gè)TOOL要編譯成可執(zhí)行文件如何描述太防?
2. LIB和TOOL可能會(huì)依賴于其他LIB妻顶,該如何描述酸员?
3. 每個(gè)LIB和TOOL都會(huì)include很多頭文件,相同的頭文件如何處理讳嘱,私有的頭文件如何處理幔嗦?
4. CMake中的一些用法
5. LLVM中用到的技巧

先舉一個(gè)簡單的例子:
//
└─tutorial.c
└─TutorialConfig.h.in
└─CMakeLists.txt
└─include
?└─mysqrt.h
└─lib
└─CMakeLists.txt
?└─MakeTable.c
?└─mysqrt.c

在這個(gè)例子中,Top level和lib中會(huì)分別定義一個(gè)CMakeLists.txt用來指定編譯規(guī)則沥潭。這個(gè)例子主要做了以下幾件事:
1)用宏USE_MYMATH來控制是否使用自己定義的sqrt()

  1. 定義了一個(gè)MakeTable.c文件生成sqrt()的一個(gè)結(jié)果表邀泉,在自己定義的sqrt()函數(shù)中查表得出簡單的結(jié)果。

3)或者通過exp和log來計(jì)算sqrt的值钝鸽,但前提是當(dāng)前庫支持exp和log汇恤,這些都通過check_function_exists檢查是否支持,如果支持相應(yīng)的宏就會(huì)打開拔恰。

4)通過CTest因谎,定義相應(yīng)的測試用例,檢查執(zhí)行結(jié)果是否正確颜懊。

Top Level中的
Tutorial.c:

// 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 "mysqrt.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);
  printf ("call local sqrt\n");
#else
  double outputValue = sqrt(inputValue);
#endif

  fprintf(stdout,"The square root of %g is %g\n",
          inputValue, outputValue);
  return 0;
}

CMakeList.txt定義如下:

cmake_minimum_required (VERSION 2.6)
project (Tutorial)

# The version number.
//"set"用來給對應(yīng)的變量賦值
set (Tutorial_VERSION_MAJOR 1)
set (Tutorial_VERSION_MINOR 0)
set(include_dir ${CMAKE_CURRENT_SOURCE_DIR}/include)

//"MESSAGE"用來打印信息财岔,包括變量的值
MESSAGE(STATUS "include folder: " ${include_dir})

//"option"用來定義宏,"ON"表示打開饭冬,"OFF"表示關(guān)閉
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"用來指定build時(shí)需要的頭文件路徑
//"PROJECT_BINARY_DIR"是內(nèi)置變量,表示工程編譯的目錄揪阶,也就是--prefix指定的目錄
include_directories("${PROJECT_BINARY_DIR}" "${include_dir}")

if (USE_MYMATH)
    include_directories(${include_dir})
    //"add_subdirectory"用來添加外部項(xiàng)目目錄昌抠,將指定的目錄添加到build任務(wù)列表中。
    //在這里"lib"目錄是否需要編譯鲁僚,是通過宏USE_MYMATH控制炊苫,
    //如果這個(gè)宏打開,就需要編譯"lib"冰沙,也就是需要通過"add_sudirectory"添加"lib"
    add_subdirectory(lib)
    set(EXTRAL_LIBS ${EXTRAL_LIBS} MathFunctions)
endif(USE_MYMATH)

# add the executable
// "add_executable"用來指定生成可執(zhí)行文件侨艾,這里會(huì)生成Tutorial.out或者Tutorial.exe
add_executable(Tutorial tutorial.c)

// 由于tutorial.c中需要用到sqrt函數(shù),這個(gè)函數(shù)要么來自于系統(tǒng)庫拓挥,要么來自于自定義庫唠梨,
//因此這里需要通過"target_link_libraries"指定鏈家的libraries
target_link_libraries(Tutorial m ${EXTRAL_LIBS}) # link to -lm

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

//引入CTest, 添加相應(yīng)的測試用例,正確的測試結(jié)果通過set_tests_properties指定侥啤。
//在這里也就是說sqrt(25) = 5,否則就會(huì)failed
#include(CTest)
MESSAGE(STATUS "Meditator testttttttttttt")
enable_testing()
add_test (TutorialRuns Tutorial 25)
add_test (TutorialComp25 Tutorial 25)
set_tests_properties(TutorialComp25 PROPERTIES PASS_REGULAR_EXPRESSION "25 is 5")

//check_function_exists檢查當(dāng)前庫是否支持log和exp当叭,如果支持相應(yīng)的宏就會(huì)打開。
include(CheckFunctionExists)
check_function_exists(log HAVE_LOG)
check_function_exists(exp HAVE_EXP)

mysqrt.c文件如下:

#include "mysqrt.h"
#include "Table.h"
double mysqrt(double input)
{
#if defined (HAVE_LOG) && defined (HAVE_EXP)
    return = exp(log(x) * 0.5);
#else
    if (input < 10)
        return sqrtTable[(int)input];
    else
        return 0;
#endif
}

MakeTable.c文件主要定義一個(gè)數(shù)組盖灸,保存一組sqrt(i)的值蚁鳖,其內(nèi)容如下:

// 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((double)i);
    fprintf(fout,"%g,\n",result);
    }

  // close the table with a zero
  fprintf(fout,"0};\n");
  fclose(fout);
  return 0;
}

lib下的CMakeLists.txt的內(nèi)容如下:

nclude_directories(${include_dir})
MESSAGE(STATUS "Meditator test cmake_current_souce_dir" ${CMAKE_CURRENT_SOURCE_DIR})

set(MAKETABLE_SRC MakeTable.c)
add_executable(MakeTable ${MAKETABLE_SRC})
target_link_libraries(MakeTable m) # link to -lm

//通過執(zhí)行COMMAND,產(chǎn)生Table.h文件
add_custom_command (
    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/Table.h
    COMMAND MakeTable ${CMAKE_CURRENT_BINARY_DIR}/Table.h
    DEPENDS MakeTable
    )

include_directories(${CMAKE_CURRENT_BINARY_DIR})

set(MYSQRT_SRC mysqrt.c)
add_library(MathFunctions ${MYSQRT_SRC} ${CMAKE_CURRENT_BINARY_DIR}/Table.h)

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

下面解釋幾個(gè)CMake內(nèi)置變量
1.CMAKE_CURRENT_SOURCE_DIR
指的是當(dāng)前處理的 CMakeLists.txt 所在的路徑赁炎,處理不同目錄下的CMaleLists醉箕,這個(gè)宏的值是不一樣的。在Top level中這個(gè)值就是Top Level所在目錄,在lib中這個(gè)值就是lib所在的目錄讥裤。
2. CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
這三個(gè)變量指代的內(nèi)容是一致的,如果是 in source 編譯,指得就是工程頂層目錄,如果是 out-of-source 編譯,指的是工程編譯發(fā)生的目錄放棒。PROJECT_BINARY_DIR 跟其他指令稍有區(qū)別,現(xiàn)在,你可以理解為他們是一致的。
3. PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR
用來表示當(dāng)前project所在的目錄
4. INCLUDE_DIRECTORIES
用來指定build時(shí)需要的頭文件路徑
5. LINK_DIRECTORIES
用來指定第三方庫所在的路徑坞琴,可以理解為gcc中的-L
6. TARGET_LINK_LIBRARIES
用來指定工程所依賴的庫
7. OPTION (USE_MYMATH "Use tutorial provided math implementation" ON)
在CMakeLists用option來控制當(dāng)前USE_MYMATH是否打開哨查,并在cmake-gui中會(huì)有對應(yīng)的選項(xiàng)出現(xiàn),默認(rèn)是ON
8. Function的用法

function(<name>[arg1 [arg2 [arg3 ...]]])
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...

endfunction(<name>)

定義一個(gè)函數(shù)名為<name>剧辐,參數(shù)名為arg1 arg2 arg3(…)寒亥。 函數(shù)體內(nèi)的命令直到函數(shù)被調(diào)用的時(shí)候才會(huì)去執(zhí)行。其中ARGC變量表示傳遞給函數(shù)的參數(shù)個(gè)數(shù)荧关。 ARGV0, ARGV1, ARGV2代表傳遞給函數(shù)的實(shí)際參數(shù)溉奕。 ARGN代表超出最后一個(gè)預(yù)期參數(shù)的參數(shù)列表,例如忍啤,函數(shù)原型聲明時(shí)加勤,只接受一個(gè)參數(shù),那么調(diào)用函數(shù)時(shí)傳遞給函數(shù)的參數(shù)列表中同波,從第二個(gè)參數(shù)(如果有的話)開始就會(huì)保存到ARGN鳄梅。
舉一個(gè)簡單的例子

cmake_minimum_required(VERSION 2.8)  
 project(ArgumentExpansion)  
   
function (argument_tester arg)  
    message(STATUS "ARGN: ${ARGN}")  
    message(STATUS "ARGC: ${ARGC}")  
    message(STATUS "ARGV: ${ARGV}")  
    message(STATUS "ARGV0: ${ARGV0}")  
  
    list(LENGTH ARGV  argv_len)  
    message(STATUS "length of ARGV: ${argv_len}")  
    set(i 0)  
    while( i LESS ${argv_len})  
         list(GET ARGV ${i} argv_value)  
         message(STATUS "argv${i}: ${argv_value}")  
         math(EXPR i "${i} + 1")  
    endwhile()   
endfunction ()  
argument_tester(arg0 arg1 arg2 arg3) 

====output====
-- ARGN: arg1;arg2;arg3
-- ARGC: 4
-- ARGV: arg0;arg1;arg2;arg3
-- ARGV0: arg0
-- ARGV1: arg1
-- length of ARGV: 4
-- argv0: arg0
-- argv1: arg1
-- argv2: arg2
-- argv3: arg3

9. LIST用法

set(SRC)  
list(APPEND SRC a.cpp b.cpp)  
list(APPEND SRC c.cpp d.cpp)  
  
function(tst_arguments src_list)  
    message("src_list = "${src_list})  
endfunction()  
  
message("SRC = "${SRC})  
tst_arguments(${SRC})  
  
==== output ====  
SRC = a.cppb.cppc.cppd.cpp  
src_list = a.cpp  

10. CMAKE_PARSE_ARGUMENTS

CMAKE_PARSE_ARGUMENTS(<prefix> <options> <one_value_keywords> <multi_value_keywords> args...)  

prefix是一個(gè)前綴
<option>是一個(gè)列表,里面可以包含一些你感興趣的KeyWord或者叫宏未檩,隨后可以通過它來看看你所需要的KeyWord是否被設(shè)置戴尸。只有當(dāng)調(diào)用的地方使用option,表示這個(gè)宏是打開的冤狡。
<one_value_keywords>是一個(gè)單值參數(shù)的KeyWord列表孙蒙。
<multi_value_keywords>是一個(gè)多值參數(shù)的KeyWord列表(如list)

function(tst_arguments)  
  CMAKE_PARSE_ARGUMENTS(  
    TEST "" "NAME;COMMAND;BASELINE"  
       "ARGSLIST"  
       ${ARGN}  
  )  
  
  message("TEST_DEFAULT_ARGS is ${TEST_DEFAULT_ARGS} from ${ARGN}")  
  message("TEST_NAME is ${TEST_NAME}")  
  message("TEST_COMMAND is ${TEST_COMMAND}")  
  message("TEST_ARGSLIST is ${TEST_ARGSLIST}")  
  message("TEST_BASELINE is ${TEST_BASELINE}")  
  
endfunction(tst_arguments) 

這里的前綴是TEST,<one_value_keywords>我們設(shè)置單值參數(shù)的KeyWord(NAME;COMMAND;BASELINE)悲雳,這將在隨后的函數(shù)調(diào)用中注明KeyWord和Value的關(guān)系挎峦,<multi_value_keywords>我們設(shè)置多值參數(shù)的KeyWord("ARGSLIST")。
調(diào)用函數(shù):

TEST_ARGUMENT(  
    NAME  
      testiso  
    COMMAND  
      "RunMe"  
    ARGSLIST  
      ${SRC}  
    BASELINE  
      "/home/sakaue/iWork"  
)  
  
==== output ====  
TEST_DEFAULT_ARGS is  from NAME;testiso;COMMAND;RunMe;ARGSLIST;a.cpp;b.cpp;c.cpp;d.cpp;BASELINE;/home/sakaue/iWork  
TEST_NAME is testiso  
TEST_COMMAND is RunMe  
TEST_ARGSLIST is a.cpp;b.cpp;c.cpp;d.cpp  
TEST_BASELINE is /home/sakaue/iWork  

下面以LLVM中用得比較多的一個(gè)function舉例:

macro(add_llvm_library name)
  if( BUILD_SHARED_LIBS )
    llvm_add_library(${name} SHARED ${ARGN})
  else()
    llvm_add_library(${name} ${ARGN})
  endif()
  set_property( GLOBAL APPEND PROPERTY LLVM_LIBS ${name} )
  ...
  ...
endmacro(add_llvm_library name)

function(llvm_add_library name)
  cmake_parse_arguments(ARG
    "MODULE;SHARED;STATIC"
    "OUTPUT_NAME"
    "ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS;OBJLIBS"
    ${ARGN})
  list(APPEND LLVM_COMMON_DEPENDS ${ARG_DEPENDS})
  if(ARG_ADDITIONAL_HEADERS)
    # Pass through ADDITIONAL_HEADERS.
    set(ARG_ADDITIONAL_HEADERS ADDITIONAL_HEADERS ${ARG_ADDITIONAL_HEADERS})
  endif()
  if(ARG_OBJLIBS)
    set(ALL_FILES ${ARG_OBJLIBS})
  else()
    llvm_process_sources(ALL_FILES ${ARG_UNPARSED_ARGUMENTS} ${ARG_ADDITIONAL_HEADERS})
  endif()

這個(gè)function在LLVM的很多CMakeLists中都會(huì)用合瓢,比如lib/IR目錄

add_llvm_library(LLVMCore
  AsmWriter.cpp
  Attributes.cpp
  AutoUpgrade.cpp
  BasicBlock.cpp
  Comdat.cpp
  ConstantFold.cpp
  ConstantRange.cpp
  Constants.cpp
  Core.cpp
  DIBuilder.cpp
  ...
  ...

  )
add_dependencies(LLVMCore intrinsics_gen)

從上面的CMakeLists中我們可以看到坦胶,在llvm_add_library(${name} SHARED ${ARGN})中
name = LLVMCore;ARGN是依賴的那些.cpp文件晴楔。因此傳遞給cmake_parse_arguments的是
share .cpp迁央。因此
前綴是ARG,
ARG_SHARED = TRUE,
ARG_MODULE=FALSE,
ARG_STATIC=FALSE。
由于
.cpp不跟其它任何參數(shù)匹配滥崩,所以
ARG_UNPARSED_ARGUMENTS = *.cpp
注意<prefix>_UNPARSED_ARGUMENTS用來表示那些不匹配的參數(shù)岖圈。

我們在LLVM中可以看到,lib中的CMakeLists一般是不要指定頭文件路徑的钙皮,比如像LLVM中的:

#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"

主要原因基于以下兩點(diǎn):
1.CMake使用 include_directories 命令來添加頭文件包含路徑蜂科,且 include_directories 命令具有繼
承性顽决。下級目錄繼承了上級目錄中CMakeLists.txt 里面 include 的 directrories。但是平級目錄之間的 CMakeList.txt 里面的include_directories 不能共享导匣。

  1. 在默認(rèn)情況下才菠,會(huì)查找當(dāng)前目錄下的所有頭文件,不包括查找子目錄下的頭文件

在LLVM的CMakeLists.txt還看到很多這樣的代碼:

include(AddLLVM)
include(TableGen)

這里的AddLLVM和TableGen是什么呢贡定?這里面定義了很多函數(shù)赋访,比如之前用的比較多的llvm_add_library都定義在llvm/cmake/modules里面。但是在include這些module之前必須指定CMAKE_MODULE_PATH,llvm中是這樣描述的:

set(CMAKE_MODULE_PATH
  ${CMAKE_MODULE_PATH}
  "${CMAKE_CURRENT_SOURCE_DIR}/cmake"
  "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules"
)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缓待,一起剝皮案震驚了整個(gè)濱河市蚓耽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌旋炒,老刑警劉巖步悠,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瘫镇,居然都是意外死亡鼎兽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門铣除,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谚咬,“玉大人,你說我怎么就攤上這事尚粘≡褙裕” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵背苦,是天一觀的道長互捌。 經(jīng)常有香客問我潘明,道長行剂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任钳降,我火速辦了婚禮厚宰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘遂填。我一直安慰自己铲觉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布吓坚。 她就那樣靜靜地躺著撵幽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪礁击。 梳的紋絲不亂的頭發(fā)上盐杂,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天逗载,我揣著相機(jī)與錄音,去河邊找鬼链烈。 笑死厉斟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的强衡。 我是一名探鬼主播擦秽,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼漩勤!你這毒婦竟也來了感挥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤锯七,失蹤者是張志新(化名)和其女友劉穎链快,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體眉尸,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡域蜗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了噪猾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片霉祸。...
    茶點(diǎn)故事閱讀 40,769評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖袱蜡,靈堂內(nèi)的尸體忽然破棺而出丝蹭,到底是詐尸還是另有隱情,我是刑警寧澤坪蚁,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布奔穿,位于F島的核電站,受9級特大地震影響敏晤,放射性物質(zhì)發(fā)生泄漏贱田。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一嘴脾、第九天 我趴在偏房一處隱蔽的房頂上張望男摧。 院中可真熱鬧,春花似錦译打、人聲如沸耗拓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乔询。三九已至,卻和暖如春韵洋,著一層夾襖步出監(jiān)牢的瞬間竿刁,已是汗流浹背岸夯。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留们妥,地道東北人猜扮。 一個(gè)月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像监婶,于是被迫代替她去往敵國和親旅赢。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容