分一下几个方面来描述:
1. 每一个LIB要编译成静态库或动态库如何描述,每一个TOOL要编译成可执行文件如何描述?
2. LIB和TOOL可能会依赖于其他LIB,该如何描述?
3. 每个LIB和TOOL都会include很多头文件,相同的头文件如何处理,私有的头文件如何处理?
4. CMake中的一些用法
5. LLVM中用到的技巧
先举一个简单的例子:
//
└─tutorial.c
└─TutorialConfig.h.in
└─CMakeLists.txt
└─include
└─mysqrt.h
└─lib
└─CMakeLists.txt
└─MakeTable.c
└─mysqrt.c
在这个例子中,Top level和lib中会分别定义一个CMakeLists.txt用来指定编译规则。这个例子主要做了以下几件事:
1)用宏USE_MYMATH来控制是否使用自己定义的sqrt()
- 定义了一个MakeTable.c文件生成sqrt()的一个结果表,在自己定义的sqrt()函数中查表得出简单的结果。
3)或者通过exp和log来计算sqrt的值,但前提是当前库支持exp和log,这些都通过check_function_exists检查是否支持,如果支持相应的宏就会打开。
4)通过CTest,定义相应的测试用例,检查执行结果是否正确。
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"用来给对应的变量赋值
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"表示关闭
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时需要的头文件路径
//"PROJECT_BINARY_DIR"是内置变量,表示工程编译的目录,也就是--prefix指定的目录
include_directories("${PROJECT_BINARY_DIR}" "${include_dir}")
if (USE_MYMATH)
include_directories(${include_dir})
//"add_subdirectory"用来添加外部项目目录,将指定的目录添加到build任务列表中。
//在这里"lib"目录是否需要编译,是通过宏USE_MYMATH控制,
//如果这个宏打开,就需要编译"lib",也就是需要通过"add_sudirectory"添加"lib"
add_subdirectory(lib)
set(EXTRAL_LIBS ${EXTRAL_LIBS} MathFunctions)
endif(USE_MYMATH)
# add the executable
// "add_executable"用来指定生成可执行文件,这里会生成Tutorial.out或者Tutorial.exe
add_executable(Tutorial tutorial.c)
// 由于tutorial.c中需要用到sqrt函数,这个函数要么来自于系统库,要么来自于自定义库,
//因此这里需要通过"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, 添加相应的测试用例,正确的测试结果通过set_tests_properties指定。
//在这里也就是说sqrt(25) = 5,否则就会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检查当前库是否支持log和exp,如果支持相应的宏就会打开。
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文件主要定义一个数组,保存一组sqrt(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的内容如下:
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
//通过执行COMMAND,产生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)
下面解释几个CMake内置变量
1.CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的 CMakeLists.txt 所在的路径,处理不同目录下的CMaleLists,这个宏的值是不一样的。在Top level中这个值就是Top Level所在目录,在lib中这个值就是lib所在的目录。
2. CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
这三个变量指代的内容是一致的,如果是 in source 编译,指得就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。
3. PROJECT_SOURCE_DIR
<projectname>_SOURCE_DIR
用来表示当前project所在的目录
4. INCLUDE_DIRECTORIES
用来指定build时需要的头文件路径
5. LINK_DIRECTORIES
用来指定第三方库所在的路径,可以理解为gcc中的-L
6. TARGET_LINK_LIBRARIES
用来指定工程所依赖的库
7. OPTION (USE_MYMATH "Use tutorial provided math implementation" ON)
在CMakeLists用option来控制当前USE_MYMATH是否打开,并在cmake-gui中会有对应的选项出现,默认是ON
8. Function的用法
function(<name>[arg1 [arg2 [arg3 ...]]])
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
endfunction(<name>)
定义一个函数名为<name>,参数名为arg1 arg2 arg3(…)。 函数体内的命令直到函数被调用的时候才会去执行。其中ARGC变量表示传递给函数的参数个数。 ARGV0, ARGV1, ARGV2代表传递给函数的实际参数。 ARGN代表超出最后一个预期参数的参数列表,例如,函数原型声明时,只接受一个参数,那么调用函数时传递给函数的参数列表中,从第二个参数(如果有的话)开始就会保存到ARGN。
举一个简单的例子
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是一个前缀
<option>是一个列表,里面可以包含一些你感兴趣的KeyWord或者叫宏,随后可以通过它来看看你所需要的KeyWord是否被设置。只有当调用的地方使用option,表示这个宏是打开的。
<one_value_keywords>是一个单值参数的KeyWord列表。
<multi_value_keywords>是一个多值参数的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>我们设置单值参数的KeyWord(NAME;COMMAND;BASELINE),这将在随后的函数调用中注明KeyWord和Value的关系,<multi_value_keywords>我们设置多值参数的KeyWord("ARGSLIST")。
调用函数:
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中用得比较多的一个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()
这个function在LLVM的很多CMakeLists中都会用,比如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不跟其它任何参数匹配,所以
ARG_UNPARSED_ARGUMENTS = *.cpp
注意<prefix>_UNPARSED_ARGUMENTS用来表示那些不匹配的参数。
我们在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"
主要原因基于以下两点:
1.CMake使用 include_directories 命令来添加头文件包含路径,且 include_directories 命令具有继
承性。下级目录继承了上级目录中CMakeLists.txt 里面 include 的 directrories。但是平级目录之间的 CMakeList.txt 里面的include_directories 不能共享。
- 在默认情况下,会查找当前目录下的所有头文件,不包括查找子目录下的头文件
在LLVM的CMakeLists.txt还看到很多这样的代码:
include(AddLLVM)
include(TableGen)
这里的AddLLVM和TableGen是什么呢?这里面定义了很多函数,比如之前用的比较多的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"
)
作者:WalkHeart
链接:https://www.jianshu.com/p/5a63414f0691
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。