Ubuntu下cmake使用入门

         CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件。其包含自己的语法结构,只要按照其语法编写成CMakeLists.txt,然后camke程序就能对其解析,生成特定平台的构建脚本。Cmake 并不直接建构出最终的软件,而是产生标准的建构文件(如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces),然后再依一般的建构方式使用。这使得熟悉某个集成开发环境(IDE)的开发者可以用标准的方式建构他的软件,这种可以使用各平台的原生建构系统的能力是CMake和SCons等其他类似系统的区别之处。

      CMake其实是一个非常灵活和复杂的工具,建议是边用边学,CMake自身也是一种编程语言,可以用其来实现基本的程序逻辑,比如循环,条件分支等等,其中最典型的应用是根据工程的配置来选择性编译部分源代码,比如有些功能只在测试版本中才能开启,和其他构建工具一样CMake的本质依然是定义各个目标之间的关联,比如项目有哪些可执行文件,动态库,资源文件组成,然后它们又是通过那些源文件编译,用到了那些外部关联,同时,CMake还允许添加单元测试,创建安装包,或者将工程拆分成若干个子目录更利于大型项目的管理。

目录

1.认识构建

2.CMake构建原理

 3.Ubuntu下CMake入门

3.1 安装cmake

3.2 cmake编译方式

 3.3 cmake常用关键字 

3.4 语法的基本原则

3.5 标准化构建工程

3.5.1 构建项目目录结构和相关文件

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

 3.5.3 修改二进制文件存放路径

 3.6 安装

3.6.1 安装方式

3.6.2 cmake安装命令:INSTALL

3.6.3 安装过程

3.7 静态库和动态库的构建

3.7.1 构建动态库

3.7.2 同时构建静态和动态库

3.7.3 安装共享库和头文件

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

3.7.5 特殊的环境变量

3.7.6 生成调试版本


1.认识构建

        软件构建所做的就是全自动完成代码编译,链接,打包的整个过程,并且还能管理不同组件,甚至包括第三方库的关联,平时使用的IDE大多都内置了构建系统,平时开发我们可能没有太留意每个构建工具通常都有各自擅长的领域,
(1)如果在VS中做C++开发,使用的是微软的MSBuild
(2)如果使用Android Studio写移动端的程序,多半用到的是Gradle等
另外还有一些更复杂,更万能的构建系统,比如Bazel,BUCK,他们试图用单个工具来完成
各种语言在不同环境下的构建cmake是一个被广泛使用的,开源免费并且完全跨平台的构建工具,如果希望在不同平台上编译运行你的软件,以后就不需要再手动配置Makefile,VS或者XCode工程了,CMake会全自动帮你做到这一切。常见的构建工具如下:

2.CMake构建原理

 3.Ubuntu下CMake入门

3.1 安装cmake

安装cmake
//更新依赖
sudo apt --fix-broken install
//安装tree命令
sudo apt --fix-broken install tree
//安装cmake
sudo apt install cmake

3.2 cmake编译方式

cmake编译方式分为两类,内部编译和外部编译,第一个CMakeLists.txt写法如下:

CMAKE_MINIMUM_REQUIRED(VERSION 3.10)
PROJECT (USECMAKE C CXX)
SET(SRC_LIST usecmake.cpp)
MESSAGE(STATUS "USECMAKE_BINARY_DIR   is " ${USECMAKE_BINARY_DIR})
MESSAGE(STATUS "USECMAKE_SOURCE_DIR   is " ${USECMAKE_SOURCE_DIR})
MESSAGE(STATUS "PROJECT_BINARY_DIR    is " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "PROJECT_SOURCE_DIR    is " ${PROJECT_SOURCE_DIR})
ADD_EXECUTABLE(useCmake ${SRC_LIST})

(1)in-source编译(内部编译)

内部构建,生产的临时文件特别多,不方便清理

例如有如下项目:

├── CMakeLists.txt
└── usecmake.cpp
执行cmake .命令后,tree -L 2命令查看,发现项目目录下产生了很多目录和文件,看着很乱。

