分一下幾個(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()
- 定義了一個(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 不能共享导匣。
- 在默認(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"
)