本篇文章学习自

介绍

一个makefile可以看成是由许多命令组成的,每个命令都遵循如下形式

target ... : prerequisites ...
command
...
...

其中target是目标,prerequisites是依赖,command是执行的命令,例如

hello.o: hello.c
gcc -o hello.o hello.c

2
compile:main.bin main.data convert
./convert
mkdir -p $(OBJDIR)
mv main.elf $(OBJDIR)/.
mv test.s $(OBJDIR)/.
mv main.bin $(OBJDIR)/.
mv main.data $(OBJDIR)/.
mv *.coe $(OBJDIR)/.
mv *.mif $(OBJDIR)/.
cp $(OBJDIR)/inst_ram.mif $(OBJDIR)/axi_ram.mif

main.bin:main.elf
${CROSS_COMPILE}objcopy -O binary -j .text $< $@

main.data:main.elf
${CROSS_COMPILE}objcopy -O binary -j .data $< $@

main.elf: start.o libinst.a
${CROSS_COMPILE}gcc -E -P -Umips -D_LOADER -U_MAIN $(CFLAGS) bin.lds.S -o bin.lds
${CROSS_COMPILE}ld -g -T bin.lds -o $@ start.o -L . -linst
${CROSS_COMPILE}objdump -alD $@ > test.s

libinst.a:
make -C inst $(TOPDIR)/$@

convert:convert.c
gcc $(ALIGNED) -std=c99 -o convert convert.c

第一个例子就是单个命令的写法,第二个例子是组合多个命令的写法。虽然有多个命令,但是实际上我们只会运行compile命令,compile命令右边的main.bin会自动在寻找可以生成main.bin的命令,然后再根据main.bin的依赖寻找其生成依赖的命令。

具体工作方式为:

  1. 输入make后,在当前目录寻找名称为Makefile或makefile的文件
  2. 如果找到,会将第一个命令作为根命令的左边作为最后的目标文件。
  3. 解析左边的目标,然后再执行当前命令

规则的书写

规则包含三个部分: 输出文件,规则的依赖文件,生成目标的方法

例如:

foo.o: foo.c defs.h
cc -c -g foo.c

这个例子中:左边是目标文件,也就是说执行这个规则最终会得到foo.o。右边的是这个文件的依赖,也就是下面生成目标文件中所需要的文件。

而下面就是得到这个规则所执行的一条条命令,注意前面是tab键而不是空格

foo.c和defs.h都是从搜寻当前目录,不会对整个目录进行递归搜索

通配符

make支持三种通配符*, ?~这和shell中是相同的含义。

*是匹配任意长度的字符,?是匹配0个或一个字符,~表示$HOME目录。

例如

clean:
rm -f *.o

# 注意: *同样不会递归搜索

文件搜寻

我们一般不会把代码放在同一目录中,但是前面搜索只会在当前目录下搜索,那么便会出现无法找到依赖而报错。解决这个问题第一种方法是指定目录

foo.o: src/foo.c include/defs.h
cc -c -g src/foo.c

但是这种方法不够灵活,我们还可以使用vpath进行搜索。

格式:

  • vpath <pattern> <directories>: 为符合pattern格式的文件设置directories。pattern需要包括%(表示匹配0或若干个字符),如%.h表示所有以h结尾的文件
  • vpath <pattern>: 清除符合pattern格式的搜索目录
  • vpath: 清除所有搜索目录

例如:

vpath %.c src
vpath %.h include
vpath %.so lib

伪目标

一般来说我们执行完目标之后都会生成目标文件,但是有一些规则我们只是单纯的想执行一些命令,并不想输出文件,这时将该命令称为伪命令

一个例子便是clean

clean:
rm -f *.o

它便是典型的伪命令,它并不想产生clean文件。但是如果某个规则依赖clean,那么他应该寻找clean文件还是这条规则呢?这时就需要.PHONY显式指定了。

.PHONY clean
clean:
rm -f *.o

使用了.PHONY后,clean便成为一条伪命令,只要执行make clean,那么便会一定执行这条指令。而如果依赖clean那也只会需要clean文件而不会执行这条指令。

嵌套执行make

在一些大型工程中,我们可能会写多个makefile,然后通过一个总控makefile进行控制,总控的makefile可以这样写

subsystem:
cd subdir && $(MAKE)
或者:

subsystem:
$(MAKE) -C subdir

总控makefile的变量通过export可以传递到下层变量中,但是不会覆盖下层变量,除非制定了-e参数。如果要传递所有变量,那么只需要写一个export,那么除了unexport指定的变量外其他变量都会被传递。

variable = value
export variable
unexport variable #不传递某些变量

变量

变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有 : 、 # 、 = 或是空字符(空格、回车等)。变量是大小写敏感的。

