参考自:《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的执行分成以下的逻辑部分:
- 配置(make *config):对内核中所有模块进行配置,以确定哪些模块需要编译进内核,哪些编译成外部模块,哪些不进行处理,同时生成一些配置相关的文件供内核真正编译文件时调用。
- 内核编译(make):根据配置部分,真正的编译整个内核源码,其中包含递归地进入各级子目录进行编译。
- 通过命令行传入 O=DIR 进行编译 : 所有编译过程生成的中间文件以及目标文件放置在指定的目录,保持源代码的干净。
- 通过命令行传入多个编译目标,比如:make clean all,make config all,等等,一次指定多个目标。
- 通过命令行传入 M=DIR 进行编译:指定编译某个目录下的模块,并将其编译成外部模块。
- 指定一些完全独立的目标,与内核真实的编译过程并不强相关,比如: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/pwd)
$(if $(KBUILD_OUTPUT),, \
$(error failed to create output directory "$(saved-output)"))
PHONY += $(MAKECMDGOALS) sub-make
$(filter-out _all sub-make $(CURDIR)/Makefile, $(MAKECMDGOALS)) _all: sub-make
@:
sub-make: FORCE
$(Q)$(MAKE) -C $(KBUILD_OUTPUT) KBUILD_SRC=$(CURDIR) \
-f $(CURDIR)/Makefile $(filter-out _all sub-make,$(MAKECMDGOALS))
# Leave processing to above invocation of make
skip-makefile := 1
endif # ifneq ($(KBUILD_OUTPUT),)
endif # ifeq ($(KBUILD_SRC),)
# We process the rest of the Makefile if this is the final invocation of make
ifeq ($(skip-makefile),)
# Do not print "Entering directory ...",
# but we want to display it when entering to the output directory
# so that IDEs/editors are able to understand relative filenames.
MAKEFLAGS += --no-print-directory
6、代码检查
Melis 支持代码检查,使用命令“make C=1”使能代码检查,检查那些需要重新编译的文件。“make C=2”用于检查所有的源码文件。
# Call a source code checker (by default, "sparse") as part of the
# C compilation.
#
# Use 'make C=1' to enable checking of only re-compiled files.
# Use 'make C=2' to enable checking of *all* source files, regardless
# of whether they are re-compiled or not.
#
# See the file "Documentation/sparse.txt" for more details, including
# where to get the "sparse" utility.
ifeq ("$(origin C)", "command line") # 判断 C 是否来源于命令行,如果 C 来源于命令行,那就将 C 赋值给变量KBUILD_CHECKSRC,如果命令行没有 C 的话 KBUILD_CHECKSRC 就为 0
KBUILD_CHECKSRC = $(C)
endif
ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC = 0
endif
7、模块编译
在 Melis 中允许单独编译某个模块,使用命令“ make M=dir”即可,旧语法“ makeSUBDIRS=dir”也是支持的。顶层 Makefile 中的代码如下:
# Use make M=dir to specify directory of external module to build
# Old syntax make ... SUBDIRS=$PWD is still supported
# Setting the environment variable KBUILD_EXTMOD take precedence
ifdef SUBDIRS # 判断是否定义了 SUBDIRS ,如果定义了SUBDIRS ,变量KBUILD_EXTMOD=SUBDIRS,这里是为了支持老语法“make SUBIDRS=dir”
KBUILD_EXTMOD ?= $(SUBDIRS)
endif
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
# If building an external module we do not care about the all: rule
# but instead _all depend on modules
PHONY += all
ifeq ($(KBUILD_EXTMOD),) # 判断 KBUILD_EXTMOD 时为空,如果为空的话目标_all 依赖 all,因此要先编译出 all。否则的话默认目标_all 依赖 modules,要先编译出 modules,也就是编译模块。一般情况下我们不会在 uboot 中编译模块,所以此处会编译 all 这个目标。
_all: all
else
_all: modules
endif
ifeq ($(KBUILD_SRC),) # 判断 KBUILD_SRC 是否为空,如果为空的话就设置变量 srctree 为当前目录,即srctree 为“.”,一般不设置 KBUILD_SRC。
# building in the source tree
srctree := .
else
ifeq ($(KBUILD_SRC)/,$(dir $(CURDIR)))
# building in a subdirectory of the source tree
srctree := ..
else
srctree := $(KBUILD_SRC)
endif
endif
objtree := . # 设置变量 objtree 为当前目录
prjtree := $(srctree)/projects
src := $(srctree)
obj := $(objtree)
VPATH := $(srctree)$(if $(KBUILD_EXTMOD),:$(KBUILD_EXTMOD)) # 设置 VPATH
export prjtree srctree objtree VPATH eboard-libs elibrary-libs # 导出变量
8、SUBARCH获取系统架构
# SUBARCH tells the usermode build what the underlying arch is. That is set
# first, and if a usermode build is happening, the "ARCH=um" on the command
# line overrides the setting of ARCH below. If a native build is happening,
# then ARCH is assigned, getting whatever value it gets normally, and
# SUBARCH is subsequently ignored.
SUBARCH := $(shell uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ \
-e s/sun4u/sparc64/ \
-e s/arm.*/arm/ -e s/sa110/arm/ \
-e s/s390x/s390/ -e s/parisc64/parisc/ \
-e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
-e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ )
include scripts/toolchain.mk # 检查GCC工具链安装情况(根据配置,解压相应工具链)
定义了一个变量 SUBARCH,用于保存当前的系统架构(译注:一般都指CPU架构),这里调用 shell 命令“uname -m”获取架构名称。shell 中的“|”表示管道,意思是将左边的输出作为右边的输入, sed -e 是替换命令,“sed -e s/i.86/x86/”表示将管道输入的字符串中的“i.86”替换为“x86”,其他的“sed -s”命令同理。
它其实就是执行了如下的命令uname -m | sed -e s/i.86/x86/ -e s/x86_64/x86/ -e s/sun4u/sparc64/ -e s/arm.*/arm/ -e s/sa110/arm/ -e s/s390x/s390/ -e s/parisc64/parisc/ -e s/ppc.*/powerpc/ -e s/mips.*/mips/ -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/
我的机子是Ubuntu 16.04 LTS x86_64的(即x86架构)运行一下
如上所见,系统执行uname得到机器、操作系统和架构的信息。因为我们得到的是uname的输出,所以我们需要做一些处理再赋给变量SUBARCH。
9、设置目标架构、交叉编译器和配置文件
# Cross compiling and selecting different set of gcc/bin-utils
# ---------------------------------------------------------------------------
#
# When performing cross compilation for other architectures ARCH shall be set
# to the target architecture. (See arch/* for the possibilities).
# ARCH can be set during invocation of make:
# make ARCH=ia64
# Another way is to have ARCH set in the environment.
# The default ARCH is the host where make is executed.
# CROSS_COMPILE specify the prefix used for all executables used
# during compilation. Only gcc and related bin-utils executables
# are prefixed with $(CROSS_COMPILE).
# CROSS_COMPILE can be set on the command line
# make CROSS_COMPILE=ia64-linux-
# Alternatively CROSS_COMPILE can be set in the environment.
# A third alternative is to store a setting in .config so that plain
# "make" in the configured kernel build directory always uses that.
# Default value for CROSS_COMPILE is not to prefix executables
# Note: Some architectures assign CROSS_COMPILE in their arch/*/Makefile
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
# Architecture as present in compile.h
UTS_MACHINE := $(ARCH)
SRCARCH := $(ARCH)
# Additional ARCH settings for x86
ifeq ($(ARCH),i386)
SRCARCH := x86
endif
ifeq ($(ARCH),x86_64)
SRCARCH := x86
endif
# Additional ARCH settings for sparc
ifeq ($(ARCH),sparc32)
SRCARCH := sparc
endif
ifeq ($(ARCH),sparc64)
SRCARCH := sparc
endif
# Additional ARCH settings for sh
ifeq ($(ARCH),sh64)
SRCARCH := sh
endif
# Additional ARCH settings for tile
ifeq ($(ARCH),tilepro)
SRCARCH := tile
endif
ifeq ($(ARCH),tilegx)
SRCARCH := tile
endif
# Where to locate arch specific headers
hdr-arch := $(SRCARCH)
获得SUBARCH之后就要设置SRCARCH和hdr-arch,SRCARCH提供了硬件架构相关代码的目录,hdr-arch提供了相关头文件的目录。
KCONFIG_CONFIG ?= .config
export KCONFIG_CONFIG # 导出 KCONFIG_CONFIG
如果没有设置过代表内核配置文件路径的变量KCONFIG_CONFIG,下一步系统会设置它,默认情况下就是.config,这个文件是不是很熟悉,它就是我们make menuconfig后的那个.config配置文件,里面写入我们内核编译的所有信息。
# SHELL used by kbuild
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
else if [ -x /bin/bash ]; then echo /bin/bash; \
else echo sh; fi ; fi)
CONFIG_SHELL是编译内核过程中要用到的shell。接下来就要设置一组和编译内核的编译器相关的变量。我们会设置主机的C和C++的编译器及相关配置项:
HOSTCC = gcc
HOSTCXX = g++
HOSTCFLAGS = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer -std=gnu89
HOSTCXXFLAGS = -O2
ifeq ($(shell $(HOSTCC) -v 2>&1 | grep -c "clang version"), 1)
HOSTCFLAGS += -Wno-unused-value -Wno-unused-parameter \
-Wno-missing-field-initializers -fno-delete-null-pointer-checks
endif
我们可以看到Makefile在这里开始适配代表C/C++编译器的变量CC和CXX。
那为什么还要HOST*这些变量呢?这是因为CC是编译内核过程中要使用的目标架构的编译器,但是HOSTCC是要被用来编译一组host程序的(下面我们就会看到)。
10、KBUILD_编译的目标
然后我们就看到变量KBUILD_MODULES和KBUILD_BUILTIN的定义,这两个变量决定了我们要编译什么东西(内核、模块或者两者都有):
# Decide whether to build built-in, modular, or both.
# Normally, just do built-in.
# Compile modules on melis by default.
# So ach sub-dir of Top can be compile seperately.
# make projects, ekernel ... ex.
KBUILD_MODULES := 1
KBUILD_BUILTIN := 1
# If we have only "make modules", don't compile built-in objects.
# When we're building modules with modversions, we need to consider
# the built-in objects during the descend as well, in order to
# make sure the checksums are up to date before we record them.
ifeq ($(MAKECMDGOALS),modules)
KBUILD_BUILTIN = $(if $(CONFIG_MODVERSIONS),1)
endif
# If we have "make <whatever> modules", compile modules
# in addition to whatever we do anyway.
# Just "make" or "make all" shall build modules as well
ifneq ($(filter all _all modules,$(MAKECMDGOALS)),)
KBUILD_MODULES := 1
endif
ifeq ($(MAKECMDGOALS),)
KBUILD_MODULES := 1
endif
export KBUILD_MODULES KBUILD_BUILTIN
export KBUILD_CHECKSRC KBUILD_SRC KBUILD_EXTMOD
在这我们可以看到这些变量的定义,并且,如果我们仅仅传递了modules给make,变量KBUILD_BUILTIN会依赖于内核配置选项CONFIG_MODVERSIONS。
11、Kbuild
接着下一步操作是引入下面的文件:
# We need some generic definitions (do not try to remake the file).
scripts/Kbuild.include: ;
-include include/config/auto.conf
include scripts/Kbuild.include
文件Kbuild或者又叫做KernelBuildSystem是一个用来管理构建内核及其模块的特殊框架。kbuild文件的语法与makefile一样。文件scripts/Kbuild.include为kbuild系统提供了一些常规的定义。因为我们包含了这个kbuild文件,我们可以看到和不同工具关联的这些变量的定义,这些工具会在内核和模块编译过程中被使用(比如链接器、编译器、来自binutils的二进制工具包,等等)。
12、交叉编译工具变量设置
上面我们只是设置了 CROSS_COMPILE 的名字,但是交叉编译器其他的工具还没有设置,下面是一些设置。
# Make variables (CC, etc...)
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
SIZE = $(CROSS_COMPILE)size
AWK = awk
GENKSYMS = scripts/genksyms/genksyms
INSTALLKERNEL := installkernel
DEPMOD = /sbin/depmod
PERL = perl
PYTHON = python
CHECK = sparse
CHECKFLAGS := -D__linux__ -Dlinux -D__STDC__ -Dunix -D__unix__ -D__melis__ \
-Wbitwise -Wno-return-void $(CF)
CFLAGS_MODULE =
AFLAGS_MODULE =
LDFLAGS_MODULE =
CFLAGS_KERNEL =
AFLAGS_KERNEL =
CFLAGS_GCOV = -fprofile-arcs -ftest-coverage
在这些定义好的变量后面,我们又定义了两个变量:USERINCLUDE和MELISINCLUDE。他们包含了头文件的路径(第一个是给用户用的,第二个是给内核用的)
# Use USERINCLUDE when you must reference the UAPI directories only.
USERINCLUDE := \
-Iinclude/generated \
-Iinclude/generated/uapi/melis \
-I$(srctree)/include/melis/elibrary \
-include $(srctree)/include/melis/kconfig.h
# Use MELISINCLUDE when you must reference the include/ directory.
# Needed to be compatible with the O= option
MELISINCLUDE := \
$(if $(KBUILD_SRC), -I$(srctree)/include) \
-Iinclude \
-Iinclude/melis \
-Iinclude/melis/common \
-Iinclude/melis/eboot \
-Iinclude/melis/eboard \
-Iinclude/melis/ekernel \
-Iinclude/melis/ekernel/arch \
-Iinclude/melis/ekernel/csp \
-Iinclude/melis/ekernel/drivers \
-Iinclude/melis/ekernel/filesystem \
-Iinclude/melis/ekernel/pthread \
-Iinclude/melis/elibrary \
-Iinclude/melis/elibrary/libc \
-Iinclude/melis/elibrary/libc/mediainfo \
-Iinclude/melis/elibrary/libc/misc \
-Iinclude/melis/elibrary/libc/misc/pub0 \
-Iinclude/melis/emodules \
-Iinclude/melis/ipc \
-Iinclude/melis/misc \
-Iinclude/melis/of \
-Iinclude/melis/sys \
-Iinclude/melis/video \
-Iekernel/subsys/finsh_cli \
-Iekernel/core/rt-thread/include \
-Iekernel/subsys/thirdparty/dfs/include \
-Iekernel/drivers/hal/source \
-Iekernel/drivers/include/osal \
$(USERINCLUDE)
ifeq (${CONFIG_ARMV7_A}, y)
MELISINCLUDE += -Iinclude/melis/ekernel/arch/cortex-v7a
endif
ifeq (${CONFIG_RISCV}, y)
MELISINCLUDE += -Iinclude/melis/ekernel/arch/riscv
#for application in the livedesk directory
SOLUTION := livedesk
#head file include at beetles -- top directory of the application solution
SOLUTION_INCLUDE := -I${MELIS_BASE}/${SOLUTION}/beetles/elibs/libapps/module_adapter \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/libapps/scene/listbar \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/anole \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/bookmark \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/formats \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/formats/encode_parser \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/formats/encode_parser/gbk \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/formats/encode_parser/utf16_big \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/formats/encode_parser/utf16_little \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/formats/encode_parser/utf8 \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/mnpl \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/monkey/regedit \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/robin \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/robin/foundation \
-I${MELIS_BASE}/${SOLUTION}/beetles/elibs/lib_ex/robin/lib \
-I${MELIS_BASE}/${SOLUTION}/beetles/include \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/applets \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/ab \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/channel \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/disp_output \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/eq \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/ff_rr_speed \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/fsm_ctrl \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/lyric \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/media_info \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/npl \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/palette \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/play_mode \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/play_speed \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/spectrum \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/star \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/subtitle \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/track \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/video_layer \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/video_win \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/vision_effect \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/volume \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/robin/zoom \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/elibs/lib_ex/scanfile \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/alarm \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/background \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/bookengine \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/display \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/headbar \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/mini_music \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/screen_lock \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/tp_adjust \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/init/ui/tp_adjust_scene \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/libapps \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/libapps/module_adapter \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/libapps/scene \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/libapps/srf \
-I${MELIS_BASE}/${SOLUTION}/beetles/include/mod_desktop \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/ \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/background/fb_lib \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/headbar/ \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/headbar/prog_bar \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/mini_music \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/screen_lock \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/ui \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/ui/assistant_scene \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/ui/dialog_scene \
-I${MELIS_BASE}/${SOLUTION}/beetles/init/ui/tp_adjust_scene \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/engine \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/engine/radio \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/engine/radio/receive \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/engine/radio/send \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/engine/walkman \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/engine/walkman/foundation \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/engine/walkman/lib \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/framework \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/functions \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/include \
-I${MELIS_BASE}/${SOLUTION}/beetles/mod_desktop/util
export SOLUTION SOLUTION_INCLUDE
endif #ifeq (${CONFIG_RISCV}, y)
这并不是最终确定的编译器标志,它们还可以在其他makefile里面更新(比如arch/里面的kbuild)。变量定义完之后,全部会被导出供其它makefile使用。
#KBUILD_CPPFLAGS := -D__KERNEL__
KBUILD_CPPFLAGS :=
KBUILD_CFLAGS := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
-fno-strict-aliasing -fno-common \
-Werror-implicit-function-declaration \
-Wno-format-security \
-fno-builtin-printf \
-D_SYS__PTHREADTYPES_H_
KBUILD_AFLAGS_KERNEL :=
KBUILD_CFLAGS_KERNEL :=
KBUILD_AFLAGS := -D__ASSEMBLY__
KBUILD_AFLAGS_MODULE := -DMODULE
KBUILD_CFLAGS_MODULE := -DMODULE
KBUILD_LDFLAGS_MODULE := -T $(srctree)/scripts/module-common.lds
# Read KERNELRELEASE from include/config/kernel.release (if it exists)
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
KERNELVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)
13、导出其他变量
export VERSION PATCHLEVEL SUBLEVEL KERNELRELEASE KERNELVERSION
export ARCH SRCARCH CONFIG_SHELL HOSTCC HOSTCFLAGS CROSS_COMPILE AS LD CC
export CPP AR NM STRIP OBJCOPY OBJDUMP SIZE
export MAKE AWK GENKSYMS INSTALLKERNEL PERL PYTHON UTS_MACHINE
export HOSTCXX HOSTCXXFLAGS LDFLAGS_MODULE CHECK CHECKFLAGS
export KBUILD_CPPFLAGS NOSTDINC_FLAGS MELISINCLUDE OBJCOPYFLAGS LDFLAGS
export KBUILD_CFLAGS CFLAGS_KERNEL CFLAGS_MODULE CFLAGS_GCOV CFLAGS_KASAN
export KBUILD_AFLAGS AFLAGS_KERNEL AFLAGS_MODULE
export KBUILD_AFLAGS_MODULE KBUILD_CFLAGS_MODULE KBUILD_LDFLAGS_MODULE
export KBUILD_AFLAGS_KERNEL KBUILD_CFLAGS_KERNEL
export KBUILD_ARFLAGS
# When compiling out-of-tree modules, put MODVERDIR in the module
# tree rather than in the kernel tree. The kernel tree might
# even be read-only.
export MODVERDIR := $(if $(KBUILD_EXTMOD),$(firstword $(KBUILD_EXTMOD))/).tmp_versions
# Files to ignore in find ... statements
export RCS_FIND_IGNORE := \( -name SCCS -o -name BitKeeper -o -name .svn -o \
-name CVS -o -name .pc -o -name .hg -o -name .git \) \
-prune -o
export RCS_TAR_IGNORE := --exclude SCCS --exclude BitKeeper --exclude .svn \
--exclude CVS --exclude .pc --exclude .hg --exclude .git
14、
# ===========================================================================
# Rules shared between *config targets and build targets
# Basic helpers built in scripts/
PHONY += scripts_basic
scripts_basic:
$(Q)$(MAKE) $(build)=scripts/basic
$(Q)rm -f .tmp_quiet_recordmcount
# To avoid any implicit rule to kick in, define an empty command.
scripts/basic/%: scripts_basic ;
首先我们可以看出scripts_basic是按照scripts/basic的makefile执行make的。下面我们看看scripts/basic下的makefile都有什么

