http://www.groad.net/bbs/simple/?f104.htm

$(Q) 变量

内核 Makefile 文件 238 行到 259 行的注释中知道,$(Q) 变量的作用是决定是否在执行命令时输出详细的命令信息。底下对其有定义:

ifeq ($(KBUILD_VERBOSE),)
quiet =
Q =
else
quiet=quiet_
Q = @
endif

定义的意思是,如果 KBUILD_VERBOSE 为 1,则 quiet 和 Q 为空,即执行命令时会输出命令执行的详细信息;否则 quiet 为 quiet_ ,Q 为 @ 。在 Makefile 中,如果一个命令前使用了 @ 符号,那么在执行命令时将不输出命令的执行详细信息。

抛开内核 Makefile 文件的庞大,这里只依样画葫芦的做一下实验,下面是一个测试 Makefile :

KBUILD_VERBOSE :=
ifeq ($(KBUILD_VERBOSE), )
quiet =
Q =
else
quite = quiet_
Q = @
endif all:
$(Q)mkdir -p /home/beyes/makefile_test/quite/test_quite

Make 一下:

beyes@debian:~/makefile_test/quite$ make
mkdir -p /home/beyes/makefile_test/quite/test_quite

由执行结果看到,Makefile 中的 mkdir 命令执行过程整个输了出来。

再改一下 Makefile 中的 KBUILD_VERBOSE := 1 为KBUILD_VERBOSE := 0,那么再次执行 make 时,已经看不见任何输出。

= 和 := 符号的区别

= 和 := 都是变量赋值符号。但是它们有些区别:

"=" 如果右值包含另一个变量,那么可以在后面定义这个变量。

":=" 如果右值包含另一个变量,则只能引用已定义的变量。

下面看示例:

drivers-y := drivers/ $(head-y)
head-y = header/
all:
@echo $(drivers-y)

输出:

$ make
drivers/

drivers-y 的后面跟着一个 $(head-y) 变量,这个变量在 drivers-y 之前并未定义过,但是由于这里使用了 := 符号,所以在 drivers-y 的下一行再定义 head-y 已然无效。

那么将上面的 drivers-y 中的符号改成 = 符号,那么便可以看到区别:

drivers-y = drivers/ $(head-y)
head-y = header/ all:
@echo $(drivers-y)

输出:

[beyes@SLinux Makefile]$ make
drivers/ header/

?=

?= 符号的用法如:

var ?= def

这里的意思是,如果 var 这个变量没有被定义过,那么它的值就被定义为 def 。如果被定义过,则 def 不会被赋值到 var 中:

var ?= def
all:
@echo $(var)

输出:

$ make
def

如果是:

var = defined
var ?= def all:
@echo $(var)

则输出:

$ make
defined

+/- 符号

make 通常会在命令运行结束后检查命令的执行的返回状态,如果返回成功,那么就启动一个子 shell 来执行下一条命令;如果在中途检测到有执行出错的情况(返回非 0 状态),那么就会放弃对当前规则后续命令的执行,甚至会终止所有规则的执行。但在某些情况下,规则中一个命令执行失败并不代表整个规则执行错误,所以完全可以忽略这条可能执行失败的命令,其忽略的方法是在命令前添加一个 '-' 符号。在 Makefile 经常看到在 include 面前添加 '-' 符号:-include ,这时当当 include 包含的文件不存在时也不会造成整个 Makefile 解析的终止。又如,当一条 rm 命令前添加 ‘-’ 符号时,如果要删除的文件不存在或删除文件失败也不会对整个流程有任何影响。

除了 '-' 符号外,还可以看到 '+' 符号,它的意思和 '-' 相反,表示不忽略。这就意味着,像 make 的命令行选项 -n(--just print), -t(touch) 并不影响之前带 '+' 符号的命令的执行。像 -n 选项,一般情况下,它只是在解析命令,而不真正执行它们,但是命令前使用了 '+' 之后, -n 选项就不能阻止命令被执行。如一个目录下有这几个文件:

$ ls
Makefile test2.txt test.txt

