当前位置:   article > 正文

通用的Makefile源码解析_cc = $()gcc

cc = $()gcc

Makefile基础

Makefile规则与示例

简单的Makefile文件

一个简单的Makefile文件包含的一系列“规则”:

  1. 目标(target) ... : 依赖(prerequiries) ...
  2. <tab>命令(command)

如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目标文件”。
命令被执行的2个条件:依赖文件比目标文件新,或目标文件还没生成。

小生给大家推荐一个Linux内核学习交流地:869634926整理了一些个人觉得比较好的Linux内核学习书籍、视频资料分享给大家,有需要的可以自行添加哦!

2个重要的函数

A. $(foreach var,list,text)
含义是for each var in list, change it to text。
对于list中每个元素,取出来赋值给var,然后把var用text替换。

例如:

  1. objs := a.o b.o
  2. dep_files := $(foreach f, $(objs), .$(f).d) // 最终dep_files := .a.o.d .b.o.d

B. $(wildcard pattern)
pattern 所列出的文件是否存在,把存在的文件都列出来。

例如:

src_files := $(wildcard *.c) // src_files中列出了当前目录下的所有.c文件

一步步完善Makefile

第1个Makefile,简单,低效:

  1. test: main.c sub.c sub.h
  2. gcc -o test main.c sub.c

第2个Makefile,效率高,但相似规则太多太啰嗦,不支持检测头文件:

  1. test: main.o sub.o
  2. gcc -o test main.o sub.o
  3. main.o: main.c
  4. gcc -c -o main.o main.c
  5. sub.o: sub.c
  6. gcc -c -o sub.o sub.c
  7. clean:
  8. rm *.o test -f

第3个Makefile,效率高,精炼,不支持检测头文件:

  1. test : main.o sub.o
  2. gcc -o test main.o sub.o
  3. %.o : %.c
  4. gcc -c -o $@ $<
  5. clean:
  6. rm *.o test -f

注:Makefile变量(特殊变量)
∗不包含扩展名的文件名;∗不包含扩展名的文件名;

∗不包含扩展名的文件名;∗不包含扩展名的文件名;

+ 所有的依赖文件,以空格分开,以出现的先后顺序,可能包含重复的依赖文件;

<第一个依赖文件的名称;<第一个依赖文件的名称;

<第一个依赖文件的名称;<第一个依赖文件的名称;

? 所有时间戳比目标文件晚的依赖文件,并以空格分开;

@目标文件的完整名称;@目标文件的完整名称;

@目标文件的完整名称;@目标文件的完整名称;

^ 所有不重复的依赖文件,以空格分开;

$% 如果目标是归档成员,则该变量表示目标的归档成员名称;

第4个Makefile,效率高,精炼,支持检测头文件,但需要手工添加头文件规则:

  1. test : main.o sub.o
  2. gcc -o test main.o sub.o
  3. %.o : %.c
  4. gcc -c -o $@ $<
  5. sub.o : sub.h
  6. clean:
  7. rm *.o test -f

第5个Makefile,效率高,精炼,支持自动检测头文件:

  1. objs := main.o sub.o
  2. test : $(objs)
  3. gcc -o test $^
  4. # 需要判断是否存在依赖文件
  5. # .main.o.d .sub.o.d
  6. dep_files := $(foreach f, $(objs), .$(f).d)
  7. dep_files := $(wildcard $(dep_files))
  8. # 把依赖文件包含进来
  9. ifneq ($(dep_files),)
  10. include $(dep_files)
  11. endif
  12. %.o : %.c
  13. # 生成依赖文件$@.d
  14. gcc -Wp,-MD,.$@.d -c -o $@ $<
  15. clean:
  16. rm *.o test -f
  17. distclean:
  18. rm $(dep_files) *.o test -f

注:
ifneq(ARG1,ARG2) 判断参数是否不相等;
include 类似于C语言的#include,将内容原封不动包含进来;
GCC命令加"-Wp,-MD,@.d"会自动生成依赖文件@.d"会自动生成依赖文件

