变量
PROJECT_BINARY_DIR 编译目录,即build目录或者说调用cmake命令的目录
PROJECT_SOURCE_DIR 工程目录,即cmakelist所在目录
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
在哪里 ADD_EXECUTABLE 或 ADD_LIBRARY,如果需要改变目标存放路径,就在哪里加入上述的定义
-
CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的 CMakeLists.txt 所在的路径。 -
CMAKE_CURRRENT_BINARY_DIR
如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-ofsource 编译,他指的是 target 编译目录。使用 ADD_SUBDIRECTORY(src bin) 可以更改这个变量的值。它指的是当前子工程生成的.cmake等文件的保存地址 -
CMAKE_CURRENT_LIST_FILE
输出调用这个变量的 CMakeLists.txt 的完整路径。 -
CMAKE_CURRENT_LIST_LINE
输出这个变量所在的行。 -
CMAKE_MODULE_PATH
这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下。比如
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
这时候你就可以通过 INCLUDE 指令来调用自己的模块了。 -
EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH
前者用来重新定义目标二进制可执行文件的存放位置,后者用来重新定义目标链接库文件的存放位置。 -
PROJECT_NAME
返回通过 PROJECT 指令定义的项目名称。 -
CMAKE_PREFIX_PATH
CMAKE_PREFIX_PATH是以分号分隔的列表,供find_package(), find_program(), find_library(), find_file()和find_path()使用,初始为空,由用户设定
命令
-
set
我们在子目录中如果想要修改父目录 CMake 文件包含的 Normal 变量。必须通过 set(… PARENT_SCOPE) 的方式。 -
list 操作
list(LENGTH <list> <output variable>) list(GET <list> <element index> [<element index> ...] <output variable>) list(APPEND <list> <element> [<element> ...]) list(FIND <list> <value> <output variable>)
其中list(FIND <list> <value> <output variable>)中的output variable储存的是数组下标。
-
FIND_FILE( < VAR> name1 path1 path2 …) VAR 变量代表找到的文件全路径,包含文件名
FIND_LIBRARY(< VAR> name1 path1 path2 …) VAR 变量表示找到的库全路径,包含库文件名
FIND_PATH(< VAR> name1 path1 path2 …) VAR 变量代表包含这个文件的路径。
FIND_PROGRAM(< VAR> name1 path1 path2 …) VAR 变量代表包含这个程序的全路径 -
add_library(libsugan ${SRC_LISTS})
#将集合里的所有的源文件生成一个静态库,该静态库的名字为libsugan -
target_link_libraries(libsugan ${OpenCV_LIBS}
${PROJECT_SOURCE_DIR}/lib/libCommonUtilities.so
${PROJECT_SOURCE_DIR}/lib/libInuStreams.so ) #链接静态库需要的依赖库
也就是这个两个函数的作用应该差不多
一个区别是:
target_link_libraries 要在 add_executable 之后
link_libraries 要在 add_executable 之前
指定了EXCLUDE_FROM_ALL 属性后,不会自动编译,只能手动编译,一般用于测试模块 -
file(GLOB_RECURSE mains ${CMAKE_CURRENT_SOURCE_DIR} *.cpp)
${CMAKE_CURRENT_SOURCE_DIR}下所有cpp文件名赋值给mains -
MESSAGE("include folder: " $ {include_dir})
在执行cmaek 命令的时候就会输入${include_dir}的值 -
INCLUDE_DIRECTORIES
INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 …)
这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径
中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的
后面,你可以通过两种方式来进行控制搜索路径添加的方式: -
string
从路径提取文件名
STRING(REGEX REPLACE “.+/(.+)\\ . .*” “\\1” FILE_NAME ${FILE_PATH}) -
OPTION 和 add_definitions
假设项目是以CMakeLists.txt 构建的.
代码中通过宏 USE_MACRO 作为区分.#ifdef USE_MACRO ... #endif
我们可以通过在项目中的CMakeLists.txt 中添加如下代码控制代码的开启和关闭.
OPTION(USE_MACRO"Build the project using macro" OFF) IF(USE_MACRO) add_definitions("-DUSE_MACRO") endif(USE_MACRO)
运行构建项目的时候可以添加参数控制宏的开启和关闭.
开启: cmake -DUSE_MACRO=on …关闭: cmake -DUSE_MACRO=off …
-
add_custom_target
构建一个目标,这个目标没有输出,只会执行一些制定的命令add_custom_target(CopyTask COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/config ${CMAKE_CURRENT_SOURCE_DIR}/etc COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/log.txt ${CMAKE_CURRENT_SOURCE_DIR}/etc )
-E表示另cmake不输出,只执行命令。copy_directory 和copy是两条命令。
-
find_program
找到可执行程序的地址
find_program(MEMORYCHECK_COMMAND valgrind REQUIRED) -
ADD_EXECUTABLE
假设我们需要生成一个可执行文件,该文件生成需要链接a.so b.so c.so d.so四个动态库
正常来讲,我们一把只需要以下两条指令即可:ADD_EXECUTABLE(main main.cpp) TARGET_LINK_LIBRARIES(main a.so b.so c.so d.so)
但是编译的时候报错,一些符号的定义找不到,而这些符号恰恰就在这几个库中,假设在a.so 和 b.so中,在上述两条指令之间加上一条指令即可编译通过:
ADD_DEPENDENCIES(main a.so b.so)
原因是运行到TARGET_LINK_LIBRARIES(main a.so b.so c.so d.so)时,a.so和b.so还没有编译,ADD_DEPENDENCIES
可以解决依赖关系 -
INSTALL
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
上面的例子会将:
可执行二进制 myrun 安装到${CMAKE_INSTALL_PREFIX}/bin 目录
动态库 libmylib 安装到${CMAKE_INSTALL_PREFIX}/lib 目录
静态库 libmystaticlib 安装到${CMAKE_INSTALL_PREFIX}/libstatic 目录 -
add_test
ADD_TEST 指令的语法是:
ADD_TEST(testname Exename arg1 arg2 …)
testname 是自定义的 test 名称,Exename 可以是构建的目标文件也可以是外部脚本等
等。后面连接传递给可执行文件的参数。如果没有在同一个 CMakeLists.txt 中打开
ENABLE_TESTING()指令,任何 ADD_TEST 都是无效的。
make test 之后就会自动运行所有定义的testnameadd_test(NAME t_webget COMMAND "${PROJECT_SOURCE_DIR}/tests/webget_t.sh") add_custom_target (check_webget COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure --timeout 10 -R 't_webget'COMMENT "Testing webget...")
在添加了一个t_webget测试后,可以运行check_webget来运行它。
-
find_package
用法:find_package(CURL) add_executable(curltest curltest.cc) if(CURL_FOUND) target_include_directories(clib PRIVATE ${CURL_INCLUDE_DIR}) target_link_libraries(curltest ${CURL_LIBRARY}) else(CURL_FOUND) message(FATAL_ERROR ”CURL library not found”) endif(CURL_FOUND)
主要通过Find.cmake或者通过Config.cmake or -config.cmake这两个文件来引入我们需要的库。
也可以自己写一个.cmake文件
将项目目录下的cmake文件夹加入到CMAKE_MODULE_PATH中,让find_pakcage能够找到我们自定义的函数库set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}") add_executable(addtest addtest.cc) find_package(ADD) if(ADD_FOUND) target_include_directories(addtest PRIVATE ${ADD_INCLUDE_DIR}) target_link_libraries(addtest ${ADD_LIBRARY}) else(ADD_FOUND) message(FATAL_ERROR "ADD library not found") endif(ADD_FOUND)
-
cmake_parse_arguments(<prefix> <options> <one_value_keywords>
<multi_value_keywords> <args>…)cmake_minimum_required(VERSION 3.5) project(PARSE) function(MY_PARSE) set(options ADULT) set(oneValueArgs NAME AGE) set(multiValueArgs SCORE) cmake_parse_arguments(STUDENT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) # 通过 prefix_参数名: 例如 STUDENT_ADULT message("isadult = ${STUDENT_ADULT}") message("name = ${STUDENT_NAME}") message("age = ${STUDENT_AGE}") message("score = ${STUDENT_SCORE}") endfunction() MY_PARSE(ADULT NAME zhangsan AGE 20 SCORE 100 200 300 400 500) add_executable(DEMO main.cpp)
运行结果
rs:cmake-parse-arguments$ cmake ./ .... isadult = TRUE name = zhangsan age = 20 score = 100;200;300;400;500 ....
编译选项
选项 | 说明 |
---|---|
-c | 用于把源码文件编译成 .o 对象文件,不进行链接过程 |
-o | 用于连接生成可执行文件,在其后可以指定输出文件的名称 |
-g | 用于在生成的目标可执行文件中,添加调试信息,可以使用GDB进行调试 |
-Idir | 用于把新目录添加到include路径上,可以使用相对和绝对路径,“-I.”、“-I./include”、“-I/opt/include” |
-Wall | 生成常见的所有告警信息,且停止编译,具体是哪些告警信息,请参见GCC手册,一般用这个足矣! |
-w | 关闭所有告警信息 |
-O | 表示编译优化选项,其后可跟优化等级0\1\2\3,默认是0,不优化 |
-fPIC | 用于生成位置无关的代码 |
-v | (在标准错误)显示执行编译阶段的命令,同时显示编译器驱动程序,预处理器,编译器的版本号 |
GCC链接选项LDFLAGS参数
选项 | 说明 |
---|---|
-l library | 链接时在标准搜索目录中寻找库文件,搜索名为liblibrary.a 或 liblibrary.so |
-L dir | 用于把新目录添加到库搜索路径上,可以使用相对和绝对路径,“-L.”、“-L./include”、“-L/opt/include” |
-Wl,option | 把选项 option 传递给连接器,如果 option 中含有逗号,就在逗号处分割成多个选项 |
-static | 使用静态库链接生成目标文件,避免使用共享库,生成目标文件会比使用动态链接库大 |
CFLAGS = -g -Wall -I./include -I./include/tinyalsa -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -lc
LDFLAGS = -L./lib
ALL:
$(CC) $(CFLAGS) $(LDFLAGS) main.c gfifo.c queue.c usbmonitor.c socket_rcv_360_server.c ./lib/srs_librtmp.a ./lib/libcrypto.a ./lib/libssl.a ./lib/libtinyalsa.a -o media_record -static -ldl -lstdc++ -lm -lpthread
clean:
rm media_record *.raw *.mp4 *.wav -rf
CFLAGS = -g -Wall -I./include -I./include/tinyalsa -Wl,--whole-archive -lpthread -Wl,--no-whole-archive -lc
-g:生成调试信息
-Wall:输出全部的告警信息
-I./include:添加头文件搜索目录./include
-I./include/tinyalsa:添加头文件搜索目录./include/tinyalsa
-Wl,--whole-archive -lpthread:将libpthread.so及以后的库中的所有符号都链接进可执行目标文件
-Wl,--no-whole-archive:关闭之前的-Wl,--whole-archive -lpthread 选项
LDFLAGS = -L./lib,将makefile当前目录下的、lib文件夹添加到库文件搜索目录
$(CC):makefile隐含变量,默认选择gcc编译器
$(CFLAGS):引用之前定义的CFLAGS变量,即编译选项参数
$(LDFLAGS):引用之前定义的LDFLAGS变量,即链接参数
$(CC) $(CFLAGS) $(LDFLAGS) main.c gfifo.c queue.c usbmonitor.c socket_rcv_360_server.c ./lib/srs_librtmp.a ./lib/libcrypto.a ./lib/libssl.a ./lib/libtinyalsa.a -o media_record -static -ldl -lstdc++ -lm -lpthread
使用定的编译器、编译选项参数、链接选项参数,编译.c文件,并使用静态方式链接制定的库文件,以及编译器目录下的libdl.a、libstdc++.a、libm.a、libpthread.a库文件生成 media_record 可执行目标文件。
- -Werror
使警告变成错误 - -Wno-unused-parameter
忽略unused-parameter的警告
privat、public
CMake是一个很强大的跨平台构建系统,但是学习难度也蛮大的
1. 指令说明
target_include_directories()
:指定目标包含的头文件
路径。官方文档target_link_libraries()
:指定目标链接的库。官方文档target_compile_options()
:指定目标的编译选项。官方文档
目标 由 add_library()
或 add_executable()
生成。
这三个指令类似,这里以 target_include_directories() 为例进行讲解。
2. 指令讲解
测试工程目录结构:
cmake-test/ 工程主目录,main.c 调用 libhello-world.so
├── CMakeLists.txt
├── hello-world 生成 libhello-world.so,调用 libhello.so 和 libworld.so
│ ├── CMakeLists.txt
│ ├── hello 生成 libhello.so
│ │ ├── CMakeLists.txt
│ │ ├── hello.c
│ │ └── hello.h libhello.so 对外的头文件
│ ├── hello_world.c
│ ├── hello_world.h libhello-world.so 对外的头文件
│ └── world 生成 libworld.so
│ ├── CMakeLists.txt
│ ├── world.c
│ └── world.h libworld.so 对外的头文件
└── main.c
调用关系:
├────libhello.so
可执行文件────libhello-world.so
├────libworld.so
三大关键字用法说明:
PRIVATE:私有的。生成 libhello-world.so 时,只在 hello_world.c 中包含了 hello.h,libhello-world.so 对外的头文件——hello_world.h 中不包含 hello.h 。而且 main.c 不会调用 hello.c 中的函数,或者说 main.c 不知道 hello.c 的存在,那么在 hello-world/CMakeLists.txt 中应该写入:
target_link_libraries(hello-world PRIVATE hello) // hello-world 表示可执行文件目标
target_include_directories(hello-world PRIVATE hello)
INTERFACE:接口,即提供了接口。生成 libhello-world.so 时,只在libhello-world.so 对外的头文件——hello_world.h 中包含 了 hello.h, hello_world.c 中不包含 hello.h,即 libhello-world.so 不使用 libhello.so 提供的功能,只使用 hello.h 中的某些信息,比如结构体。但是 main.c 需要使用 libhello.so 中的功能。那么在 hello-world/CMakeLists.txt 中应该写入:
target_link_libraries(hello-world INTERFACE hello)
target_include_directories(hello-world INTERFACE hello)
PUBLIC:公开的。PUBLIC = PRIVATE + INTERFACE。生成 libhello-world.so 时,在 hello_world.c 和 hello_world.h 中都包含了 hello.h。并且 main.c 中也需要使用 libhello.so 提供的功能。那么在 hello-world/CMakeLists.txt 中应该写入:
target_link_libraries(hello-world PUBLIC hello)
target_include_directories(hello-world PUBLIC hello)
实际上,这三个关键字指定的是目标文件依赖项的使用范围(scope)或者一种传递(propagate)。具体来说:就是对内而言,是否使用?对外而言,是否要传递出去?
本例中:
可执行文件依赖 libhello-world.so, libhello-world.so 依赖 libhello.so 和 libworld.so。
- main.c 不使用 libhello.so 的任何功能,因此 libhello-world.so 不需要将其依赖—— libhello.so 传递给 main.c,hello-world/CMakeLists.txt 中使用 PRIVATE 关键字;
- main.c 使用 libhello.so 的功能,但是libhello-world.so 不使用,hello-world/CMakeLists.txt 中使用 INTERFACE 关键字;
- main.c 和 libhello-world.so 都使用 libhello.so 的功能,hello-world/CMakeLists.txt 中使用 PUBLIC 关键字;
3. include_directories(dir)
target_include_directories()
的功能完全可以使用 include_directories()
实现。但是我还是建议使用 target_include_directories()
。为什么?保持清晰!
include_directories(header-dir)
是一个全局包含,向下传递。什么意思呢?就是说如果某个目录的 CMakeLists.txt 中使用了该指令,其下所有的子目录默认也包含了header-dir 目录。
上述例子中,如果在顶层的 cmake-test/CMakeLists.txt 中加入:
include_directories(hello-world) include_directories(hello-world/hello) include_directories(hello-world/world)
那么整个工程的源文件在编译时都会增加:
-I hello-world -I hello-world/hello -I hello-world/world ...
这样各级子目录中无需使用 target_include_directories() 或者 include_directories()了。如果此时查看详细的编译过程(make VERBOSE=1)就会发现编译过程是一大坨,很不舒服。
当然了,在最终子目录的 CMakeLists.txt 文件中,使用 include_directories() 和 target_include_directories() 的效果是相同的。
4. 目录划分
每一个目录都是一个模块,目录内部应将对外和对内的头文件进行区分,由模块的调用者决定模块是否被传递(PRIVATE,INTERFACE,PUBLIC)。
5. 个人理解
三大关键字针对的是处在依赖链上的各个库文件,由于各个库是分开编译的,需要考虑底层库本身以及底层库使用的头文件或者宏定义是否需要包括在上层或者传递到更上层的库文件中,这时就可以使用三大关键字。调试选项
当然可以加入调试信息的,方法如下:
修改CMakeLists.txt文件,如我修改如下:
# Version 2.4 might work, but it won't find static libraries with odd names
cmake_minimum_required(VERSION 2.6)
# CMake used to complain without this
#if(COMMAND cmake_policy)
# cmake_policy(SET CMP0003 NEW)
#endif(COMMAND cmake_policy)
SET(CMAKE_BUILD_TYPE "Debug")
SET(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g2 -ggdb")
SET(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAGS} -O3 -Wall")
在CMakeLists.txt文件的开头部分增加上面的几个SET语法行,简单解释如下:
在cmake中有一个全局的环境变量,CMAKE_BUILD_TYPE,可以取Release或者Debug等值。然后可以通过设置CMAKE_CXX_FLAGS_DEBUG来设置在debug时的CXXFLAGS,这个值大家肯定都熟悉的哈。如果不需要添加调试信息,就直接修改CMAKE_BUILD_TYPE的值。
这些优化级别,主要有:
-
Release —— 不可以打断点调试,程序开发完成后发行使用的版本,占的体积小。 它对代码做了优化,因此速度会非常快,在编译器中使用命令: -O3 -DNDEBUG 可选择此版本。
-
Debug ——调试的版本,体积大。在编译器中使用命令: -g 可选择此版本。
-
MinSizeRel—— 最小体积版本 在编译器中使用命令:-Os -DNDEBUG可选择此版本。
-
RelWithDebInfo—— 既优化又能调试。在编译器中使用命令:-O2 -g -DNDEBUG可选择此版本。
gcc和g++
我们知道编译和链接阶段是靠g++和gcc编辑器来完成,这两个编译阶段是相同的,但是链接阶段g++默认链接c++库,所以一般情况下用gcc编译c文件,而g++编译cpp文件。当然g++也可以编译c文件,而gcc编译cpp文件则需要在后面加上参数-lstdc++,作用就是链接c++库。
Makefile:xxx: recipe for target ‘xxx’ failed
使用Makefile 运行当前目录下生成的可执行文件时出现:Makefile:3: recipe for target ‘test’ failed 类似的错误。该问题很大可能是程序运行结束后返回给make 的值是非0导致的结果。
1.test.c
#include<stdio.h>
int main(){
printf("----------------\n");
return 2;
}
2.Makefile
target : prerequests
[tab] command
命令的前面一定要是tab键
test : test.c
clang test.c -o test
./test
命令行运行:make test
ctest -R
利用正则表达式匹配并运行cmakeelist中用add_test添加的测试项目。
第三方库
可以利用FetchContent_Declare从github或从本地导入一个cmake工程,并且用FetchContent_MakeAvailable使这个工程像find_package导入的工程一样使用
FetchContent_Declare(
UCMake
GIT_REPOSITORY "https://github.com/Ubpa/UCMake"
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ucmake-src
GIT_TAG ${UCMake_VERSION}
)
FetchContent_MakeAvailable(UCMake)
构造一个ProjectConfig.cmake
有一个项目的目录是这样的
FooBar/
|-- CMakeLists.txt
|-- FooBarConfig.cmake.in
|-- FooBarConfigVersion.cmake.in
|-- foo/
| |-- CMakeLists.txt
| |-- config.h.in
| |-- foo.h
| `-- foo.c
`-- bar/
|-- CMakeLists.txt
`-- bar.c
现在希望make install 后在安装目录会生成一个FooBarConfig.cmake, 从而其它项目可以通过这个cmake文件导入FooBar项目。
-
foo/CmakeLists.txt
configure_file(config.h.in "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) add_library(foo SHARED foo.c foo.h) set_target_properties(foo PROPERTIES PUBLIC_HEADER "foo.h;${CMAKE_CURRENT_BINARY_DIR}/config.h") install(TARGETS foo # IMPORTANT: Add the foo library to the "export-set" EXPORT FooBarTargets RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin LIBRARY DESTINATION "${INSTALL_LIB_DIR}" COMPONENT shlib PUBLIC_HEADER DESTINATION "${INSTALL_INCLUDE_DIR}/foo" COMPONENT dev)
configure_file会把config.h.in文件中用@@符号包围的变量,用cmakelist中的对应变量值代替,并生成config.h文件。
set_target_propertie 指明了在安装目标foo时,应该同时安装哪些头文件。
install(TARGETS foo 。。。。。) 把foo 和其头文件保存到对应的安装目录中,并且将这些地址保存到布局FooBarTargets中。
当在主CmakeLists中调用install(TARGETS foo # IMPORTANT: Add the foo library to the "export-set" EXPORT FooBarTargets
时,布局FooBarTargets会被写到安装目录的FooBarTargets.cmake文件中。
-
bar/CmakeLists.txt
add_executable(bar bar.c) target_link_libraries(bar foo) install(TARGETS bar # IMPORTANT: Add the bar executable to the "export-set" EXPORT FooBarTargets RUNTIME DESTINATION "${INSTALL_BIN_DIR}" COMPONENT bin)
-
FooBarConfig.cmake.in
# - Config file for the FooBar package # It defines the following variables # FOOBAR_INCLUDE_DIRS - include directories for FooBar # FOOBAR_LIBRARIES - libraries to link against # FOOBAR_EXECUTABLE - the bar executable # Compute paths get_filename_component(FOOBAR_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) set(FOOBAR_INCLUDE_DIRS "@CONF_INCLUDE_DIRS@") # Our library dependencies (contains definitions for IMPORTED targets) include("${FOOBAR_CMAKE_DIR}/FooBarTargets.cmake") # These are IMPORTED targets created by FooBarTargets.cmake set(FOOBAR_LIBRARIES foo) set(FOOBAR_EXECUTABLE bar)
通过替换@CONF_INCLUDE_DIRS@可以得到FooBarConfig.cmake, 它是其它项目导入安装好的FoorBar项目所需的唯一文件。 FooBarConfig.cmake中除了通过FooBarTargets.cmake导入了foo和bar项目外,还指明了Foor和Bar所需头文件的路径
"@CONF_INCLUDE_DIRS@"
这个路径最好是包含${FOOBAR_CMAKE_DIR}
的动态路径,这样安装目录就可以随意移动。
所以在主Cmakelists中,它被替换成"\${FOOBAR_CMAKE_DIR}/${REL_INCLUDE_DIR}"
其中\
符号表示转义,${REL_INCLUDE_DIR}
是cmake安装目录到include安装目录的相对路径 -
CmakeLists.txt
cmake_minimum_required(VERSION 2.8) project(FooBar C) set(FOOBAR_MAJOR_VERSION 0) set(FOOBAR_MINOR_VERSION 1) set(FOOBAR_PATCH_VERSION 0) set(FOOBAR_VERSION ${FOOBAR_MAJOR_VERSION}.${FOOBAR_MINOR_VERSION}.${FOOBAR_PATCH_VERSION}) # Offer the user the choice of overriding the installation directories set(INSTALL_LIB_DIR lib CACHE PATH "Installation directory for libraries") set(INSTALL_BIN_DIR bin CACHE PATH "Installation directory for executables") set(INSTALL_INCLUDE_DIR include CACHE PATH "Installation directory for header files") if(WIN32 AND NOT CYGWIN) set(DEF_INSTALL_CMAKE_DIR CMake) else() set(DEF_INSTALL_CMAKE_DIR lib/CMake/FooBar) endif() set(INSTALL_CMAKE_DIR ${DEF_INSTALL_CMAKE_DIR} CACHE PATH "Installation directory for CMake files") # Make relative paths absolute (needed later on) foreach(p LIB BIN INCLUDE CMAKE) set(var INSTALL_${p}_DIR) if(NOT IS_ABSOLUTE "${${var}}") set(${var} "${CMAKE_INSTALL_PREFIX}/${${var}}") endif() endforeach() # set up include-directories include_directories( "${PROJECT_SOURCE_DIR}" # to find foo/foo.h "${PROJECT_BINARY_DIR}") # to find foo/config.h # Add sub-directories add_subdirectory(foo) add_subdirectory(bar) # The interesting stuff goes here # =============================== # Add all targets to the build-tree export set export(TARGETS foo bar FILE "${PROJECT_BINARY_DIR}/FooBarTargets.cmake") # Export the package for use from the build-tree # (this registers the build-tree with a global CMake-registry) export(PACKAGE FooBar) # Create the FooBarConfig.cmake and FooBarConfigVersion files file(RELATIVE_PATH REL_INCLUDE_DIR "${INSTALL_CMAKE_DIR}" "${INSTALL_INCLUDE_DIR}") # ... for the build tree set(CONF_INCLUDE_DIRS "${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}") configure_file(FooBarConfig.cmake.in "${PROJECT_BINARY_DIR}/FooBarConfig.cmake" @ONLY) # ... for the install tree set(CONF_INCLUDE_DIRS "\${FOOBAR_CMAKE_DIR}/${REL_INCLUDE_DIR}") configure_file(FooBarConfig.cmake.in "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FooBarConfig.cmake" @ONLY) # ... for both configure_file(FooBarConfigVersion.cmake.in "${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake" @ONLY) # Install the FooBarConfig.cmake and FooBarConfigVersion.cmake install(FILES "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/FooBarConfig.cmake" "${PROJECT_BINARY_DIR}/FooBarConfigVersion.cmake" DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev) # Install the export set for use with the install-tree install(EXPORT FooBarTargets DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)
export(TARGETS foo bar FILE "${PROJECT_BINARY_DIR}/FooBarTargets.cmake")
是把对应build-tree的布局保存到buid_tree里的FooBarTargets.cmake中。而
install(EXPORT FooBarTargets DESTINATION "${INSTALL_CMAKE_DIR}" COMPONENT dev)
是把对应install-tree的布局保存到安装目录下的FooBarTargets.cmake中。