美文网首页
CMakeLists用法总结

CMakeLists用法总结

作者: WalkHeart | 来源:发表于2018-04-18 18:38 被阅读0次

    分一下几个方面来描述:

    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 <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 不能共享。

    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用法总结

          本文链接:https://www.haomeiwen.com/subject/ozhmkftx.html