@.d"会自动生成依赖文件@.d"会自动生成依赖文件

@.d;

通用Makefile

可用来编译应用程序,其特点:
1)支持多个目录、多层目录、多个文件;
2)支持给所有文件设置编译选项;
3)支持给某个目录设置编译选项;
4)支持给某个文件单独设置编译选项;
5)简单、易用;

零星知识点

A. make命令的使用
执行make命令时,会去当前目录下查找名为"Makefile"的文件,并根据它的指示执行操作,生成第一个目标。
可以使用"-f"选项指定要查找并执行的文件,而不再使用名为"Makefile"的文件,例如:

make -f Makefile.build

可以使用"-C"选项指定目录,切换到其他目录里去,例如:

make -C a/ -f Makefile.build

注:切换到目录"a/",指定查找文件Makefile.build

可以指定目标,不再默认生成第一个目标:

make -C a/ -f Makefile.build other_target

B. 立即变量、延时变量
变量定义的语法形式:

  1. A = xxx // 延时变量
  2. B ?= xxx // 延时变量,只有第一个定义时赋值才成功;如果曾定义过,此赋值无效
  3. C := xxx // 立即变量
  4. D += yyy // 如果D在前面是延时变量,那么现在还是延时变量;
  5. // 如果D在前面是立即变量,那么现在还是立即变量

延时变量的值,在使用时才展开、确定。例如:

  1. A = $@ # 目标文件完整名称
  2. test: # 此时才定义目标文件名称
  3. @echo $A

上面变量A在执行时才确定,值为test,是延时变量。
如果用"A := @",A是立即变量,而此时@",A是立即变量,而此时

@",A是立即变量,而此时@",A是立即变量,而此时

@为空,因此A值为空。

C. 变量的导出(export)
编译程序时,我们不断使用"make -C dir"切换到其他目录,执行其他目录的Makefile。如果想要某个变量值在所有目录都可见,需要将其export出来。
例如,"CC = $(CROSS_COMPIE)gcc",CC变量表示编译器,在整个makefile执行过程中都是一样的。定义它后,要用"export CC"将其导出。

D. Makefile中可以使用shell命令
例如:

TOPDIR := $(shell pwd)

立即变量TOPDIR等于shell命令pwd的执行结果,即当前目录(通常是执行make命令的目录)。

E. 在Makefile中怎么放置第1个目标
执行make命令时,如果不指定目标,那么它默认生成第1个目标。所以“第一个目标”位置很重要。有时不太方便将第1个目标完整地放在文件前面,此时可以做文件的前面直接放置目标,在后面再完善它的依赖和命令。
例如:

  1. first_target: // 这句话放前面
  2. ... // 其他代码,比如include其他文件后得到后面的xxx变量
  3. first_target: $(xxx) $(yyy) // 在文件的后面再来完善
  4. command

F. 假想目标(伪目标)

如果Makefile中有这样的目标:

  1. clean:
  2. rm -f $(shell find -name "*.o")
  3. rm -f $(TARGET)

如果当前目录下,恰好有名为"clean"的文件,那么执行"make clean"时就不会执行那些删除命令。

此时,我们需要把"clean"这个目标,设置为"假想目标",这样可以确保执行"make clean"时执行删除命令。

使用下面语句,将"clean"设置为假想目标:

.PHONY : clean

G. 常用的函数

1)foreach
用法:$(foreach var, list,text)
解释:for each var in list, change it to text
对于list中每个元素,取出来赋值给var,然后将var改为text所描述的形式

例如:

  1. objs := a.o b.o
  2. dep_files := $(foreach f, $(objs), .$(f).d) // 最终dep_files := .a.o.d .b.o.d

2)wildcard
用法:$(wildcard pattern)
解释:pattern所列出文件如果存在,就把存在的文件都列出来

例如:

src_files := $(wildcard *.c) // 最终src_files中列出了当前目录下所有.c文件

