CMake基础

CMake基础

一、CMake简介与在Linux环境下简单使用CMake的示例

参考链接Linux环境下CMake的示例

1.1 CMake简介

Make 工具里面,比较出名的有 GNU Make,Qt 的 qmake,微软的 MS nmake,BSD Make(pmake),Makepp,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。

而每次对出现的问题进行解决,即是一次对效率化的贡献。因此 CMake 应运而生。

它可以让程序员通过一个与开发平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的 Makefile 和工程文件,如 *nix 平台的 Makefile 或者 win 平台下面的 VS 工程。

一些比较出名的开源项目以及商业项目,也都是采用 CMake 作为项目架构系统,比如 KDE 以及OpenCV,奈飞(Netflix),第二人生(Second Life),还有 LLVM 和 Clang,MySQL 等等。

1.2 安装CMake

CMake 目前已经成为各大 Linux 发行版提供的组件,比如 Ubuntu 直接在系统中包含,所以,需要自己动手安装的可能性很小。如果你使用的操作系统没有提供 CMake 或者包含的版本较旧,有以下两种下载方法。

可以直接在命令行下载CMake:

sudo apt-get install CMakek

也可以从官网下载最新版本:

http://www.CMake.org/HTML/Download.html

1.3 最简单的例子:HelloWorld

下面选择一个最简单的例子 Helloworld 来演练一下 CMake 的完整构建过程,并不会深入的探讨 CMake,仅仅展示一个简单的例子,并加以粗略的解释。

1,准备工作

首先,在 /backup 目录建立一个 CMake 目录,用来放置我们学习过程中的所有练习。

mkdir -p /backup/CMake

然后在 CMake 建立第一个练习目录 t1 。

cd /backup/CMake
mkdir t1
cd t1

在 t1 目录建立 main.c 和 CMakeLists.txt(注意文件名大小写)。

main.c 文件内容:

#include<stdio.h>
int main()
{
    printf("Hello world!\n");
    return 0;
}

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

2,开始构建

所有的文件创建完成后,t1 目录中应该存在 main.c 和 CMakeLists.txt 两个文件。接下来我们来构建这个工程,在这个目录运行(注意命令后面的点号,代表本目录):

CMake .

输出大概是这个样子:

-- Check for working C compiler: /usr/bin/gcc
-- Check for working C compiler: /usr/bin/gcc -- works
-- Check size of void*
-- Check size of void* - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- This is BINARY dir /backup/CMake/t1
-- This is SOURCE dir /backup/CMake/t1
-- Configuring done
-- Generating done
-- Build files have been written to: /backup/CMake/t1

再让我们看一下目录中的内容,你会发现,系统自动生成了: CMakeFiles、CMakeCache.txt、CMake_install.CMake 等文件,并且生成了 Makefile。

现在不需要理会这些文件的作用,以后你也可以不去理会。最关键的是,它自动生成了Makefile.。

然后进行工程的实际构建,在这个目录输入 make 命令,大概会得到如下的彩色输出:

Scanning dependencies of target hello
[100%] Building C object CMakeFiles/hello.dir/main.o
Linking C executable hello
[100%] Built target hello

如果你需要看到 make 构建的详细过程,可以使用 make VERBOSE=1 或者 VERBOSE=1 make 命令来进行构建。

这时候,我们需要的目标文件 hello 已经构建完成,位于当前目录,尝试运行一下:

./hello

得到输出:

Hello World from Main 

恭喜您,到这里为止您已经完全掌握了 CMake 的使用方法。

3,简单的解释

我们来重新看一下 CMakeLists.txt,这个文件是 CMake 的构建定义文件,文件名是大小写相关的,如果工程存在多个目录,需要确保每个要管理的目录都存在一个CMakeLists.txt。(关于多目录构建,后面我们会提到,这里不作过多解释)。

上面例子中的 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 指令的语法是 :

PROJECT(projectname  [CXX]  [C]  [Java]) 

你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,默认情况表示支持所有语言。

