Melis学习笔记(二):顶层Makefile分析

本文详细分析了Melis系统顶层Makefile的结构和功能,包括版本号、MAKEFLAGS变量、命令输出控制、编译结果输出目录设置、模块编译、系统架构获取、目标架构和编译器配置等。还介绍了Makefile中的赋值语句(=, :=, ?=, +=)以及$@、$^和$<的含义和用法。" 46413567,1435363,cocos2d-js 使用ttf字体全面指南,"['cocos2d', '游戏开发', 'JavaScript', '字体渲染', '资源管理']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考自:《uboot顶层Makefile》
    《uboot学习笔记2-顶层Makefile分析(一)》
    《Makefile中:=, =, ?=和+=的含义》
    《Makefile入门二、理解$@、$^和$<》

中文文档:《跟我一起写Makefile》

Melis使用的是kbuild编译系统,在编译可加载模块时,其makefile的风格和常用的编译C程序的makefile有所不同,尽管如此,makefile的作用总归是给编译器提供编译信息。

Makefile可以嵌套,也就是顶层 Makefile 可以调用子目录中的 Makefile 文件。 Makefile 嵌套在大项目中很常见,一般大项目里面所有的源代码都不会放到同一个目录中,各个功能模块的源代码都是分开的,各自存放在各自的目录中。每个功能模块目录下都有一个 Makefile,这个 Makefile 只处理本模块的编译链接工作,这样所有的编译链接工作就不用全部放到一个 Makefile 中,可以使得 Makefile 变得简洁明了。

该文对Melis系统顶层Makefile进行了详细介绍,并简要说明其子目录Makefile的编写规则。

一、顶层Makefile

因为Melis顶层Makefile有很多注释,所以截图分段来看。(备注:Melis顶层Makefile参考uboot源文件中的Makefile)

top Makefile 整体框架

当我们执行 make 编译的时候,我们将整个 top Makefile的执行分成以下的逻辑部分:

  1. 配置(make *config):对内核中所有模块进行配置,以确定哪些模块需要编译进内核,哪些编译成外部模块,哪些不进行处理,同时生成一些配置相关的文件供内核真正编译文件时调用。
  2. 内核编译(make):根据配置部分,真正的编译整个内核源码,其中包含递归地进入各级子目录进行编译。
  3. 通过命令行传入 O=DIR 进行编译 : 所有编译过程生成的中间文件以及目标文件放置在指定的目录,保持源代码的干净。
  4. 通过命令行传入多个编译目标,比如:make clean all,make config all,等等,一次指定多个目标。
  5. 通过命令行传入 M=DIR 进行编译:指定编译某个目录下的模块,并将其编译成外部模块。
  6. 指定一些完全独立的目标,与内核真实的编译过程并不强相关,比如:make clean(清除编译结果),make kernelrelease(获取编译后的发型版本号),make headers_(内核头文件相关)。

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

  • 显式规则。显式规则说明了如何生成一个或多个目标文件。这是由Makefile的书写者明显指出要生成的文件、文件的依赖文件和生成的命令。
  • 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写 Makefile,这是由make所支持的。
  • 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
  • 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
  • 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用 # 字符,这个就像C/C++中的 // 一样。如果你要在你的Makefile中使用 # 字符,可以用反斜杠进行转义,如: # 。

1、版本号

VERSION = 4          # 主版本号
PATCHLEVEL = 0       # 补丁版本号
SUBLEVEL = 0         # 次版本号
EXTRAVERSION =       # 附件版本信息
NAME = MELIS SDK     # 工程名

2、MAKEFLAGS变量

make 是支持递归调用的,也就是在 Makefile 中使用“make”命令来执行其他的 Makefile文件,一般都是子目录中的 Makefile 文件。假如在当前目录下存在一个“subdir”子目录,这个子目录中又有其对应的 Makefile 文件,那么这个工程在编译的时候其主目录中的 Makefile 就可以调用子目录中的 Makefile,以此来完成所有子目录的编译。主目录的 Makefile 可以使用如下代码来编译这个子目录:

$(MAKE) -C subdir

