目录
- 0.前期准备
- 0.1、程序编译链接:
- 1.Makefile基础
- 1.1、认识Makefile
- 1.2、Makefile定义模式:
- (1) 定义模式:
- (2) 执行Makefile:
- 1.3、Makefile的变量
- (1) 变量定义:
- (2) **变量的赋值符**:
- (3) 自动化变量
- 1.4 伪目标
- 1.5 文件路径搜索
- (1) VPATH 变量
- (2) vpath关键字
- 2.Makefile高级语法
- 2.1、**使用其他Makefile**
- (1) 引用Makefile
- (2) 嵌套Makefile
- 2.2、定于命令包
- 2.3、条件判断语句
- 2.4、函数
- (1) 内嵌函数
- (2) 自定义函数
- 3.实例演示
- 版本一(同目录)
- **Makefile 写法1:**(最简单)
- **Makefile 写法2:**(使用变量)
- 版本二(不同目录)
- **Makefile 写法3:**(不同目录)
0.前期准备
0.1、程序编译链接:
(以 hello.c 程序为例)
预处理阶段:将引入的头文件 #… 对于文件内容插入程序中,得到 hello.i
编译阶段:高级语言转换为汇编语言。得到 hello.s
汇编阶段:翻译成机器语言指令,打包成可重定位目标程序。得到 hello.o二进制文件
链接阶段:将 printf.o 和 hello.o 链接合并。得到 hello 可执行文件。
1.Makefile基础
1.1、认识Makefile
make(工程管理工具):帮助我们实现项目的自动编译。
Makefile(实现工程管理的脚本文件):制订规则,来说明如何编译,编译的顺序等等。由make工具来执行。
Makefile的五个主要部分:
-
显示规则:说明如何生成一个或多个目标文件。
-
隐晦规则:make的自动推导功能所执行的规则。(make -p 可以查看)
-
变量定义:Makefile中定义的变量。
-
文件指示:Makefile中引用其他makefile;指定Makefile中有效部分;定义一个多行命令。
-
注释:使用 “#" 注释,(如需使用 “#” 字符,需要进行转义”#“)。
make的工作流程:
-
读入主Makefile (主Makefile中可以引用其他Makefile)
-
读入被 include 的其他Makefile
-
初始化文件中的变量
-
推导隐晦规则, 并分析所有规则
-
为所有的目标文件创建依赖关系链
-
根据依赖关系, 决定哪些目标要重新生成
-
执行生成命令
1.2、Makefile定义模式:
(1) 定义模式:
target ... : prerequisites ...
command #注意:command前面给一定得有【tab】键才能识别
...
...
-
target (目标) :可以是Object File,也可以是执行文件。还可以是一个标签(Label)。
-
prerequisites (依赖) :生成target所需的文件或目标。
-
command (命令) :也就是make需要执行的命令。每个命令一定以**【tab】**键为开头。
重点注意:
每个命令一定以**【tab】**键为开头。
第一个目标为最终目标。
示例:
main.o: main.c head.h
gcc -c main.c -o main.o
注意前缀:
@:命令前加 @ 符号后,可取消当前命令的打印。
- :命令执行有错的话,忽略错误,继续执行。
(2) 执行Makefile:
$ make #第一种:直接终端输入【make】执行
$ make -f my_mkfile #第二种:自定义的文件名,【make -f 文件名】执行
1.3、Makefile的变量
(1) 变量定义:
#定义
[变量名] [赋值符] [变量值]
#使用
$(变量名)
export 可以声明全局变量。
%.o:%c 表示: [任意].o : [与之匹配的任意].c
*c表示:所有从 .c 文件。
示例:
#定义
SRCS = main.c
export LD = ld #export指定为全局变量
#使用
$(SRCS)
$(LD)
(2) 变量的赋值符:
= 是最基本的赋值。
:= 是覆盖之前的值。
?= 是如果没有被赋值过就赋予等号后面的值。
+= 是添加等号后面的值。
其中 = 和 := 的区别在于:
:= 只能使用前面定义好的变量,
= 可以使用整个文件中定义的变量
示例:
(3) 自动化变量
自动变量 | 含义 |
---|---|
$@ | 当前目标集合 |
$< | 第一个依赖. 多个时, 逐个取出 |
$? | 比目标新的依赖的集合 |
$^ | 所有依赖的集合, 会去除重复的依赖 |
$+ | 所有依赖的集合, 不去除重复的依赖 |
$* | 这个变量表示目标模式中"%"及其之前的部分 |
$% | 当目标是函数库文件时, 表示其中的目标文件名 |
1.4 伪目标
指明:使用 “.PHONY” 指明是"伪目标”。
使用:make [伪目标]
作用:能防止重命名,不检查是否更新,不会生成文件。
示例:
.PHONY: clean
clean:
rm -rf *.o my_exe
使用:
make clean
1.5 文件路径搜索
(1) VPATH 变量
VPATH = src:../headers
指定 VPATH 后,如果当前目录没有找到文件,就会去定义的目录下找。
查找顺序:当前目录 > src目录 > …/headers目录。(用 : 分隔)
(2) vpath关键字
它可以指定不同的文件在不同的搜索目录中。使用方法有三种:
-
vpath 为符合模式的文件指定搜索目录。
-
vpath 清除符合模式的文件的搜索目录。
-
vpath 清除所有已被设置好了的文件搜索目录。
vapth使用方法中的需要包含%字符。%的意思是匹配零或若干字符,例如,%.h表示所有以.h结尾的文件。指定了要搜索的文件集,而则指定了的文件集的搜索的目录。例如:
vpath %.h ../headers
该语句表示,要求make在…/headers目录下搜索所有以.h结尾的文件。(如果某文件在当前目录没有找到的话)
2.Makefile高级语法
2.1、使用其他Makefile
(1) 引用Makefile
语法:
include <filename> #(filename 可以包含通配符和路径)
在 include 前面可以有一些空字符,但是绝不能是[Tab]键开始。
找不到文件时,不会立即报错,再尝试寻找,如果再找不到,报致命错误。使用减号可以跳过错误继续执行。
例如:-include 和其它版本make兼容的相关命令是 sinclude(一样效果)。
示例:
(other/文件夹在主Makefile文件夹下)
# Makefile 内容---------------------------------------------------
all:
@echo "主 Makefile begin"
@make other-all
@echo "主 Makefile end"
include ./other/Makefile
# ./other/Makefile 内容
other-all:
@echo "other makefile begin"
@echo "other makefile end"
# bash中执行 make-------------------------------------------------------
$ make
主 Makefile begin
make[1]: Entering directory `/path/to/test/makefile'
other makefile begin
other makefile end
make[1]: Leaving directory `/path/to/test/makefile'
主 Makefile end
(2) 嵌套Makefile
使用其它 Makefile的可以引用,可以嵌套。 这里我们来看看嵌套 Makefile。
示例:
# Makefile 内容
export VALUE1 := export.c <-- 用了 export, 此变量能够传递到 ./other/Makefile 中
VALUE2 := no-export.c <-- 此变量不能传递到 ./other/Makefile 中
all:
@echo "主 Makefile begin"
@cd ./other && make
@echo "主 Makefile end"
# ./other/Makefile 内容
other-all:
@echo "other makefile begin"
@echo "VALUE1: " $(VALUE1)
@echo "VALUE2: " $(VALUE2)
@echo "other makefile end"
# bash中执行 make
$ make
主 Makefile begin
make[1]: Entering directory `/path/to/test/makefile/other'
other makefile begin
VALUE1: export.c <-- VALUE1 传递成功
VALUE2: <-- VALUE2 传递失败
other makefile end
make[1]: Leaving directory `/path/to/test/makefile/other'
主 Makefile end
2.2、定于命令包
命令包有点像是个函数, 将连续的相同的命令合成一条, 减少 Makefile 中的代码量, 便于以后维护。
语法:
define <command-name>
command
...
endef
示例:
# Makefile 内容
define run-hello-makefile
@echo -n "Hello"
@echo " Makefile!"
@echo "这里可以执行多条 Shell 命令!"
endef
all:
$(run-hello-makefile)
# bash 中运行make
$ make
Hello Makefile!
这里可以执行多条 Shell 命令!
2.3、条件判断语句
语法:
<conditional-directive>
<text-if-true>
endif
# 或者
<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
条件关键字:
ifeq (<arg1>, <arg2>) # arg1和arg2相同为真
ifneq (<arg1>, <arg2>) # arg1和arg2不同为真
ifdef <variable-name> # <variable-name>值非空为真
ifndef <variable-name> # <variable-name>值为空为真
示例:
示例1: ifeq的例子, ifneq和ifeq的使用方法类似, 就是取反
# Makefile 内容------------------------------
all:
ifeq ("aa", "bb")
@echo "equal"
else
@echo "not equal"
endif
# bash 中执行 make----------------------------------
$ make
not equal
示例2**: ifdef**的例子, ifndef和ifdef的使用方法类似, 就是取反
# Makefile 内容
SRCS := program.c
all:
ifdef SRCS
@echo $(SRCS)
else
@echo "no SRCS"
endif
# bash 中执行 make
$ make
program.c
2.4、函数
函数主要分为两类:make内嵌函数和用户自定义函数。
(1) 内嵌函数
Makefile 中自带了一些函数, 利用这些函数可以简化 Makefile 的编写.
函数调用语法如下:
$(<function> <arguments>) # 或 ${<function> <arguments>}
#- <function> 是函数名
#- <arguments> 是函数参数
其中,函数名与参数之间以空格间隔,参数之间以逗号分隔。
特殊函数:
-
foreach函数:用来做循环用的
-
if函数:作用与ifeq条件判断语句类似
-
call函数:用来替换某个表达式中的参数
-
origin函数:用于查询变量的来源
-
shell函数:用于执行shell命令
-
error函数:用于产生一个致命错误
-
warning函数:用于产生一个警告
更多函数及细节使用参考文档:使用函数 — 跟我一起写Makefile 1.0 文档)
(2) 自定义函数
示例:
PHONY: all
#自定义函数部分
define func
@echo "pram1 = $(0)"
@echo "pram2 = $(1)"
endef
#函数调用
all:
$(call func, hello zhaixue.cc)
3.实例演示
版本一(同目录)
目录结构:
# 目录结构
└── 01Makefile
├── head.h
├── main.c
├── Makefile
├── test1.c
└── test2.c
程序代码:
//main.c文件-----------------------------------------------
#include<stdio.h>
#include"head.h" //注意这里head.h一定要用“”,同时要保证路径正确
int main(){
test1();
test2();
return 0;
}
//head.h文件-----------------------------------------------
#include<stdio.h>
void test1();
void test2();
//test1.c文件----------------------------------------------
#include<stdio.h>
void test1(){
printf("I am T1\n");
return ;
}
//test2.c文件---------------------------------------------
#include<stdio.h>
void test2(){
printf("I am T2\n");
return ;
}
Makefile 写法1:(最简单)
#a.out : main.c test1.c test2.c head.h
# gcc -o a.out main.c test1.c test2.c
my_exe: main.o test1.o test2.o
gcc main.o test1.o test2.o -o my_exe
main.o: main.c head.h
gcc -c main.c -o main.o
test1.o: test1.c
gcc -c test1.c -o test1.o
test2.o: test2.c
gcc -c test2.c -o test2.o
.PHONY:clean
clean:
rm -rf *.o my_exe
Makefile 写法2:(使用变量)
#定义参数------------------------------------------------------------
TGT = my_exe
SRCS = main.c test1.c test2.c
OBJ = $(SRCS:.c=.o) #相当于OBJ=main.o test1.o test2.o
CC = gcc
HEAD_PATH = $(shell pwd) #找到head.h文件
CFLAGS = -I$( HEAD_PATH) -Wall #编译选项,同时可以找到头文件
#开始编辑规则---------------------------------------------------------
$(TGT): $(OBJ)
$(CC) $(CFLAGS) $^ -o $@ #有了$(CFLAGS)就可以找到head.h文件
%.o:%.c
$(CC) -c $< -o $@
.PHONY:clean
clean:
rm -rf $(OBJ) $(TGT)
版本二(不同目录)
目录结构变更:
03Makefile
├── head
│ └── head.h
├── main
│ └── main.c
├── Makefile
└── test1
├── test1.c
└── test2
└── test2.c
Makefile 写法3:(不同目录)
(1)主Makefile
作用:将子目录下 .c 生成的 .o 文件,在当前目录下链接生成可执行文件。
#主Makefile
#变量参数---------------------------------------------------------
#指定路径
export TOP_DIR = $(shell pwd) #当前目录
#export HEAD_DIR = $(TOP_DIR)/head #头文件目录
export HEAD_DIR = /home/wjh/桌面/3.1Makefile/head #使用$(TOP_DIR)/head,会在/head前面多空格,不是想要的路径。
SUB_DIR = main test1 #子目录
#目标、依赖
TGT = my_exe
export SUB_TGT = built-in.o #子目标
#编译器、链接器
#CROSS_COMPILER = arm-linux- #交叉编译环境
export CC = $(CROSS_COMPILER)gcc
export LD = ld
export CFLAGS = -I $(HEAD_DIR) -Wall #编译选项,同时可以找到头文件
#$( HEAD_DIR)或$(HEAD_PATH) 。$(HEAD_DIR)过不了。因为(TOP_DIR)/head不是想要的路径。
export LDFLAGS = #链接选项
#开始编辑规则----------------------------------------------------------
#最终目标
$(TGT): $(SUB_DIR)
$(CC) $(CFLAGS) $(^:=/$(SUB_TGT)) -o $(TGT) #有了$(CFLAGS)就可以找到头文件
#$(^:=/$(SUB_TGT))相当于$^和:=和子目录下的子目标/$(SUB_TGT)的组合
#进入所有子目录
$(SUB_DIR):
make -C $@ #-C可以让make进入到后面指定目录
.PHONY:clean $(SUB_DIR)
clean:
rm -rf $(TGT)
#for..in..作用是进入子目录清理文件
for dir in $(SUB_DIR); do \
make -C $$dir clean;\
done
(2)子Makefile
作用:说明如何生成当前目录下的子目标。(是由当前目录下的.c生成的.o和当前下的子目录下的子目标临时打包生成的)
#子Makefile
#修改下面两项即可
#main文件夹 #test1文件夹 #test1.c文件夹
SRCS = main.c #SRCS = test1.c #SRCS = test2.c
SUB_DIR = #SUB_DIR = test2 #SUB_DIR =
#生成当前目录下的子目标。-r为生成临时文件
#(是由当前目录下的.c生成的.o和当前下的子目录下的子目标临时打包生成的)
$(SUB_TGT): $(SRCS:.c=.o) $(SUB_DIR)
$(LD) $(LDFLAGS) $(SRCS:.c=.o) $(SUB_DIR:=/$(SUB_TGT)) \
-r -o $@
%.o: %.c
$(CC) $(CFLAGS) $< -c
%.d: %.c
$(CC) $(CFLAGS) $< -MM > $@ #将 %.c 所有依赖写入对于的 %.d 文件中。(-MM :显示依赖关系)
sinclude $(SRCS:.c=.d)
$(SUB_DIR):
make -C $@ #-C参数常用来实现递归调用,加该参数选项,意为进入指定目录
.PHONY:clean $(SUB_DIR)
clean:
rm -rf $(SUB_TGT) $(SRCS:.c=.o) $(SRCS:.c=.d)
#for..in..作用是进入子目录清理文件
for dir in $(SUB_DIR); do\
make -C $$dir clean;\
done