5 Makefile
Makefile中跟目标相关的语法:命令必须使用TAB缩进
[目标1]:[依赖]
[命令1]
[命令2]
[目标2]:[依赖]
[命令1]
[命令2]
- 目标:指make要做的事情,可以是一个简单的代号,也可以是目标文件,需要顶格书写一个Makefile可以有多个目标,写在最前面的第一 个目标,会被Make程序确立为 “默认目标”。
- 依赖:要达成目标需要依赖的某些文件或其它目标。在编译的例子中,hello_main依赖于hello_main.c、hello_func.c源文 件,若这些文件更新了会重新进行编译。
- 命令1,命令2…命令n:make达成目标所需要的命令。只有当目标不存在或依赖文件的修改时间比目标文件还要新时,才会执行命令。要特别注意命令的开头要用“Tab”键,不能使用空格代替。
5.1 伪目标
在Makefile中编写的目标,在make看来其实都是目标文件,例如make在执行时由于在目录找不到targeta文件,所以每次make targeta的时候,它都会去执行targeta的命令,期待执行后能得到名为targeta的 同名文件。如果目录下真的有targeta、targetb、targetc的文件,即假如目标文件和依赖文件都存在且是最新的,那么make targeta就不会被正常执行了,这会引起误会。
Makefile使用“.PHONY”前缀来区分目标代号和目标文件,并且这种目标代号被称为“伪目标”。
.PHONY:clean
clean:
rm -f *.o hello_main
GNU组织发布的软件工程代码的Makefile,常常会有类似以上代码中定义的clean伪目标, 用于清除编译的输出文件。常见的还有“all”、“install”、“print”、“tar”等 分别用于编译所有内容、安装已编译好的程序、列出被修改的文件及打包成tar文件。 虽然并没有固定的要求伪目标必须用这些名字,但可以参考这些习惯来编写自己的Makefile
5.2 默认规则
在前面《GCC编译过程》章节中提到整个编译过程包含如下图中的步骤,make在执行时也是 使用同样的流程,不过在Makefile的实际应用中,通常会把编译和最终的链接过程分开。
hello_main目标文件本质上并不是依赖hello_main.c和hello_func.c文件, 而是依赖于hello_main.o和hello_func.o, 把这两个文件链接起来就能得到hello_main目标文件。 另外,由于make有一条默认规则,当找不到xxx.o文件时, 会查找目录下的同名xxx.c文件进行编译。
5.3 使用变量
使用C自动编译成 *.o的默认规则有个缺陷,由于没有显式地表示 *.o依赖于.h头文件, 假如我们修改了头文件的内容,那么 *.o并不会更新,这是不可接受的。 并且默认规则使用固定的“cc”进行编译,假如我们想使用ARM-GCC进行交叉编译, 那么系统默认的“cc”会导致编译错误。
要解决这些问题并且让Makefile变得更加通用,需要引入变量和分支进行处理。
5.3.1 基本语法
在Makefile中的变量,有点像C语言的宏定义,在引用变量的地方使用变量值进行替换。变量的命名可以包含字符、数字、下划线,区分大小写,定义变量的方式有以下四种:
- “=” :延时赋值,该变量只有在调用的时候,才会被赋值
- “:=” :直接赋值,与延时赋值相反,使用直接赋值的话,变量的值定义时就已经确定了。
- “?=” :若变量的值为空,则进行赋值,通常用于设置默认值。
- “+=” :追加赋值,可以往变量后面增加新的内容。
TARGET = hello_main #定义变量
CC = gcc
CFLAGS = -I.
DEPS = hello_func.h
OBJS = hello_main.o hello_func.o
#目标文件
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(CFLAGS)
#*.o文件的生成规则
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
#伪目标
.PHONY: clean
clean:
rm -f *.o hello_main
5.3.2 其他自动化变量
符号 | 意义 |
---|---|
$@ | 匹配目标文件 |
$% | 与 @ 类似,但 @类似,但 @类似,但%仅匹配“库”类型的目标文件 |
$< | 依赖中的第一个目标文件 |
$^ | 所有的依赖目标,如果依赖中有重复的,只保留一份 |
$+ | 所有的依赖目标,即使依赖中有重复的也原样保留 |
$? | 所有比目标要新的依赖目标 |
5.4 使用分支
ifeq(arg1, arg2)
分支1
else
分支2
endif
修改
#定义变量
#ARCH默认为x86,使用gcc编译器,
#否则使用arm编译器
ARCH ?= x86
TARGET = hello_main
CFLAGS = -I.
DEPS = hello_func.h
OBJS = hello_main.o hello_func.o
#根据输入的ARCH变量来选择编译器
#ARCH=x86,使用gcc
#ARCH=arm,使用arm-gcc
ifeq ($(ARCH),x86)
CC = gcc
else
CC = arm-linux-gnueabihf-gcc
endif
#目标文件
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(CFLAGS)
#*.o文件的生成规则
%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
#伪目标
.PHONY: clean
clean:
rm -f *.o hello_main
5.5 最终Makefile
ARCH ?= x86
TARGET = hello_main
BUILD_DIR = build_$(ARCH) #存放中间文件的路径
SRC_DIR = $(shell find drv -maxdepth 3 -type d) #当前目录下查找名为drv的文件夹,并在最多搜索3层深度内的子目录中查找。-type d选项指示find只返回目录(文件夹)而不是文件
INC_DIR = drv \ #存放头文件的文件夹
drv/adc \
EX_FILES =
SRCS = $(filter-out ${EX_FILES}, $(foreach dir, $(SRC_DIRS), $(wildcard $(addprefix $(dir)/*, .c)))) #源文件
OBJS = $(addprefix $(BUILD_DIR)/,$(notdir $(SRC_FILES:.c=.o))) #目标文件(*.o)
DEPS = $(wildcard $(INC_DIR)/*.h) #头文件
#指定头文件的路径
CFLAGS = $(patsubst %, -I%, $(INC_DIR))
#根据输入的ARCH变量来选择编译器
#ARCH=x86,使用gcc
#ARCH=arm,使用arm-gcc
ifeq ($(ARCH),x86)
CC = gcc
else
CC = arm-linux-gnueabihf-gcc
endif
#目标文件
$(BUILD_DIR)/$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(CFLAGS)
#*.o文件的生成规则
$(BUILD_DIR)/%.o: %.c $(DEPS)
@mkdir -p $(BUILD_DIR)
$(CC) -c -o $@ $< $(CFLAGS)
#伪目标
.PHONY: clean cleanall
#按架构删除
clean:
rm -rf $(BUILD_DIR)
最终Makefile只做记录未经测试