3) filter
用法:$(filter pattern...,text)
解释:把text中符合pattern格式的内容,filter(过滤)出来以留下来。

例如:

  1. obj-y := a.o b.o c/ d/
  2. DIR := $(filter %/, $(obj-y)) // 结果为:c/ d/

4)filter-out
用法:$(filter-out pattern..., text)
解释:把text中复合pattern格式的内容,filter-out(过滤)出来以丢弃。

例如:

  1. obj-y := a.o b.o c/ d/
  2. DIR := $(filter-out %/, $(obj-y)) // 结果为:a.o b.o

5)subst
用法:$(subst from,to,text)
解释:将text中的东西,从from替换为to

$(subst a,the,There is a big tree) // 结果为:There is the big tree

6)patsubst
用法:$(patsubst pattern, replacement, text)
解释:寻找"text"中符合格式"pattern"的字,用"replacement"替换它们。"pattern"和"replacement"中可以使用通配符。

例如:

  1. subdir-y := c/ d/
  2. subdir-y := $(patsubst %/, %, $(subdir-y)) // 结果为:c d

设计思想

A. 在Makefile文件中确定要编译的文件、目录,比如obj-y += main.oobj-y += a/"Makefile"文件总是被"Makefile.build"包含的。

B. 在Makefile.build中设置编译规则,有3条:
1)怎么编译子目录?进入子目录编译:

  1. $(subdir-y):
  2. make -C $@ -f $(TOPDIR)/Makefile.build

2)怎么编译带你过去目录中的文件?

  1. %.o : %.c
  2. $(CC) $(CFLAGS) $(EXTRA_CLAGS) $(FLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<

3)当前目录下的.o和子目录下的built-in.o要打包起来:

  1. built-in.o: $(cur_objs) $(subdir_objs)
  2. $(LD) -r -o $@ $^

C. 顶层Makefile中把顶层目录的built-in.o链接成APP

  1. $(TARGET) : built-in.o
  2. $(CC) $(LDFLAGS) -o $(TARGET) built-in.o

通用Makefile源码解析

韦东山通用Makefile,是韦东山老师(百问网)为嵌入式Linux应用项目编写的工程构建makefile。支持多目录、单目标的项目。

目录结构

根目录下,存在Makefile和Makefile.build两个文件。这两个文件非常重要,make命令能递归查找每个子目录,就是这2个Makefile文件的功劳。

在每个需要搜索源码的子目录下,都要添加一个子Makefile文件,便于递归搜索。

项目目录结构:

  1. $ tree
  2. .
  3. ├── bin
  4. │ └── led.sh
  5. ├── business
  6. │ ├── main.c
  7. │ └── Makefile
  8. ├── config
  9. │ ├── config.c
  10. │ └── Makefile
  11. ├── display
  12. │ ├── disp_manager.c
  13. │ ├── framebuffer.c
  14. │ └── Makefile
  15. ├── font
  16. │ ├── font_manager.c
  17. │ ├── freetype.c
  18. │ └── Makefile
  19. ├── include
  20. │ ├── common.h
  21. │ ├── config.h
  22. │ ├── disp_manager.h
  23. ...
  24. ├── Makefile
  25. ├── Makefile.build
  26. ├── page
  27. │ ├── main_page.c
  28. │ ├── Makefile
  29. │ └── page_manager.c
  30. ├── ui
  31. │ ├── button.c
  32. │ └── Makefile
  33. └── unittest
  34. ├── client.c
  35. ├── disp_test.c
  36. ...

通用Makefile源码