(2)out-of-source(外部编译)(强烈使用外部构建方式)

外部构建,就会把生成的临时文件放在build目录下,不会对源文件有任何影响

具体做法:

新建目录build, cmake {path}({path}代表项目工程的根目录)进行编译
一般的做法是在项目根目录下创建目录build, 进入build,执行cmake ..编译

编译后目录结构如下,感觉很清爽

 3.3 cmake常用关键字 

(1)PROJECT
作用:用来指定工程的名字和支持的语言,默认支持所有语言
举例:
PROJECT (USECMAKE)           工程的名为USECMAKE,并且支持所有语言—建议
PROJECT (USECMAKE CXX)     工程的名为USECMAKE,并且支持语言是C++
PROJECT (USECMAKE C CXX)   工程的名为USECMAKE,并且支持语言是C和C++
该指定隐式定义了两个CMAKE的变量
<projectname>_BINARY_DIR,本例中是 USECMAKE_BINARY_DIR
<projectname>_SOURCE_DIR,本例中是 USECMAKE_SOURCE_DIR
MESSAGE关键字就可以直接使用者两个变量,当前都指向当前的工作目录
cmake定义两个预定义变量:PROJECT_BINARY_DIR和PROJECT_SOURCE_DIR,这两个变量和<projectname>_BINARY_DIR,<projectname>_SOURCE_DIR一样,表示存储二进制文件和源码目录

(2)SET
作用:用来定义变量的
举例:
SET(SRC_LIST main.cpp)    SRC_LIST变量就包含了main.cpp
也可以指定多个信息,比如: SET(SRC_LIST main.cpp t1.cpp t2.cpp ...)
可以使用空格隔开,也可以使用分号隔开,例如:SET(SRC_LIST main.cpp;t1.cpp;t2.cpp;...)

(3)MESSAGE
作用:向终端输出信息
主要包含三种信息:
SEND_ERROR,产生错误,生成过程被跳过。
SATUS,输出前缀为—的信息。
FATAL_ERROR,立即终止所有 cmake 过程.
举例:
MESSAGE(STATUS "USECMAKE_BINARY_DIR   is " ${USECMAKE_BINARY_DIR})
MESSAGE(STATUS "USECMAKE_SOURCE_DIR   is " ${USECMAKE_SOURCE_DIR})
MESSAGE(STATUS "PROJECT_BINARY_DIR    is " ${PROJECT_BINARY_DIR})
MESSAGE(STATUS "PROJECT_SOURCE_DIR    is " ${PROJECT_SOURCE_DIR})

(4) ADD_EXECUTABLE
作用:生成可执行文件
举例:
ADD_EXECUTABLE(useCmake  ${SRC_LIST})     生成的可执行文件名是useCmake,源文件读取变量SRC_LIST中的内容
也可以直接写 ADD_EXECUTABLE(useCmake  usecmake.cpp t1.cpp t2.cpp)
所以一个项目最简单的写法举例如下: 
PROJECT(USECMAKE)
ADD_EXECUTABLE(useCmake  usecmake.cpp)
注意:工程名的 USECMAKE 和生成的可执行文件 useCmake 是没有任何关系的

(5) ADD_SUBDIRECTORY
用法: ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])
- 这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存放的位置
- EXCLUDE_FROM_ALL函数是将写的目录从编译中排除,如程序中的example
- ADD_SUBDIRECTORY(src bin)
将 src 子目录加入工程并指定编译输出(包含编译中间结果)路径为bin目录   
如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在build/src 目录

其他常用关键在可参考如下:

CMake入门教程 - 知乎

3.4 语法的基本原则