Makefile 的内容为:

all:
@rm -f test.txt
+@rm -f test2.txt

使用 -n 选项来运行 make:

$ make -n
rm -f test.txt
rm -f test2.txt

再检查一下当前目录:

$ ls
Makefile test.txt

可见 test2.txt 已经被删除。

@:

在一些 Makefile 中可能会在伪目标下的命令中看到 @: 这个符号,其实这不是代表一个特殊变量。这里的 @ 符号和 @echo 中表示的意思(不显示命令执行的详细内容)一样,而冒号 ":" 实际上是 shell 中的内置符号, 它表示的是一种空命令,什么都不做,也就是执行到它时,它上面的命令都已经成功执行,最后成功退出。

$$

在使用变量时,需要在变量前加 "$" 符号,但最好是用 () 或 {} 将变量括起来,比如 $(VAR) 或 ${VAR} 。如果要使用真实的 "$“ 字符,那么需要用两个 "$" 表示,即 "$$"。

比如下面的代码:

$ cat Makefile
all:
@echo "$$HOME"
@echo "$$BASH"
@echo "$$PATH"

运行输出:

$ make
/home/beyes
/bin/sh
/usr/local/Trolltech/Qt-4.3./bin:/usr/lib/qt-3.3/bin:/usr/kerberos/bin:/usr/local/bin:/bin:/usr/bin:/home/beyes/bin

上面,$HOME,$BASH,$PATH 都是系统内置变量。引用这些变量时,就需要用 $$,如果只用一个 $ (如 @echo "$HOME"),那么你不会看到你所希望看到的结果。

$^,$+,$*,$(@D),$(@F),$(*D),$(*F),$(%D),$(%F),

$(%D),$(%F),$(<D),$(<F),$(^D),$(^F),$(+D),$(+F),

$(?D),$(?F)

$^、$+:
$^ 表示所有依赖文件列表。一个文件可重复出现在目标的依赖中,$^ 只记录它的一次引用情况,也就是说 $^ 会去掉重复的依赖文件。$+ 类似于 $^,但它保留了依赖文件中重复出现的文件。下面举例说明这两个变量的区别。

先在一个目录下建立 3 个文件:

$ echo "are" > test1.txt
$ echo "you" > test2.txt
$ echo "ok" > test3.txt

测试代码:

all:test1.txt test2.txt test3.txt test1.txt
$(shell cat $^ > integra1)
$(shell cat $+ > integra2)

运行输出:

beyes@debian:~/Makefile/prereq$ cat integra1
are
you
ok
beyes@debian:~/Makefile/prereq$ cat integra2
are
you
ok
are

由输出可以很清楚的看到这两个变量的区别。

$*
$* 有一个形象的词来称呼它 --- “茎” 。如果目标文件名中带有一个可识别的后缀,那么 $* 就表示文件中移除后缀以外的部分。比如 main.o 作为目标时,$* 就表示为 main 。如果目标包含不可识别的后缀时,该变量为空。

$(@D)
代表目标文件的目录部分(要去掉目录部分的最后一个斜杠),如下例所示:

/home/beyes/Makefile/main.o:
@echo $(@D)

运行输出:

$ make
/home/beyes/Makefile

如果目标中不包含斜杠,那么输出值为 '.' 表示当前目录。

$(@F)
目标文件的完整文件名初目录以外的部分,换句话说表示的是实际的文件名。如下示例:

/home/beyes/Makefile/main.o:
@echo $(@F)

运行输出:

$ make
main.o

$(*D) 和 $(*F)

分别表示目标 “茎” 中的目录部分和文件名部分。如下示例:

/home/beyes/Makefile/main.o:
@echo $(*D)
@echo $(*F)

运行输出:

$ make
/home/beyes/Makefile
main

$(%D) 和 $(%F)

当以 "archive(member)" 这样形式的静态库为目标时,分别表示库文件成员 "member" 名中的目录部分和文件名部分。它仅对这种形式的规则目标有效。如下示例:

Mylib.a(/home/beyes/main/are.o):
@echo $(%D)
@echo $(%F)