scripts/basic/Makefile包含了编译两个主机程序fixdep和bin2的目标。
第一个工具是fixdep:
用来优化gcc生成的依赖列表,然后在重新编译源文件的时候告诉make。
第二个工具是bin2c,
它依赖于内核配置选项CONFIG_BUILD_BIN2C,并且它是一个用来将标准输入接口(译注:即stdin)收到的二进制流通过标准输出接口(即:stdout)转换成C头文件的非常小的C程序。你可能注意到这里有些奇怪的标志,如hostprogs-y等。这个标志用于所有的kbuild文件,更多的信息你可以从documentation获得。
在我们这里,hostprogs-y告诉kbuild这里有个名为fixed的程序,这个程序会通过和Makefile相同目录的fixdep.c编译而来。
我们make时执行make之后,终端的第一个输出就是kbuild的结果


PHONY += outputmakefile
# outputmakefile generates a Makefile in the output directory, if using a
# separate output directory. This allows convenient use of make in the
# output directory.
outputmakefile:
ifneq ($(KBUILD_SRC),)
$(Q)ln -fsn $(srctree) source
$(Q)$(CONFIG_SHELL) $(srctree)/scripts/mkmakefile \
$(srctree) $(objtree) $(VERSION) $(PATCHLEVEL)
endif
# Support for using generic headers in asm-generic
PHONY += asm-generic
asm-generic:
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.asm-generic \
src=asm obj=arch/$(SRCARCH)/include/generated/asm
$(Q)$(MAKE) -f $(srctree)/scripts/Makefile.asm-generic \
src=uapi/asm obj=arch/$(SRCARCH)/include/generated/uapi/asm
15、
(未完待补充)
二、Makefile中赋值语句(=, :=, ?=和+=)
在Makefile语法中,时不时会见到各种“=”号的赋值语句,除了常见的“=”和“:=”,还有“?=”等。
那么这些赋值等号分别表示什么含义呢?
1、“=”
“=”是最普通的等号,然而在Makefile中确实最容易搞错的赋值等号,使用”=”进行赋值,变量的值是整个makefile中最后被指定的值。不太容易理解,举个例子如下:
VIR_A = A
VIR_B = $(VIR_A) B
VIR_A = AA
经过上面的赋值后,最后VIR_B的值是AA B,而不是A B。在make时,会把整个makefile展开,拉通决定变量的值。
2、“:=”
相比于前面“最普通”的”=”,”:=”就容易理解多了。”:=”就表示直接赋值,赋予当前位置的值。同样举个例子说明:
VIR_A := A
VIR_B := $(VIR_A) B
VIR_A := AA
最后变量VIR_B的值是A B,即根据当前位置进行赋值。因此相比于”=”,”:=”才是真正意义上的直接赋值。
3、“?=”
“?=”表示如果该变量没有被赋值,则赋予等号后的值。举例:
VIR ?= new_value
如果VIR在之前没有被赋值,那么VIR的值就为new_value。
VIR := old_value
VIR ?= new_value
这种情况下,VIR的值就是old_value。
4、“+=”
“+=”和平时写代码的理解是一样的,表示将等号后面的值添加到前面的变量上。
三、理解Makefile中的$@、$^和$<
1、理解$@、$^、$<的含义
Makefile中,格式为这样的
target : prerequisties
目标:需要依赖的条件
简单的工程直接这样:
hello:hello.cc
gcc hello.cc -o hello
但如果文件多了呢?按部就班写会显得很长,所有这时候makefile中的常用命令就产生了,如下:
$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表
2、举例
将头文件放在include文件夹,cpp文件放在source文件夹,test.cc为可执行文件。工程目录为:
.
├── include
│ ├── myString.h
│ └── utils.h
├── Makefile
├── source
│ ├── myString.cpp
│ └── utils.cpp
└── test.cc
Makefile文件为:
TARGET = test.out
CXXFLAGS += -std=gnu++11
CC = g++
#主函数文件
CXX_FILES := ./test.cc
#cpp文件
SRC_DIRS := ./source/myString.cpp
SRC_DIRS += ./source/utils.cpp
INC_DIRS := -I ./include
LIBS += -lstdc++
LIBS += -ldl -lpthread
$(TARGET):$(CXX_FILES) $(SRC_DIRS)
$(info target: $@)
$(info all: $^)
$(info first: $<)
$(info SRC_DIRS_all: $(SRC_DIRS))
$(CC) -o $@ $^ $(INC_DIRS) $(CXXFLAGS) $(LIBS)
.PHONY:clean
clean:
rm -rf $(TARGET)
输出结果如下:

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

被折叠的 条评论
为什么被折叠?