(1)使用 ${} 以进行变量的引用,而在 IF 等语句中,则直接用变量名而不是 ${} 来读取变量值 
(2)指令(参数 1 参数 2...) 参数使用括弧括起,参数之间使用空格或分号分开
(3)指令是大小写无关的,参数和变量是大小写相关的,但推荐你全部使用大写指令,这样看起来更直观
语法注意事项:
    SET(SRC_LIST usecmake.cpp) 可以写成 SET(SRC_LIST “usecmake.cpp”),如果源文件名中含有空格,就必须要加双引号
    ADD_EXECUTABLE(useCmake  usecmake) 后缀可以不写,他会自动去找.c和.cpp,最好不要这样写,可能会有这两个文件usecmake.cpp和usecmake

3.5 标准化构建工程

3.5.1 构建项目目录结构和相关文件

(1)为工程添加一个子目录src,用来放置工程源代码
(2)添加一个子目录doc,用来放置这个工程的文档usecmake.txt
(3)在工程目录添加文本文件COPYRIGHT, README
(4)在工程目录添加一个runcmake.sh脚本,用来调用hello二进制可执行程序
(5)将构建后的目标文件放入构建目录的 bin 子目录
(6)将doc目录的内容以及 COPYRIGHT/README 安装到/usr/share/doc/cmake/

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

目录结构如下:

外层CMakeLists.txt
PROJECT(USECMAKE)
ADD_SUBDIRECTORY(src bin) #bin目录自动新建

src下的CMakeLists.txt
ADD_EXECUTABLE(useCmakemake.cpp)

cd build 

cmake ..

运行后可以看到可执行程序useCmake位于目录bin下

 3.5.3 修改二进制文件存放路径

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为如下:

SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/test/bin)

SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/test/lib)

ADD_EXECUTABLE(useCmake usecmake.cpp)

生成的二进制文件路径也发生了变化:

 3.6 安装

3.6.1 安装方式

(1)从代码编译后直接 make install 安装
(2)打包时的指定 目录安装
    简单的指定目录:make install DESTDIR=/tmp/test
    稍微复杂一点指定目录:./configure –prefix=/usr

3.6.2 cmake安装命令:INSTALL

INSTALL可以安装的项:二进制、动态库、静态库以及文件、目录、脚本等
一个CMAKE的新变量:CMAKE_INSTALL_PREFIX来指定安装路径
例如有如下目录结构:

 (1) 安装文件COPYRIGHT和README
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/)
FILES:文件
DESTINATION:指定路径
路径写法:
    绝对路径
     相对路径,相对路径实际路径是:${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径
CMAKE_INSTALL_PREFIX默认是在/usr/local/
也可以在在cmake的时候指定CMAKE_INSTALL_PREFIX变量的路径
cmake -DCMAKE_INSTALL_PREFIX=/usr 
(2)安装脚本runUseCmake.sh
INSTALL(PROGRAMS runUseCmake.sh DESTINATION bin)
PROGRAMS:非目标文件的可执行程序安装(比如脚本之类)
说明:默认安装到/usr/bin
(3)安装 doc中的USECMAKE.txt
方法一:在 doc 目录建立CMakeLists.txt ,通过install下的file
方法二:在工程目录通过 INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)安装
DIRECTORY 后面连接的是所在 Source 目录的相对路径
注意:doc 和 doc/的区别
目录名不以/结尾:这个目录将被安装为目标路径下的
目录名以/结尾:将这个目录中的内容安装到目标路径

顶层CMakeLists.txt内容如下:

PROJECT(USECMAKE)
ADD_SUBDIRECTORY(src bin)
INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/)
INSTALL(PROGRAMS runUseCmake.sh DESTINATION bin)
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)

3.6.3 安装过程

cmake ..
make
make install

在目录/usr/local下看到成功安装:

3.7 静态库和动态库的构建

例如构建目录如下:

 

 testlib.h

#pragma once

void ShowInfo();
int CalAdd(int num1, int num2); 

testlib.cpp

#include <iostream>

using namespace std;

void ShowInfo() {
    std::cout << "I am lib module !" << std::endl;
}

int CalAdd(int num1, int num2) {
    return num1 + num2;
}

3.7.1 构建动态库

