且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《嵌入式Linux开发实用教程》——1.2 Makefile基本知识

更新时间:2022-08-21 16:58:00

本节书摘来异步社区《嵌入式Linux开发实用教程》一书中的第1章,第1.2节,作者:朱兆祺 ,李强 ,袁晋蓉 ,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.2 Makefile基本知识

嵌入式Linux开发实用教程
Makefile如今能得以广泛应用,这还得归功于它被包含在UNIX系统中。在make诞生之前,UNIX系统的编译系统主要由“make”、“install”shell脚本程序和程序的源代码组成。它可以把不同目标的命令组成一个文件,而且可以抽象化依赖关系的检查和存档。这是向现代编译环境发展的重要一步。1977年,斯图亚特·费尔德曼在贝尔实验室里制作了这个软件。2003年,斯图亚特·费尔德曼因发明了这样一个重要的工具而接受了美国计算机协会(ACM)颁发的软件系统奖。

Makefile文件可以实现自动化编译,只需要一个“make”命令,整个工程就能完全自动编译,极大地提高了软件开发的效率。目前虽有众多依赖关系检查工具,但是make是应用最广泛的一个。一个程序员会不会写Makefile,从一个侧面说明了这个程序员是否具备完成大型工程的能力。

1.2.1 Makefile规则

一个简单的Makefile语句由目标、依赖条件、指令组成。

smdk6400_config  :  unconfig  
   @mkdir -p $(obj)include $(obj)board/samsung/smdk6400```
smdk6400_config:目标;

unconfig:先决条件;

@mkdir -p $(obj)include $(obj)board/samsung/smdk6400:指令。这里特别注意,“@”前面是Tab键,并且必须是Tab键,而不能是空格。

目标和先决条件是依赖关系,目标是依赖于先决条件生成的。

####1.2.2 Makefile变量
1.变量的引用方式
使用“$(OBJTREE)”或者“${ OBJTREE }”来引用OBJTREE这个变量的定义。这个引用方式似乎很像C语言中的指针变量,使用*p来取存放在指针p中的值。

obj := $(OBJTREE)/
OBJTREE  := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))
export BUILD_DIR=/tmp/build`
$(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))的含义:如果“BUILD_DIR”变量值不为空,则将变量“BUILD_DIR”指定的目录作为一个子目录;否则将目录“CURDIR”作为一个子目录。

2.递归展开式变量
这类变量的定义是通过“=”和“define”来定义的。

student = lilei
CLASS = $(student) $(teacher)
teacher = yang

all:  
   @echo $(CLASS)```
其优点是:这种类型递归展开式的变量在定义时,可以引用其他之前没有定义过的变量,这个变量可能在后续部分定义,或者是通过make的命令行选项传递的变量来定义。

其缺点是:其一,使用此风格的变量定义,可能会由于出现变量的递归定义而导致make陷入到无限的变量展开过程中,最终使make执行失败。

x = $(y)
y = $(z)
z = $(x)`
这样的话会使得Makefile出错,因为到最终引用了自己。

其二,这种风格的变量定义中如果使用了函数,那么包含在变量值中的函数总会在变量被引用的地方执行。

3.直接展开式变量
为了避免递归展开式变量存在的问题和不方便,GNU make支持另外一种风格的变量,称为直接展开式变量。这种风格的变量使用“:=”定义。在使用“:=”定义变量时,变量值中对其他变量或者函数的引用在定义变量时被展开,也就是对变量进行替换。

X := student
Y := $(X)
X := teacher
all:  
   @echo $(X) $(Y)```
这里的输出是:teacher student。

这个直接展开式变量在定义时就完成了对所引用变量和函数的展开,因此不能实现对其后定义变量的引用。

4.条件赋值
在对变量进行赋值之前,会对其进行判断,只有在这个变量之前没有进行赋值的情况下才会对这个变量进行赋值。

X := student
X ?= teacher
all:  
  @echo $(X)`
由于X在之前被赋值了,所以这里的输出是student。

5.变量的替换引用
对于一个已经定义的变量,可以使用变量的替换引用将变量中的后缀字符使用指定的字符替换。格式为“$(X:a=b)”(或者“${X:a=b}”),即将变量“X”中所有以“a”字符结尾的字替换为以“b”结尾的字。

X := fun.o main.o
Y := $(X: .o=.c)
all:  
   @echo $(X) $(Y)```
特别注意的是,$(X: .o=.c)的“=”两边不能有空格。这里的输出是:fun.o main.o fun.c main.c。

6.追加变量值
追加变量值是指一个通用变量在定义之后的其他一个地方,可以对其值进行追加。也就是说可以在定义时(也可以不定义而直接追加)给它赋一个基本值,后续根据需要可随时对它的值进行追加(增加它的值)。在Makefile中使用“+=”(追加方式)来实现对一个变量值的追加操作。

X = fun.o main.o
X += sub.o
all:  
  @echo $(x)`