这个指令隐式的定义了两个 CMake 变量:
<projectname>_BINARY_DIR以及<projectname>_SOURCE_DIR,这里就是HELLO_BINARY_DIRHELLO_SOURCE_DIR(所以 CMakeLists.txt 中两个 MESSAGE指令可以直接使用这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所在路径/backup/CMake/t1,后面我们会讲到外部编译,两者所指代的内容会有所不同。

同时 CMake 系统也帮助我们预定义了 PROJECT_BINARY_DIRPROJECT_SOURCE_DIR变量,他们的值分别跟 HELLO_BINARY_DIRHELLO_SOURCE_DIR 一致。为了统一起见,建议以后直接使用PROJECT_BINARY_DIRPROJECT_SOURCE_DIR,即使修改了工程名称,也不会影响这两个变量。

SET

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 指令的语法是:

MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display" ...) 

这个指令用于向终端输出用户定义的信息,包含了三种类型:

  • SEND_ERROR,产生错误,生成过程被跳过。
  • SATUS,输出前缀为—的信息。
  • FATAL_ERROR,立即终止所有 CMake 过程.

我们在这里使用的是 STATUS 信息输出,演示了由 PROJECT 指令定义的两个隐式变量HELLO_BINARY_DIRHELLO_SOURCE_DIR

ADD_EXECUTABLE

ADD_EXECUTABLE 指令的语法是:

ADD_EXECUTABLE(hello ${SRC_LIST}) 

定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是 SRC_LIST 中定义的源文件列表, 本例中你也可以直接写成ADD_EXECUTABLE(hello main.c)

将本例改写成一个最简化的 CMakeLists.txt:

PROJECT(HELLO)
ADD_EXECUTABLE(hello main.c)

可以使用 make clean 对构建结果进行清理。

1.4 基本语法规则

前面提到过,CMake 其实仍然要使用”CMake 语言和语法”去构建,上面的内容就是所谓的”CMake 语言和语法”,最简单的语法规则是:

  • 变量使用${}方式取值,但是在 IF 控制语句中可以直接使用变量名
  • 指令(参数 1 参数 2… ) 【参数使用括弧括起,参数之间使用空格或分号分开】
  • 指令是大小写无关的,参数和变量是大小写相关的。但推荐你全部使用大写指令。

1.5 内部构建与外部构建

上面的例子展示的是“内部构建”,相信看到生成的临时文件比您的代码文件还要多的时候,估计这辈子你都不希望再使用内部构建。

而外部编译的过程如下:

  1. 首先,请清除 t1 目录中除 main.c CMakeLists.txt 之外的所有中间文件,最关键的是 CMakeCache.txt。
  2. 在 t1 目录中建立 build 目录,当然你也可以在任何地方建立 build 目录,不一定必须在工程目录中。
  3. 进入 build 目录,运行 CMake …(注意,…代表父目录,因为父目录存在我们需要的CMakeLists.txt,如果你在其他地方建立了 build 目录,需要运行 CMake <工程的全路径>),查看一下 build 目录,就会发现了生成了编译需要的 Makefile 以及其他的中间文件。
  4. 运行 make 构建工程,就会在当前目录(build 目录)中获得目标文件 hello。

上述过程就是所谓的 out-of-source 外部编译,一个最大的好处是,对于原有的工程没有任何影响,所有动作全部发生在编译目录。通过这一点,也足以说服我们全部采用外部编译方式构建工程。

1.6 小结

本小节描述了使用 CMake 构建 Hello World 程序的全部过程,并介绍了三个简单的指令:PROJECT、MESSAGE、ADD_EXECUTABLE 以及变量调用的方法,同时提及了两个隐式变量 <projectname>_SOURCE_DIR<projectname>_BINARY_DIR,演示了变量调用的方法 。

二、cmake 常用变量和常用环境变量

参考链接cmake 常用变量和常用环境变量

2.1 cmake 变量引用的方式

使用 ${} 进行变量的引用。在 IF 等语句中,是直接使用变量名而不通过 ${} 取值。

2.2 cmake 自定义变量的方式

主要有隐式定义和显式定义两种,举一个隐式定义的例子,就是 PROJECT 指令,他会隐式的定义_BINARY_DIR 和_SOURCE_DIR 两个变量。

而显式定义的例子,可以使用 SET 指令,就可以构建一个自定义变量了。

2.3 cmake 常用变量

1,PROJECT_BINARY_DIR

如果是 in source 编译,这个变量指得就是工程顶层目录,如果是 out-of-source 编译,指的是工程编译发生的目录。另外 _BINARY_DIR 和 CMAKE_BINARY_DIR 跟这个变量指代的内容是一致的。


2,PROJECT_SOURCE _DIR

不论采用何种编译方式,都是工程顶层目录。也就是在 in source 编译时,他跟 PROJECT_BINARY_DIR 等变量一致。另外 _SOURCE_DIR 和 CMAKE_SOURCE_DIR 跟这个变量指代的内容是一致的。


3,CMAKE_CURRENT_SOURCE_DIR

指的是当前处理的 CMakeLists.txt 所在的路径。


4,CMAKE_CURRRENT_BINARY_DIR

如果是 in-source 编译,它跟 CMAKE_CURRENT_SOURCE_DIR 一致,如果是 out-ofsource 编译,他指的是 target 编译目录。使用 ADD_SUBDIRECTORY(src bin) 可以更改这个变量的值。


5,CMAKE_CURRENT_LIST_FILE

输出调用这个变量的 CMakeLists.txt 的完整路径。


6,CMAKE_CURRENT_LIST_LINE

输出这个变量所在的行。


7,CMAKE_MODULE_PATH

这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设置一下。比如
SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
这时候你就可以通过 INCLUDE 指令来调用自己的模块了。


8,EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH

前者用来重新定义目标二进制可执行文件的存放位置,后者用来重新定义目标链接库文件的存放位置。


9,PROJECT_NAME

返回通过 PROJECT 指令定义的项目名称。

2.4 cmake 调用环境变量的方式

使用 $ENV{NAME} 指令就可以调用系统的环境变量了。比如

MESSAGE(STATUS “HOME dir: $ENV{HOME}”)

设置环境变量的方式是:

SET(ENV{变量名} 值)

2.5 系统信息

1,CMAKE_MAJOR_VERSION,CMAKE 主版本号,比如 2.4.6 中的 2
2,CMAKE_MINOR_VERSION,CMAKE 次版本号,比如 2.4.6 中的 4
3,CMAKE_PATCH_VERSION,CMAKE 补丁等级,比如 2.4.6 中的 6
4,CMAKE_SYSTEM,系统名称,比如 Linux-2.6.22
5,CMAKE_SYSTEM_NAME,不包含版本的系统名,比如 Linux
6,CMAKE_SYSTEM_VERSION,系统版本,比如 2.6.22

2.6 主要的开关选项

1,CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS

用来控制 IF ELSE 语句的书写方式。

2,BUILD_SHARED_LIBS

这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD_LIBRARY 并没有指定库类型的情况下,默认编译生成的库都是静态库。

如果 SET(BUILD_SHARED_LIBS ON)后,默认生成的为动态库。

3,CMAKE_C_FLAGS

设置 C 编译选项,也可以通过指令 ADD_DEFINITIONS()添加。

4,CMAKE_CXX_FLAGS

设置 C++编译选项,也可以通过指令 ADD_DEFINITIONS()添加。

2.7 小结

本文介绍了一些较常用的 cmake 变量,这些变量仅仅是所有 cmake 变量的很少一部分,如果需要了解更多的 cmake 变量,更好的方式是阅读一些成功项目的 cmake 工程文件,比如 KDE4 的代码。

三、cmake 常用指令

参考链接

cmake 常用指令

简书 金戈大王-CMake之find_package

CSDN FishBear_move_on-cmake教程4(find_package使用)

CSDN WhateverYoung

《CMake实践》

3.1 基本指令

1.1 ADD_DEFINITIONS

向 C/C++编译器添加-D 定义,比如:

ADD_DEFINITIONS(-DENABLE_DEBUG -DABC),参数之间用空格分割。

如果你的代码中定义了#ifdef ENABLE_DEBUG #endif,这个代码块就会生效。


1.2 ADD_DEPENDENCIES

ADD_DEPENDENCIES(target-name depend-target1 depend-target2 …)

如果两个targets有依赖关系(通过target_link_libraries解决)并且依赖库也是通过编译源码产生的。这时候一句 add_dependencies 可以在直接编译上层target时,自动检查下层依赖库是否已经生成。没有的话先编译下层依赖库,然后再编译上层target,最后 link depend target。若只有一个targets有依赖关系,一般选择使用 target_link_libraries。


1.3 ADD_EXECUTABLE

ADD_EXECUTABLE(hello main.cpp)

定义了这个工程会生成一个文件名为 hello 的可执行文件,相关的源文件是mian.cpp。


1.4 ADD_LIBRARY

该指令的主要作用就是将指定的源文件生成链接库文件,然后添加到工程中去。语法如下:

add_library( [STATIC | SHARED | MODULE] [EXCLUDE_FROM_ALL] [source1] [source2] […])

其中表示链接库文件的名字,该链接库文件会根据命令里列出的源文件来创建。而 STATIC、SHARED 和 MODULE 的作用是指定生成的链接库文件的类型,STATIC 为静态链接库,SHARED 为动态链接库,而 MODULE,在使用 dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。 EXCLUDE_FROM_ALL 参数的含义是将这个target排除在all target列表之外,这样当执行make时,这个target就不会被编译。而语法中的source1 source2分别表示各个源文件。


1.5 ADD_SUBDIRECTORY

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置。EXCLUDE_FROM_ALL 参数的含义是将这个子目录的所有target排除在all target列表之外,这样当执行make时,这个子目录的所有target就不会被编译。


1.6 CMAKE_MINIMUM_REQUIRED

CMAKE_MINIMUM_REQUIRED(VERSION versionNumber [FATAL_ERROR])

检查cmake的版本,要求至少为versionNumber。例如 CMAKE_MINIMUM_REQUIRED(VERSION 2.5 FATAL_ERROR) ,如果 cmake 版本小于 2.5,则出现严重错误,整个过程中止。


1.7 INCLUDE_DIRECTORIES

INCLUDE_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 [dir2 …])