变量在申明时需要赋初值,然后在引用时使用${VAR}的方式

例如:

objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)

$(objects) : defs.h

=,:=和?=

变量可以由其他变量定义,如

foo = $(bar)
bar = $(ugh)
ugh = Huh?
最终foo的值为Huh?

这种方法变量可以写在后面,他会先获得所有变量再进行解析,但是他的问题是可能导致递归定义:

CFLAGS = $(CFLAGS) -O
或者
A = $(B)
B = $(A)
这时会报错

另外可以使用:=操作符定义变量,这种方法就和平常定义变量一样了,不能使用未定义的变量

?=的含义是如果该变量未被定义过,那么将使用右边的值,如果已经被定义过那么将什么也不做。如FOO ?= bar

高级用法

  1. 变量值的替换: $(var:a=b)含义为将所有以a结尾的字符替换成b字符,如bar := $(foo:.o=.c)
  2. 变量的嵌套:
    x = y
    y = z
    a := $($(x))
  3. 追加变量值: 如objects += another.o在objects变量后追加another.o
  4. 目标变量: 针对某个规则基于特定的目标值。如果我们需要针对某个特定的目标设定一些变量的话可以
    <target ...> : <variable-assignment>;

    prog : CFLAGS = -g
    prog : prog.o foo.o bar.o
    $(CC) $(CFLAGS) prog.o foo.o bar.o
    即在prog这条命令中CFLAGS就是-g,而全局的CFLAGS可能不是这个

    条件语句

示例:

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif

语法:

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif
  • conditional-directive: 条件关键字,有ifeq, ifneq, ifdef, ifndef
  • text-if-true: 条件,一般格式是(<arg1>, <arg2>)如果arg1和arg2相同则为真

函数

基本语法: $(<function> <arguments>)

  • function: 函数名
  • arguments: 函数的参数

字符串处理函数

$(subst \,\,\)

  • 功能: 将text中的from字符串替换成to字符串
  • 例如:
    $(subst ee,EE,feet on the street)
    结果为: fEEt on the strEEt

$(patsubst \,\\,\)

  • 功能:将text中复合pattern模式的字符串替换成replacement。注意pattern可以包含%,表示任意多个字符,replacement中也可以由%表示pattern中%所表示的字符
  • 示例:
    $(patsubst %.c,%.o,x.c.c bar.c)
    结果为x.c.o bar.o

$(strip \)

  • 功能: 去掉string开头和结尾的空字符

$(findstring \,\)

  • 功能: 在in中查找find
  • 返回: 如果找到了返回find,没找到返回空

$(filter \,\)

  • 功能: 保留在text中符合pattern的字符串,pattern可以有多个
  • 示例:
    sources := foo.c bar.c baz.s ugh.h
    foo: $(sources)
    cc $(filter %.c %.s,$(sources)) -o foo
    结果为foo.c bar.c ugh.h

$(filter-out \,\)

  • 功能: 去除符合pattern的字符串

$(sort \)

  • 功能: 给list中的字符升序排序,并且会去除掉相同的单词

$(word \,\)

  • 功能: 取text中第n个单词(最小值是1)
  • 示例:
    $(word 2, foo bar baz)
    返回bar

$(wordlist \,\,\)

  • 功能: 从text中取ss到e的单词

$(words \)

  • 功能: 统计单词的个数

$(firstword \)

  • 功能: 取首个单词

文件名操作函数

$(dir \):

  • 功能: 取出文件名的目录部分,也就是最后一个/前面的部分,如果没有/则返回./

$(notdir \):

  • 功能: 取出文件名部分,也就是最后一个/后面的部分

$(suffix \):

  • 功能: 取出各个文件名的后缀

$(basename \)

  • 功能: 取出各个文件名的前缀

$(addsuffix \,\):

  • 功能: 为文件名添加后缀

$(addprefix \,\)

  • 功能: 将prefix添加到names的前缀的最前面
  • 示例:
    $(addprefix src/,foo bar)
    结果为 src/foo src/bar

$(join \,\)

  • 功能: 将list2的单词添加到list1对应位置的后面,如果list2比list1多,那么结果会返回多出的list2
  • 示例:
    $(join aaa bbb , 111 222 333)
    结果:aaa111 bbb222 333

    foreach,if

$(foreach \,\,\)

  • 功能: 将list中的单词逐一取出放到var中,然后再运行text表达式
  • 示例:
    names := a b c d

    files := $(foreach n,$(names),$(n).o)

$(if \,\,\)

  • 功能: 条件语句

call

call可以用来定义自己的函数

$(call \,\,\,…,\)

  • 功能: 执行expression表达式,后面的是这个表达式的参数
  • 示例:
    reverse =  $(2) $(1)

    foo = $(call reverse,a,b)