Makefile
一、Makefile 简单使用
Makefile是Linux系统下的一种编译脚本,更快、更方便的编译一个c/c++程序
- 描述了整个工程的编译、链接规则
- 软件项目自动化编译
二、简单了解编译和链接过程
源代码(helloworld.c)->预处理器->编译器->汇编代码(helloworld.s)->汇编器(helloworld.o)->目标代码->链接器->可执行程序(helloworld.exe)
冯·诺依曼结构:CPU、硬盘(断电不消失)、内存(断电消失)
启动运行过程 从硬盘镜像代码加载到内存中,cpu一条条去读取加载到内存中的指令去执行
三、Makefile基本语法
规则、变量、条件执行、文本/文件名处理函数、文件包含、注释
规则
target: dependencies
command
注释
#
变量
cc = gcc
条件
ifeq ($(DEBUG), "true")
cc = gcc -g
else
cc = gcc
endif
搜索当前 目录下的所有.c文件
SRCS = $(willdcard *.c)
加前缀 目录加到文件名前面
OBJS = $(addprefix 目录,文件名)
文件包含
# 类似于C语言的#include,这里使用include命令
include makefile内容的文件
函数
- 文本处理函数:字符串替换、查找、过滤、排序、统计等
- 文件名处理函数:取目录/文件名、前后缀、加前缀/后缀、单词链连接等函数
依赖关系树
Makefile的目的:构建依赖关系树
如何表示依赖关系树
依赖关系树的生命周期
- 解析阶段载入内存
- 运行阶段根据其进行编译、根据时间戳生成文件
- 有新文件添加、减少会动态改变依赖关系树
四、规则基本构成
目标:目标依赖
命令
4.1.注意事项
- 命令必须使用tab键开头、一般是shell命令
- 一个规则中可以无目标依赖,仅仅实现某种操作
test:
@echo "test..."
clean:
rm *.o
- 一个规则中可以没有命令,仅仅描述依赖关系
all:test
- 一个规则中必须有一个目标
4.2.目标
除伪目标外每个目标都会生成目标文件
- 默认目标
- 一个Makefile里可以有多个目标
- 一般会选择第一个作为默认目标
- 多目标
- 一个规则中可以有多个目标
- 多个目标具有相同的生成命令
targetX targetY:
命令
- 多规则目标
- 多个规则可以是同一个目标
- Make在解析时,会将多个规则的依赖合并
all: targetX
all: targetY
#等同
all: targetX targetY
targetX:
@echo ""
targetY:
@echo ""
- 伪目标
- 并不是一个真正的文件名,可以看做是一个标签
- 无依赖,相比一般文件不会去重新生成、执行
- 伪目标,可以无条件执行
.PHONY:all clean
all: targetX targetY
targetX:
@echo ""
targetY:
@echo ""
clean:
rm *.o
4.3.目标依赖
- 文件时间戳
- 根据时间戳来判断目标依赖文件是否更新
- 所有文件编译过,则对所有文件编译,生成可执行程序
- 在上次make之后修改过的C文件,会被重新编译
- 在上次make之后修改过的头文件,依赖此头文件的会被重新编译
- 自动产生依赖
- gcc -M命令生成改文件要依赖的文件
gcc -M main.c
gcc -MM main.c
- 隐式规则
cc = gcc
main:foo.o bar.o
$(cc) -o main foo.o bar.o
#a.o:a.c
# $(cc) -c foo.c
#b.o:b.c
# $(cc) -c bar.c
- 模式匹配
# $@ = 目标文件,$^ = 所有的依赖文件,$< = 第一个依赖文件。
cc = gcc
main:foo.o bar.o
$(cc) -o main foo.o bar.o
%.o:%c
$(cc) -o $@ -c $^
4.4.生成命令
-
命令的组成
- shell命令组成、tab键开头
-
命令执行
- 每条命令,make会开一个进程
- 每条命令执行完,make会检测每个命令的返回码
- 若命令返回成功(0),make继续执行下个命令
- 若命令执行出错,make会终止执行当前规则,退出
cc = gcc
main:foo.o ba.o
@echo "start build main"
$(cc) -o main foo.o bar.o
@echo "build main success"
%.o:%c
$(cc) -o $@ -c $^
- 并发执行命令
make -j3 #开启多个进程执行
- 命令同一进程执行
all:
cd /
pwd #显示当前目录
#改成下面方式,同一个进程 命令间使用;(分号)+ (空格)+\(分隔符),变成一个逻辑命令
all:
cd /; \
pwd
五、变量
5.1.变量基础
5.1.1.变量定义
- CC = gcc
5.1.2.变量赋值
- 追加赋值: +=
- 条件赋值: ?=
STR = hello
STR += world!
v1 = a
v1 ?= b
v2 ?= b
all:
@echo "STR = $(STR)" # hello world!
@echo "v1 = $(v1)" # a
@echo "v2 = $(v2)" # b
5.1.3.变量引用
- {CC}
cc = gcc
BIN = main
OBJS = foo.o bar.o
$(BIN):$(OBJS)
$(cc) -o $(BIN) $(OBJS)
%.o:%.c
$(cc) -o $@ -c $^
5.2.变量分类
5.2.1.立即展开变量
- 使用 := 操作符赋值
- 在解析阶段直接赋值常量字符串
.PHONY:all
HELLO = Good
TIME = morning!
STRING := $(HELLO) $(TIME)
$(info $(STRING))
TIME = afternoon!
$(info $(STRING))
all:
@echo "done"
# 结果
# Good morning!
# Good morning!
# done
5.2.2.延迟展开变量
- 使用 = 操作符赋值
- 在运行阶段,实际使用变量时再进行求值
.PHONY:all
HELLO = Good
TIME = morning!
STRING = $(HELLO) $(TIME)
$(info $(STRING))
TIME = afternoon!
$(info $(STRING))
all:
@echo "done"
# 结果
# Good morning!
# Good afternoon!
# done
5.2.3.注意事项
- 一般在目标、目标依赖中使用立即展开变量
- 在命令中一般使用延迟展开变量
5.3.目标变量
5.3.1.一般变量
- 默认为全局变量
5.3.2.目标变量
- 该目标所依赖的规则中都可以使用
cc = gcc
BIN = main
OBJS = foo.o bar.o
N = 1
$(BIN): N = 2
$(BIN):$(OBJS)
@echo "BIN: N = $(N)" # 2
$(cc) -o $(BIN) $(OBJS)
foo.o: N = 3
foo.o:foo.c
@echo "foo.o: N = $(N)" # 3
$(cc) -c foo.c
bar.o:bar.c
@echo "bar.o: N = $(N)" # 这里是几呢?
$(cc) -c bar.c
clean:
@echo "clean: N = $(N)" # 1
5.3.3.使用目标变量
- 做到文件级的编译选项
5.4.模式变量
5.4.1目标变量
- 变量可以定义在某个目标上
5.4.2.模式变量
- 变量可以定义在符号某种模式的目标上
cc = gcc
BIN = main
OBJS = foo.o bar.o
N = 1
$(BIN): N = 2
$(BIN):$(OBJS)
@echo "BIN: N = $(N)" # 2
$(cc) -o $(BIN) $(OBJS)
%.o: N = 3
foo.o:foo.c
@echo "foo.o: N = $(N)" # 3
$(cc) -c foo.c
bar.o:bar.c
@echo "bar.o: N = $(N)" # 3
$(cc) -c bar.c
clean:
@echo "clean: N = $(N)" # 1
5.5.自动变量
5.5.1自动变量是局部变量
5.5.2目标
- $@
5.5.3所有依赖
- $^
5.5.4.第一个依赖
- $<
5.5.5.使用举例
- gcc -o ^
cc = gcc
BIN = main
OBJS = foo.o bar.o
$(BIN):$(OBJS)
@echo "BIN----------$@:$^"
$(cc) -o $@ $^
foo.o:foo.c
@echo "foo----------$@:$^"
$(cc) -o $@ -c $^
bar.o:bar.c
@echo "bar----------$@:$^"
$(cc) -o $@ -c $^
5.6.系统环境变量
5.6.1.作用范围
- 变量在make开始运行时被载入到Makefile文件中
- 对所有的Makefile都有效
- 若Makefile中定义同名变量,系统环境变量将被覆盖
- 命令行中传递同名变量,系统环境变量将被覆盖 # $make MAKE=nmake
5.6.2.常见的系统环境变量
- CFLAGS
- SHELL
- MAKE
.PHONY:all
all:
@echo "CFLAGS = $(CFLAGS)"
@echo "SHELL = $(SHELL)" # /bin/sh
@echo "MAKE = $(MAKE)" # make
@echo "HOSTNAME = $(HOSTNAME)"
5.7.变量的传递
5.7.1.Makefile在多目录下递归执行
- $(MAKE) -C subdir
- cd subdir && $(MAKE)
N = 3
all:
@echo "build...."
cd test && make N = $(N)
5.7.2.通过export传递变量
export N = 3
all:
@echo "build...."
cd test && make
5.7.3.通过命令行传递变量
- make N = 3
六、条件执行
常用形式
- ifxxx (arg1,arg2)
其它合法形式
- ifxxx "arg1" "arg2"
- ifxxx 'arg1' 'arg2'
- ifxxx "arg1" 'arg2'
- ifxxx 'arg1' "arg2"
上面‘ifxxx’为下方关键字
关键字 | 功能 |
---|---|
ifeq | 判断参数是否相等,相等为true,否则为false |
ifneq | 判读参数是否不相等,不相等为true,否则为false |
ifdef | 判断变量是否有值,有值为true,否则为false |
ifndef | 判断变量是否没有值,没有值为true,否则为false |
# Makefile 内容
x := A
y := $(x)
z :=
test:
ifeq ($(x),$(y)) # 注意:在ifeq 前面不能使用\tab键,而是使用空格键
@echo "x == y"
else
@echo "x != y"
endif
ifneq ($(x),$(y))
@echo "x != y"
else
@echo "x == y"
endif
ifdef $(y)
@echo "y is Not empty"
else
@echo "y is empty"
endif
ifndef $(z)
@echo "z is empty"
else
@echo "z is Not empty"
endif
# bash 中执行 make
$make
##输出:
x == y
x == y
y is Not empty
z is empty
多分支格式:
x = 123
y = 456
z = 123
ifeq $(x),$(y)
@echo "x == y"
else ifeq $(x),$(z)
@echo "x == z"
else
@echo "x != y && x != z"
endif
七、函数
Makefile中自带了一些函数,利用这些函数可以简化Makefile的编写
函数调用语法如下:
$(<function> <arguments>)
#或者
${<function> <arguments>}
- <function> 是函数名
- <arguments> 是函数参数
7.1.字符串函数
7.1.1.字符串替换函数:$(subst <from>,<to>,<text>)*
功能:把字符串<text>中的<from>替换为<to>
返回:替换过的字符串
# Makefile 内容
all:
@echo $(subst t,e,maktfilt)
# bash 中执行 make
$make
##输出:
makefile
7.1.2.模式字符串替换函数: $(patsubst <pattern>,<replacement>,<text>)
功能: 查找<text>中的单词(单词以"空格", "tab", "换行"来分割) 是否符合 <pattern>, 符合的话, 用 <replacement> 替代.
返回: 替换过的字符串
# Makefile 内容
all:
@echo $(patsubst %.c,%.o,foo.c bar.c)
# bash 中执行 make
$make
##输出:
foo.o bar.o
7.1.3.去空格函数: $(strip <string>)
功能: 去掉 <string> 字符串中开头和结尾的空字符
返回: 被去掉空格的字符串值
# Makefile 内容
VAL := " foo.c bar.c foo.o bar.o "
all:
@echo "去除空格前:" $(VAL)
@echo "去除空格后:" $(strip $(VAL))
# bash 中执行 make
$make
##输出:
foo.c bar.c foo.o bar.o
foo.c bar.c foo.o bar.o
7.1.4.查找字符串函数: $(findstring <find>,<in>)
功能: 在字符串 <in> 中查找 <find> 字符串
返回: 如果找到, 返回 <find> 字符串, 否则返回空字符串
# Makefile 内容
VAL := " foo.c bar.c foo.o bar.o "
all:
@echo $(findstring foo,$(VAL))
@echo $(findstring fbar,$(VAL))
# bash 中执行 make
$make
##输出:
foo
7.1.5.过滤函数: $(filter <pattern...>,<text>)
功能: 以 <pattern> 模式过滤字符串 <text>, 保留 符合模式 <pattern> 的单词, 可以有多个模式
返回: 符合模式 <pattern> 的字符串
# Makefile 内容
all:
@echo $(filter %.o %.a,foo.c foo.o main.a)
# bash 中执行 make
$ make
##输出:
foo.o main.a
7.1.6.反过滤函数: $(filter-out <pattern...>,<text>)
功能: 以 <pattern> 模式过滤字符串 <text>, 去除 符合模式 <pattern> 的单词, 可以有多个模式
返回: 不符合模式 <pattern> 的字符串
# Makefile 内容
all:
@echo $(filter-out %.o %.a,foo.c foo.o main.a)
# bash 中执行 make
$ make
##输出:
foo.c
7.1.7.排序函数: $(sort <list>)
功能: 给字符串 <list> 中的单词排序 (升序)
返回: 排序后的字符串
# Makefile 内容
all:
@echo $(sort bac abc acb cab)
# bash 中执行 make
$ make
##输出:
abc acb bac cab
7.1.8.取单词函数: $(word <n>,<text>)
功能: 取字符串 <text> 中的 第<n>个单词 (n从1开始)
返回: <text> 中的第<n>个单词, 如果<n> 比 <text> 中单词个数要大, 则返回空字符串
# Makefile 内容
all:
@echo $(word 1,aa bb cc dd)
@echo $(word 5,aa bb cc dd)
@echo $(word 4,aa bb cc dd)
# bash 中执行 make
$ make
##输出:
aa
dd
7.1.9.取单词串函数: $(wordlist <s>,<e>,<text>)
功能: 从字符串<text>中取从<s>开始到<e>的单词串. <s>和<e>是一个数字.
返回: 从<s>到<e>的字符串
# Makefile 内容
all:
@echo $(wordlist 1,3,aa bb cc dd)
@echo $(word 5,6,aa bb cc dd)
@echo $(word 2,5,aa bb cc dd)
# bash 中执行 make
$ make
##输出:
aa bb cc
bb
7.1.10.单词个数统计函数: $(words <text>)
功能: 统计字符串 <text> 中单词的个数
返回: 单词个数
# Makefile 内容
all:
@echo $(words aa bb cc dd)
@echo $(words aabbccdd)
@echo $(words )
# bash 中执行 make
$ make
##输出:
4
1
0
7.1.11.首单词函数: $(firstword <text>)
功能: 取字符串 <text> 中的第一个单词
返回: 字符串 <text> 中的第一个单词
# Makefile 内容
all:
@echo $(firstword aa bb cc dd)
@echo $(firstword aabbccdd)
@echo $(firstword )
# bash 中执行 make
$ make
##输出:
aa
aabbccdd
7.2.文件名函数
7.2.1.取目录函数: $(dir <names...>)
功能: 从文件名序列 <names> 中取出目录部分
返回: 文件名序列 <names> 中的目录部分
# Makefile 内容
all:
@echo $(dir /home/a.c ./bb.c ../c.c d.c)
# bash 中执行 make
$ make
##输出:
/home/ ./ ../ ./
7.2.2.取文件函数: $(notdir <names...>)
功能: 从文件名序列 <names> 中取出非目录部分
返回: 文件名序列 <names> 中的非目录部分
# Makefile 内容
all:
@echo $(notdir /home/a.c ./bb.c ../c.c d.c)
# bash 中执行 make
$ make
##输出:
a.c bb.c c.c d.c
7.2.3.取后缀函数: $(suffix <names...>)
功能: 从文件名序列 <names> 中取出各个文件名的后缀
返回: 文件名序列 <names> 中各个文件名的后缀, 没有后缀则返回空字符串
# Makefile 内容
all:
@echo $(suffix /home/a.c ./b.o ../c.a d)
# bash 中执行 make
$ make
##输出:
.c .o .a
7.2.4.取前缀函数: $(basename <names...>)
功能: 从文件名序列 <names> 中取出各个文件名的前缀
返回: 文件名序列 <names> 中各个文件名的前缀, 没有前缀则返回空字符串
# Makefile 内容
all:
@echo $(basename /home/a.c ./b.o ../c.a /home/.d .e)
# bash 中执行 make
$ make
##输出:
/home/a ./b ../c /home/
7.2.5.加后缀函数: $(addsuffix <suffix>,<names...>)
功能: 把后缀 <suffix> 加到 <names> 中的每个单词后面
返回: 加过后缀的文件名序列
# Makefile 内容
all:
@echo $(addsuffix .c,/home/a b ./c.o ../d.c)
# bash 中执行 make
$ make
##输出:
/home/a.c b.c ./c.o.c ../d.c.c
7.2.6.加前缀函数: $(addprefix <prefix>,<names...>)
功能: 把前缀 <prefix> 加到 <names> 中的每个单词前面
返回: 加过前缀的文件名序列
# Makefile 内容
all:
@echo $(addprefix test_,/home/a.c b.c ./d.c)
# bash 中执行 make
$ make
##输出:
test_/home/a.c test_b.c test_./d.c
7.2.7.连接函数: $(join <list1>,<list2>)
功能: <list2> 中对应的单词加到 <list1> 后面
返回: 连接后的字符串
# Makefile 内容
all:
@echo $(join a b c d,1 2 3 4)
@echo $(join a b c d,1 2 3 4 5)
@echo $(join a b c d e,1 2 3 4)
# bash 中执行 make
$ make
##输出:
a1 b2 c3 d4
a1 b2 c3 d4 5
a1 b2 c3 d4 e
7.3.foreach
语法:
$(foreach <var>,<list>,<text>)
# Makefile 内容
targets := a b c d
objects := $(foreach i,$(targets),$(i).o)
all:
@echo $(targets)
@echo $(objects)
# bash 中执行 make
$ make
##输出:
a b c d
a.o b.o c.o d.o
7.4.if
这里的if是个函数, 和前面的条件判断不一样, 前面的条件判断属于Makefile的关键字
语法:
$(if <condition>,<then-part>)
$(if <condition>,<then-part>,<else-part>)
# Makefile 内容
val := a
objects := $(if $(val),$(val).o,nothing)
no-objects := $(if $(no-val),$(val).o,nothing)
all:
@echo $(objects)
@echo $(no-objects)
# bash 中执行 make
$ make
##输出:
a.o
nothing
7.5.call - 创建新的参数化函数
语法:
$(call <expression>,<parm1>,<parm2>,<parm3>...)
# Makefile 内容
log = "====debug====" $(1) "====end===="
all:
@echo $(call log,"正在 Make")
# bash 中执行 make
$ make
##输出:
====debug==== 正在 Make ====end====
7.6.origin - 判断变量的来源
语法:
$(origin <variable>)
返回值有如下类型:
类型 | 含义 |
---|---|
undefined | <variable> 没有定义过 |
default | <variable> 是个默认的定义, 比如 CC 变量 |
environment | <variable> 是个环境变量, 并且 make时没有使用 -e 参数 |
file | <variable> 定义在Makefile中 |
command line | <variable> 定义在命令行中 |
override | <variable> 被 override 重新定义过 |
automatic | <variable> 是自动化变量 |
# Makefile 内容
val-in-file := test-file
override val-override := test-override
all:
@echo $(origin not-define) # not-define 没有定义
@echo $(origin CC) # CC 是Makefile默认定义的变量
@echo $(origin PATH) # PATH 是 bash 环境变量
@echo $(origin val-in-file) # 此Makefile中定义的变量
@echo $(origin val-in-cmd) # 这个变量会加在 make 的参数中
@echo $(origin val-override) # 此Makefile中定义的override变量
@echo $(origin @) # 自动变量, 具体前面的介绍
# bash 中执行 make
$ make val-in-cmd=val-cmd
##输出:
undefined
default
environment
file
command line
override
automatic
7.7.shell
语法:
$(shell <shell command>)
它的作用就是执行一个shell命令, 并将shell命令的结果作为函数的返回.
作用和 <shell command> 一样
7.8.make 控制函数
7.8.1.产生一个致命错误: $(error <text ...>)
功能: 输出错误信息, 停止Makefile的运行
# Makefile 内容
all:
$(error there is an error!)
@echo "这里不会执行!"
# bash 中执行 make
$ make
##输出:
Makefile:2: *** there is an error!. Stop.
7.8.2.输出警告: $(warning <text ...>)
功能: 输出警告信息, Makefile继续运行
# Makefile 内容
all:
$(warning there is an warning!)
@echo "这里会执行!"
# bash 中执行 make
$ make
##输出:
Makefile:2: there is an warning!
这里会执行!
八、Makefile中一些GNU约定俗成的伪目标
如果有过在Linux上, 从源码安装软件的经历的话, 就会对 make clean, make install 比较熟悉.
举例:
伪目标 | 含义 |
---|---|
call | 所有目标的目标,其功能一般是编译所有的目标 |
clean | 删除所有被make创建的文件 |
install | 安装已编译好的程序,其实就是把目标可执行文件拷贝到指定的目录中去 |
列出改变过的源文件 | |
tar | 把源程序打包备份. 也就是一个tar文件 |
dist | 创建一个压缩文件, 一般是把tar文件压成Z文件. 或是gz文件 |
TAGS | 更新所有的目标, 以备完整地重编译使用 |
check 或 test | 一般用来测试makefile的流程 |
网友评论