指定头文件的搜索路径。例如我现在想要#include “cv.h”,但是这个cv.h的路径是 /usr/local/include/opencv,那么总不能在主函数头前写#include “/usr/local/include/opencv/cv.h” 吧,这个时候就用到include_directories了,它提供了一个搜索头文件暂时的根目录,即你可以在 CMakeLists.txt 中写上 include_directories(/usr/local/include) 来让库文件搜索以 /usr/local/include 为基础,然后在main函数前写上 #include “opencv/cv.h" 即可。


1.8 LINK_DIRECTORIES
LINK_DIRECTORIES(directory1 directory2 …)

添加需要链接的库文件目录,相当于g++命令的-L选项的作用。 该指令有时候不一定需要,因为find_package和find_library指令可以得到库文件的绝对路径。不过你自己写的动态库文件放在自己新建的目录下时,可以用该指令指定该目录的路径以便工程能够找到。例子如下:

LINK_DIRECTORIES("/opt/MATLAB/R2012a/bin/glnxa64")


1.9 LINK_LIBRARIES

LINK_LIBRARIES(library1 library2 …)

添加需要链接的库文件路径,注意这里是全路径,要用在add_executable之前。例子如下:

LINK_LIBRARIES("/opt/MATLAB/R2012a/bin/glnxa64/libeng.so")


