抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

CodingStudio

努力进步

引言

CMake学习笔记


0 CMake

  • cmake:高级编译配置工具:当多个人用不同的语言或者编译器开发一个项目,最终要输出一个可执行文件或者共享库,所有的操作通过编译CMakeLists.txt完成

1 CMake一个HelloWord

  1. 编写cpp程序
  2. 写CMakeLists.txt
  3. 使用cmake,生成makefile文件
  4. 使用make编译
  5. 最终生成了Hello的可执行程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 1. main.cpp
#include <iostream>

int main(){
std::cout << "hello word" << std::endl;
}

// 2.CMakeLists.txt
PROJECT (HELLO)
SET(SRC_LIST main.cpp)
MESSAGE(STATUS "This is BINARY dir " ${HELLO_BINARY_DIR})
MESSAGE(STATUS "This is SOURCE dir "${HELLO_SOURCE_DIR})
ADD_EXECUTABLE(hello ${SRC_LIST})

// 3.cmake
cmake .
// 目录下就生成了文件: CMakeFiles, CMakeCache.txt, cmake_install.cmake等文件
// 并且生成了Makefile(重要)

// 4. make
make

2 CMake语法介绍

2.1 PROJECT关键字

  • 指定工程的名字和支持的语言,默认支持所有语言
    • PROJECT (HELLO)
      • 指定了工程的名字,并且支持所有语言(建议)
    • PROJECT (HELLO CXX)
      • 指定了工程的名字,并且支持语言是C++
    • PROJECT (HELLO C CXX)
      • 指定了工程的名字,并且支持语言是C和C++
  • 该指令隐式定义了两个CMAKE的变量
    • <projectname>_BINARY_DIR,本例中是 HELLO_BINARY_DIR
    • <projectname>_SOURCE_DIR,本例中是 HELLO_SOURCE_DIR
    • MESSAGE关键字可以直接使用者两个变量
  • 问题:如果改了工程名,这两个变量名也会改变
    • 解决:定义两个预定义变量:PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR,这两个变量和HELLO_BINARY_DIR,HELLO_SOURCE_DIR是一致的

2.2 SET关键字

  • 显示的指定变量的
    • SET(SRC_LIST main.cpp) SRC_LIST变量就包含了main.cpp
    • SET(SRC_LIST main.cpp t1.cpp t2.cpp)

2.3 MESSAGE关键字

  • 向终端输出用户自定义的信息
  • 主要包含三种信息:
    • SEND_ERROR, 产生错误,生成过程被跳过
    • SATUS, 输出前缀为—的信息
    • FATAL_ERROR, 立即终止所有 cmake 过程

2.4 ADD_EXECUTABLE关键字

  • 生成可执行文件
    • ADD_EXECUTABLE(hello ${SRC_LIST}), 生成的可执行文件名是hello,源文件读取变量SRC_LIST中的内容
      • 也可以直接写 ADD_EXECUTABLE(hello main.cpp)
1
2
3
// 上面的CMakeLists.txt可简化写为
PROJECT(HELLO)
ADD_EXECUTABLE(hello main.cpp)
  • 注意:工程名的 HELLO 和生成的可执行文件 hello 没有任何关系

2.5 语法的基本原则

  • 变量使用${}方式取值
    • 但在 IF 控制语句中是直接使用变量名
  • 指令(参数 1 参数 2…) 参数使用括弧括起,参数之间使用空格或分号分开
    • 例子 ADD_EXECUTABLE(hello main.cpp func.cpp)或者ADD_EXECUTABLE(hello main.cpp;func.cpp)
  • 指令是大小写无关的,参数和变量是大小写相关的
    • 推荐全部使用大写
  • 注意事项
    • SET(SRC_LIST main.cpp) 可以写成 SET(SRC_LIST “main.cpp”)
      • 如果源文件名中含有空格,就必须要加双引号
    • ADD_EXECUTABLE(hello main) 不写后缀会自动去找.c和.cpp(不推荐)

3 内部构建和外部构建

  • 上述例子就是内部构建,生产的临时文件特别多,不方便清理
  • 外部构建会把生成的临时文件放在build目录下,不会对源文件有任何影响
    • 强烈使用外部构建方式