项目根目录CMakeLists.txt
PROJECT(testlib)
ADD_SUBDIRECTORY(lib bin)
lib中CMakeLists.txt
SET(LIBUSECMAKE_SRC testlib.cpp)
ADD_LIBRARY(testlib SHARED ${LIBUSECMAKE_SRC})
说明:ADD_LIBRARY
USECMAKE:库名,生成的名字前面会加上lib,最终产生的文件是libUSECMAKE.so
SHARED:动态库    STATIC:静态库
${LIBUSECMAKE_SRC}:源文件 

在目录build中执行:

cmake ..

make

 

 可以看到libtestlib.so被成功构建出来

3.7.2 同时构建静态和动态库

// 如果用这种方式,只会构建一个动态库,不会构建出静态库,虽然静态库的后缀是.a
ADD_LIBRARY(testlib SHARED ${LIBUSECMAKE_SRC})
ADD_LIBRARY(testlib STATIC ${LIBUSECMAKE_SRC})
// 修改静态库的名字,这样是可以的
ADD_LIBRARY(testlib SHARED ${LIBUSECMAKE_SRC})
ADD_LIBRARY(testlib_static STATIC ${LIBUSECMAKE_SRC})

例如构建结果如下:

但是我们往往希望他们的名字是相同的,只是后缀不同而已,可以使用SET_TARGET_PROPERTIES来设置输出的名称,脚本如下:

SET(LIBUSECMAKE_SRC testlib.cpp)
ADD_LIBRARY(testlib SHARED ${LIBUSECMAKE_SRC})
ADD_LIBRARY(testlib_static STATIC ${LIBUSECMAKE_SRC})
#对testlib_static的重名为testlib
SET_TARGET_PROPERTIES(testlib_static PROPERTIES OUTPUT_NAME "testlib")

编译输出如下:

 可以看到产生的动态库和静态库是同名的。 

若出现重命名一样的情况,需要两条SET_TARGET_PROPERTIES才可以,举例如下:

SET(LIBUSECMAKE_SRC testlib.cpp)

