3.C++ Make

1.Makefile

1.1 什么是 Makefile

一个工程中有很多文件,文件之间都是相辅相成有着编译的先后顺序,但是如果自己手动根据编译顺序编译文件造成速度非常慢。Makefile 是”自动化编译“,只需一个 make 指令系统就会根据编译顺序帮自己编译文件

1.2 Makefile 文件命名规则

image-20240408221524107

1.3 代码演示

1.生成一个 Makefile 文件

vim Makefile

2.将执行规则书写入 Makefile

image-20211101121336569

3.使用 make 编译 Makefile 文件

sudo apt install make // 安装 make 
make // 在 Makefile 所在文件夹执行指令

可以看到下图中输出了 Makefile 中的相关指令,与此同时在对应的文件下生成的 app 可执行文件

image-20240408221610240

4.执行 app 可执行文件

./app

这个 Makefile 是 make 默认执行的文件,如果不想叫 Makefile 也可以自定义名称,那么在 make 时后面就要拼接相应的文件名称。比如新建的 Makefile 替代文件是 abc

make abc

1.4 Makefile 的工作原理

image-20240408221652903

1.命令执行之前,检查规则中的依赖是否存在

当发现前面的命令找不到时 Makefile 文件会顺着继续向下查找后面的目标有没有有关生成前面相关文件的,如下图,一开始是没有 add.o 文件的,但是 makefile 文件会继续向下查找有没有生成 .o 的文件指令

Makefile 的指令都是为第一条指令服务的

image-20240408221722726

2.检测更新

Makefile 会对文件进行检测,比如使用 make 编译过 Makefile 文件的情况下,再使用 make 编译 Makefile 文件,就发现提示

image-20211101162023622

这是因为 make 是检查更新的,如果其中的文件没有更改,则就不会执行 Makefile 中的指令

1.5 Makefile 的优化

下面就要对下图的这些定义进行优化。优化一共分为三个步骤:使用变量将长字符串代替;使用通配符将重复的后缀代替;使用查找替换将长字符串中的后缀名进行查找替换

image-20211101143607539

1.使用变量进行优化

如上图所示可以使用变量代替一个长的字符串

image-20211101150106817

重新定义 Makefile 文件使用变量形式将长字符串进行取代

在最开头定义了两个变量 : src 和 target 并为其赋值。在后面使用 $(变量名) 的方法得到该变量的值。这样长字符串就在前面被定义了

image-20211101151125890

2.使用通配符简化命令代码

在文件中有很多 .c ;.o 的文件,% 可以理解为是 * 的操作;%.o 类似于 *.o这样的话会将所有的 .o 文件生成 .c 文件

image-20211101151440792 image-20211101152933807

3.使用“查找替换”将所有文件名替换

查找:

在自定义变量中定义了可以进行通配的文件名,下面对这些文件名进行替换

示例的意思是将本路径所有 *.c 和 sub 文件下的 *.c 文件名查找出来

image-20211101153710078

替换:

image-20211101154009105

查找到 text 中的单词 patten 并用 replacement 进行替换

image-20211101160206121

最后 make 执行的结果

image-20211101154752330

1.6 中间文件的清除

从 .c 文件到 app 可执行文件生成过程中产生大量 .o 的中间文件,下面使用 clean 的方法将中间过程的 .o 文件进行删除。同样是在 Makefile 中进行定义

image-20211101160933915

执行下面的指令就可以进行 clean 操作

image-20211101161118123

对 clean 操作生成伪目标

指示 clean 是一个假的定义操作。真的定义操作利用了自定义 Makefile 的方法定义的

.PHONY:clean 
image-20211101161435904

2.Cmake 的使用

2.1 执行一个文件

S1:写一个 .cpp 文件

#main.cpp
#include <iostream>
int main(){
  std::cout <<  "hello word" << std::endl;
}

S2:写一个 CMakeLists.txt 文件

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

S3:对该文件进行 cmake

cmake . // 其中 . 就代表 CMakeLists.txt 在当前目录中

然后就进行 build ,目录下就会生成文件,关键是生成了 Makefile 的文件

image-20220824213013180

S4:再使用 make 对文件进行编译,生成可执行文件

