Makefile 详解


原文链接: Makefile 详解

Makefile中规避错误以及$符号的转义 -> $$

在处理时, 需要注意下Makefile对$符号的处理. Makefile的变量大都是$(VAR)的形式, 因此在增加$(DESTDIR)就需要将$字符转义一下(引用Bash变量的时候也一样). 因此我们可以用sed来实现一下:

sed -i -e '/^install/,/^\s*$$/{s/$$(PREFIX)/$$(DESTDIR)\/$$(PREFIX)/g}' Makefile

修改完之后, 打包完成~

基本原则

.PYTHON伪目标总是不如其它文件“新”,因此它总是被执行。
@ 使命令在被执行前不被回显。
– 使任何命令行的任何非零退出状态都被忽略。

  • 使命令行可以通过指定 -n、-q 或 -t 选项来执行。

模式变量 %

argets定义了一系列的目标文件,可以有通配符。是目标的一个集合。
target-parrtern是指明了targets的模式,也就是的目标集模式。
prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。
如果我们的定义成“%.o”,意思是我们的集合中都是以“.o”结尾的,而如果我们的定义成“%.c”,意思是对所形成的目标集进行二次定义,其计算方法是,取模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”进行转义,来标明真实的“%”字符。

看一个例子:

objects = foo.o bar.o
all: $(objects)
$(objects):
%.o: %.c
  $(CC) -c $(CFLAGS) $< -o $@

上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,
也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.cbar.c”。
而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:

foo.o : foo.c
  $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
  $(CC) -c $(CFLAGS) bar.c -o bar.o

试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子:

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c

       $(CC) -c $(CFLAGS) $< -o $@

$(filter %.elc,$(files)): %.elc: %.el

       emacs -f batch-byte-compile $<