根目录下的Makefile,主要有这几点作用:

  1. 提供项目make命令执行入口,提供所有编译的目标;
  2. 定义全局变量、项目编译选项、链接选项;
  3. 通过obj-y指定要搜索的子目录;
  4. 切换目录,递归执行make命令,并执行根目录下的Makefile.build文件;
  1. # 根目录下的Makefile
  2. # 延时变量, 只有第一次定义赋值才成功.而该变量在/etc/profile中. 已定义为arm-linux-gnueabihf-
  3. CROSS_COMPILE ?=
  4. # 定义延时变量
  5. # e.g. as = arm-linux-gnueabihf-as
  6. AS = $(CROSS_COMPILE)as
  7. LD = $(CROSS_COMPILE)ld
  8. CC = $(CROSS_COMPILE)gcc
  9. CPP = $(CC) -E
  10. AR = $(CROSS_COMPILE)ar
  11. NM = $(CROSS_COMPILE)nm
  12. STRIP = $(CROSS_COMPILE)strip
  13. OBJCOPY = $(CROSS_COMPILE)objcopy
  14. OBJDUMP = $(CROSS_COMPILE)objdump
  15. # export全局变量, 可供其他Makefile使用
  16. export AS LD CC CPP AR NM
  17. export STRIP OBJCOPY OBJDUMP
  18. # 定义编译选项
  19. CFLAGS := -Wall -O2 -g # 立即变量
  20. # -I 指定头文件目录
  21. # $(shell pwd) 将shell命令pwd输出(即当前目录)作为结果
  22. # 该句含义是为编译器指定头文件目录为当前目录(执行make命令的目录)下的include目录
  23. CFLAGS += -I $(shell pwd)/include # 追加赋值
  24. # 定义链接选项
  25. LDFLAGS := -lts -lpthread -lfreetype -lm
  26. # export 全局变量
  27. export CFLAGS LDFLAGS
  28. # 当前目录作为顶层目录TOPDIR
  29. TOPDIR := $(shell pwd)
  30. export TOPDIR
  31. # 定义立即变量, 目标名称, 也是最终生成的二进制目标文件名称
  32. TARGET := test
  33. # 定义变量记录要搜索的子目录(子目录必须包含一个makefile文件)
  34. # 注意: 由于一个目标只能包含一个main函数,因此unittest目录和business目录,只能加入一个
  35. obj-y += display/
  36. # obj-y += unittest/
  37. obj-y += input/
  38. obj-y += font/
  39. obj-y += ui/
  40. obj-y += page/
  41. obj-y += config/
  42. obj-y += business/
  43. # 第一个目标
  44. all : start_recursive_build $(TARGET)
  45. @echo $(TARGET) has been built!
  46. start_recursive_build:
  47. # 切换到目录 $(TOPDIR), 找Makefile.build文件, 并执行make命令
  48. # 我们查看Makefile.build文件
  49. @echo start_recursive_build
  50. @echo obj-y = $(obj-y)
  51. make -C ./ -f $(TOPDIR)/Makefile.build
  52. # 依赖built-in.o 由Makefile.build生成
  53. $(TARGET) : built-in.o
  54. $(CC) -o $(TARGET) built-in.o $(LDFLAGS)
  55. clean:
  56. rm -f $(shell find -name "*.o")
  57. rm -f $(TARGET)
  58. rm -f test
  59. distclean:
  60. rm -f $(shell find -name "*.o")
  61. rm -f $(shell find -name "*.d")
  62. rm -f $(TARGET)
  63. rm -f test