这里的输出是:fun.o main.o sub.o。

1.2.3 Makfile常用关键字

1.ifneq关键字
这个关键字是用来判断两个参数是否不相等。格式为:

ifneq “Value1”“Value2”
ifneq (Value1,Value2)```
在判断之前先要将Value1和Value2的值进行展开和替换,如在U-Boot-2013.04的顶层目录Makefile中,对U-Boot的版本参数就使用了ifneq关键字进行判断。

VERSION   = 2013
PATCHLEVEL  = 04
SUBLEVEL   =
EXTRAVERSION =

ifneq "$(SUBLEVEL)" ""
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
else
U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)
endif`
先将SUBLEVEL使用$()展开和替换,如果SUBLEVEL的值不是空,则执行:

U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)```
也就是说,如果$(SUBLEVEL) = 1的话,那么U_BOOT_VERSION = 2013.04.1。

如果SUBLEVEL的值是空,则执行:

U_BOOT_VERSION = $(VERSION).$(PATCHLEVEL)$(EXTRAVERSION)

那么此时U_BOOT_VERSION = 2013.04。

2.ifeq关键字
ifeq关键字和ifneq关键字是相对而言的,用来判断两个参数是否相等。格式为:

ifeq “Value1”“Value2”
ifeq (Value1,Value2)`
和ifneq一样,先要将Value1和Value2展开替换之后,再进行比较。

ifeq ($(HOSTARCH),$(ARCH))
CROSS_COMPILE=/usr/local/arm/4.4.1/bin/arm-linux-
endif```
如果HOSTARCH展开替换之后和ARCH展开替换之后相等,则:

CROSS_COMPILE=/usr/local/arm/4.4.1/bin/arm-linux-
否则CROSS_COMPILE不等于/usr/local/arm/4.4.1/bin/arm-linux-。

3.ifndef关键字
ifndef关键字用来判断一个变量是否没有进行定义。格式:

ifndef Value`
由于在Makefile中,没有定义的变量的值为空。

ifndef CONFIG_SANDBOX
SUBDIRS += $(SUBDIR_EXAMPLES)
endif```
如果CONFIG_SANDBOX值为空,条件成立,执行如下语句:

SUBDIRS += $(SUBDIR_EXAMPLES)`
否则不执行。

4.ifdef关键字
ifdef关键字用来判断一个变量是否已经进行定义过。格式:

ifdef Value```
如:

ifdef CONFIG_SYS_LDSCRIPT  
    # need to strip off double quotes  
    LDSCRIPT := $(subst ",,$(CONFIG_SYS_LDSCRIPT))
endif`
如果CONFIG_SYS_LDSCRIPT定义过,则执行:

LDSCRIPT := $(subst ",,$(CONFIG_SYS_LDSCRIPT))```
否则不执行。

####1.2.4 Makefile常用函数
1.Makefile函数语法
在Makefile中,函数的调用和变量的调用类似,都是使用“$”进行标识。语法如下:

$(函数名 函数的参数)
${函数名 函数的参数}
函数名与函数的参数之间使用空格隔开,而函数的参数间使用逗号进行分隔。以上两种写法都是可以的,但是为了风格统一,请不要两者进行混合使用。

2.shell函数
make可以使用shell函数和外部通信。shell函数本身的返回值是其参数的执行结果,没有进行任何处理,对结果的处理是由make进行的。当对函数的引用出现在规则的命令行中,命令行在执行时函数才被展开。展开时函数参数(shell命令)的执行是在另外一个shell进程中完成的,因此需要对出现在规则命令行的多级“shell”函数引用需要谨慎处理,否则会影响效率(每一级的“shell”函数的参数都会有各自的shell进程)。

建立一个测试程序,Makefile的内容:

zhu := $(shell cat func)

all:
    @ echo $(zhu)`
Func文件中的内容:

juxst zhuzhaoqi```
执行完成Makefile之后:

zhuzhaoqi@zhuzhaoqi-desktop:~/u-boot/Makefile/shellfunction$ make
juxst zhuzhaoqi`
在U-Boot和Linux内核源码中将会大量使用到shell函数。

3.subst函数
subst函数是字符串替换函数,语法为:

$(subst 被替换字串 替换字串 替换操作字符串)
执行subst函数之后,返回的是执行替换操作之后的字符串。如下:

name := zhu zhaoqi
Alphabet_befor := z
Alphabet_after := Z
Name := $(subst $(Alphabet_befor), $(Alphabet_after), $(name))

all:
    echo $(Name)```
执行上面的Makefile,输出结果为:

echo  Zhu Zhaoqi
Zhu Zhaoqi`
即将“z”替换成了“Z”。

4.dir函数
dir函数作用为取出该文件的目录,其语法为:

$(dir 文件名称)
执行该函数之后返回文件目录部分。

Makefile中常用函数较多,笔者就不一一例举了,读者可参考相关书籍进行深入了解。