C++ Cmake

发布时间 2023-10-06 00:38:41作者: havelearned
cmake的定义是什么?---高级编译配置工具
**当多个人用不同的语言或者编译器开发一个项目,最终要输出一个可执行文件或者共享库(d训,so等等)这时候神器就出现了-CMak!**
**所有操作都是通过编译CMakeLists.txt来完成的一简单**
官方网站是www.cmake.org,可以通过访问官方网站获得更多关于cmake的信息
**学习CMake的目的,为将来处理大型的C/C++,JAVA项目做准备**

下载安装: www.cmake.org

演示

创建main.cpp

#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

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

使用命令: cmake . 生成文件

-rw-r--r-- 1 root root 11960 8月  13 23:18 CMakeCache.txt
drwxr-xr-x 5 root root  4096 8月  13 23:27 CMakeFiles
-rw-r--r-- 1 root root  1555 8月  13 23:27 cmake_install.cmake
-rw-r--r-- 1 root root   189 8月  13 23:27 CMakeLists.txt
-rw-r--r-- 1 root root    79 8月  13 22:33 main.cpp
-rw-r--r-- 1 root root  4611 8月  13 23:27 Makefile

使用命令: make 开始编译,得到hello的可执行文件 ,执行它: ./hello

-rw-r--r-- 1 root root 11960 8月  13 23:18 CMakeCache.txt
drwxr-xr-x 5 root root  4096 8月  13 23:29 CMakeFiles
-rw-r--r-- 1 root root  1555 8月  13 23:27 cmake_install.cmake
-rw-r--r-- 1 root root   189 8月  13 23:27 CMakeLists.txt
-rwxr-xr-x 1 root root  8968 8月  13 23:29 **hello**
-rw-r--r-- 1 root root    79 8月  13 22:33 main.cpp
-rw-r--r-- 1 root root  4611 8月  13 23:27 Makefile

Cmake语法介绍

PROJECT关键字

可以用来指定工程的名字和支持的语言,默认支持所有语言
PROJECT(HELLO) 指定了工程的名字,并且支持所有语言-建议
PROJECT(HELLO CXX) 指定了工程的名字,并且支持语言是C+
PROJECT (HELLO C CXX) 指定了工程的名字,并且支持语言是C和C++

该指定隐式定义了两个CMAKE的变量
**<项目名称>_BINARY_DIR,本例中是HELLO_BINARY_DIR
<项目名称>_SOURCE_DIR,本例中是HELLO_SOURCE_DIR
MESSAGE 关键字就可以直接使⽤者两个变量,当前都指向当前的⼯作⽬录,后⾯会讲外部编译**

**问题:如果改了⼯程名,这两个变量名也会改变**
解决:**⼜定义两个预定义变**量:**PROJECT_BINARY_DIR**和**PROJECT_SOURCE_DIR**,这**两个变量和
HELLO_BINARY_DIR,HELLO_SOURCE_DIR是⼀致的**。所以改了⼯程名也没有关系

SET 关键字

⽤来显示的指定变量的
SET(SRC_LIST main.cpp) SRC_LIST 变量就包含了 main.cpp
也可以 SET(SRC_LIST main.cpp t1.cpp t2.cpp)

MESSAGE

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

ADD_EXECUTABLE关键字

**⽣成可执⾏⽂件**
**ADD_EXECUTABLE(hello ${SRC_LIST}) ⽣成的可执⾏⽂件名是hello**,源⽂件读取变量SRC_LIST中的内容
**也可以直接写 ADD_EXECUTABLE(hello main.cpp)**
上述例⼦可以简化的写成
PROJECT(HELLO) ADD_EXECUTABLE(hello main.cpp)
**注意:⼯程名的 HELLO 和⽣成的可执⾏⽂件 hello 是没有任何关系的**

---------------语法的基本原则
变量使⽤${}⽅式取值,但是在 IF 控制语句中是直接使⽤变量名
指令(参数 1 参数 2...) 参数使⽤括弧括起,参数之间使⽤空格或分号分开。 以上⾯的 ADD_EXECUTABLE 指令
为例,如果存在另外⼀个 func.cpp 源⽂件
就要写成: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,最好不要这样写,可能会有这两个⽂
件main.cpp和main

内部构建和外部构建

  • 上述例⼦就是内部构建,他⽣产的临时⽂件特别多,不⽅便清理
  • 外部构建,就会把⽣成的临时⽂件放在build⽬录下,不会对源⽂件有任何影响强烈使⽤外部构建⽅式

外部构建方式:

创建文件CMakeLists.txt 和main.cpp

-rw-r--r-- 1 root root 189 8月  13 23:27 CMakeLists.txt
-rw-r--r-- 1 root root  79 8月  13 22:33 main.cpp

