内核顶层Makefile相关1

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

$(Q) 变量

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

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

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

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

KBUILD_VERBOSE := 1 
ifeq ($(KBUILD_VERBOSE), 1) 
    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.2/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 := 1 
TEST_MSG := "are you ok?" 
ifeq ($(KBUILD_VERBOSE), 1) 
    $(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:7: *** KBUILD_VERBOSE is 1, 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_$(1)), $(cmd_$@)) 
                    $(filter-out $(cmd_$@), $(cmd_$(1))) )

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 := 1
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 =0
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 =0
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 := 1
hostprogs-m :=
ifneq ($(hostprogs-y)$(hostprogs-m),)
    testvar := "defined"
endif

all:
    @echo "$(testvar)"

运行输出:

$ make
    defined

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

原文地址:https://www.cnblogs.com/baiyw/p/3303758.html