Makefile语法[1/10]
前言
makefile关系到了整个工程的编译规则。会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。
作为一个专业程序员,为了胜任软件架构师的职责,必须在makefile方面拿到8分左右,假设精通=10分。
本文以GNU make Version 4.3英文手册作为参考,先过一遍直译,然后加上自己的理解,最后打磨成舒畅、易懂、全面的中文教程。
参考:3.79版中文博客,陈皓博客跟我一起写 Makefile。
最最后搞懂下列脚本,小试牛刀:
# common argument
# -----------------------------------------------------------------------------------------------
# to find tools
CASE_ROOT ?=
SW_ROOT ?=
#------------------------------------------------------------------------------------------------
# file to compile
GCC_FILE ?= *.c
#================================================================================================
# all action
all : gcc
clear : gcc_clear
distclear : gcc_distclear
#================================================================================================
# gcc compile
#------------------------------------------------------------------------------------------------
# fixed config
# some fixed argument for hr3tvc to gcc tools
export ARCH = mips64
export CONFIG_PRINT = 1
export KBUILD_MODE = KERNEL_MODE
export CONFIG_BOOT = 1
export CONFIG_REAL_DDR = 1
#------------------------------------------------------------------------------------------------
# some argument to gcc command
CFLAGS += -I$(CASE_ROOT)/common -I./
CFLAGS += -I$(CASE_ROOT)/head
CFLAGS += -I$(CASE_ROOT)/device_test
CFLAGS += -I$(CASE_ROOT)/trunk/include/frame
CFLAGS += -D__MIPS64__
#------------------------------------------------------------------------------------------------
SRCS_DIRNAMES=$(dir $(GCC_FILE))
SRCS_FILENAMES=$(notdir $(GCC_FILE))
SRCS_BASENAMES=$(basename $(SRCS_FILENAMES))
#SRCS_BASENAMES = $(basename $(shell ls $(GCC_FILE)))
#ELF_OBJS = $(SRCS_BASENAMES:%=%_$(KBUILD_MODE).vmem)
ELF_OBJS = $(SRCS_BASENAMES:%=ELF/%.elf)
HEX_OBJS = $(SRCS_BASENAMES:%=HEX/%.hex)
BIN_OBJS = $(SRCS_BASENAMES:%=BIN/%.bin)
VMEM_OBJS = $(SRCS_BASENAMES:%=VMEM/%.vmem)
#ELF_CHECK_OBJS = $(shell ls ELF/*.elf)
#------------------------------------------------------------------------------------------------
# action of gcc
gcc :gcc_obj_dir $(ELF_OBJS) $(BIN_OBJS) $(VMEM_OBJS) $(HEX_OBJS)
gcc_obj_dir :
$(Q)mkdir -p ELF HEX BIN VMEM LOG
.depend: ${SRCS_FILES}
$(CC) -M $(CFLAGS) $(SRCS_FILES) > $@
sinclude .depend
include $(SW_ROOT)/Makefile.inc # gcc tools
#gcc_check :
# @for case in $(ELF_CHECK_OBJS); do \
# $(SIM) $(SIM_FLAGS) ./$$case & (sleep 1; killall -q $(SIM)) || echo; \
# done
clean :
$(Q)rm -rf *.o ELF HEX BIN VMEM* LOG .depend
gcc_clear :
$(Q)rm -rf *.o
1. 什么是make
可执行文件由目标文件链接而成,目标文件由源文件编译而成。大型工程中往往有多个可执行文件,更有大量的源文件,在文件更新后影响了哪些依赖的目标?make
只是一个解释器,或者理解成一个程序,它根据按照固定语法组成的Makefile
文件,翻译文件依赖关系、鉴别更新时间、一键化生成最新的目标/目标集合。
1.1 使用手册
新手看概述和每章的前面几小结,有个大致的了解,实际遇到问题再查手册、或者网上搜。
熟悉其他版本make的老鸟,看看14、15章的新特性。
走马观花看个总结的人,看看9.7、4.8。
1.2 找到bug?
make --help
看看命令怎么用,确有bug的联系GNU。
2. Makefiles简介
make命令执行时,需要一个 Makefile 文件,以告诉make
命令需要怎么样的去编译和链接程序。
首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
————————————————
2.1 Makefile的规则
一份makefile中包含许多“rules",除此以外还可以包含其他文本,“rules"的一般格式为:
target ... : prerequisites ...
recipe
...
其中target
可以是可执行文件名、目标名,或者标签(例如clean)。标签用于标识下面的命令想要做的动作集合。
prerequisites
是创建target
的输入,或者说依赖。
recipe
字面意是“秘方、菜谱”,是make要做的动作,可以是多条命令,命令可以在一行也可以多行,但是必须以Tab键开头。
如果 target
还没生成过,或者任意一个prerequisite
的更新时间更近,则会根据recipe
重新生成target
。
在source insight中可以设置makefile文件的Tab键可视化,便于与空格区别。
2.2 一个简单的Makefile例子
官方手册中给出一个例子,最终可执行文件是edit,工程包括3个头文件、8个源文件。直白的依赖关系和生成规则如下:
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
其中,\
换行,下一行从哪开始都行,make
实际上将他们解释为同一行;
所有recipe都是Tab键开头;
如上:执行make
生成edit文件,执行make clean
删除8个目标文件和一个可执行文件,清理现场。
(如果makefile文件有多个target,你在执行make
时又不指定target,则默认生成第一个target)
2.3 make处理Makefile的过程
缺省地,make从第一个target(不以.
开头的target)开始,作为默认目的地(default goal)。
本例中, make
执行时从第一个target也就是edit这一行开始,发现没有edit,则找依赖是否满足。发现依赖也还不存在,则根据每个依赖的依赖生成edit的依赖,再生成edit。
2.4 使用变量简化makefile文件
下列语句将8个目标文件重复写了两遍,又浪费时间又容易出错。
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
怎么定义变量、使用变量呢?如果都是字符,make解释器怎么认出来谁是变量,谁是文件名、命令、参数呢?
在make的世界里,define变量用字符,使用变量用$取值。改写后如下:
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
clean :
rm edit $(objects)
2.5 让make自行推导recipes
GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的菜谱,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。
只要make看到一个[.o]文件,它就会自动的把对应[.c]文件加在依赖关系中,并且根据[.c]生成[.o]的菜谱:cc -c whatever.c -o whatever.o
也会被推导出来。根据make已经学会的种种隐含规则,于是,我们的makefile再也不用写得这么复杂。改写后如下:
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
这里干嘛有个.PHONY
?它告诉make,这个clean是个标签,不是一个实实在在的文件。这有什么用?想象一下,假设当前文件恰好有个文件,名字就叫clean,就是这么不巧,那make发现:目标clean文件已经存在,而且它也没有依赖,所以也不会需要被更新,那么就永远不会执行下面的命令。这真是讨厌的事,所以,用.PHONY
来"欺骗"make解释器,强迫它乖乖听话做clean
的动作。
2.6 另类风格的Makefile
上面的写法也挺麻烦的,make也支持根据prerequisite对target重组,但是这种风格不容易理解,挺另类的,一般人玩不转,个人觉得没必要这样秀技。
objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h
.PHONY : clean
clean :
rm edit $(objects)
2.7 清空目标文件的规则
比如上例中的标签clean,已经很直白了。注意,不要把clean写在最前面,不然它就成了default goal了。
另外,在rm命令前面加了一个减号-
的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。
.PHONY : clean
clean :
-rm edit $(objects)
总结
上面就是一个makefile的概貌,也是makefile的基础,下面还有很多makefile的相关细节,准备好了吗?不看细节,许多makefile文件你还是看不懂的,这才刚开了一个头。