创建一个目录

mkdir build
cd build
cmake <path> #main.cpp和CMakeLists.txt目录位置
------------------
ls
-rw-r--r-- 1 root root 11978 8月  14 00:06 CMakeCache.txt
drwxr-xr-x 5 root root  4096 8月  14 00:06 CMakeFiles
-rw-r--r-- 1 root root  1567 8月  14 00:06 cmake_install.cmake
-rw-r--r-- 1 root root  4635 8月  14 00:06 Makefile
make
...
./hello

CMake工程

创建对应目录

├── build
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    └── main.cpp

外层CMakeLists.txt

PROJECT(HELLO) #项目名称
ADD_SUBDIRECTORY(src bin) #添加子目录文件和二进制文件存放目录

ADD_SUBDIRECTORY 指令

ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
- 这个指令⽤于向当前⼯程添加存放源⽂件的⼦⽬录,并可以指定中间⼆进制和⽬标⼆进制存放的位置
- EXCLUDE_FROM_ALL函数是将写的⽬录从编译中排除,如程序中的example 
- ADD_SUBDIRECTORY(src bin)
    - 将 src ⼦⽬录加⼊⼯程并指定编译输出(包含编译中间结果)路径为bin ⽬录
    - 如果不进⾏ bin ⽬录的指定,那么编译结果(包括中间结果)都将存放在build/src ⽬录

更改⼆进制的保存路径
SET 指令重新定义 EXECUTABLE_OUTPUT_PATH 和 LIBRARY_OUTPUT_PATH 变量 来指定最终的⽬标⼆进制的位置
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) 
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
思考:加载哪个CMakeLists.txt当中
哪⾥要改变⽬标存放路径,就在哪⾥加⼊上述的定义,所以应该在src下的CMakeLists.txt下写

/src/CMakeLists.txt

ADD_EXECUTABLE(hello main.cpp) # 可执行文件

CMake 安装

  • ⼀种是从代码编译后直接 make install 安装
  • ⼀种是打包时的指定 ⽬录安装。
    • 简单的可以这样指定⽬录:make install DESTDIR=/tmp/test
    • 稍微复杂⼀点可以这样指定⽬录:./configure –prefix=/usr

目录结构:

├── build
├── CMakeLists.txt
├── COPYRIGHT  #版权文件
├── doc 
│   └── hello.txt
├──  # 介绍文件
├── runhello.sh #linux 脚本文件
└── src
    ├── CMakeLists.txt
    └── main.cpp

CMAKE_INSTALL_PREFIX 指令

CMAKE_INSTALL_PREFIX 默认是在 /usr/local/

安装: COPYRIGHT README ,编辑文件 CMakeLists.txt

PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
# 安装文件 COPYRIGHT  README,到目录 SHARE/DOC/CMAKE/
# 如果是相对路径 share/doc/cmake/ 那么cmake会自动拼接一个变量:
# ${CMAKE_INSTALL_PREFIX}/share/doc/cmake/ ,默认是在/usr/local/,全路径为:/usr/local/share/doc/
**INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/)**

安装 :脚本 runhello.sh

PROGRAMS:⾮⽬标⽂件的可执⾏程序安装(⽐如脚本之类)

 INSTALL(PROGRAMS runhello.sh DESTINATION bin) 

说明:实际安装到的是 /usr/bin

安装 doc 中的 hello.txt

  • ⼀、是通过在 doc ⽬录建⽴CMakeLists.txt ,通过install下的file
  • ⼆、是直接在⼯程⽬录通过
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)

