ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Makefile笔记 韦东山通用Makefile解析

2022-08-28 00:04:34  阅读:231  来源: 互联网

标签:文件 obj 东山 子目录 Makefile build 解析 subdir


目录

Makefile基础

Makefile规则与示例

简单的Makefile文件

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

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

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

2个重要的函数

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

例如:

objs := a.o b.o
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,简单,低效:

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

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

test: main.o sub.o
    gcc -o test main.o sub.o

main.o: main.c
    gcc -c -o main.o main.c

sub.o: sub.c
    gcc -c -o sub.o sub.c

clean:
    rm *.o test -f

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

test : main.o sub.o
    gcc -o test main.o sub.o

%.o : %.c
    gcc -c -o $@ $<

clean:
    rm *.o test -f

注:Makefile变量(特殊变量)
\(* 不包含扩展名的文件名; \)+ 所有的依赖文件,以空格分开,以出现的先后顺序,可能包含重复的依赖文件;
\(< 第一个依赖文件的名称; \)? 所有时间戳比目标文件晚的依赖文件,并以空格分开;
\(@ 目标文件的完整名称; \)^ 所有不重复的依赖文件,以空格分开;
$% 如果目标是归档成员,则该变量表示目标的归档成员名称;

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

test : main.o sub.o
    gcc -o test main.o sub.o

%.o : %.c
    gcc -c -o $@ $<

sub.o : sub.h

clean:
    rm *.o test -f

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

objs := main.o sub.o

test : $(objs)
    gcc -o test $^

# 需要判断是否存在依赖文件
# .main.o.d .sub.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))

# 把依赖文件包含进来
ifneq ($(dep_files),)
    include $(dep_files)
endif

%.o : %.c
    # 生成依赖文件$@.d
    gcc -Wp,-MD,.$@.d -c -o $@ $<

clean:
    rm *.o test -f

distclean:
    rm $(dep_files) *.o test -f

注:
ifneq(ARG1,ARG2) 判断参数是否不相等;
include 类似于C语言的#include,将内容原封不动包含进来;
GCC命令加"-Wp,-MD,\(@.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. 立即变量、延时变量
变量定义的语法形式:

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

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

A = $@ # 目标文件完整名称

test: # 此时才定义目标文件名称
    @echo $A

上面变量A在执行时才确定,值为test,是延时变量。
如果用"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个目标完整地放在文件前面,此时可以做文件的前面直接放置目标,在后面再完善它的依赖和命令。
例如:

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

F. 假想目标(伪目标)

如果Makefile中有这样的目标:

clean:
    rm -f $(shell find -name "*.o")
    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所描述的形式

例如:

objs := a.o b.o
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(过滤)出来以留下来。

例如:

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

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

例如:

obj-y := a.o b.o c/ d/
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"中可以使用通配符。

例如:

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

设计思想

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

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

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

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

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

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

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

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

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

通用Makefile源码解析

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

目录结构

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

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

项目目录结构:

$ tree
.
├── bin
│   └── led.sh
├── business
│   ├── main.c
│   └── Makefile
├── config
│   ├── config.c
│   └── Makefile
├── display
│   ├── disp_manager.c
│   ├── framebuffer.c
│   └── Makefile
├── font
│   ├── font_manager.c
│   ├── freetype.c
│   └── Makefile
├── include
│   ├── common.h
│   ├── config.h
│   ├── disp_manager.h
...
├── Makefile
├── Makefile.build
├── page
│   ├── main_page.c
│   ├── Makefile
│   └── page_manager.c
├── ui
│   ├── button.c
│   └── Makefile
└── unittest
    ├── client.c
    ├── disp_test.c
...

通用Makefile源码

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

  1. 提供项目make命令执行入口,提供所有编译的目标;
  2. 定义全局变量、项目编译选项、链接选项;
  3. 通过obj-y指定要搜索的子目录;
  4. 切换目录,递归执行make命令,并执行根目录下的Makefile.build文件;
# 根目录下的Makefile

# 延时变量, 只有第一次定义赋值才成功.而该变量在/etc/profile中. 已定义为arm-linux-gnueabihf-
CROSS_COMPILE ?= 
# 定义延时变量
# e.g. as = arm-linux-gnueabihf-as
AS		= $(CROSS_COMPILE)as
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm

STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump

# export全局变量, 可供其他Makefile使用
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

# 定义编译选项
CFLAGS := -Wall -O2 -g # 立即变量
# -I 指定头文件目录
# $(shell pwd) 将shell命令pwd输出(即当前目录)作为结果
# 该句含义是为编译器指定头文件目录为当前目录(执行make命令的目录)下的include目录
CFLAGS += -I $(shell pwd)/include # 追加赋值

# 定义链接选项
LDFLAGS := -lts -lpthread -lfreetype -lm