1.10 TARGET_LINK_LIBRARIES

TARGET_LINK_LIBRARIES(target library1 <debug | optimized> library2 …)

为库或二进制可执行文件添加库链接,要用在add_executable之后。 上述指令中的target是指通过add_executable()和add_library()指令生成已经创建的目标文件。例子如下:

TARGET_LINK_LIBRARIES(myProject hello),连接libhello.so库
TARGET_LINK_LIBRARIES(myProject libhello.a)


1.11 PKG_CHECK_MODULES

pkg_check_modules(<PREFIX> [REQUIRED] [QUIET]
                  [NO_CMAKE_PATH] [NO_CMAKE_ENVIRONMENT_PATH]
                  <MODULE> [<MODULE>]*)

pkg_check_modules 是 CMake 自己的 pkg-config 模块 的一个用来简化的封装:你不用再检查 CMake 的版本,加载合适的模块,检查是否被加载,等等,参数和传给 find_package 的一样:先是待返回变量的前缀,然后是包名(pkg-config 的)。这样就定义了<prefix>_INCLUDE_DIRS和其他的这类变量,后续的用法就与 find_package 一致。

3.2 find_package 指令

3.2.1 原理

语法如下:

find_package(<package> [version] [EXACT] [QUIET]
             [REQUIRED] [[COMPONENTS] [components...]]
             [NO_POLICY_SCOPE])

