Makefile的使用

Makefile规则

规则解析

一个简单的 Makefile 文件包含一系列的“规则”,样式如下:

目标(target) : 依赖(prerequiries)…
<tab>命令(command)
  • 目标(target):可以是要生成的文件的名称,如可执行文件或OBJ文件;也可以是一个执行的动作名称,诸如'clean'
  • 依赖(prerequiries):是用来产生目标的材料(比如源文件),一个目标经常有几个依赖

一个规则可以含有几个命令,每个命令占一行,命令被执行的 2 个条件:

  1. 依赖文件比目标文件
  2. 目标文件还没生成

例1 

hello: hello.c
    gcc -o hello hello.c
clean:
    rm -f hello

注意必须以 Tab 键缩进第 2、4 行,不能以空格键缩进,直接执行 make 命令即可编译程序,执行   make clean 即可清除编译出来的结果,make 命令根据文件更新的时间戳来决定哪些文件需要重新编译,这使得可以避免编译已经编译过的、没有变化的程序,可以大大提高编译效率,下面是执行逻辑:

  • 执行 make 命令时,仅当 hello.c 文件比 hello 文件新,才会执行 gcc -o hello hello.c 命令
  • 运行 make clean 时,由于目标 clean 没有依赖,它的命令 rm -f hello 将被强制执行

这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。

执行顺序

执行 make 命令时如果不指定目标,那么它默认是去生成第 1 个目标。所以"第 1 个目标",位置很重要。有时候不太方便把第 1 个目标完整地放在文件前面,这时可以在文件的前面直接放置目标,在后面再完善它的依赖与命令。比如:

First_target: // 这句话放在前面
.... // 其他代码,比如 include 其他文件得到后面的 xxx 变量
First_target : $(xxx) $(yyy) // 在文件的后面再来完善
    command

例1:由于只生成第 1 个目标,所以下面只生成了hello.o没有生成hello 

PROM    = hello
CXX = ./../../buildroot/output/rockchip_rk3308_release/host/bin/aarch64-rockchip-linux-gnu-g++

hello.o : hello.c
	$(CXX) -c hello.c
hello : hello.o
	$(CXX) -o $(PROM) hello.o