# export 全局变量
export CFLAGS LDFLAGS

# 当前目录作为顶层目录TOPDIR
TOPDIR := $(shell pwd)
export TOPDIR

# 定义立即变量, 目标名称, 也是最终生成的二进制目标文件名称
TARGET := test

# 定义变量记录要搜索的子目录(子目录必须包含一个makefile文件)
# 注意: 由于一个目标只能包含一个main函数,因此unittest目录和business目录,只能加入一个
obj-y += display/
# obj-y += unittest/
obj-y += input/
obj-y += font/
obj-y += ui/
obj-y += page/
obj-y += config/
obj-y += business/

# 第一个目标
all : start_recursive_build $(TARGET)
	@echo $(TARGET) has been built!

start_recursive_build:
# 切换到目录 $(TOPDIR), 找Makefile.build文件, 并执行make命令
# 我们查看Makefile.build文件
	@echo start_recursive_build
	@echo obj-y = $(obj-y)
	make -C ./ -f $(TOPDIR)/Makefile.build

# 依赖built-in.o 由Makefile.build生成
$(TARGET) : built-in.o
	$(CC) -o $(TARGET) built-in.o $(LDFLAGS)

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

distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)
	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. 设置伪目标;
# Makefile.build
# 立即变量, 用于记录伪目标
PHONY := __build
# 先声明目标, 等到文件后面完善
__build:

# 定义立即变量, 值为空
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=

# include当前目录下的Makefile, 注意执行命令时, 是会通过make -C命令切换工作目录的
# 也就是说, 不同make命令执行路径下, Makefile指的是不同的文件
include Makefile

# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d

# 定义立即变量
# $(filter %/, $(obj-y)) 从变量obj-y中过滤出以"/"结尾的目录名
# $(patsubst %/,%,$(filter %/, $(obj-y))) 去掉obj-y中以"/"结尾的目录名中的"/"
__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))

# 追加赋值, subdir-y表示要子目录
subdir-y	+= $(__subdir-y)

# c/built-in.o d/built-in.o

# foreach(var,list,text), 意为foreach var in list, change it to text
# 将子目录列表subdir-y中, 每一项(每个文件名)f, 都修改为f/built-in.o
# 也就是说, 每个子目录, 都会对应生成一个名为 "子目录名/built-in.o"的文件 (.o文件是链接文件)
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)

# a.o b.o

# 定义立即变量, 从obj-y中过滤掉目录名(名称以"/"结尾), 只剩下普通文件(.o文件)
cur_objs := $(filter-out %/, $(obj-y))
# 将cur_objs中的每个文件名f, 替换为f.d, 即加上".d"后缀, 代表该源码文件的依赖文件
dep_files := $(foreach f,$(cur_objs),.$(f).d)
# 如果依赖文件存在, 就列出来重新赋值给dep_files
dep_files := $(wildcard $(dep_files))

# 如果依赖文件列表不为空, 就直接包含(include)依赖文件列表
ifneq ($(dep_files),)
  include $(dep_files)
endif

# 每个子目录名(不含"/")追加到伪目标
PHONY += $(subdir-y)

# 定义规则 (目标 : 依赖)
__build : $(subdir-y) built-in.o

# 以子目录每一项为目标, 而每一项都是一个目录名
$(subdir-y):
	@echo subdir-y = $@
# 进入到每个子目录($@代表目标名称), 查找并执行顶层目录的Makefile.build
	make -C $@ -f $(TOPDIR)/Makefile.build

# 定义built-in.o依赖规则
# cur_objs 从obj-y过滤出的普通文件(.o文件)
# subdir_objs 子目录下的built-in.o
built-in.o : $(cur_objs) $(subdir_objs)
# LD 代表交叉编译器的链接器; -r 选项代表可重定位的输出, 一个输出文件可再次作为ld输入
# -o 设置输出文件; $@ 目标名称; $^ 所有不重复的依赖文件
	$(LD) -r -o $@ $^

# 定义延时变量, 单个目标的依赖文件: .目标名称.d
dep_file = .$@.d

# 模式规则, 所有没有显式规则的%.o目标, 会匹配该隐含规则
# 实际不一定会调用
%.o : %.c
# 生成依赖文件dep_file (即.$@.d), 后面Makefile文件会用
	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<

# 定义伪目标
.PHONY : $(PHONY)

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

EXTRA_CFLAGS :=
CFLAGS_file.o := 

obj-y += font.o

源码参见:electronic_test_tools | gitee


参考

韦东山《嵌入式Linux应用开发完全手册V4.0-IMX6ULLa开发板》

标签:文件,obj,东山,子目录,Makefile,build,解析,subdir
来源: https://www.cnblogs.com/fortunely/p/16631713.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有