功能:采用两种模式搜索外部库。

[version]参数需要一个版本号,它是正在查找的包应该兼容的版本号(格式是major[.minor[.patch[.tweak]]])。EXACT选项要求该版本号必须精确匹配。QUIET选项将会禁掉包没有被发现时的警告信息。REQUIRED选项表示如果包没有找到的话,cmake的过程会终止,并输出警告信息。在REQUIRED选项之后,或者如果没有指定REQUIRED选项但是指定了COMPONENTS选项,在它们的后面可以列出一些与包相关的部件清单(components list)。

首先明确一点,cmake本身不提供任何搜索库的便捷方法,所有搜索库并给变量赋值的操作必须由cmake代码完成,比如下面将要提到的FindXXX.cmake和XXXConfig.cmake。只不过,库的作者通常会提供这两个文件,以方便使用者调用。

搜索有以下两种模式:

  • Module模式:搜索CMAKE_MODULE_PATH指定路径下的FindXXX.cmake文件,执行该文件从而找到XXX库。其中,具体查找库并给XXX_INCLUDE_DIRSXXX_LIBRARIES两个变量赋值的操作由FindXXX.cmake模块完成。
  • Config模式:搜索XXX_DIR指定路径下的XXXConfig.cmake文件,执行该文件从而找到XXX库。其中具体查找库并给XXX_INCLUDE_DIRSXXX_LIBRARIES两个变量赋值的操作由XXXConfig.cmake模块完成。

两种模式看起来似乎差不多,不过cmake默认采取Module模式,如果Module模式未找到库,才会采取Config模式。如果XXX_DIR路径下找不到XXXConfig.cmake文件,则会找/usr/local/lib/cmake/XXX/中的XXXConfig.cmake文件。总之,Config模式是一个备选策略。通常,库安装时会拷贝一份XXXConfig.cmake到系统目录中,因此在没有显式指定搜索路径时也可以顺利找到。

若XXX安装时没有安装到系统目录,因此无法自动找到XXXConfig.cmake,可以在CMakeLists.txt最前面添加XXX的搜索路径。

set(XXX_DIR /home/wjg/projects/XXX/build CACHE PATH "Directory that contains XXXConfig.cmake")   #添加CaffeConfig.cmake的搜索路径
3.2.2 使用

当编译一个需要使用第三方库的软件时,我们需要知道:

去哪儿找头文件 .h对比GCC的 -I 参数
去哪儿找库文件 (.so/.dll/.lib/.dylib/…)对比GCC的 -L 参数
需要链接的库文件的名字对比GCC的 -l 参数

比如说,我们需要一个第三方库 curl,那么我们的 CMakeLists.txt 需要指定头文件目录和库文件,类似:

include_directiories(/usr/include/curl)
target_link_libraries(myprogram path/curl.so)

如果借助于cmake提供的finder会怎么样呢?使用cmake的Modules目录下的FindCURL.cmake,相应的CMakeList.txt 文件:

find_package(CURL REQUIRED)
include_directories(${CURL_INCLUDE_DIR})
target_link_libraries(curltest ${CURL_LIBRARY})

为了能支持各种常见的库和包,CMake自带了很多模块。可以通过命令 cmake –help-module-list (输入cmake –help,然后双击Tab会有命令提示)得到你的CMake支持的模块的列表:直接查看模块路径。比如Ubuntu linux上,模块的路径是 ls /usr/share/cmake/Modules/:

ll -th /usr/share/cmake-3.5/Modules/
......
-rw-r--r--  1 root root  76K Sep 27  2016 FindBoost.cmake
-rw-r--r--  1 root root 2.7K Mar 24  2016 FindCoin3D.cmake
-rw-r--r--  1 root root  77K Mar 24  2016 FindCUDA.cmake
-rw-r--r--  1 root root 3.1K Mar 24  2016 FindCups.cmake
-rw-r--r--  1 root root 2.4K Mar 24  2016 FindCURL.cmake
........

让我们以bzip2库为例。CMake中有个 FindBZip2.cmake 模块。只要使用 find_package(BZip2) 调用这个模块,cmake会自动给一些变量赋值,然后就可以在CMake脚本中使用它们了。变量的列表可以查看cmake模块文件,或者使用命令:

root@xy:~/cmake_practice/cmake_build/build_demo10# cmake --help-module FindBZip2
FindBZip2
---------

Try to find BZip2

Once done this will define
::
BZIP2_FOUND - system has BZip2
BZIP2_INCLUDE_DIR - the BZip2 include directory
BZIP2_LIBRARIES - Link these to use BZip2
BZIP2_NEED_PREFIX - this is set if the functions are prefixed with BZ2_
BZIP2_VERSION_STRING - the version of BZip2 found (since CMake 2.8.8)

cmake 会将路径赋值给对应的变量,我们以curl的cmake为例,其部分内容如下:

find_path(CURL_INCLUDE_DIR NAMES curl/curl.h)
mark_as_advanced(CURL_INCLUDE_DIR)

# Look for the library (sorted from most current/relevant entry to least).
find_library(CURL_LIBRARY NAMES
        curl
        # Windows MSVC prebuilts:
        curllib
        libcurl_imp
        curllib_static
        # Windows older "Win32 - MSVC" prebuilts (libcurl.lib, e.g. libcurl-7.15.5-win32-msvc.zip):
        libcurl
        )

比如一个使用bzip2的简单程序,编译器需要知道 bzlib.h 的位置,链接器需要找到bzip2库。(动态链接的话,Unix上是 libbz2.so 类似的文件,Windows上是 libbz2.dll )

project(helloworld)
add_executable(helloworld hello.c)
find_package (BZip2)
if (BZIP2_FOUND)
    include_directories(${BZIP_INCLUDE_DIRS})
    target_link_libraries (helloworld ${BZIP2_LIBRARIES})
endif (BZIP2_FOUND)

3.3 INSTALL 指令

安装的方法有两种,一种是从代码编译后直接make install安装,一种是打包时的指定目录安装(INSTALL)。

这里需要引入一个新的cmake 指令 INSTALL和一个非常有用的变量CMAKE_INSTALL_PREFIX。 CMAKE_INSTALL_PREFIX变量类似于configure脚本的 –prefix,常见的使用方法看 起来是这个样子:

cmake -DCMAKE_INSTALL_PREFIX=/usr .

INSTALL指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及 文件、目录、脚本等。


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安装到 C M A K E I N S T A L L P R E F I X / b i n 目 录 。 动 态 库 l i b m y l i b 安 装 到 {CMAKE_INSTALL_PREFIX}/bin目录。 动态库libmylib安装到 CMAKEINSTALLPREFIX/binlibmylib{CMAKE_INSTALL_PREFIX}/lib目录。
静态库libmystaticlib安装到${CMAKE_INSTALL_PREFIX}/libstatic目录。
特别注意的是你不需要关心TARGETS具体生成的路径,只需要写上TARGETS名称就可以了。


普通文件的安装

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...]] [...])