DIRECTORY 后⾯连接的是所在 Source ⽬录的相对路径
注意:abc 和 abc/有很⼤的区别
⽬录名不以/结尾:这个⽬录将被安装为⽬标路径下的
⽬录名以/结尾:将这个⽬录中的内容安装到⽬标路径`

开始安装

cd build
cmake ..
make
[root@VM-12-3-centos build]# make install
[100%] Built target hello
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/share/doc/cmake/COPYRIGHT
-- Installing: /usr/local/share/doc/cmake/README
-- Installing: /usr/local/bin/runhello.sh
-- Installing: /usr/local/share/doc/cmake
-- Installing: /usr/local/share/doc/cmake/hello.txt


CMake 静态库和动态库的构建

任务:

  • 建⽴⼀个静态库和动态库,提供 HelloFunc 函数供其他程序编程使⽤,HelloFunc 向终端输HelloWorld 字符串。
  • 安装头⽂件与共享库。
    • 静态库和动态库的区别
    • 静态库的扩展名⼀般为“.a”或“.lib”;动态库的扩展名⼀般为“.so”或“.dll”。
    • 静态库在编译时会直接整合到⽬标程序中,编译成功的可执⾏⽂件可独⽴运⾏
    • 动态库在编译时不会放到连接的⽬标程序中,即可执⾏⽂件⽆法单独运⾏。

目录结构

├── build
├── CMakeLists.txt
└── lib
    ├── CMakeLists.txt
    ├── hello.cpp
    └── hello.h

CMakeLists.txt

PROJECT(HELLO)
ADD_SUBDIRECTORY(lib bin)

lib/ CMakeLists.txt

SET(LIBHELLO_SRC hello.cpp)
ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})

ADD_LIBRARY

ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})
hello:就是正常的库名,⽣成的名字前⾯会加上lib,最终产⽣的⽂件是libhello.so,自动加上前缀lib,后缀.os
SHARED,动态库 
STATIC,静态库 ${LIBHELLO_SRC} :源⽂件

开始执行:

cd build
cmake ..
[root@VM-12-3-centos build]# make
Scanning dependencies of target hello
[100%] Building CXX object bin/CMakeFiles/hello.dir/hello.o
Linking CXX shared library libhello.so
[100%] Built target hello

drwxr-xr-x 3 root root 4096 8月  14 01:43 CMakeFiles
-rw-r--r-- 1 root root 1151 8月  14 01:43 cmake_install.cmake
-rwxr-xr-x 1 root root 8640 8月  14 01:43 **libhello.so**
-rw-r--r-- 1 root root 5045 8月  14 01:43 Makefile


同时构建静态和动态库

// 如果⽤这种⽅式,只会构建⼀个动态库,不会构建出静态库,虽然静态库的后缀是.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})


**总之这样是不行的**

SET_TARGET_PROPERTIES
这条指令可以⽤来设置输出的名称,对于动态库,还可以⽤来指定动态库版本和 API 版本
同时构建静态和动态库

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)

//VERSION 指代动态库版本,SOVERSION 指代 API 版本
SET_TARGET_PROPERTIES(hello PROPERTIES VERSION 1.0 SOVERSION 1) 

开始执行:

cd build
cmake ..
make
cd bin/
ll

drwxr-xr-x 4 root root 4096 8月  14 01:54 CMakeFiles
-rw-r--r-- 1 root root 1151 8月  14 01:54 cmake_install.cmake
-rw-r--r-- 1 root root 2828 8月  14 01:54 **libhello.a**
-rwxr-xr-x 1 root root 8640 8月  14 01:54 **libhello.so**
-rw-r--r-- 1 root root 5997 8月  14 01:54 Makefile


安装共享库和头⽂件

本例中我们将 hello 的共享库安装到/lib⽬录,
将 hello.h 安装到/include/hello ⽬录

首先安装头文件: lib/CMateLists.txt

//⽂件放到该⽬录下
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 ..

make
make install
Install the project...
-- Install configuration: ""
-- Installing: /usr/include/hello/hello.h
-- Installing: /usr/lib/libhello.so.1.0
-- Installing: /usr/lib/libhello.so.1
-- Installing: /usr/lib/libhello.so
-- Installing: /usr/lib/libhello.a


使用外部共享库和静态库

目录结构

├── build
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    └── main.cpp

CMakeLists.txt

PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)

src/CMakeLists.txt

add_executable(hello main.cpp)

src/main

#include <hello.h>
int main(){
  println();
}


开始执行:

cd build
cmake ..
make 
/home/CPlusPlus/cmake03/src/main.cpp:1:19: 致命错误:hello.h:没有那个文件或目录
 #include <hello.h>
                   ^
编译中断。很显然没有找到头文件

解决:make后头⽂件找不到的问题, 编辑文件 lib/CMakeLists.txt

#接着上一个段的文件生成在这个目录下,告诉cmake到这个文件下去找
INCLUDE_DIRECTORIES(/usr/include/hello)

解决:找到引⽤的函数问题


报错信息:undefined reference to `HelloFunc()'
关键字:LINK_DIRECTORIES 添加⾮标准的共享库搜索路径
关键字:TARGET_LINK_LIBRARIES 添加需要链接的共享库
在CMakeLists.txt中插⼊链接共享库,主要要插在executable的后⾯
TARGET_LINK_LIBRARIES(main [libhello.so](http://libhello.so))
查看main的链接情况

再次编辑 lib/CMakeLists.txt, 添加下面的关键字
**TARGET_LINK_LIBRARIES(main libhello.a)**

特殊的环境变量 CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH
注意:这两个是环境变量⽽不是 cmake 变量,可以在linux的bash中进⾏设置
我们上⾯例⼦中使⽤了绝对路径INCLUDE_DIRECTORIES(/usr/include/hello)来指明include路径的位置
我们还可以使⽤另外⼀种⽅式,使⽤环境变量export CMAKE_INCLUDE_PATH=/usr/include/hello