3.1 外部构建方式举例

  1. 建立一个build目录,可以在任何地方(建议在当前目录下)
  2. 进入build目录, 运行cmake ..
    • ..表示上一级目录,也可以写 CMakeLists.txt所在的绝对路径,生产的文件都在build目录下
  3. 在build目录下,运行make来构建工程
  • 注意外部构建的两个变量
    1. HELLO_SOURCE_DIR 工程路径
    2. HELLO_BINARY_DIR 编译路径(build目录)

3.2 将目标文件放入构建目录的 bin 子目录

1
2
3
4
5
6
7
8
9
10
11
12
// 项目的结构
.
├── build
├── CMakeLists.txt
├── doc // 用来放置这个工程的文档 hello.txt
├── hello.txt
├── COPYRIGHT
├── README
├── runhello.sh // 用来调用 hello 二进制
└── src
├── CMakeLists.txt
└── main.cpp
  • 每个目录下都要有一个CMakeLists.txt说明
1
2
3
4
5
6
7
// 外层CMakeLists.txt
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)

// src下的CMakeLists.txt

ADD_EXECUTABLE(hello main.cpp)

3.2.1 ADD_SUBDIRECTORY 指令

  • ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])

  • 用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置

    • EXCLUDE_FROM_ALL函数是将写的目录从编译中排除,如程序中的example
    • ADD_SUBDIRECTORY(src bin)
      • 将 src 子目录加入工程并指定编译输出(包含编译中间结果)路径为bin 目录
      • 如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在build/src 目录

3.2.2 更改二进制的保存路径

  • 指定最终的目标二进制的位置: SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量
    • SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
    • SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
  • 哪里要改变目标存放路径,就在哪里加入上述的定义,所以应该在src下的CMakeLists.txt下写

4 安装

  • 一种是从代码编译后直接 make install 安装
  • 一种是从打包时的指定目录进行安装
    • 简单的可以这样指定目录:make install DESTDIR=/tmp/test
    • 稍微复杂一点可以这样指定目录:./configure –prefix=/usr
  • CMake语法新内容:
    • INSTALL可以安装:二进制、动态库、静态库以及文件、目录、脚本等
    • CMAKE一个新的变量:CMAKE_INSTALL_PREFIX

4.1安装文件COPYRIGHT和README

  • INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/)
  • FILES:文件
  • DESTINATION:
    1. 可以写绝对路径
    2. 可以写相对路径,相对路径实际路径是:${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>
      • CMAKE_INSTALL_PREFIX 默认是在 /usr/local/
  • cmake -DCMAKE_INSTALL_PREFIX=/usr 在cmake的时候指定CMAKE_INSTALL_PREFIX变量的路径

4.2 安装脚本runhello.sh

  • PROGRAMS:非目标文件的可执行程序安装(比如脚本之类)
  • INSTALL(PROGRAMS runhello.sh DESTINATION bin)
    • 实际安装到的是 /usr/bin

4.3 安装 doc 中的 hello.txt

  1. 在 doc 目录建立CMakeLists.txt ,通过install下的file
  2. 直接在工程目录通过
    • INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)
      • DIRECTORY 后面连接的是所在 Source 目录的相对路径
  • abc 和 abc/有很大的区别
    • 目录名不以/结尾:这个目录将被安装为目标路径下的
    • 目录名以/结尾:将这个目录中的内容安装到目标路径

4.4 安装过程

1
2
3
cmake ..
make
make install

5 静态库和动态库的构建

  • 任务

    1. 建立一个静态库和动态库,提供 HelloFunc 函数供其他程序编程使用,HelloFunc 向终端输出 Hello World 字符串
    2. 安装头文件与共享库
  • 静态库和动态库的区别

    • 静态库的扩展名一般为“.a”或“.lib”;动态库的扩展名一般为“.so”或“.dll”。
    • 静态库在编译时会直接整合到目标程序中,编译成功的可执行文件可独立运行
    • 动态库在编译时不会放到连接的目标程序中,即可执行文件无法单独运行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// -------
// 构建实例
// -------

// 项目目录
.
├── build
├── CMakeLists.txt
└── lib
├── CMakeLists.txt
├── hello.cpp
└── hello.h

// hello.h
#ifndef HELLO_H
#define Hello_H

void HelloFunc();

#endif

// hello.cpp
#include "hello.h"
#include <iostream>
void HelloFunc(){
std::cout << "Hello World" << std::endl;
}

// CMakeLists.txt
PROJECT(HELLO)
ADD_SUBDIRECTORY(lib bin)

