makefile

2018.5.12 by jianfeng

基础

最终目标:依赖A 依赖B
    最终目标命令
依赖A: 子依赖A1 子依赖A2
    依赖A命令
依赖B:子依赖B1 子依赖B2
    依赖B命令
......

# ------------------------------
# 执行顺序说明:
# 从底向上,从左到右。

指令

  • make <target>

    指定makeifle目标

  • make -f <makefile>

    指定makefile文件

  • make -C <subdir> <target>

    到指定目录下执行make

语法

赋值

  • :=

    直接展开,例如 a=$(b),那么变量b中的值,直接赋值给a

  • =

    递归展开,例如 a=$(b),那么只有在a被调用的时候才会展开b的值。

  • +=

    追加操作符

  • ?=
# 注意:
# 1. $字符在makefile有特殊用途,如果在echo中作为普通字符,那么使用$$
# 2. $@ 在bash中也有特殊用途,如果在在echo中作为普通字符,那么使用\$$@

b=$(a)
c:=$(a)
d?=$(a)

a=123

all: A B
    @echo "all ->"
    @echo "----------"
    @echo a=${a}
    @echo b=${b}
    @echo c=$(c)
    @echo d=$(d)
    @echo "\$$@ = "$@
    @echo "$$< = "$<
    @echo "$$^ = "$^

A: A1 A2
    @echo "A ->"
B:
    @echo "B ->"

A1:
    @echo "A1 -> "
A2:
    @echo "A2 -> "

# ------------------------------
# 上述makefile示例的结果:
# ------------------------------
A1 -> 
A2 -> 
A ->
B ->
all ->
----------
a=123
b=123
c=
d=123
$@ = all
$< = A
$^ = A B

变量引用

  • $(obj)

    makefile中引用变量。(在bash中,obj是等价的)

  • ${CC}

    makefile引用变量(常用)。但在bash中。${CC}是变量引用,$(CC)是命令

  • $@

    代表规则中的目标文件名

  • $<

    代表规则的第一个依赖的文件名

  • $^

    代表规则中所有依赖的列表,文件名称用空格分割

变量的替换

  • ${VAR:A=B}

    将变量VAR中所有的字符串A用B来替换

src := main.c test.c
obj := $(src : .c=.o)

静态规则模式

  • %.o

    其中%是makefile的通配符。%.o表示当前目录下的所有.o文件

$(objects) : %.o : %.c
    ${CC} -o $@ -c $<

伪目标

  • .PHONY: <伪目标>
    1. 避免目标和实际文件名称冲突

    当存在clean文件时,由于该规则没有依赖,因此目标被认为是最新的,从而不执行规则下的命令。例如提示"make: 'clean' is up to date."

    1. 提高执行make的效率。
      • 多目录结构,并行处理且方便定位错误。例如示例1
      • 一个makefile生成多个可执行文件
### 示例1

SUBDIRS = Source Libary Demo
subdirs:
    for dir in $(SUBDIRS); do $(MAKE) -C $$dir; done

# 在子目录在执行make
# 但是上述操作方式有两点问题:
# 1. 如果make出错时,make不会停止。不好定位哪个make出的错。
# 2. 没有用到make对目录的并行处理功能
# 换成如下方式:

SUBDIRS = Source Libary Demo
.PHONY : subdirs $(SUBDIRS)
subdirs: $(SUBDIRS)
$(SUBDIRS):
    $(MAKE) -C $@


### 示例2
.PHONY : all
all: Source Demo Test
Source : sm4.c
    $(CC) -o $@ $^
Demo : demo.c
    $(CC) -o $@ $^
Test : test.c
    $(CC) -o $@ $^

示例


# 编译工具
CC := gcc
AR := ar

INCDIR  := $(shell pwd)

# C预处理器使用 flag是编译器可选择的选项
# 不使用标准库和标准头文件
HEADER  := -I/usr/local/include/libusb-1.0
HEADER  += -I$(INCDIR)/Library
HEADER  += -I$(INCDIR)/Source

LIB += -L/usr/local/lib -lusb-1.0

# C编译器的falg
# -Wall警告信息开
# -O2优化程度2
# -fno-buitin不用标准的,用自己的..
CFLAGS :=  -Wall -O0

#导出这些变量到全局,给其对应的子文件makefile使用
export CC AR CFLAGS HEADER LIB

#
obj := Source/main.o
obj += Source/demo.o
# 
obj += Library/usbdrv.a

# -----------------------------------------
# 				命令
# 最后注意:因为我封装的库usbdrv.a中调用了libusb
# 的库,因此再最终生成*.out文件时,也需要包含该库
# $(LIB),否则会出现找不到函数的问题
# undefined reference to `libusb_init'
# ... ...
# -----------------------------------------
all: $(obj)
#	$(CC) -o main.out $^
    $(CC) -o main.out $^ $(LIB)
%.o : %.c
    $(CC) $(CFLAGS) $(HEADER) -o $@ -c $< $(LIB)

Library/usbdrv.a:
    # cd Library; make; cd ..
    make -C Library
clean:
    cd Source; rm *.o ; cd ..
    cd Library; make clean; cd ..
    rm -f *.out *.bin

子目录下的驱动层,生成静态库。

objs := usbdrv.o

usbdrv.a: $(objs)
    ${AR} -r -o $@ $^
    
%.o:%.c
    ${CC} $(CFLAGS) $(HEADER) -o $@ -c $< $(LIB) 

clean:
    rm -f usbdrv.a *.o