$(MAKE)就是调用“make”命令, -C 指定子目录,subdir就是子目录,有时候需要将变量导入子目录,export就是导入,unxeport就是不导入,需要注意的是,“SHELL”和“MAKEFLAGS”,这两个变量除非使用“unexport”声明,否则的话在整个make的执行过程中,它们的值始终自动的传递给子make。

# o Do not use make's built-in rules and variables
#   (this increases performance and avoids hard-to-debug behaviour);
# o Look for make include files relative to root of kernel src
MAKEFLAGS += -rR --include-dir=$(CURDIR)
# 使用+=来给MAKEFLAGS增加了一些变量
# -rR 的意思是禁止使用内置的隐含规则和变量定义
# --include-dir 指明搜索路径,CURDIR就是当前路径

# Avoid funny character set dependencies
unexport LC_ALL  # 不导入
LC_COLLATE=C
LC_NUMERIC=C
export LC_COLLATE LC_NUMERIC  #导入上面两个变量

# Avoid interference with shell env settings
unexport GREP_OPTIONS  # 不导入

3、命令输出

Melis 默认编译是不会在终端中显示完整的命令,都是短命令。

在这里插入图片描述
在终端中输出短命令虽然看起来很清爽,但是不利于分析 Melis 的编译过程。可以通过设置变量“V=1“来实现完整的命令输出。

# Beautify output
# ---------------------------------------------------------------------------
#
# Normally, we echo the whole command before executing it. By making
# that echo $($(quiet)$(cmd)), we now have the possibility to set
# $(quiet) to choose other forms of output instead, e.g.
#
#         quiet_cmd_cc_o_c = Compiling $(RELDIR)/$@
#         cmd_cc_o_c       = $(CC) $(c_flags) -c -o $@ $<
#
# If $(quiet) is empty, the whole command will be printed.
# If it is set to "quiet_", only the short version will be printed.
# If it is set to "silent_", nothing will be printed at all, since
# the variable $(silent_cmd_cc_o_c) doesn't exist.
#
# A simple variant is to prefix commands with $(Q) - that's useful
# for commands that shall be hidden in non-verbose mode.
#
#	$(Q)ln $@ :<
#
# If KBUILD_VERBOSE equals 0 then the above command will be hidden.
# If KBUILD_VERBOSE equals 1 then the above command is displayed.
#
# To put more focus on warnings, be less verbose as default
# Use 'make V=1' to see the full commands

ifeq ("$(origin V)", "command line")  # 判断变量V的来源,如果来源命令行
  KBUILD_VERBOSE = $(V)  # KBUILD_VERBOSE就是V的值,为1
endif
ifndef KBUILD_VERBOSE
  KBUILD_VERBOSE = 0  # 没定义就是0
endif

#用变量 quiet 和 Q 来控制编译的时候是否在终端输出完整的命令
ifeq ($(KBUILD_VERBOSE),1)  # 由上面得知,KBUILD_VERBOSE如果等于1,V=1就进入下面
  quiet =
  Q =
else
  quiet=quiet_
  Q = @
endif

(origin V)和"command line"是否相等,这里使用到了origin函数,其语法是(origin )。origin函数不操作变量的值,只是告诉你变量是哪里来的,$(origin V)就是告诉变量V的来源,也就是判断变量V是否来自命令行定义,origin函数返回值有下面几种:

  • undefine:从来没有定义过
  • default:如果是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。“environment” 如果是一个环境变量, 并且当 Makefile 被执行时, “-e”参数没有被打开。
  • file:变量被定义在Makefile中
  • command line:在命令行定义
  • override:变量被override指示符重新定义
  • automatic:是命令运行中的自动化变量

在Makefile文件中有许多下面的

$(Q)$(MAKE) $(build)=tool

如果V = 0, Q=@,就不会在中断输出命令了;否则就会输出命令。

有些命令会有两个版本,比如:

quiet_cmd_sym ?= SYM $@
cmd_sym ?= $(OBJDUMP) -t $<> $@

sym命令分为 “quiet_cmd_sym” 和 “cmd_sym” 两个版本,这两个命令的功能都是一样的,区别在于 make 执行的时候输出的命令不同。quiet_cmd_xxx 命令输出信息少,也就是短命令,而 cmd_xxx 命令输出信息多,也就是完整的命令。

