[原文地址](https://www.jianshu.com/p/442e71755643)
## 简介
` `makefile文件用于管理和组织代码工程的编译和链接,其不是可执行文件,其被make工具解析并完成相关动作。
` `Makefile文件里描述的是编译的时候依赖关系,宏定义,编译参数,链接生成的程序名字等等。
` `等Makefile文件写好后,需要用make程序来执行Makefile,所以需要先安装make程序。
```
sudo apt install make -y
```
## 基本语法
### 文件包含
` `语法:include 文件名
` `作用:将其它makefile文件包含进来,组成一个更大的makefile文件,这样有利于makefile模块化编程。通常我们将一些配置选项分开成一个独立的makefile文件,这样有利于makefile文件的管理,或将模块代码的依赖关系和需要编译的文件信息独自写到一个 makefile文件中,最终通过include命令形成一个顶层makefile文件来完成整个工程代码的编译和链接。
### 变量定义
` `语法:变量名 := 变量值
` `在makefile中,经常先定义一个变量,然后往该变量中追加新的值(通过+=符号),比如先定义一个C_SRCS变量(该值可以为空),然后将代码文件test1.c和test2.c添加到C_SRCS中,其代码如下所示:
```
C_SRCS :=
C_SRCS += test1.c test2.c
```
` `在makefile中有一类特殊的变量,其名称为 **自动变量**,自动变量的值会依据规则中的target 和 prerequisites自动计算其值,自动变量一般以开头$为起始,下面将列出一些常见的自动变量:
```
$@ 为规则中的target名称。
$< 为规则中第一个prerequisite名称
```
### 内置命令
` `Makefile中内置了一些常用的命令。
* 有字符串处理函数
```
1. subst
2. patsubst
3. strip
4. findstring
5. filter
6. filter-out
7. sort
8. word
9. wordlist
10. words
11. firstword
12. lastword
```
* 文件名处理函数
```
1. dir
2. notdir
3. suffix
4. basename
5. addsuffix
6. addprefix
7. join
8. wildcard
9. realpath
10. abspath
```
* 条件处理函数`if`;
* 循环处理函数`foreach`等
**以下是一些常用的函数:**
* [ ] **wildcard 函数**:其语法为$(wildcard pattern),pattern为匹配的模式,比如$(wildcard %.c) 为查找当前路径下面文件名以.c结尾的文件。
* [ ] foreach 函数:其语法为$(foreach var,list,text),每循环一次var从list中按顺序取值一个,然后执行一次text代码并记录结果,最终返回所用text代码运行的结果。比如
dirs := C_DIR S_DIR
file := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
将C_DIR和S_DIR文件夹下面的所有文件添加到file变量中。
* [ ] **dir 函数**:其语法为$(dir names…),用于获取names中文件夹路径,比如
$(dir src/foo.c hacks)
将获得文件夹路径 src/ ./
* [ ] **notdir 函数**:其语法为$(notdir names…),用于获取names中除去路径的信息,比如
$(notdir src/foo.c hacks)
将获得文件信息 foo.c hacks
* [ ] **basename 函数**:其语法为$(basename names…),用于获取names中除去后缀信息,比如
$(basename src/foo.c src-1.0/bar hacks)
将获得信息 src/foo src-1.0/bar hacks
* [ ] **addsuffix 函数**:其语法为$(addsuffix suffix,names…),用于往names中添加后缀信息suffix,比如
$(addsuffix .c,foo bar)
将获得文件信息 foo.c bar.c
* [ ] **addprefix 函数**:其语法为$(addprefix prefix,names…),用于往names中添加前缀信息prefix,比如
$(addprefix src/,foo bar)
将获得信息src/foo src/bar
patsubst 函数:其语法为$(patsubst pattern,replacement,text),根据 pattern信息将text替换成replacement,比如
objects = foo.o bar.o baz.o
files = $(patsubst %.o,%.c,$( objects))
将获得信息 foo.c bar.c baz.c
其可以简单写成
objects = foo.o bar.o baz.o
files = $(objects:.o=.c)
## 规则定义
` `规则是makefile中最重要的概念,其告诉make 目标文件的依赖关系,以及如何生成及更新这些目标文件。在makefile文件规则有2种,一种是显式规则,另一种是隐式规则。
` `显式规则用于说明 何时及如何重新生成目标,其列出了目标依赖的文件信息,并通过调用命令来创建或更新目标,其语法一般为:
```
targets : prerequisites
recipe
…
```
` `targets为要生成或更新的目标,prerequisites为目标依赖的关系,recipe为生成目标的命令,一个规则可以有多条recipe,比如
```
foo.o : foo.c defs.h
cc -c -g foo.c
其中foo.o为target,foo.c defs.h 为prerequisites,cc -c -g foo.c为recipe。
```
` `隐式规则用于说明 何时及如何来重新生成一类目标文件根据其名称,其描述了目标是如何依赖于名称相似的文件(一般来说除去后缀信息,其目标与依赖文件的名称是一样的),并调用命令来创建或更新目标,比如
```
%.o : %.c
$(CC) -c $(CFLAGS) $< -o $@
```
` ` 这个隐式规则说明了.o的目标文件依赖于同名的.c文件,其中$< 及 $@为自动变量,$<为第一个prerequisites条件,也就是 目标名称.c,$@为目标,也就是 目标名称.o。
` `在makefile中,我们通常要编写3种隐式规则,第1种为代码链接规则,第2种为源代码编译规则,第3种为汇编代码编译规则。
## 文件搜索路径设置
` ` Make命令默认会在当前路径中搜索prerequisites中的文件,比如头文件,但我们在写程序时,经常将头文件和源文件隔开放在不同的文件夹下,这种该怎么处理呢?1、我们可以通过VPATH变量来解决,2、我们可以通过vpath指令来解决。
**VPATH变量**
` `VPATH变量为所有的prerequisites指定文件路径,路径之间可以通过 :或空格隔开,比如
```
**VPATH变量**
VPATH变量为所有的prerequisites指定文件路径,路径之间可以通过 :或空格隔开,比如
```
**vpath指令**
` `vpath指令的作用与变量VPATH的作用差不多,但vpath有更多的灵活性,其语法为:
vpath pattern directories
` `pattern为需要查找的文件匹配模式信息,directories为要查找的文件路径,比如
```
vpath %.h ../headers
```
` `其代表在上一层文件夹headers中查找 .h头文件信息。
## 实例说明
` `看个最简单的Makefile的例子,把下面的内容保存到文件Makefile里:
~~~
# 最简单的Makefile
hello:
@echo "hello makefile"
~~~
**注意**:
1. makefile文件中命令行的行首不能用空格,而要用Tab键
2. makefile文件中的字符格式有要求,必须是英文字符,不能有中文字符。
` `然后make一下,如果make的时候当前目录有叫Makefile的文件,默认执行这个Makefile,如果需要指定其他文件名,需要用make -f。
![](https://img.kancloud.cn/3c/1f/3c1fd36193b85d705900aa714def303e_599x206.png)
` `上面Makefile文件里的hello表示目标,这个目标的执行动作是打印一行字符串"hello Makefile"。 我们可以有多个目标,修改Makefile,加入目标hello2:
```
# 最简单的Makefile
hello:
@echo "hello makefile"
hello2:
@echo "hello linux"
```
![](https://img.kancloud.cn/a1/94/a1949f24fba3de59fd20c64cf76ac794_569x93.png)
` `每个目标可以有相应的依赖项,看下面的例子
```
# 最简单的Makefile
hello: ready
@echo "hello makefile"
hello2:
@echo "hello linux"
ready:
@echo "i am ready"
```
![](https://img.kancloud.cn/fd/3d/fd3d167c6fb0c05c3d1c776b1bf25bba_551x130.png)
` `目标可以执行一个空的动作,修改Makefile为下面的内容:
```
# 最简单的Makefile
all :hello hello2
hello: ready
@echo "hello makefile"
hello2:
@echo "hello linux"
ready:
@echo "i am ready"
```
![](https://img.kancloud.cn/0e/d7/0ed738cccebb2a29b8aa4f2eca5c73c9_495x94.png)
` `们可以看出Makefile实际上非常简单,就是一个个目标与依赖结合起来的有序的解析过程。
` `了解了Makefile的执行过程,我们只要把目标的动作改为编译和链接就可以了,下面我们看执行一个编译动作的命令。
` `linux下编译c++代码用g++,c++代码的文件后缀为.cpp或.cc,编译c代码用gcc,c代码的文件后缀为.c。
链接程序也用g++。
** g++的常用参数说明**
* -c 表示编译代码
* -o 表示指定生成的文件名
* -g 表示编译时候加入调试符号信息,debug时候需要这些信息
* -I (大写i)表示设置头文件路径,-I./表示头文件路径为./
* -Wall 表示输出所有警告信息
* -D 表示设置宏定义,-DDEBUG表示编译debug版本,-DNDEBUG表示编译release版本
* -O 表示编译时候的优化级别,有4个级别-O0,-O1,-O2 -O3,-O0表示不优化,-O3表示最高优化级别
* -shared 表示生成动态库
* -L 指定库路径,如-L.表示库路径为当前目录
* -l (小写L)指定库名,如-lc表示引用libc.so
` `新装的ubuntu可能没有g++,可以先在bash里输入g++ -v试一下,如果没有安装会提示先安装,这时候跟着提示apt install g++就可以了。
安装gcc也一样。
` `看一下最简单的编译.cpp代码的Makefile怎么写
~~~c
1
2 main : main.o
3 g++ -o $@ $^
4
5 .cpp.o:
6 g++ -c -o $@ $<
~~~
` `解释一下上面的Makefile:
* 第2行main表示一个目标名为main,依赖为main.o,.o是代码编译后生成的obj文件
* 第3行表示目标main的执行动作,
就是执行链接程序的命令。
* 第5行.cpp.o表示这是一个目标,作用是把.cpp文件编译为.o文件
* 第6行是该目标的具体编译命令,-c表示编译, -o @表示指定生成文件名
* 看下面的测试:
![](https://img.kancloud.cn/94/a1/94a18d80ff4dd88f053eba858a12c144_536x131.png)
> 小知识
Makefile里的echo和rm前面带了@表示不要打印执行该命令时候命令本身的输出,比如
rm -rf \*.o main在执行的时候会输出这句命令"rm -rf \*.o main" 如果把rm改为@rm,
再make clean的时候就不会输出"rm -rf \*.o main"命令本身了。
` `看到这里基本上再回头看开完整Makefile就能看懂了,需要重点说的是编译的参数,头文件路径和库引用的写法,下面我们一步一步写上注释
```
~~~c
# 这句是链接时候的命令,在g++前面加入了@echo linking $@
# 这样在链接时候就会先输出linking xxx,
# 这行直接写g++也是没有任何问题的
LINK = @echo linking $@ && g++
# 编译c++代码时候用的,一样会显示compiling xxx
GCC = @echo compiling $@ && g++
# 编译c代码时候用gcc
GC = @echo compiling $@ && gcc
# 生成静态库时候用ar命令
AR = @echo generating static library $@ && ar crv
# 这是编译时候的参数设置,下面-g表示编译时候加入调试信息,
# -DDEBUG表示编译debug版本
# -W -Wall表示输出所有警告
# -fPIC是生成dll时候用的
FLAGS = -g -DDEBUG -W -Wall -fPIC
GCCFLAGS =
DEFINES =
# 这里指出头文件的目录为./
HEADER = -I./
# 需要引用的库文件
LIBS =
LINKFLAGS =
# 更多头文件可以用 += 加进来
#HEADER += -I./
# 如果需要librt.so,就把-lrt加进来
#LIBS += -lrt
# 如果需要写多线程程序,就需要引用-pthread
#LIBS += -pthread
# 这里是主要需要修改的地方,每一个.c或.cpp对应于这里的一项,
# 如main.cpp对应于main.o
# 多个.o可以用空格分开,也可以像下面这样用"\"换行,然后写在新一行
OBJECT := main.o \
# 下面举个例子,这里编译表示两个代码文件
# OBJECT := main.o \
# other.o
BIN_PATH = ./
TARGET = main
# 链接用的,可以看到后面加了$(LIBS),因为只有链接时候才需要引用库文件
$(TARGET) : $(OBJECT)
$(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS)
# 编译cpp代码用这个目标
.cpp.o:
$(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $<
# 编译c代码用这个
.c.o:
$(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $<
# 把生成的$(TARGET)拷贝到$(BIN_PATH)下
install: $(TARGET)
cp $(TARGET) $(BIN_PATH)
clean:
rm -rf $(TARGET) *.o *.so *.a
~~~
```
## 生成动态链接库
` `linux动态链接库的后缀为 .so,生成动态链接库也比windows要方便的多,只要在link的时候加上-shared参数就可以了,下面我们看个例子。
先来看一下完整测试的目录结构,最上层目录是test_makefile_so
~~~c
test_makefile_so
├── Makefile #这个是总的Makefile,管理所有子目录的Makefile
├── bin
│ ├── libfun.so
│ └── main
└── src
├── Makefile
├── fun
│ ├── Makefile
│ ├── fun.cpp
│ └── fun.h
└── main.cpp
3 directories, 8 files
~~~
` `main.cpp的内容
~~~c
#include <stdio.h>
#include "fun.h"
int main(int, char**){
printf("hello so\n");
int a = 1;
int b = 2;
int c = sum( a, b );
printf( "sum: %d + %d = %d\n", a, b, c );
return 0;
}
~~~
` `src/Makefile的内容
~~~c
LINK = @echo linking $@ && g++
GCC = @echo compiling $@ && g++
GC = @echo compiling $@ && gcc
AR = @echo generating static library $@ && ar crv
FLAGS = -g -DDEBUG -W -Wall -fPIC
GCCFLAGS =
DEFINES =
HEADER = -I./
LIBS =
LINKFLAGS =
HEADER += -I./fun
#LIBS += -lrt
#LIBS += -pthread
#这里表示链接的时候从bin目录下找libfun.so
LIBS += -L../bin -lfun
OBJECT := main.o \
#这里加了bin的相对路径,编完的main会install到bin目录下
BIN_PATH = ../bin/
TARGET = main
$(TARGET) : $(OBJECT)
$(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS)
.cpp.o:
$(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $<
.c.o:
$(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $<
install: $(TARGET)
cp $(TARGET) $(BIN_PATH)
clean:
rm -rf $(TARGET) *.o *.so
~~~
` `fun.h的内容
~~~c
#ifndef __fun_h__
#define __fun_h__
int sum(int a, int b);
#endif//__fun_h__
~~~
` `fun.cpp的内容
~~~c
#include "fun.h"
int sum(int a, int b){
return a+b;
}
~~~
` `fun/Makefile的内容
~~~c
LINK = @echo linking $@ && g++
GCC = @echo compiling $@ && g++
GC = @echo compiling $@ && gcc
FLAGS = -g -DDEBUG -W -Wall -fPIC
GCCFLAGS =
DEFINES =
HEADER = -I./
LIBS =
#修改的地方1: 这里加了-shared,表示生成动态库
LINKFLAGS = -shared
#HEADER += -I./
#LIBS += -lrt
#LIBS += -pthread
OBJECT := fun.o \ #修改的地方2: 表示编译fun.cpp
#修改的地方3: 指出了bin的相对路径
BIN_PATH = ../../bin/
#修改的地方3,生成的文件名叫libfun.so,动态库一般以lib为前缀
TARGET = libfun.so
$(TARGET) : $(OBJECT)
$(LINK) $(FLAGS) $(LINKFLAGS) -o $@ $^ $(LIBS)
.cpp.o:
$(GCC) -c $(HEADER) $(FLAGS) $(GCCFLAGS) -fpermissive -o $@ $<
.c.o:
$(GC) -c $(HEADER) $(FLAGS) -fpermissive -o $@ $<
install: $(TARGET)
cp $(TARGET) $(BIN_PATH)
clean:
rm -rf $(TARGET) *.o *.so
~~~
先编译动态库libfun.so,因为main程序要依赖libfun.so。
在fun目录下先make install一下,会生成libfun.so,并且自动拷贝到bin目录下
~~~c
bash$ cd fun
bash$ make install
compiling fun.o
linking libfun.so
cp libfun.so ../../bin/
~~~
切换到src目录下,再编译main程序
~~~c
bash$ make clean; make install
rm -rf main *.o *.so
compiling main.o
linking main
cp main ../bin/
~~~
然后切到bin目录下,运行main程序
~~~c
bash$ cd ../bin
# bin目录下现在有两个文件,libfun.so和main
bash$ ls
libfun.so main
# 运行main程序
bash$ ./main
mac:bin tpf$ ./main
hello so
sum: 1 + 2 = 3 #这行是由libfun.so里的函数执行的
~~~
- 第1章 电脑操作篇
- 1.1 电脑高清壁纸下载地址
- 1.2 音乐外链在线获取
- 1.3 markdown,js等表格生成神器
- 1.4 在线使用文档,表格,演示文档
- 1.5 开发在线工具
- 1.5.1 toolbox
- 1.5.2 菜鸟工具
- 1.6 vs code远程调试
- 1.7 windows批处理命令
- 1.8 windows安装cygwin运行linux指令
- 1.9 windows下某些程序运行慢
- 1.10 win下为鼠标右键添加新项目
- 1.11 win上自己常用的开发软件
- 1.12 win下vscode配置
- 第2章 Electron 用前端技术开发跨平台桌面应用
- 2.1 介绍
- 2.2 入门链接地址
- 2.3 cnpm使用
- 第3章 Git使用
- 3.1 介绍
- 3.2 同步GitHub的基本使用方法
- 3.3 同步Gitee的基本使用方法
- 3.4 获取当前git分支
- 3.5 LF和CRLF换行的转换
- 第4章 HTML,CSS,JS
- 4.1 HTML速查列表
- 第5章 python使用
- 5.1 文件操作
- 5.2 一句话建立服务器
- 第6章 我的女友叫Linux
- 6.1 使用shell写俄罗斯方块
- 6.2 那些有趣的shell
- 6.2.1 40个有趣的LInux命令行
- 6.2.2 命令行下的网易云搜索播放器
- 6.2.3 从网上获取一条语句并显示
- 6.3 在linux上写汇编
- 6.4 在linux终端连接另一台linux
- 6.5 makefile文件的编写
- 6.6 deepin挂载远程文件夹到本地文件夹
- 6.7 本地lnux和远程linux进行文件拷贝
- 6.8 超好用的linux下的ssh管理工具(electerm)
- 6.9 那些不重要的技巧
- 6.10 linux文件加密
- 6.11 论文画图软件gnuplot
- 6.12 自定义mrun命令用于执行当前路径下的run文件
- 6.13 fish shell后台运行程序
- 第7章 在线工具收集
- 7.1 各种编程语言的在线编辑运行
- 7.2 html js 在线尝试
- 第8章 搭建自己的私有云盘
- 第9章 linux下的一些软件
- 9.1 remarkable--markdown文件轻量编辑器
- 9.2 gnuplot画图软件
- 9.3 Graphviz绘图(流程图,状态图)
- 第10章 TCL脚本编程
- 10.1 基础教程
- 10.2 在tcl脚本文件其他tcl脚本文件运行
- 10.3 在tcl脚本文件中调用bash/fish
- 10.4 TCL培训教程
- 10.5 tcl脚本参数传递
- 第11章 看云的使用
- 11.1 markdown添加公式
- 11.2 看云在linux本地编辑脚本
- 第12章 Go语言在linux下的使用
- 12.1 简介
- 12.2 调用自己的包
- 12.3 Go语言学习的资料
- 12.4 golang使用flag完成命令行解析
- 12.5 Golang文件操作大全
- 12.5.1 创建空文件
- 12.5.2 Truncate文件
- 12.5.3 得到文件信息
- 12.5.4 重命名和移动
- 12.5.5 删除文件
- 12.5.6 打开和关闭文件
- 12.5.7 检查文件是否存在
- 12.5.8 检查读写权限
- 12.5.9 改变权限、拥有者、时间戳
- 12.5.10 硬链接和软链接
- 12.5.11 复制文件
- 12.5.12 跳转到文件指定位置(Seek)
- 12.5.13 写文件
- 12.5.14 快写文件
- 12.5.15 使用缓存写
- 12.5.16 读取最多N个字节
- 12.5.17 文件追加内容
- 12.6 操作CSV文件
- 第13章 搜集资源的一些方法
- 13.1 电子书_电子课本
- 第14章 EndNote的使用
- 14.1 安装
- 14.2 文件检索