CMakeLists用法总结

分一下几个方面来描述:

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()

  1. 定义了一个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 
#include 
#include 
#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 
#include 
#include 

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
_BINARY_DIR
这三个变量指代的内容是一致的,如果是 in source 编译,指得就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。PROJECT_BINARY_DIR 跟其他指令稍有区别,现在,你可以理解为他们是一致的。
3. PROJECT_SOURCE_DIR
_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([arg1 [arg2 [arg3 ...]]])
    COMMAND1(ARGS ...)
    COMMAND2(ARGS ...)
    ...

endfunction()

定义一个函数名为,参数名为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(    args...)  

prefix是一个前缀

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,我们设置单值参数的KeyWord(NAME;COMMAND;BASELINE),这将在随后的函数调用中注明KeyWord和Value的关系,我们设置多值参数的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
注意_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 不能共享。

  1. 在默认情况下,会查找当前目录下的所有头文件,不包括查找子目录下的头文件

在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"
)

你可能感兴趣的:(CMakeLists用法总结)