顶层Makefile的内容主要结构为:
确定版本号及主机信息
实现静默编译功能
设置各种路径
设置编译工具链
设置规则
设置与cpu相关的伪目标
需要注意的是,结构顺序并不代表代码执行顺序。
1.确定版本号及主机信息
VERSION = 2016 PATCHLEVEL = 11 SUBLEVEL = EXTRAVERSION = NAME = version_h := include/generated/version_autogenerated.h timestamp_h := include/generated/timestamp_autogenerated.h UBOOTVERSION = $(VERSION)$(if $(PATCHLEVEL),.$(PATCHLEVEL)$(if $(SUBLEVEL),.$(SUBLEVEL)))$(EXTRAVERSION)
最开始四个变量的含义依次为:主版本号、次版本号、再次版本号、附加的版本信息(值为空,可以给用户使用)
timestamp_autogenerated.h 这个文件是在make之后自动生成的,文件内容是一条宏,这条宏给将其他.c文件提供uboot的编译时间
version_autogenerated.h这个文件是在make之后自动生成的,文件内容是一条宏,这条宏给将其他.c文件提供uboot的版本号
UBOOTVERSION 为最终生成的uboot版本
验证方法:自己修改主Makefile中几个Version有关的变量,然后重新编译uboot,然后烧录到SD卡中,从SD卡启动,然后去看启动时uboot打印出来的版本信息,看看变化是不是和自己的分析一致。
HOSTARCH := $(shell uname -m | \ sed -e s/i.86/x86/ \ -e s/sun4u/sparc64/ \ -e s/arm.*/arm/ \ -e s/sa110/arm/ \ -e s/ppc64/powerpc/ \ -e s/ppc/powerpc/ \ -e s/macppc/powerpc/\ -e s/sh.*/sh/)
HOSTARCH这个名字:HOST是主机;ARCH是architecture(架构)的缩写,表示CPU的架构。所以HOSTARCH就表示主机的CPU的架构
makefile的函数调用与变量调用很类似,格式是$ ( function arguments),其实上面一大段的意思是变量HOSTARCH的值是一个函数的返回值shell是makefile中的一个函数,$ (shell XXX)会被解析成执行shell命令XXX;此处是执行了一条 uname -m |sed -e ……,符号 \是makefile的换行符其中,|是shell语法中的管道结构,例如:XXX | YYY ,表达式XXX 的输出将作为表达式YYY的输入,YYY的输出才是整句表达式的输出uname -m 指令将输出负责编译的主机cpu架构,比如ixx86;sed -e是替换命令,比如把ixx86替换为x86,由此可见这个HOSTARCH变量的值将得到负责编译的主机cpu架构。大部分情况下我们得到的都是x86
HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \ sed -e 's/\(cygwin\).*/cygwin/')
这个HOSTOS变量和上一句HOSTARCH变量的原理类似,管道第一部分uname -s会得到负责编译的主机的OS,比如Linux
管道第二部分是将大写转换成小写
管道第三部分的意思是如果前面一个部分得到了cygwin系统,则格式要转换一下。不必深究,因为cygwin基本没人用……
由此可见这个HOSTOS变量的值将得到负责编译的主机操作系统,大部分情况下我们得到的都是linux
CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \ else if [ -x /bin/bash ]; then echo /bin/bash; \ else echo sh; fi ; fi) export HOSTARCH HOSTOS
SHELL定义使用哪种sh解释器来解释脚本,BASH的值为/bin/sh也可能为,整个语句的意思是判断/bin/sh是否可执行
如果可执行将/bin/sh 赋值给变量SHELL
如果不可执行,进一步判断/bin/bash的可执行性
如果/bin/sh和/bin/bash都不可执行,则直接将sh赋值给SHELL变量
export HOSTARCH HOSTOS
导出上面变量到全局,使其为环境变量,让其他的文件也可以使用架构和系统信息
2.实现静默编译功能
平时默认编译时命令行会打印出来很多编译信息。但是有时候我们不希望看到这些编译信息,就后台编译即可。这就叫静默编译。
# 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
使用方法就是编译时make -s,-s会作为MAKEFLAGS传给Makefile,老版本uboot作用下XECHO变量就会被变成空(默认等于echo),于是实现了静默编译。
# If the user is running make -s (silent mode), suppress echoing of # commands ifneq ($(filter 4.%,$(MAKE_VERSION)),) # make-4 ifneq ($(filter %s ,$(firstword x$(MAKEFLAGS))),) quiet=silent_ endif else # make-3.8x ifneq ($(filter s% -s%,$(MAKEFLAGS)),) quiet=silent_ endif endif export quiet Q KBUILD_VERBOSE
此外,相关设置也需要kbuild值来决定是否开启,我理解只有等于1时才会生效,相关注释是这样说的
# 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
3.设置各种路径
两种编译方法
(1)编译复杂项目,Makefile提供2种编译管理方法。默认情况下是当前文件夹中的.c文件,编译出来的.o文件会放在同一文件夹下。这种方式叫原地编译。原地编译的好处就是处理起来简单。
(2)原地编译有一些坏处:第一,污染了源文件目录。第二的缺陷就是一套源代码只能按照一种配置和编译方法进行处理,无法同时维护2个或2个以上的配置编译方式。
(假如这套源代码有2种配置,那么编译其中一种配置后,要想编译另一种配置需要先make distclean清除第一种的编译生成文件才可以进行。)
(3)为了解决以上2种缺陷,uboot支持单独输出文件夹方式的编译(linux kernel也支持,而且uboot的这种技术就是从linux kernel偷师而来)。基本思路就是在编译时另外指定一个输出目录,将来所有的编译生成的.o文件或生成的其他文件全部丢到那个输出目录下去。源代码目录不做任何污染,这样输出目录就只是承载了本次配置编译的所有结果。
(4)具体用法:默认的就是原地编译。如果需要指定具体的输出目录编译则有2种方式来指定输出目录。(具体参考Makefile注释内容)
# 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)
第一种:make O=输出目录
第二种:export BUILD_DIR=输出目录 然后再make
如果两个都指定了(既有BUILD_DIR环境变量存在,又有O=xx),则O=xx具有更高优先级,会覆盖方法二。
而且方法一必须每次输入make时都要输入参数(不论是make clean还是make config 的时候),格式如make O=/tmp/build disclean
# That's our default target when none is given on the command line PHONY := _all _all: # Cancel implicit rules on top Makefile $(CURDIR)/Makefile Makefile: ; ifneq ($(KBUILD_OUTPUT),) # Invoke a second make in the output directory, passing relevant variables # check that the output directory actually exists saved-output := $(KBUILD_OUTPUT) 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))
这句是shell语法中简写的if表达式,其作用是当输出文件夹路径不存在时就创建它
其中包含两个表达式,表达式1||表达式2,当表达式1为真时,表达式2不会被解释器执行,因为总结果一定为真,(尽管总的结果没有意义)。唯有表达式1为假时,解释器才会去执行表达式2,这样就能用逻辑表达式来实现if语句的功能了
表达式1中,方括号是固定用法,是为了突出里面表达式的作用是判断语句。-p是shell中判断路径是否存在的符号,如果路径存在则为表达式1为真;如果路径不存在,表达式1为假,解释器会执行表达式2,即创建输出文件夹路径。
这整个一段功能是确保输出文件夹路径创建成功,&&的功能是连续执行两句语句,当cd $(BUILD_DIR)执行完后,执行/bin/pwd,即打印当前路径;
$(if xxx,yyy,zzz)是makefile的判断函数,如果xxx为真,则执行yyy并返回值,否则执行zzz并返回值,由此可知如果未成功创建BUILD_DIR,就会输出错误打印信息;
objtree := . src := $(srctree) obj := $(objtree)
(1)OBJTREE:编译出的.o文件存放的目录的根目录。在默认编译下,OBJTREE等于当前目录;在O=xx编译下,OBJTREE就等于我们设置的那个输出目录。
(2)SRCTREE: 源码目录,其实就是源代码的根目录,也就是当前目录。
总结:在默认编译下,OBJTREE和SRCTREE相等;在O=xx这种编译下OBJTREE和SRCTREE不相等。Makefile中定义这两个变量,其实就是为了记录编译后的.o文件往哪里放,就是为了实现O=xx的这种编译方式的。