运行输出:

$ make
/home/beyes/main
are.o

$(<D) 和 $(<F)

分别表示规则中第一个依赖文件的目录部分和文件名部分。如下示例:

/home/beyes/main/are.o:
@echo hello something.o:
@echo makefile all:/home/beyes/main/are.o something.o
@echo $(<D)
@echo $(<F)

运行输出:

$ make all
hello
makefile
/home/beyes/main
are.o

$(^D) 和 $(^F)

分别表示所有依赖文件的目录部分和文件部分(不存在同一文件)。如下示例:

/home/beyes/main/are.o:
@echo hello /home/beyes/something.o:
@echo makefile all:/home/beyes/main/are.o /home/beyes/something.o
@echo $(^D)
@echo $(^F)

运行输出:

$ make all
hello
makefile
/home/beyes/main /home/beyes
are.o something.o

$(+D) 和 $(+F)

分别表示所有依赖文件的目录部分和文件部分(可存在重复文件)。

$(?D) 和 $(?F)
分别表示被更新的依赖文件的目录部分和问及文件部分。

error 函数

error 函数表示产生了一个致命错误,当它执行后,编译会停止。
它的使用方法是:

$(error <错误消息>)

测试代码:

KBUILD_VERBOSE :=
TEST_MSG := "are you ok?"
ifeq ($(KBUILD_VERBOSE), )
$(error KBUILD_VERBOSE is $(KBUILD_VERBOSE), we will stop here)
quite =
Q =
else
quite = quiet_
Q = @
endif all:
$(Q)echo $(TEST_MSG)

运行输出:

beyes@debian:~/makefile_test/error$ make
Makefile:: *** KBUILD_VERBOSE is , we will stop here。 停止。

由输出可见,all: 目标下的 echo 语句并没有执行输出,整个编译在 error 函数执行后停止了。如果将上面的 KBUILD_VERBOSE 的值改为 0 ,那么再 make 时输出:

beyes@debian:~/makefile_test/error$ make
are you ok?

$@, $^, $< , $? 符号

Makefile $@, $^, $<

$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表

如一个目录下有如下文件:

$ ls
hello.c hi.c main.c Makefile

按照 Makefile 规则规规矩矩的写:

main: main.o hello.o hi.o
gcc -o main main.o hello.o hi.o main.o: main.c
cc -c main.c hello.o: hello.c
cc -c hello.c hi.o: hi.c
cc -c hi.c clean:
rm *.o
rm main

改为用上述符号进行替代:

main: main.o hello.o hi.o
gcc -o $@ $^ main.o: main.c
cc -c $< hello.o: hello.c
cc -c $< hi.o: hi.c
cc -c $< clean:
rm *.o
rm main
beyes@debian:~/makefile_test/semicolon/real$ make
cc -c main.c
cc -c hello.c
cc -c hi.c
gcc -o main main.o hello.o hi.o
beyes@debian:~/makefile_test/semicolon/real$ ls
hello.c hello.o hi.c hi.o main main.c main.o Makefile

patsubst 和 filter 函数

filter 函数的使用形式如:

$(filter <pattern…>, <text>)

该函数的功能是,以 <pattern> 模式过滤 <text> 字符串中的单词,保留符合 <pattern> 模式的单词。

该函数的返回值是符合 <pattern> 模式的字符串。

patsubst 函数的使用形式如:

$(patsubst <pattern>, <replacement>, <text>)

该函数的功能是,查找 <text> 中的单词(这些单词以“空格”、“Tab”或“回车”,“换行”分隔)是否符合 <pattern> 中的模式,如果匹配,那么使用 <replacement> 替换。这里,<pattern> 可以包括通配符 “%” (表示任意长字符串)。如果 <replacement> 中也包含 “%” 符号,那么 <replacement> 中的 “%” 所代表的字符串就是 <pattern> 中的的那个字符串。若要使用 % 字符,那么要使用 ‘\’ 符号进行转义,即 “\%” 。

函数的返回值是替换过的字符串。

