makefile通用写法

来源:互联网 发布:android数据储存教程 编辑:程序博客网 时间:2024/05/17 05:57

一.makefile的作用

          Makefile是用于自动编译和链接的,一个工程有很多文件组成,每一个文件的改变都会导致工程的重新链接,但是不是所有的文件都需要重新编译,Makefile中记录有文件的信 息,在make时会决定在链接的时候需要重新编译哪些文件。Makefile的宗旨就是:让编译器知道要编译一个文件需要依赖其他的哪些文件。当那些依赖文件有了改变,编译器会自动发现最终的生成文件已经过时,而应该重新编译相应的模块。 makefile带来的好处就是—"自动化编译",一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。默认的情况下,make命令会在当前目录下按顺序找寻文件名为"GNUmakefile"、"makefile"、"Makefile"的文件,找到了解释这个文件。当然也可以使用make -f DIR/makefile 来指定用于makefile文件

二.makefile的几点基础知识

      1.赋值符号的区别
         =  是最基本的赋值,用到了之后才赋值,不能在变量后追加内容
     := 是覆盖之前的值,立即赋值,可以在变量后追加内容
    ?= 是如果没有被赋值过就赋予等号后面的值
    += 是添加等号后面的值

2.自动变量

    $<    第一个依赖文件的名称
    $?    所有的依赖文件,以空格分开,这些依赖文件的修改日期比目    标的创建日期晚
    $@  目标的完整名称
    $^    所有的依赖文件,以空格分开,不包含重复的依赖文件

3.几个常用的函数

1. $(patsubst %.c,%.o,x.c.c bar.c)
把字串“x.c.cbar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.obar.o”
2.$(filter <pattern...>,<text> )
以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。 
3.$(filter-out <pattern...>,<text> )
4.$(foreach <var>,<list>,<text> )
把参数<list>中的单词逐一取出放到参数<var>所指定的变量中, 然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环这个过程。
5.shell函数,例如files := $(shell echo *.c)

三.通用Makefile的编译过程

       从顶层开始递归进入子目录,当进入到一个目录的最底层时,开始使用编译器编译,再将该层的所有.o文件打包成build-in.o,返回它的上一层目录再递归进入子目录,当编译完所有的子目录后,就开始编译顶层的.c文件,最后将顶层的.o文件和顶层每个子目录的build-in.o链接成我们的目标文件。

思维导图:


四、实战

假如有这样一个目录结构的工程

a

----d

----a.c

b

----b.c

c

----c.c

main.c



顶级Makefile:

[plain] view plain copy
  1. CROSS_COMPILE =  
  2. AS      = $(CROSS_COMPILE)as  
  3. LD      = $(CROSS_COMPILE)ld  
  4. CC      = $(CROSS_COMPILE)gcc  
  5. CPP     = $(CC) -E  
  6. AR      = $(CROSS_COMPILE)ar  
  7. NM      = $(CROSS_COMPILE)nm  
  8.   
  9. STRIP       = $(CROSS_COMPILE)strip  
  10. OBJCOPY     = $(CROSS_COMPILE)objcopy  
  11. OBJDUMP     = $(CROSS_COMPILE)objdump  
  12.   
  13. export AS LD CC CPP AR NM  
  14. export STRIP OBJCOPY OBJDUMP  
  15.   
  16. CFLAGS := -Wall -O2 -g  
  17.   
  18. LDFLAGS :=   
  19.   
  20. export CFLAGS LDFLAGS  
  21.   
  22. TOPDIR := $(shell pwd)  
  23. export TOPDIR  
  24.   
  25. TARGET := test  
  26.   
  27.   
  28.  obj-y += main.o  
  29.  obj-y += a/  
  30.  obj-y += b/  
  31.  obj-y += c/  
  32.   
  33. all :   
  34.     make -C ./ -f $(TOPDIR)/Makefile.build  
  35.     $(CC) $(LDFLAGS) -o $(TARGET) built-in.o  
  36.   
  37.   
  38. clean:  
  39.     rm -f $(shell find -name "*.o")  
  40.     rm -f $(shell find -name "*.d")  
  41.     rm -f $(TARGET)  
  42. .PHONY:all clean   


这里前面就是定义一些变量,all是工程默认的目标,它是一个伪目标,进入伪目标后执行的命令就是执行Makefile.build,这里就会引起递归调用,在Makefile.build中又会调用Makefile.build.一直到Makefile.build返回以后,会使用Makefile.build最后生成的built-in.o生成最终的目标文件。

Makefile.build:

[plain] view plain copy
  1. PHONY := build  
  2. build :  
  3. obj-y :=  
  4. subdir-y :=  
  5. include Makefile  
  6. __subdir-y  := $(patsubst %/,%,$(filter %/, $(obj-y)))  
  7. subdir-y    += $(__subdir-y)  
  8. subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)  
  9. cur_objs := $(filter-out %/, $(obj-y))  
  10. dep_files := $(foreach f,$(cur_objs),.$(f).d)  
  11. #dep_files := $(wildcard $(dep_files))  
  12. #ifneq ($(dep_files),)  
  13. #  include $(dep_files)  
  14. #endif  
  15. PHONY += $(subdir-y)  
  16. build : $(subdir-y) built-in.o  
  17. $(subdir-y):  
  18.     make -C $@ -f $(TOPDIR)/Makefile.build  
  19. built-in.o : $(cur_objs) $(subdir_objs)  
  20.     $(LD) -r -o $@ $^  
  21. dep_file = .$@.d  
  22. %.o : %.c  
  23.     $(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<  
  24. .PHONY : $(PHONY)  

Makefile.build会加载顶级目录下的Makefile,在顶级目录下的Makefile中已经给obj-y添加了一些条目,subdir-y就是获取子目录,然后对每一个子目录又调用Makefile.build。

当递归到没有子目录的目录时,Makefile.build开始返回,并使用$(CC)对源文件进行编译,将所有的.c生成.o文件,并将当前目录下的.o和子目录下的build-in.o连接成当前目录下的build-in.o,并回返上级目录,一次往复,最终返回到顶级目录,在顶级目录下生成build-in.o。返回到顶级目录后,Makefile.build返回到了Makefile中,Makefile在使用build-in.o生成指定的目标文件。至此,递归结束,整个系统编译完成。

这里的-MD选项和.$@.d的作用是 生成头文件的依赖关系时,把依赖关系写入到这个文件中去。

要想彻底看懂的话,建议认真推倒下每一个变量的值,比对目录与文件,会更好理清编译的过程。

子目录下的Makefile:

[plain] view plain copy
  1. obj-y += a.o  
  2. obj-y += d/  

子目录的Makefile就是添加该目录中的文件与子目录,子目录以/结尾。

验证:

执行make

打印如下:

[plain] view plain copy
  1. make -C ./ -f /home/dragon/liujinwei/uni-makefile/Makefile.build  
  2. make[1]: Entering directory `/home/dragon/liujinwei/uni-makefile'  
  3. make -C a -f /home/dragon/liujinwei/uni-makefile/Makefile.build  
  4. make[2]: Entering directory `/home/dragon/liujinwei/uni-makefile/a'  
  5. make -C d -f /home/dragon/liujinwei/uni-makefile/Makefile.build  
  6. make[3]: Entering directory `/home/dragon/liujinwei/uni-makefile/a/d'  
  7. gcc -Wall -O2 -g -Wp,-MD,.d.o.d -c -o d.o d.c  
  8. ld -r -o built-in.o d.o  
  9. make[3]: Leaving directory `/home/dragon/liujinwei/uni-makefile/a/d'  
  10. gcc -Wall -O2 -g -Wp,-MD,.a.o.d -c -o a.o a.c  
  11. ld -r -o built-in.o a.o d/built-in.o  
  12. make[2]: Leaving directory `/home/dragon/liujinwei/uni-makefile/a'  
  13. make -C b -f /home/dragon/liujinwei/uni-makefile/Makefile.build  
  14. make[2]: Entering directory `/home/dragon/liujinwei/uni-makefile/b'  
  15. gcc -Wall -O2 -g -Wp,-MD,.b.o.d -c -o b.o b.c  
  16. ld -r -o built-in.o b.o  
  17. make[2]: Leaving directory `/home/dragon/liujinwei/uni-makefile/b'  
  18. make -C c -f /home/dragon/liujinwei/uni-makefile/Makefile.build  
  19. make[2]: Entering directory `/home/dragon/liujinwei/uni-makefile/c'  
  20. gcc -Wall -O2 -g -Wp,-MD,.c.o.d -c -o c.o c.c  
  21. ld -r -o built-in.o c.o  
  22. make[2]: Leaving directory `/home/dragon/liujinwei/uni-makefile/c'  
  23. gcc -Wall -O2 -g -Wp,-MD,.main.o.d -c -o main.o main.c  
  24. ld -r -o built-in.o main.o a/built-in.o b/built-in.o c/built-in.o  
  25. make[1]: Leaving directory `/home/dragon/liujinwei/uni-makefile'  
  26. gcc  -o test built-in.o  
  27.   
  28. #### make completed successfully  ####  


make之后的目录:

顶级:


a目录:


d目录:




makefile:

                                                                               #定义各个编译用到的工具
CROSS_COMPILE =                                         #交叉编译器 arm-none-Linux-gnueabi- 
AS = $(CROSS_COMPILE)as               #汇编器
LD = $(CROSS_COMPILE)ld                #连接器
CC = $(CROSS_COMPILE)gc c            #编译器
CPP = $(CC) -E                                           #预处理
AR = $(CROSS_COMPILE)ar               #归档文件
NM = $(CROSS_COMPILE)nm              #列出object文件中的符号

STRIP = $(CROSS_COMPILE)strip          #丢弃目标文件中的符号
OBJCOPY = $(CROSS_COMPILE)objcopy      #转换目标文件格式
OBJDUMP = $(CROSS_COMPILE)objdump      #反汇编

export AS LD CC CPP AR NM                #将变量导出供下一个makefile使用
export STRIP OBJCOPY OBJDUMP             

CFLAGS := -Wall -Werror -O2 -g           #CFLAGS 指定编译参数
CFLAGS += -I $(shell pwd)/include
LDFLAGS := -L/yanlib/jrtplib/lib -L/yanlib/x26420110920/lib/
LIBS := -lx264 -ljrtp -ljthread
export CFLAGS LDFLAGS LIBS
TOPDIR := $(shell pwd)
export TOPDIR
TARGET := firsttest
obj-y += main.o          #obj-y  main.o 编译链接进项目
obj-y += compress/
obj-y += print/
obj-y += transmission/
.PHONY : all clean distclean
all :                                        
make -C ./ -f $(TOPDIR)/Makefile.build      #
$(CC) $(LDFLAGS) -o $(TARGET) built-in.o    #

  # make的递归执行,make的“-C”选项,是首先进入子目录而后再执行make。
  #当需要将一个普通命名的文件作为makefile文件时,需要使用make的“-f”、“--file”或者“--makefile”选项来指定。
clean:
rm -f $(shell find -name "*.o")  #删除命令,默认是 rm -f
rm -f $(TARGET)
distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)
#执行make时,目标“all”被作为终极目标。
#伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它 是否要执行。
#我们只有通过显示地指明这个“目标”才能让其生效。
#为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,
#不管是否有这个文件,这个目标就是“伪目标”。 