make 

2.2 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 过程.

在我们进行 cmake 的时候这两句话也会被显示出来

image-20220824214933257

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

2.3 cmake 的内部构建和外部构建

上面的方法,在 hello.cpp 的所在目录下进行 cmake 属于内部构建,但是我们经常看到在 build 中进行构建从而生成那一堆 cmake 文件,那样的方法叫做外部构建

mkdir build
cd build
cmake .. // CMakeLists.txt 在 build 外面

2.4 创建一个 cpp 工程

一个工程中有多个文件,我们要对这所有的文件进行编译,下面是文件的目录

文件保存在 /Users/xuguagua/Documents/C++/mianshi/cmake_demo 中

[root@localhost cmake]# tree
.
├── build
├── CMakeLists.txt
└── src
    ├── CMakeLists.txt
    └── main.cpp

在每一个文件夹下都要创建 CMakeLists.txt,所以在 src 中也要创建一个 CMakeLists.txt 文件

1.在最外层 CMakeLists.txt 写的内容

这里会单独创建一个 bin 目录,即使 bin 中没有创建文件夹也会自动创建,并将可执行文件放入

cmake_minimum_required(VERSION 3.23)
project(cmake_demo)
set(CMAKE_CXX_STANDARD 14)
ADD_SUBDIRECTORY(src bin) # 将最外层的 cmake 关联到 src 的 cmake。bin 文件夹如果不存在则自动创建

2.在 src CMakeLists.txt 中

这个文件只需要指明可执行文件是谁,叫啥

ADD_EXECUTABLE(hello main.cpp) # hello 是生成的可执行软件的名字

2.5 cmake install 方法

将项目生成的库文件、头文件、可执行文件或相关文件等安装到指定位置(系统目录,或发行包目录)。在cmake中,这主要是通过install方法在CMakeLists.txt中配置,make install命令安装相关文件来实现的。

S1:首先我们创建下面的工程目录

├── build
├── CMakeLists.txt
├── COPYRIGHT
├── doc
│   └── hello.txt
├── README
├── runhello.sh
└── src
    ├── CMakeLists.txt
    └── main.cpp
image-20220824223944846

安装文件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变量的路径

安装脚本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/有很大的区别

目录名不以/结尾:这个目录将被安装为目标路径下的

目录名以/结尾:将这个目录中的内容安装到目标路径

安装过程

cmake ..
make
sudo make install

最终文件

那么在 CMakeLists.txt 中的显示就是如下

INSTALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/) # 安装这两个文件到这个网址
INSTALL(PROGRAMS runhello.sh DESTINATION bin)
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake)

在执行完安装后相应的路径下就会看到这几个文件

image-20220825063147457

2.6 创建动态库

有时候我们想让自己的库生成 .so 文件或者 .dll 文件,供外面使用,那么我们就要为该 pro 创建一个共享库,步骤如下

文件目录如下:

[root@localhost cmake2]# tree
.
├── build
├── CMakeLists.txt
└── lib
    ├── CMakeLists.txt
    ├── hello.cpp
    └── hello.h

其中 hello.cpp 的内容

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

hello.h 的内容

#ifndef HELLO_H
#define Hello_H
void HelloFunc();
#endif

然后就在 CMakeLists.txt 文件中放入相应的链接

项目中的cmake内容

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
  • SHARED,动态库 STATIC,静态库
  • ${LIBHELLO_SRC} :源文件

2.7 同时生成静态库和动态库

S1:在上一步创建好 hello.cpp 文件之后需要同时生成 .so 和 .a 文件

在 lib 的 CMake 中执行指令

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)


最后生成效果如下图所示,.so 是动态库,.a 是静态库

image-20220825072808421

动态库的版本号

一般动态库都有一个版本号的关联

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 版本。

S2:将库安装到 /usr 文件夹下

安装共享库和头文件

本例中我们将 hello 的共享库安装到/lib目录,

将 hello.h 安装到/include/hello 目录

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

2.8 使用共享库

刚才我们已经生成了 .so 文件和 .a 文件,并放到了 /usr 的目录下,下面就尝试在工程下调用这两个库

  • 29
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值