嵌入式Linux开发之Makefile

Posted 大奥特曼打小怪兽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式Linux开发之Makefile相关的知识,希望对你有一定的参考价值。

makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

makefile的好处就是:

—“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。二、节约编译时间(没改动的文件不编译)。

make是一个命令工具,是一个解释makefile中指令的命令工具。

一、Makefile规则

1.1 基本规则

一个简单的Makefile文件包含一系列的规则,其样式如下:

target:prerequisites
    command
    ...
    ...
=====================
目标 : 依赖文件
[tab键] 命令
      ...
      ...
  • target: 是目标文件,可以是object file,也可以是执行文件,还可以是一个标签label。如果目标文件的更新时间晚于依赖文件的更新时间,则说明依赖文件没有改动,目标文件不需要重新编译。否则重新编译并更新目标。
  • prerequisites:即目标文件由哪些文件生成。如果依赖条件中存在不存在的依赖条件,则会寻找其它规则是否可以产生依赖条件。例如:规则一是生成目标 hello.elf需要使用到依赖条件 hello.o,但是 hello.o 不存在。则 Makefile 会寻找到一个生成 hello.o 的规则二并执行。
  • command:即通过执行该命令,由依赖文件生成目标文件。注意每条命令前必须有且仅有一个 tab 保持缩进,这是语法要求。
  • ALL:Makefile 文件默认只生成第一个目标文件即完成编译,但是我们可以通过 “ALL” 指定需要生成的目标文件。

这是一个文件依赖关系,也就是说target是由一个或多个目标文件依赖于prerequisites中的文件,其生成规则定义在command中,而且只要prerequisites中有一个以上的文件比target文件更新的话,command所定义的命令就会被执行,这是makefile的最基本规则,也是makefile中最核心的内容。

1.2 示例

我们仍然以hello.c文件为例:

#include <stdio.h>
int main(int argc,char *argv[])
{
  printf("Hello World!\\n");
  return 0;
}

然后编写Makefile(这里使用的是gcc、而不是arm-linux-gcc):

ALL: hello.o

hello.o: hello.c
        gcc hello.c -o hello.o

编译并执行:

make
./hello.o

二、Makefile函数 

2.1 获取匹配模式文件名函数wildcard

  • 语法:$(wildcard PATTERN)
  • 函数功能:列出当前目录下所有符合模式“ PATTERN”格式的文件名。“PATTERN”使用 shell可识别的通配符,包括“ ?”(单字符)、“*”(多字符)等。
  • 返回值:空格分割的、存在当前目录下的所有符合模式“ PATTERN”的文件名。

例如:

SRC = $(wildcard ./*.c)

匹配目录下所有的 .c 文件,并将其赋值给 SRC 变量。

2.2 模式替换函数patsubst

pat 是 pattern 的缩写,subst 是 substring 的缩写。

  • 语法:$(patsubst PATTERN,REPLACEMENT,TEXT)
  • 函数功能:搜索“ TEXT”中以空格分开的单词,将否符合模式“ TATTERN ”替换为“REPLACEMENT ”。参数“PATTERN”中可以使用模式通配符 “%”来代表一个单词中的若干字符。 如果参数“REPLACEMENT ”中也包含一个“%”,那么“ REPLACEMENT ”中的“ %”将是“ TATTERN”中的那个“ %”所代表的字符串。在“ TATTERN ”和“REPLACEMENT ”中,只有第一个“ %”被作为模式字符来处理,之后出现的不再作模式字符(作为一个字符)。在参数中如果需要将第一个出现的“ %”作为字符本身而不作为模式字符时,可使用反斜杠“ ”进行转义处理(转义处理的机制和使用静态模式的转义一致,
  • 返回值:替换后的新字符串。

参数 “TEXT ”单词之间的多个空格在处理时被合并为一个空格,并忽略前导和结尾空格。

例如:

OBJ = $(patsubst %.c, %.o, $(SRC))

这个函数有三个参数,意思是取出 SRC 中所有的值,然后将 “.c” 替换为 “.o”,最后赋值给 OBJ 变量。

2.3 foreach 函数

函数“foreach ”不同于其它函数。它是一个循环函数。类似于 Linux 的 shell 中的for 语句:

  • 语法:$(foreach VAR,LIST,TEXT)
  • 函数功能: 这个函数的工作过程是这样的:如果需要(存在变量或者函数的引用),首先展开变量“ VAR”和“ LIST”的引用;而表达式“ TEXT”中的变量引用不展开。执行时把“ LIST”中使用空格分割的单词依次取出赋值给变量“VAR”,然后执行“ TEXT”表达式。重复直到“ LIST”的最后一个单词(为空时结束)。“TEXT ”中的变量或者函数引用在执行时才被展开,因此如果在“TEXT”中存在对“ VAR”的引用,那么“ VAR”的值在每一次展开式将会到的不同的值。
  • 返回值: 空格分割的多次表达式“ TEXT ”的计算的结果。

2.4 过滤函数 -filter

“filter”函数可以用来去除一个变量中的某些字符串, 我们下边的例子中就是用到了此函数。

1).找出符合PATTERN 格式的值

  • 语法:$(filter PATTERN ⋯,TEXT)
  • 函数功能:过滤掉字串“ TEXT”中所有不符合模式“ PATTERN ”的单词,保留所有符合此模式的单词。可以使用多个模式。模式中一般需要包含模式字符“%”。存在多个模式时,模式表达式之间使用空格分割。
  • 返回值:空格分割的“ TEXT”字串中所有符合模式“ PATTERN ”的字串。

2).找出不符合PATTERN 格式的值

  • 语法:$(filter-out PATTERN ⋯,TEXT)
  • 函数功能:过滤掉字串“ TEXT”中所有符合模式“ PATTERN ”的单词,保留所有不符合此模式的单词。可以使用多个模式。模式中一般需要包含模式字符“%”。存在多个模式时,模式表达式之间使用空格分割。
  • 返回值:空格分割的“ TEXT”字串中所有不符合模式“ PATTERN ”的字串。

假如我们目录下有很多个 “.c” 后缀的源文件,就不需要写很多条规则语句了,

2.5 示例

我们修改我们的Makefile文件:

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))

ALL: hello.out

hello.out: $(OBJ)
        gcc $(OBJ) -o hello.out

$(OBJ): $(SRC)
        gcc -c  $(SRC) -o $(OBJ)

这里我们先将所有的 “.c” 文件编译为 “.o” 文件,这样后面更改某个 “.c” 文件时,其它的 “.c” 文件将不再编译,而只是编译有更改的 “.c” 文件,可以大大节约大项目中的编译速度。

需要注意的是:

  • .o文件一般是通过编译的但还未链接的
  • .out文件一般都是经过相应的链接产生的可执行文件(linux下)

三、makefile通配符

Makefile 中也有一些已经定义好的常用变量,这里介绍其中常用的3个。

3.1 $@

表示规则中目标,例如 hello.out

3.2 $<

表示规则中的第一个依赖条件,例如 hello.c

3.3 $^

表示规则中的所有依赖条件,由于我们示例中都只有一个依赖条件,这种情况下 $^ 和 $< 区别不大

3.4 示例

我们修改我们的Makefile文件:

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
 
ALL: hello.out
 
hello.out: $(OBJ)
        gcc -o $@ $<
 
$(OBJ): $(SRC)
        gcc -c -o $@ $<

四、其它常用功能

4.1 代码清理clean

我们可以编译一条属于自己的 clean 语句,来清理 make 命令所产生的所有文件。例如

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))

ALL: hello.out

hello.out: $(OBJ)
        gcc -o $@ $<

$(OBJ): $(SRC)
        gcc -c -o $@ $<
clean:
        rm -rf $(OBJ) *.out

这样我们就可以使用make clean 命令来清理生成的文件了:

提示:make命令是可以带上目标名的,如果make后面不跟目标名字的话,默认生成第一个目标,当带上目标名的话,生成指定的目标。

4.2 伪目标 .PHONY

上面我们写了一个 clean 语句,使得我们执行 “make clean” 命令的时候,可以清理我们生成的文件。

但是假如还存在一个文件名就是 clean 文件,那么我们再执行 “make clean” 命令的时候就只是显示:

make clean
make: `clean\' is up to date.

为什么?我们看一看Makefile的核心规则:

  • 目标文件不存在;
  • 某个依赖文件比目标文件新;

但是现在目录中有名为clean的文件,那目标文件存在,那就取决于依赖,但是在Makefile中clean目标没有依赖,所以没有办法通过判断依赖的的时间去更新clean目标,所以clean目标文件一直都是目录中那么clean文件。所以说,如果目录中有何clean同名文件时就没有办法执行clean操作了。

解决方法就是我们使用伪目标,把这个目标定义为假想目标这样就可以避免出现上面的问题了,例如:

SRC = $(wildcard *.c)
OBJ = $(patsubst %.c, %.o, $(SRC))
 
ALL: hello.out
 
hello.out: $(OBJ)
        gcc -o $@ $<
 
$(OBJ): $(SRC)
        gcc -c -o $@ $<
 
clean:
        -rm -rf $(OBJ) hello.out
 
.PHONY: clean ALL

通常,我们也会把 ALL 也设置为伪目标。 

五、Makefile变量

变量的种类分为:

  • 既时变量(简单变量):例子: 赋值方式: A := xxx    #A的值在定义就可以确定,即刻赋值
  • 延时变量:例子:赋值方式:A =xxx      #A的值在使用到的时候才会确定

  • ?= : 延时变量,第一次定义才有效,如果这个变量在前面已经定义过,那么不执行这句赋值
  • +=  : 可以是即时变量也可以是延时变量,取决于这个变量的定义

比如我们编写Makefile文件:

A:=$(C)

B=$(C)

C=123

ALL:
        @echo A=$(A)
        @echo B=$(B)

C赋值给A、但是C现在的值为空,B是延时变量,等到用到时才确定,所以执行到C=123才会显示B的值,也就是123.

参考文章

[1] Makefile简单使用实例

[2] Makefile 语法入门

以上是关于嵌入式Linux开发之Makefile的主要内容,如果未能解决你的问题,请参考以下文章

linux嵌入式驱动开发,makefile到问题

嵌入式linux核心课程 2.uboot和系统移植-第2部分-2.2.补基础之shell和Makefile

嵌入式Linux开发之第三方库交叉编译

嵌入式Linux开发之第三方库交叉编译

嵌入式Linux开发之第三方库交叉编译

Linux虚拟机下makefile的编写应用