如果变量quiet为空的话,整个命令都会输出。
如果变量quiet为“quiet_”的话,仅输出短版本。
如果变量quiet为“silent_”的话,整个命令都不会输出。

4、静默输出

由上一节可知,如果V=1就输出很多命令,如果V不等于0就会短命令输出,有时候不需要输出命令,这时候就是静默输出。编译的时候使用make -s即可。

# If the user is running make -s (silent mode), suppress echoing of
# commands

ifneq ($(filter 4.%,$(MAKE_VERSION)),)	# make-4.x版本,filter是过滤的,在变量中仅保留符合4.%的词
ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),)  # 是否静默输出
  quiet=silent_
endif
else					# make-3.8x版本
ifneq ($(filter s% -s%,$(MAKEFLAGS)),)  # 如果加入-s 那条件成立,静默输出
  quiet=silent_
endif
endif

ifeq ($(MELIS_BASE),)  #初始化Melis环境变量
  $(error Please execute 'source melis-env.sh' first.)
endif

export quiet Q KBUILD_VERBOSE  # 导出这三个输出结果给其他子makefile

上面用到了Makefile中的filter和firstword函数,先来介绍一下这两个函数。

原型: $(filter ,)
名称: 过滤函数
功能: 以pattern模式过滤text字符串中的单词,保留符合模式pattern的单词,可以有多个模式。
返回: 返回符合pattern模式的字串

原型:(firstword <text>)
名称: 首单词函数
功能: 取字符串<text>中的第一个单词。
返回: 返回字符串<text>的第一个单词。

5、设置编译结果输出目录

Melis 可以将编译出来的目标文件输出到单独的目录中,在make中使用“O”来指定输出目录,比如“make O=out”就是设置目标文件输出到out目录中,不指定O参数的话,源文件和编译产生的文件分开。

# kbuild supports saving output files in a separate directory.
# To locate output files in a separate directory two syntaxes are supported.
# In both cases the working directory must be the root of the kernel src.
# 1) O=
# Use "make O=dir/to/store/output/files/"
#
# 2) Set KBUILD_OUTPUT
# Set the environment variable KBUILD_OUTPUT to point to the directory
# where the output files shall be placed.
# export KBUILD_OUTPUT=dir/to/store/output/files/
# make
#
# The O= assignment takes precedence over the KBUILD_OUTPUT environment
# variable.

# KBUILD_SRC is set on invocation of make in OBJ directory
# KBUILD_SRC is not intended to be used by the regular user (for now)
ifeq ($(KBUILD_SRC),)

# OK, Make called in directory where kernel src resides
# Do we want to locate output files in a separate directory?
ifeq ("$(origin O)", "command line")  # 如果O来自命令行,就按照命令行后面的输出
  KBUILD_OUTPUT := $(O)  # KBUILD_OUTPUT就是输出目录
endif

# That's our default target when none is given on the command line
PHONY := _all   # “PHONY”来显示地指明一个目标是“伪目标”,向 make 说明,不管是否有这个文件,这个目标就是“伪目标”。
_all:

# Cancel implicit rules on top Makefile
$(CURDIR)/Makefile Makefile: ;      # 取消顶部Makefile上的隐式规则

ifneq ($(words $(subst :, ,$(CURDIR))), 1)
  $(error main directory cannot contain spaces nor colons)
endif

ifneq ($(KBUILD_OUTPUT),)  # 判断 KBUILD_OUTPUT 是否为空,如果不为空,则进入下面的代码,创建目录
# Invoke a second make in the output directory, passing relevant variables
# check that the output directory actually exists
saved-output := $(KBUILD_OUTPUT)
# 调用 mkdir 命令,创建 KBUILD_OUTPUT 目录,并且将创建成功以后的绝对路径赋值给 KBUILD_OUTPUT。至此,通过 O 指定的输出目录就存在了。
KBUILD_OUTPUT := $(shell mkdir -p $(KBUILD_OUTPUT) && cd $(KBUILD_OUTPUT) \
								&& /bin/p
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值