一直没有掌握了ros中的cmake机制,故系统学习一下自己编写cmakelist.txt。
知乎网友回答写的非常不错。
根据项目地址进行学习。
在学习过程中,要升级cmake版本,我使用的是ubuntu14,升级前的cmake版本为2.8.12.2
按照网友的方法升级成为 3.17.0
学习前对编译的理解:
关于库与可执行文件:在c++工程中,只有带main函数的文件才会生成可执行程序,而另外一些代码,我们将其打包成一个东西,叫做库。
将库打包成静态库或动态库后,在结合头文件,我们就能利用他两生成可执行程序。下面举个例子,使用cmake来生成一个可执行程序。
- 生成库文件
(在一个文件夹下建立一个hello.c的源文件和CMakeLists.txt)
hello.c内容如下:
#include<stdio.h>
void HelloFunc()
{
printf("Hello World\n");
}
CMakeLists.txt内容如下:
add_library(hello hello.c)
然后,我们在该目录下执行cmake .
和 make
即可以看到生成libhello.a文件。
- 编写main.c文件,作为生成可执行文件的源文件。编写头文件,以利用刚生成的库文件。
main.c内容如下:
#include "hello.h"
void main()
{
HelloFunc();
}
hello.h内容如下:
#ifndef HELLO_H
#define HELLO_H
void HelloFunc();
#endif
- 在原先的CMakeLists.txt下添加:
add_library(hello hello.c)
add_executable(hello_world main.c)
target_link_libraries(hello_world libhello.a)
第二句表示生成可执行文件hello
第三句表示将目标文件链接到生成的静态库文件上。以使用库文件中的函数。
cmake指令学习
例1 CMakeLists.txt如下:
PROJECT (HELLO)
SET(SRC_LIST main.c)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})
PROJECT 指令的语法是:
PROJECT(projectname [CXX] [C] [Java])
你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。
这个指令隐式的定义了两个 cmake 变量:
<projectname>_BINARY_DIR 以及<projectname>_SOURCE_DIR
SET 指令的语法是:
SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])
SET 指令可以用来显式的定义变量即可。
比如我们用到的是 SET(SRC_LIST main.c),如果有多个源文件,也可以定义成:
SET(SRC_LIST main.c t1.c t2.c)
MESSAGE 指令的语法是:
MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] “message to display”
…)
这个指令用于向终端输出用户定义的信息,包含了三种类型:
- SEND_ERROR,产生错误,生成过程被跳过。
- SATUS ,输出前缀为 — 的信息。
- FATAL_ERROR,立即终止所有 cmake 过程.
ADD_EXECUTABLE(hello ${SRC_LIST})
定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中
定义的源文件列表, 本例中你也可以直接写成 ADD_EXECUTABLE(hello main.c)。
ADD_SUBDIRECTORY 指令
ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL 参数的含义是将这个目录从编译过程中排除,比如,工程的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建(当然,你也可以通过定义依赖来解决此类问题)。
换个地方保存目标二进制
不论是 SUBDIRS 还是 ADD_SUBDIRECTORY 指令(不论是否指定编译输出目录),我们都可以通过 SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成的中间文件)
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
表示可执行二进制的输出路径为 build/bin 和库的输出路径为 build/lib.
如何安装:
安装的需要有两种,一种是从代码编译后直接 make install 安装,一种是打包时的指定
目录安装。
你可以通过:
make install
将 hello 直接安装到/usr/bin 目录,也可以通过 make install DESTDIR=/tmp/test 将他安装在 /tmp/test/usr/bin 目录,打包时这个方式经常被使用。
那么我们的 HelloWorld 应该怎么进行安装呢?
这里需要引入一个新的 cmake 指令 INSTALL 和一个非常有用的变量
CMAKE_INSTALL_PREFIX。
CMAKE_INSTALL_PREFIX 变量类似于 configure 脚本的 – prefix,常见的使用方法看起来是这个样子:
cmake -DCMAKE_INSTALL_PREFIX=/usr
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及文件、目录、脚本等。
- 目标文件的安装:
INSTALL(TARGETS targets... [[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>] [PERMISSIONS permissions...] [CONFIGURATIONS
[Debug|Release|...]] [COMPONENT <component>] [OPTIONAL] ] [...])
- 参数中的 TARGETS 后面跟的就是我们通过 ADD_EXECUTABLE 或者 ADD_LIBRARY 定义的 目标文件,可能是可执行二进制、动态库、静态库。
- 目标类型也就相对应的有三种,
ARCHIVE 特指静态库,
LIBRARY 特指动态库,
RUNTIME特指可执行目标二进制 - DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候 CMAKE_INSTALL_PREFIX 其实就无效了。
如果你希望使用 CMAKE_INSTALL_PREFIX 来定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是
${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
举个简单的例子:
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 目录
- 普通文件的安装:
INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果
默认不定义权限 PERMISSIONS,安装后的权限为:
OWNER_WRITE, OWNER_READ,GROUP_READ,和 WORLD_READ,即 644 权限。
- 非目标文件的可执行程序安装(比如脚本之类):
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为:
OWNER_EXECUTE, GROUP_EXECUTE, 和 WORLD_EXECUTE,即 755 权限
- 目录的安装:
INSTALL(DIRECTORY dirs... DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
PATTERN 用于使用正则表达式进行过滤,
PERMISSIONS 用于指定 PATTERN 过滤后的文件权限。
我们来看一个例子:
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myproj
PATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ)
这条指令的执行结果是:
将 icons 目录安装到 <prefix>/share/myproj,
将 scripts/中的内容安装到<prefix>/share/myproj
不包含目录名为 CVS 的目录,
对于 scripts/* 文件指定权限为 OWNER_EXECUTE
OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ.
静态库与动态库构建
SET(LIBHELLO_SRC hello.c)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})
SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC}) 添加动态库
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC}) 添加静态库,但是,当此命令会和动态库的名字hello重合,故使用SET_TARGET_PROPERTIES命令:
其基本语法是:
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本。
在本例中,我们需要作的是向 lib/CMakeLists.txt 中添加一条:
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME “hello”)
这样,我们就可以同时得到 libhello.so/libhello.a 两个库了。
利用已经建立好的静态库与动态库:
ADD_EXECUTABLE(main main.c)
INCLUDE_DIRECTORIES(/usr/include/hello)
#TARGET_LINK_LIBRARIES(main libhello.so)
TARGET_LINK_LIBRARIES(main libhello.a)
-
通过 INCLUDE_DIRECTORIES 指令加入非标准的头文件搜索路径。
-
通过 LINK_DIRECTORIES 指令加入非标准的库文件搜索路径。
-
通过 TARGET_LINK_LIBRARIES 为库或可执行二进制加入库链接。
特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH
这两个是环境变量而不是 cmake 变量。使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者CMAKE_INCLUDE_PATH=/home/include cmake …等方式。
cmake 常用变量和常用环境变量
cmake 自定义变量的方式:
主要有隐式定义和显式定义两种,
- 前面举了一个隐式定义的例子,就是 PROJECT 指令,他会隐式的定义
<projectname>_BINARY_DIR
和<projectname>_SOURCE_DIR
两个变量。 - 显式定义的例子我们前面也提到了,使用 SET 指令,就可以构建一个自定义变量了。 比如:
SET(HELLO_SRC main.SOURCE_PATHc)
, PROJECT_BINARY_DIR 可以通过${HELLO_SRC}来引用这个自定义变量了.
cmake 常用变量:
CMAKE_BINARY_DIR
PROJECT_BINARY_DIR
<projectname>_BINARY_DIR
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。
CMAKE_CURRENT_SOURCE_DIR
指的是当前处理的 CMakeLists.txt 所在的路径,比如上面我们提到的 src 子目录。
CMAKE_CURRRENT_BINARY_DIR
如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-of-source 编译,他指的是 target 编译目录。
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 指令来调用自己的模块了。
PROJECT_NAME
返回通过 PROJECT 指令定义的项目名称。
cmake 调用环境变量的方式
使用$ENV{NAME}指令就可以调用系统的环境变量了。
比如MESSAGE(STATUS “HOME dir: $ENV{HOME}”)
设置环境变量的方式是:SET(ENV{变量名} 值)