#生成静态库
ADD_LIBRARY(testlib_static STATIC ${LIBUSECMAKE_SRC})
#对testlib_static的重名为testlib 重命名1
SET_TARGET_PROPERTIES(testlib_static PROPERTIES  OUTPUT_NAME "testlib")
#如果出现多个重命名一样的,cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为在构建 testlib.so时, 就会清理掉 testlib.a
#使用如下命令防止清理
SET_TARGET_PROPERTIES(testlib_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

#生成动态库
ADD_LIBRARY(testlib SHARED ${LIBUSECMAKE_SRC})
#对testlib_static的重名为testlib  重命名2
SET_TARGET_PROPERTIES(testlib PROPERTIES  OUTPUT_NAME "testlib")
SET_TARGET_PROPERTIES(testlib PROPERTIES CLEAN_DIRECT_OUTPUT 1)

另外对于动态库,还可以使用SET_TARGET_PROPERTIES用来指定动态库版本和 API 版本

例如:
CMakeLists.txt增加如下描述:
SET_TARGET_PROPERTIES(testlib PROPERTIES VERSION 1.2 SOVERSION 1)

VERSION:动态库版本
SOVERSION:API 版本

3.7.3 安装共享库和头文件

脚本如下:

SET(LIBUSECMAKE_SRC testlib.cpp)

#生成静态库
ADD_LIBRARY(testlib_static STATIC ${LIBUSECMAKE_SRC})
#对testlib_static的重名为testlib 重命名1
SET_TARGET_PROPERTIES(testlib_static PROPERTIES  OUTPUT_NAME "testlib")
#如果出现多个重命名一样的,cmake 在构建一个新的target 时,会尝试清理掉其他使用这个名字的库,因为在构建 testlib.so时, 就会清理掉 testlib.a
#使用如下命令防止清理
SET_TARGET_PROPERTIES(testlib_static PROPERTIES CLEAN_DIRECT_OUTPUT 1)

#生成动态库
ADD_LIBRARY(testlib SHARED ${LIBUSECMAKE_SRC})
#对testlib_static的重名为testlib  重命名2
SET_TARGET_PROPERTIES(testlib PROPERTIES  OUTPUT_NAME "testlib")
SET_TARGET_PROPERTIES(testlib PROPERTIES CLEAN_DIRECT_OUTPUT 1)

#安装头文件和动态库,静态库
INSTALL(FILES testlib.h DESTINATION include/test)
INSTALL(TARGETS testlib testlib_static LIBRARY DESTINATION lib ARCHIVE DESTINATION lib)

 cmake ..

make install

  

可以看到头文件,静态库和动态库被成功安装

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

测试程序目录结构如下:

 根目录CMakeLists.txt内容如下:

PROJECT(USECMAKE)
ADD_SUBDIRECTORY(src bin)

src/CMakeLists.txt内容如下:

INCLUDE_DIRECTORIES(/usr/local/include/test)
SET(SRC_LIST usecmake.cpp)
ADD_EXECUTABLE(useCmake ${SRC_LIST})
#标准库路径下搜索引用,TARGET_LINK_LIBRARIES要写在ADD_EXECUTABLE之后
TARGET_LINK_LIBRARIES(useCmake libtestlib.so)

usecmake.cpp内容如下:

#include <iostream>
//方法一:自动去/usr/local/include查找头文件
//#include "test/testlib.h"
//方法二: CMakeLists.txt中增加: INCLUDE_DIRECTORIES(/usr/local/include/test)
#include "testlib.h"

using namespace std;

int main(int argc, char *argv[]) {
    std::cout << "hello CMake !" << std::endl;
    ShowInfo();
    std::cout << CalAdd(111, 222) << std::endl;
    return 0;
}

运行结果如下:

常见问题和注意事项说明:

 问题一:make后找不到头文件
解决:
方法一: 以#include <USECMAKE/USECMAKE.h>方式来引用头文件
方法二: INCLUDE_DIRECTORIES,该命令可以来向工程添加多个特定的头文件搜索路径,路径之间用空格分割
如在CMakeLists.txt中加入头文件搜索路径
INCLUDE_DIRECTORIES(/usr/include/test)

问题二:找不到动态库函数定义
解决:
方法一:
LINK_DIRECTORIES     添加非标准的共享库搜索路径
例如: LINK_DIRECTORIES(/home/myproject/libs)
方法二:标准的共享库搜索路径 #/usr/local/lib
TARGET_LINK_LIBRARIES    添加需要链接的共享库
TARGET_LINK_LIBRARIES使用时,只需要给出动态链接库的名字就行
在CMakeLists.txt中插入链接共享库,主要要插在executable的后面
例如引用so库的CMakeLists.txt写法如下:
INCLUDE_DIRECTORIES(/usr/include/test)
ADD_EXECUTABLE(usecmake usecmake.cpp)
TARGET_LINK_LIBRARIES(usecmake libtestlib.so)

链接静态库
TARGET_LINK_LIBRARIES(usecmake libUSECMAKE.a)

注意:64位系统默认动态库搜索路径为:/usr/lib
32位默认是:/usr/lib32


//查看程序链接依赖命令:ldd
ldd {progrme}

3.7.5 特殊的环境变量

CMAKE_INCLUDE_PATH 和 CMAKE_LIBRARY_PATH(指定第三方非标准库搜索路径)
注意:这两个是环境变量而不是 cmake 变量,可以在linux的bash中进行设置
如上使用了绝对路径INCLUDE_DIRECTORIES(/usr/include/test)来指明include路径
也可以通过环境变量来指定
export CMAKE_INCLUDE_PATH=/usr/include/test

3.7.6 生成调试版本

cmake .. -DCMAKE_BUILD_TYPE=debug

备注:CMake参考文档

cmake-commands(7) — CMake 3.26.0-rc6 Documentation

猜你喜欢

转载自blog.csdn.net/hsy12342611/article/details/128171534