$(filter%.o,$(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。

自动变量 $

$@ --代表目标文件(target)
$^ --代表所有的依赖文件(components)
$< --代表第一个依赖文件(components中最左边的那个)。

要生成目标 ... : 生成目标所需的依赖 ...

command #必须要以[Tab]键开始

export 传递变量到下级 Makefile 中

$@
表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,"$@"就是匹配于
目标中模式定义的集合。
$% 仅匹配目标文件
仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a
(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix
下是[.a],Windows 下是[.lib]),那么,其值为空。
$<
依赖目标中的第一个目标名字。如果依赖目标是以模式( 即"%")定义的,那么"$<"将
是符合模式的一系列的文件集。注意,其是一个一个取出来的。
$? 依赖的集合(只提取比目标新的)
所有比目标新的依赖目标的集合。以空格分隔。
$^ 依赖的集合(去重)
所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量
会去除重复的依赖目标,只保留一份。
$+ 依赖的集合(不去重)
这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
$* 去除后缀名(不推荐使用)
这个变量表示目标模式中"%"及其之前的部分。如果目标是"dir/a.foo.b",并且目标的
模式是"a.%.b",那么,"$"的值就是"dir/a.foo"。这个变量对于构造有关联的文件名是比
较有较。如果目标中没有模式的定义,那么"$
"也就不能被推导出,但是,如果目标文件的
后缀是 make 所识别的,那么"$"就是除了后缀的那一部分。例如:如果目标是"foo.c",因
为".c"是 make 所能识别的后缀名,所以,"$
"的值就是"foo"。这个特性是 GNU make 的,

很有可能不兼容于其它版本的 make,所以,你应该尽量避免使用"$",除非是在隐含规则
或是静态模式中。如果目标中的后缀是 make 所不能识别的,那么"$
"就是空值。

在Makefile中写shell代码有点诡异,和不同的shell语法不太一样,如果不了解,看Makefile会莫名其妙。下面总结了一些。

Makefile和Shell

  1. Makefile本质上来讲也是shell脚本,即每条command都是shell进程,运行完shell进程都会退出
    Makefile test: gcc -c main.c -o main.o gcc -c a.c -o a.o
    这样输入make test,结果相当于两个进程,都退出了。

等价于在shell下输命令一样。

my_test:
  cd /home;mkdir test

这里make my_test,结果相当于一个进程。当前目录是/root 而

own_test:
  cd /home
  mkdir test

此时make own_test,相当于两个进程,
第一个进程是cd /home,运行完回到了/root。这时再运行第二个shell命令就会在/root下创建一个test目录.(makefile中的shell进程命令,跟直接在shell输的命令相同,但是它都会结束本身,即exit)

Makefile 中变量赋值

1:尽在Makefile文件的目标项冒号后的另起一行的代码才是shell代码。

第一种方式:

xx = xx1 // 这里时makefile代码

第二种方式:
yy:xx = xx2 // 这是是makefile代码,makefile允许变量赋值时,'='号两边留空格 ??????可以吗

第三种方式:
yy:
xx=xx3 // 只有这里是shell代码 ,shell不允许‘=’号两边有空格哦。
yy:
xx= xx3 // 只有这里是shell代码 ,shell不允许‘=’号两边有空格哦。

注意此时xx的值是" xx3",多了一个空格

有一个例外:
xx=$(shell 这里的代码也是shell代码)

变量传递

2:Makefile中的shell,每一行是一个进程,不同行之间变量值不能传递。所以,Makefile中的shell不管多长也要写在一行。
eg:

SUBDIR=src example

all:

@for subdir in $(SUBDIR); / // 这里往下是一行shell

do/

   echo "building " $$subdir; /

done

变量引用 Makefile变量$ shell的变量$$

3:Makefile中的变量以$开头,使用$(VAR)或${VAR}来引用变量的定义。 所以,为了避免和shell的变量冲突,shell的变量以$$开头

注意:Makefile中在对一些简单变量的引用,我们也可以不使用“()”和“{}”来标记变量名,而直接使用“$x”的格式来实现,此种用法仅限于变量名为单字符的情况。另外自动化变量也使用这种格式。对于一般多字符变量的引用必须使用括号了标记,否则make将把变量名的首字母作为作为变量而不是整个字符串(“$PATH”在Makefile中实际上是“$(P)ATH”)。这一点和shell中变量的引用方式不同。shell中变量的引用可以是“${xx}”或者“$xx”格式。但在Makefile中多字符变量名的引用只能是“$(xx)”或者“${xx}”格式。

eg1:从当前目录路径中提取出 /application 或 /base_class 之前的部分
PROJECT_ROOT_DIR = $(shell pwd | awk -F'/application|/base_class' '{print $$1}')
eg2:上例中$$subdir就是shell中的变量, 而$(SUBDIR)是Makefile的中的变量

变量输出 @隐藏自身的输出 - 出错继续运行

如果make执行的命令前面加了@字符,则不显示命令本身而只显示它的结果; Android中会定义某个变量等于@,例如 hide:= @
通常make执行的命令如果出错(该命令的退出状态非0)就立刻终止,不再执行后续命令,但如果命令前面加了-号,即使这条命令出错,make也会继续执行后续命令。
通常rm命令和mkdir命令前面要加-号,因为rm要删除的文件可能不存在,mkdir要创建的目录可能已存在,这两个命令都有可能出错,但这种错误是应该忽略的。

1、在Makefile中只能在target中调用Shell脚本,其他地方是不能输出的。比如如下代码就是没有任何输出:

VAR="Hello"
echo "$(VAR)"

all:
  .....

以上代码任何时候都不会输出,没有在target内,如果上述代码改为如下:

VAR="Hello"

all:
   echo "$(VAR)"
   .....

以上代码,在make all的时候将会执行echo命令。

最后打印结果是:
echo ""Hello""
"Hello"

2、在Makefile中执行shell命令,一行创建一个进程来执行。这也是为什么很多Makefile中有很多行的末尾都是“; \”,以此来保证代码是一行而不是多行,这样Makefile可以在一个进程中执行,例如:

SUBDIR=src example
all:
   @for subdir in $(SUBDIR); \
   do\
       echo "building "; \
   done

上述可以看出for循环中每行都是以”; \”结尾的。

3、Makefile中所有以$打头的单词都会被解释成Makefile中的变量。如果你需要调用shell中的变量(或者正则表达式中锚定句位$),都需要加两个$符号($$)。实例如下:

PATH="/data/"

all:
   echo ${PATH}/*Makefile中变量,即"/data"*/
   echo $$PATH /*shell中的变量*/

例子中的第一个${PATH}引用的是Makefile中的变量,而不是shell中的PATH环境变量,后者引用的事Shell中的PATH环境变量。

以上三点的是Makefile调用shell应该注意的地方,写Makefile一定要注意。
`