下面使用一个实例说明这两个函数的应用。下面的代码摘自内核源码树下顶层的 Makefile, 为了演示,做了一点修改:

init-y := init/ test
drivers-y := drivers/ sound/
net-y := net/ test2
libs-y := lib/
core-y := usr/
vmlinux-dirs := $(patsubst %/,%,$(filter %/, $(init-y) \
$(core-y) $(drivers-y) \
$(net-y) $(libs-y) ))
vmlinux-dirs2 := $(init-y) $(drivers-y) $(net-y) $(libs-y) $(core-y) all:
@echo vmlinux-dirs2: $(vmlinux-dirs2)
@echo vmlinux-dirs: $(vmlinux-dirs)

运行输出:

linux-suse10:~/Makefile_test # make
vmlinux-dirs2: init/ test drivers/ sound/ net/ test2 lib/ usr/
vmlinux-dirs: init usr drivers sound net lib

由上面的vmlinux-dirs2 输出可见,所有变量内容都被输出。

对vmlinux-dirs 的输出,则体现了上述两个函数的应用。首先 filter 过滤掉了没有以 / 符号作为结尾的字符串;然后再经过 patsubst 函数过滤掉了所有以 / 符号结尾字符串中的 / 符号。

filter-out -- 反过滤函数

格式:

$(filter-out <pattern...>,<text>)

说明:

以 <pattern> 模式过滤 <text> 字符串中的单词,去除符合模式 <pattern> 的单词。返回不符合模式 <pattern> 的字符串。如果 <text> 是 <pattern> 一样或者是其子集,那么返回空。
如:

objs = hello.c world.c are.c you.c ok.c
new = hello.c world.c are.c you.c ok.c add.c all:
@echo $(filter-out $(new), $(objs))

运行输出时为空。当 new 改为和 objs 一样时,同样输出为空。当 new 改为hello.c world.c are.c you.c 时,输出 ok.c 。

再做一个实验,如果 new 中包含的字符串比 objs 中的还是少一个 ok.c ,但是字符串的顺序和 ojbs 中的不一样,那结果是不是仍然输出 ok.c 呢?答案是一定的!这一无关顺序的“智能”特性比较重要,像在内核 Makefile 的参数检查中(比较新旧编译选项是否一样)就体现了这一点,如在 script/Kbuild.include 中对arg-check 的定义体现了这一点:

arg-check = $(strip $(filter-out $(cmd_$()), $(cmd_$@)) \
$(filter-out $(cmd_$@), $(cmd_$())) )

origin 函数 -- 告知变量的出生情况

origin 函数的作用是告诉你变量是哪里来的,其出生状况如何,他并不改变变量。其语法是:

$(origin <variable>)

上面,<variable> 为变量的名字,而不是引用,所以一般没有 $ 字符在前。origin 函数通过返回值来告诉你 <variable> 的出生情况。下面用实例说明:

1. 当从来未定义过该变量时,origin 函数返回 "undefined" 。如下面的 Makefile 代码:

all:
@echo $(origin V)

运行输出:

$ make
undefined

2. 如果该变量为环境变量,那么返回 "enviroment" 。如下面的 Makefile 代码:

all:
@echo $(origin USER)

运行输出:

$ make
environment

其中 USER 这个变量为系统定义的当前用户,使用 env 命令可以看到。

3. 如果变量是个默认定义,那么返回 "default"。如下面的 Makefile 代码:

all:
@echo $(origin CC)

运行输出:

$ make
default

4. 如果一个变量被定义在 Makefile 文件中,那么返回 "file" 。如下面的 Makefile 代码:

V :=
all:
@echo $(origin V)

运行输出:

$ make
file

5. 如果变量来自命令行,那么返回 "command line" 。如下面的 Makefile 代码:

all:
@echo $(origin MyVar)

运行方法:

$ make MyVar="Are you ok?"
command line

6. 如果变量被 override 被重新定义过,那么返回 "override"。如下面的 Makefile 代码:

verride SHELL = /bin/sh
all:
@echo $(origin SHELL)

运行输出:

$ make
override