// lib/CMakeLists.txt
SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

5.1 同时构建静态和动态库

1
2
3
4
5
6
7
8
9
// 静态库的后缀是.a
// 如果用这种方式,只会构建一个动态库,不会构建出静态库
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello STATIC ${LIBHELLO_SRC})

// 修改静态库的名字,这样是可以的
// 但是往往希望名字是相同的,只是后缀不同
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

5.1.1 ADD_LIBRARY

  • ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

5.1.2 SET_TARGET_PROPERTIES

  • 可以用来设置输出的名称
    • 对于动态库,还可以用来指定动态库版本和 API 版本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ------------------
// 同时构建静态和动态库
// ------------------
SET(LIBHELLO_SRC hello.cpp)

ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})

// 对hello_static的重名为hello
SET_TARGET_PROPERTIES(hello_static PROPERTIES OUTPUT_NAME "hello")
// cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为,在构建 libhello.so 时, 就会清理掉 libhello.a
SET_TARGET_PROPERTIES(hello_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

SET_TARGET_PROPERTIES(hello PROPERTIES OUTPUT_NAME "hello")
SET_TARGET_PROPERTIES(hello PROPERTIES CLEAN_DIRECT_OUTPUT 1)

5.1.3 动态库的版本号

  • 一般动态库都有一个版本号的关联
    • libhello.so.1.2
    • libhello.so ->libhello.so.1
    • libhello.so.1->libhello.so.1.2
  • CMakeLists.txt 插入的内容
    • SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
      • VERSION 指代动态库版本,SOVERSION 指代 API 版本

5.2 安装共享库和头文件

  • 将 hello 的共享库安装到<prefix>/lib目录
  • 将 hello.h 安装到<prefix>/include/hello 目录
1
2
3
4
5
6
//文件放到该目录下
INSTALL(FILES hello.h DESTINATION include/hello)

//二进制,静态库,动态库安装都用TARGETS
//ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME 特指可执行目标二进制。
INSTALL(TARGETS hello hello_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)
  • 注意:安装的时候,指定一下路径,放到系统下: cmake -DCMAKE_INSTALL_PREFIX=/usr ..

5.3 使用外部共享库和头文件

  • 新建一个目录来使用外部共享库和头文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 项目结构
.
├── build
├── CMakeLists.txt
└── src
├── CMakeLists.txt
└── main.cpp

// main.cpp
#include <hello.h>

int main(){
HelloFunc();
}

5.3.1 解决:make后头文件找不到的问题

PS:include <hello/hello.h> 这样include是可以,这么做的话,就没啥好讲的了

关键字:INCLUDE_DIRECTORIES 这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割

在CMakeLists.txt中加入头文件搜索路径

INCLUDE_DIRECTORIES(/usr/include/hello)

5.3.2 解决:找到引用的函数问题

  • 报错信息:undefined reference to `HelloFunc()’
  • 关键字:LINK_DIRECTORIES 添加非标准的共享库搜索路径
    • 指定第三方库所在路径,LINK_DIRECTORIES(/home/myproject/libs)
  • 关键字:TARGET_LINK_LIBRARIES 添加需要链接的共享库
    • 使用TARGET_LINK_LIBRARIES的时候,只需要给出动态链接库的名字就行了
  • 在CMakeLists.txt中插入链接共享库,主要要插在executable的后面
  • 链接静态库TARGET_LINK_LIBRARIES(main libhello.a)

5.3.3 特殊的环境变量

  • CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH
    • 环境变量而不是 cmake 变量
  • 指明include路径的方法(两种方法)
    • 使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)来指明include路径的位置
    • 使用环境变量export CMAKE_INCLUDE_PATH=/usr/include/hello

6 其他内容

  • 生产debug版本的方法cmake .. -DCMAKE_BUILD_TYPE=debug
1
2
3
4
5
6
7
8
<!-- 遍历src下的所有cpp文件的main函数进行构建 -->
<!-- src/CMakeLists.txt中添加的内容 -->
file (GLOB_RECURSE files *.cpp)
foreach (file ${files})
string(REGEX REPLACE ".+/(.+)\\..*" "\\1" exe ${file})
add_executable (${exe} ${file})
message (\ \ \ \ --\ src/${exe}.cpp\ will\ be\ compiled\ to\ bin/${exe})
endforeach ()

评论