根目录Makefile.build,主要工作是:

  1. 包含(include)每个子目录下的Makefile文件;
  2. 取出每个子Makefile中定义的.o文件,再根据%.o:%.c 模式规则,自动寻找.c源码文件;
  3. 取出每个子Makefile中定义的子目录,再用make -C命令切换到子目录,从而实现递归目录编译;
  4. 为每个.o文件,生成依赖文件(.d),并包含进Makefile.build;
  5. 为每个子目录(含有Makefile)生成一个built-in.o文件,便于根目录下的Makefile文件编译、链接;
  6. 设置伪目标;
  1. # Makefile.build
  2. # 立即变量, 用于记录伪目标
  3. PHONY := __build
  4. # 先声明目标, 等到文件后面完善
  5. __build:
  6. # 定义立即变量, 值为空
  7. obj-y :=
  8. subdir-y :=
  9. EXTRA_CFLAGS :=
  10. # include当前目录下的Makefile, 注意执行命令时, 是会通过make -C命令切换工作目录的
  11. # 也就是说, 不同make命令执行路径下, Makefile指的是不同的文件
  12. include Makefile
  13. # obj-y := a.o b.o c/ d/
  14. # $(filter %/, $(obj-y)) : c/ d/
  15. # __subdir-y : c d
  16. # subdir-y : c d
  17. # 定义立即变量
  18. # $(filter %/, $(obj-y)) 从变量obj-y中过滤出以"/"结尾的目录名
  19. # $(patsubst %/,%,$(filter %/, $(obj-y))) 去掉obj-y中以"/"结尾的目录名中的"/"
  20. __subdir-y := $(patsubst %/,%,$(filter %/, $(obj-y)))
  21. # 追加赋值, subdir-y表示要子目录
  22. subdir-y += $(__subdir-y)
  23. # c/built-in.o d/built-in.o
  24. # foreach(var,list,text), 意为foreach var in list, change it to text
  25. # 将子目录列表subdir-y中, 每一项(每个文件名)f, 都修改为f/built-in.o
  26. # 也就是说, 每个子目录, 都会对应生成一个名为 "子目录名/built-in.o"的文件 (.o文件是链接文件)
  27. subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)
  28. # a.o b.o
  29. # 定义立即变量, 从obj-y中过滤掉目录名(名称以"/"结尾), 只剩下普通文件(.o文件)
  30. cur_objs := $(filter-out %/, $(obj-y))
  31. # 将cur_objs中的每个文件名f, 替换为f.d, 即加上".d"后缀, 代表该源码文件的依赖文件
  32. dep_files := $(foreach f,$(cur_objs),.$(f).d)
  33. # 如果依赖文件存在, 就列出来重新赋值给dep_files
  34. dep_files := $(wildcard $(dep_files))
  35. # 如果依赖文件列表不为空, 就直接包含(include)依赖文件列表
  36. ifneq ($(dep_files),)
  37. include $(dep_files)
  38. endif
  39. # 每个子目录名(不含"/")追加到伪目标
  40. PHONY += $(subdir-y)
  41. # 定义规则 (目标 : 依赖)
  42. __build : $(subdir-y) built-in.o
  43. # 以子目录每一项为目标, 而每一项都是一个目录名
  44. $(subdir-y):
  45. @echo subdir-y = $@
  46. # 进入到每个子目录($@代表目标名称), 查找并执行顶层目录的Makefile.build
  47. make -C $@ -f $(TOPDIR)/Makefile.build
  48. # 定义built-in.o依赖规则
  49. # cur_objs 从obj-y过滤出的普通文件(.o文件)
  50. # subdir_objs 子目录下的built-in.o
  51. built-in.o : $(cur_objs) $(subdir_objs)
  52. # LD 代表交叉编译器的链接器; -r 选项代表可重定位的输出, 一个输出文件可再次作为ld输入
  53. # -o 设置输出文件; $@ 目标名称; $^ 所有不重复的依赖文件
  54. $(LD) -r -o $@ $^
  55. # 定义延时变量, 单个目标的依赖文件: .目标名称.d
  56. dep_file = .$@.d
  57. # 模式规则, 所有没有显式规则的%.o目标, 会匹配该隐含规则
  58. # 实际不一定会调用
  59. %.o : %.c
  60. # 生成依赖文件dep_file (即.$@.d), 后面Makefile文件会用
  61. $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
  62. # 定义伪目标
  63. .PHONY : $(PHONY)

子目录makefile。比如font/ 子目录,如果根目录下的Makefile添加将"font/"添加进了obj-y,那么子目录必须包含一个子makefile。

  1. EXTRA_CFLAGS :=
  2. CFLAGS_file.o :=
  3. obj-y += font.o

源码参见:electronic_test_tools | gitee

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/623905
推荐阅读
相关标签
  

闽ICP备14008679号