上面,SHELL 原本是个环境变量,但在 Makefile 里被 override 指示符重定义过。

7. 如果变量是自动化变量(如 $@, $< 等),那么返回 "automatic" 。如下面的 Makefile 代码:

all:
@echo $(origin @)

运行输出:

$ make
automatic

ifdef, ifndef, ifeq, ifneq, endif

用内核 Makefile 中的一段代码说明:

# To put more focus on warnings, be less verbose as default
# Use 'make V=1' to see the full commands
ifdef V
ifeq ("$(origin V)", "command line")
KBUILD_VERBOSE = $(V)
endif
endif ifndef KBUILD_VERBOSE
KBUILD_VERBOSE =
endif

代码中注释的意思是,为了能将精力集中在警告信息上面,默认上不输出详细而显得冗余的编译信息,如果想看到完整的命令执行情况,可以在 make 时使用参数 V=1 。

下面根据代码分析这 4 个符号的作用,实际上它们和 C 语言中的意思是一样的。 
ifdef V 表示如果 V 变量被定义过,那么会执行下面的 ifeq 语句。V 变量的定义来源可以有不同,如在文件中定义,在命令行中定义,在环境变量中定义等。 
ifeq ("$(origin V)", "command line") 表示若 V 是在命令行里已被定义,那么执行下面的 KBUILD_VERBOSE = $(V) 语句。也就是说,ifeq 用以判断后面括号里的两个值是否相等,如果相等则执行下面的语句。如果不相等,则不执行。
ifndef 表示如果后面的变量没有被定义过,则执行其下面的语句。 
上面,每个 ifdef , ifeq 和 ifndef 都要和一个 endif 匹配以构成一个完整的判断式。

另外,在内核 Makefile 中还有如对 (C) 以及 (M) 等选项同样分析。
(C) 选项有是关于代码检查的选择:

# 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.
ifdef C
ifeq ("$(origin C)", "command line")
KBUILD_CHECKSRC = $(C)
endif
endif ifndef KBUILD_CHECKSRC
KBUILD_CHECKSRC =
endif

(M) 选项关于模块的编译:

# 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
KBUILD_EXTMOD ?= $(SUBDIRS)
endif ifdef M
ifeq ("$(origin M)", "command line")
KBUILD_EXTMOD := $(M)
endif
endif

像我们经常编译驱动模块时会使用下面的命令:make -C /lib/modules/`uname -r`/build M=`pwd` modules

上面,使用 pwd 命令给出了要编译的模块所在的路径。

像 ifeq 和 ifneq 的第一个参数也可以不止一个变量,当有多个变量时,可能需要考虑每个变量的条件是否都满足判断条件,直到所有的变量都不满足时,才认为整个表达式是不满足的。示例代码如下:

hostprogs-y :=
hostprogs-m :=
ifneq ($(hostprogs-y)$(hostprogs-m),)
testvar := "defined"
endif all:
@echo "$(testvar)"

运行输出:

$ make
defined

如果将上面代码中对 hostprogs-y 的定义设为空,那么输出将为空;而 hostprogs-y 和 hostprogs-m 这两个变量只要有一个味真,那么就认为整个 ifneq 判断成立。