这个比较难一些

makefile.build:

PHONY := __build                #定义一个PHONY变量
__build:                        #开头说明__build伪目标,使其成为Makefile.build的第一个目标
obj-y :=
subdir-y :=
include Makefile
#前面我们包含了Makefile,在Makefile中我们定义了obj-y += xxx(main.o compress/  ...... )
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d
__subdir-y:= $(patsubst %/,%,$(filter %/, $(obj-y)))  #筛选出当前目录的目标变量中的子目录,并且去掉/,在这里我们获得了子目录的名字
subdir-y+= $(__subdir-y)                              #文件子目录compress/ print/ 等等。
#函数名称:过滤函数—filter。
#sources := foo.c bar.c baz.s ugh.h
# $(filter %.c %.s,$(sources))
#数返回值为“foo.c bar.c baz.s”
#函数名称:模式替换函数—patsubst。
#例如:$(patsubst %.c,%.o,x.c.c bar.c)
#把字串“x.c.c bar.c”中以.c结尾的单词替换成以.o结尾的字符。函数的返回结果是“x.c.o bar.o”

# c/built-in.o d/built-in.o                              #built-in.o 打包
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)  #对于subdir-y里面的每一个值(目录),增加一个相应的目录/built-in.o的变量值
# foreach 函数
#$(foreach VAR,LIST,TEXT)
#执行时把“LIST”中使用空格分割的单词依次取出赋值给变量“VAR”,然后执行“TEXT”表达式
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))                  # 得到obj-y中的.o文件
dep_files := $(foreach f,$(cur_objs),.$(f).d)           # .$(f).d 第一个.表示隐藏的 后面的.表示后缀
dep_files := $(wildcard $(dep_files))
#函数wildcard
#在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。
ifneq ($(dep_files),)
  include $(dep_files)
endif
PHONY += $(subdir-y)
__build : $(subdir-y) built-in.o                       # 第一个规则
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build               # 依次进入该子目录变量里面存储的值,使用的Makefile.build进行编译
built-in.o : $(cur_objs) $(subdir_objs)                # 第一个规则的第二个依赖规则
$(LD) -r -o $@ $^                                    # 该规则的命令:将该目录下的.o和$(subdir_obj)打包成built-in.o文件
dep_file = .$@.d
%.o : %.c
$(CC) $(CFLAGS) -Wp,-MD,$(dep_file) -c -o $@ $<      # 用于将目录下所有的.c文件编译成.o文件 ,
                                                                                                  # -wp,-MD 会生成相应的依赖文件,
                                                                                                  #其中自动化变量“$<”代表规则的依赖,“$@”代表规则的目标
.PHONY : $(PHONY)                                                                    # 将PHONY声明为伪目标
原创粉丝点击