这里主要介绍其中的DIRECTORY、PATTERN以及PERMISSIONS参数。
DIRECTORY后面连接的是所在Source目录的相对路径,但务必注意:
abc和abc/有很大的区别。 abc意味着abc这个目录会安装在目标路径下;abc/意味着abc这个目录的内容会被安装在目标路径下。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目录安装到 /share/myproj,将scripts/中的内容安装到 /share/myproj。
不包含目录名为CVS的目录,对于scripts/*文件指定权限为 OWNER_EXECUTE 不包含目录名为CVS的目录,对于scripts/*文件指定权限为 OWNER_EXECUTE OWNER_WRITE OWNER_READ GROUP_EXECUTE GROUP_READ。

四、usb_cam的CMakeLists解析

参考链接usb_cam的CMakeLists解析

4.1 usb_cam软件包简介

现在市面上最常见的还是USB摄像头,物美价廉,要想使USB摄像头在ROS下正常工作,我们就需要一个软件包来支持,现在ROS下最常用的usb摄像头软件包就是usb_cam,简单理解该软件包就是V4L(Video for Linux)USB摄像头驱动在ROS在的一个移植版本。

下面我们在github上来下载该源码,github上的usb_cam源码网址如下:

https://github.com/ros-drivers/usb_cam

1521186605257481.png

通过下图来查看整个下载操作流程:

Screenshot from 2018-03-16 16:01:37.png

下面来查看usb_cam源码介绍:

Screenshot from 2018-03-16 16:18:28.png

4.2 源码剖析

下载好源码后,找到CMakeFiles.txt文件,修改添加注释后的内容如下:

#检查cmake的版本,要求至少为2.8.3
cmake_minimum_required(VERSION 2.8.3)

#定义工程名为usb_cam
project(usb_cam)

#搜索外部库(包)catkin,并在后面列出了与 catkin包 相关的一些部件清单
find_package(catkin REQUIRED COMPONENTS image_transport roscpp std_msgs std_srvs sensor_msgs camera_info_manager)

#先搜索cmake自己的PkgConfig模块,才能使用pkg_check_modules
find_package(PkgConfig REQUIRED)
#pkg_check_module与find_package功能基本一直,也是搜索外部库 
pkg_check_modules(avcodec libavcodec REQUIRED)  #libavcodec库用于读取视频文件
pkg_check_modules(swscale libswscale REQUIRED)  #libswscale库用于各种图像像素格式的转换

#catkin_package 宏是 catkin 的宏之一,声明要传递给依赖项目的内容,生成 cmake 配置文件。
catkin_package(
  INCLUDE_DIRS include
  LIBRARIES ${PROJECT_NAME}
)

#用来向工程添加多个头文件搜索路径
include_directories(
  include
  ${catkin_INCLUDE_DIRS}
  ${avcodec_INCLUDE_DIRS}
  ${swscale_INCLUDE_DIRS}
)

#将指定的源文件usb_cam.cpp生成链接库文件,然后添加到工程中去
add_library(${PROJECT_NAME} src/usb_cam.cpp)

#用来向工程usb_cam添加多个链接库
target_link_libraries(${PROJECT_NAME} 
  ${catkin_LIBRARIES}
  ${avcodec_LIBRARIES}
  ${swscale_LIBRARIES}
)

##生成一个名为usb_cam_node的可执行文件,其对应源文件是nodes/usb_cam_node.cpp
add_executable(${PROJECT_NAME}_node nodes/usb_cam_node.cpp)

#用来向可执行文件usb_cam_node添加多个链接库
target_link_libraries(${PROJECT_NAME}_node 
  ${PROJECT_NAME}
  ${avcodec_LIBRARIES}
  ${swscale_LIBRARIES}
  ${catkin_LIBRARIES}
)

#可执行文件usb_cam_node安装到CATKIN的BIN目录下 (在可执行文件链接了外部库后)
#静态库usb_cam安装到CATKIN的LIB目录下 (在工程链接了外部库后)
install(TARGETS ${PROJECT_NAME}_node ${PROJECT_NAME}
  RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
  LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
)

#在/launch目录下安装一个启动文件
install(DIRECTORY launch/
  DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch
  FILES_MATCHING PATTERN "*.launch"
)

#在catkin的include目录下安装include/usb_cam目录下的所有内容
install(DIRECTORY include/${PROJECT_NAME}/
   DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
   FILES_MATCHING PATTERN "*.h" PATTERN "*.hpp"
)

具体的分析已在注释中详细说明了,有些不明白的指令可以参考我的另一篇博客 - CMake学习笔记三:cmake 常用指令

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值