内核顶层Makefile相关1的更多相关文章

  1. 内核顶层Makefile相关3

    http://www.groad.net/bbs/simple/?f104.html 伪目标 .PHONY是一个特殊工作目标(special target),它用来指定一个假想的工作目标,也就是说它后 ...

  2. 内核顶层Makefile相关4

    http://www.groad.net/bbs/simple/?f104.html make 的递归执行与 MAKEFLAGS 变量 make 的递归调用是指:在 Makefile 中使用 make ...

  3. 内核顶层Makefile相关2

    http://www.groad.net/bbs/simple/?f104.html if  函数 if 函数的语法有两种形式: () $(if <condition>, <then ...

  4. Linux 内核的 Makefile

    Linux内核的配置系统的基本结构 Linux内核的配置系统由三个部分组成,分别是: 1.Makefile:分布在 Linux 内核源代码根目录及各层目录中,定义 Linux 内核的编译规则: 2.配 ...

  5. 浅谈内核的Makefile、Kconfig和.config文件

    Linux内核源码文件繁多,搞不清Makefile.Kconfig..config间的关系,不了解内核编译体系,编译修改内核有问题无从下手,自己写的驱动不知道怎么编进内核,不知道怎么配置内核,这些问题 ...

  6. Linux中mod相关的命令 内核模块化 mod相关命令都是用来动态加载内核模块/驱动程序模块

    Linux中mod相关的命令 内核模块化   mod相关命令都是用来动态加载内核模块/驱动程序模块 http://baike.baidu.com/link?url=lxiKxFvYm-UfJIxMjz ...

  7. uboot 顶层makefile细节分析

    uboot的源文件众多,学习庞然大物首先找到脊椎--顶层的makfile,逐一破解.但是,uboot的makefile同样是一个庞然大物,所以也要找到它的主线.倘若过分专注部分细节,很难做到把握全局, ...

  8. (笔记)Linux内核中内存相关的操作函数

    linux内核中内存相关的操作函数 1.kmalloc()/kfree() static __always_inline void *kmalloc(size_t size, gfp_t flags) ...

  9. 【总结】嵌入式linux内核中Makefile、Kconfig、.config的关系及增加开机Hello World【转】

    本文转载自:http://blog.csdn.net/fengyuwuzu0519/article/details/73772109 为了弄清内核的组织结构,我们先来实现下面这个简单的例子. 一.增加 ...

随机推荐

  1. 九度oj 题目1370:数组中出现次数超过一半的数字

    题目描述: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字.例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}.由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2 ...

  2. 通过FEDERATED存储引擎同步两实例间的表数据

    需求情景:实例1中A库中的三个视图是实例2中的B库所依赖的,B需要A库中三个视图的实时数据. 方案:通过FEDERATED来完成跨势力的查询FEDERATED存储引擎表只会创建表结构,不会存储表数据, ...

  3. maven项目打包 编码gbk的不可映射字符

    中文系统默认gbk编码格式,你的代码是utf8格式的.所以报错 <build> <plugins> <plugin> <groupId>org.apac ...

  4. 【前端学习笔记】2015-09-10~~~~ css层叠样式表~~格式

    关于颜色的定义: 有几种方式:1.直接接英文单词   2.#16进制  3.rgb(100%:0%:0%)或者里面写像素,此处百分号不能省略. 加多个属性时,用“;”分开 前面如果是定义标签的样式时, ...

  5. Unity中LoadLevel与LoadLevelAsync的区别

    1.LoadLevel 同步加载 写法:Application.LoadLevel(“name”); 优点:读取场景使用同步的方法就可以,因为是同步方法所以读取的速度是最快的,也不用更新界面,因为同步 ...

  6. IP,子网掩码,网关,DNS的关系解析

    IP地址: 是给每个连接在Internet上的主机分配的一个32bit地址. 地址有两部分组成,一部分为网络地址,另一部分为主机地址. IP地址分为A.B.C.D.E 5类.常用的是B和C两类. 网络 ...

  7. 转 Python爬虫入门三之Urllib库的基本使用

    静觅 » Python爬虫入门三之Urllib库的基本使用 1.分分钟扒一个网页下来 怎样扒网页呢?其实就是根据URL来获取它的网页信息,虽然我们在浏览器中看到的是一幅幅优美的画面,但是其实是由浏览器 ...

  8. C++ 构造函数 析构函数 虚函数

    C++:构造函数和析构函数能否为虚函数? 简单回答是:构造函数不能为虚函数,而析构函数可以且常常是虚函数. (1) 构造函数不能为虚函数 让我们来看看大牛C++之父 Bjarne Stroustrup ...

  9. ubuntu16.04安装ibus中文输入法

    按照网上给的安装方法,没成功,在切换ibus的时候总是报错,记录下解决办法. 安装语言包 System Settings–>Language Support–>Install/Remove ...

  10. hdu 5701(区间查询思路题)

    中位数计数 Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Subm ...