例2:如果想生成多个可执行目标,我们可以使用 make all 命令来编译所有的目标(如果把all置成第一个目标,那么只需执行 make

.PHONY: all
all: pro1 pro2 pro3 pro4

Makefile的命令参数 

执行 make 命令时,它会去当前目录下查找名为“Makefile”的文件,并根据它的指示去执行操作,生成第一个目标。(GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行)

我们可以使用  "-f " 选项指定文件,不再使用名为" Makefile "的文件,比如:

make -f Makefile.build
make -f hchen.mk

我们可以使用 "-C" 选项指定目录,切换到其他目录里去,比如:

make -C a/ -f Makefile.build 

我们可以指定目标,不再默认生成第一个目标:

make -C a/ -f Makefile.build other_target
  • -f:指定 "makefile" 文件;
  • -i:忽略命令执行返回的出错信息;
  • -s:沉默模式,在执行之前不输出相应的命令行信息;
  • -r:禁止使用build-in规则;
  • -n:非执行模式,输出所有执行命令,但并不执行;
  • -t:更新目标文件;
  • -q:make操作将根据目标文件是否已经更新返回"0"或非"0"的状态信息;
  • -p:输出所有宏定义和目标文件描述;
  • -d:Debug模式,输出有关文件和检测时间的详细信息。
  • -C dir:在读取makefile 之前改变到指定的目录dir;
  • -I dir:当包含其他makefile文件时,利用该选项指定搜索目录;

假想目标.PHONY

我们的 Makefile 中有这样的目标:

clean:
    rm -f $(shell find -name "*.o")
    rm -f $(TARGET)

如果当前目录下恰好有名为" clean "的文件,那么执行" make clean "时它就不会执行删除命令。关键字.PHONY可以解决这问题,告诉make该磁盘上的"clean"是“假的”,这时make为生成这个目标就会将其规则执行一次。.PHONY 修饰clean后,make clean一定会执行rm命令。

.PHONY : clean 

Makefile中的变量

变量赋值

在 GNU make 中对变量的赋值有两种方式:延时变量、立即变量

A = xxx // 延时变量
B ?= xxx // 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效
C := xxx // 立即变量
D += yyy // 如果 D 在前面是延时变量,那么现在它还是延时变量;
// 如果 D 在前面是立即变量,那么现在它还是立即变量

上列变量中,变量 A 是延时变量,它的值在使用时才展开、才确定。比如:

A = $@
test:
    @echo $A

上述 Makefile 中,变量 A 的值在执行时才确定,它等于 test,是延时变量。如果使用" A:= $@ ",这是立即变量,这时$@为空,所以 A 的值就是空,以下三个变量称为自动变量:

  • $@ -- 目标文件
  • $^ -- 所有的依赖文件
  • $< -- 第一个依赖文件
  • $? -- 更新过的依赖文件

变量导出

在编译程序时,我们会不断地使用 make -C dir 切换到其他目录,执行其他目录里的Makefile。如果想让某个变量的值在所有目录中都可见,要把它 export 出来,如下所示:CC 变量表示编译器,在整个过程中都是一样的。定义它之后,要使用 "export CC" 把它导出来。

export CC = $(CROSS_COMPILE)gcc

赋值shell命令

这是个立即变量,TOPDIR 等于 shell 命令 pwd 的结果

TOPDIR := $(shell pwd)

常用变量名

变量名虽然可以随意命名,但也有以下规范(大多数人都这样进行命名):

  • all:这个伪目标是所有目标的目标,其功能一般是编译所有的目标
  • clean:这个伪目标功能是删除所有被make创建的文件
  • install:这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去
  • print:这个伪目标的功能是列出改变过的源文件
  • tar:这个伪目标功能是把源程序打包备份。也就是一个tar文件
  • dist:这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件
  • TAGS:这个伪目标功能是更新所有的目标,以备完整地重编译使用
  • checktest:这两个伪目标一般用来测试makefile的流程

Makefile命令前缀

@

通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用"@"字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:

@echo 正在编译XXX模块......

结果输出:

正在编译XXX模块

如果命令前没有带@,那么会输出:

echo 正在编译XXX模块
正在编译XXX模块
  • 如果make执行时,带入make参数" -n "或" --just-print ",那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。
  • 而make参数" -s "或" --slient "则是全面禁止命令的显示

减号-

其表示,无论include过程中出现什么错误,都不要报错继续执行:

-include<filename>

Makefile中通配符%与通配符*的区别

Makefile中%和*的区别

Makefile中常用函数

$(function arguments)

这里'function'是函数名,'arguments'是该函数的参数。参数和函数名之间是用空格或Tab 隔开,如果有多个参数,它们之间用逗号隔开。这些空格和逗号不是参数值的一部分。内核的 Makefile 中用到大量的函数,现在介绍一些常用的:

$(foreach var,list,text)

简单地说,就是 for each var in list, change it to text。 对 list 中的每一个元素,取出来赋给 var,然后把 var 改为 text 所描述的形式。例子:

objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d) // 最终 dep_files := .a.o.d .b.o.d

$(wildcard pattern)

pattern 所列出的文件是否存在,把存在的文件都列出来,例子:

src_files := $( wildcard *.c) // 最终 src_files 中列出了当前目录下的所有.c 文件

$(filter pattern...,text)

把 text 中符合 pattern 格式的内容,filter(过滤)出来、留下来,例子:

$(filter %.c %.s,foo.c bar.c baz.s ugh.h) //结果为'foo.c bar.c baz.s'

$(filter-out pattern...,text)  

返回在`text’中由空格隔开且不匹配格式'pattern...'的字,去除符合格式'pattern...’的字

$(filter %.c %.s,foo.c bar.c baz.s ugh.h) //结果为'ugh.h'。

$(patsubst pattern,replacement,text)

寻找 'text' 中符合格式 'pattern' 的字,用'replacement'替换它们。'pattern' 和 'replacement' 中可以使用通配符,比如:

src := $(shell ls *.c)
objs := $(patsubst %.c,%.o,$(src))

$(subst from,to,text) 

在文本 'text' 中使用 'to' 替换每一处 'from' ,比如:

$(subst ee,EE,feet on the street) //结果为‘fEEt on the street’

$(strip string) 

去掉前导和结尾空格,并将中间的多个空格压缩为单个空格。比如:

$(strip a   b c ) //结果为'a b c'

$(findstring find,in)

在字符串 'in' 中搜寻 'find' ,如果找到,则返回值是 'find' ,否则返回值为空,比如:

$(findstring a,a b c) //'a'
$(findstring a,b c)   //''空字符串

Makefile中的隐含规则

参考链接


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值