程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)

嵌入式Linux开发之Makefile

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

makefile的好处就是:

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

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

由于u-boot的Makefile中存在相互调用,这里介绍一下make -f和make -C区别:

  • -C选项 :Makefile中使用make -C会改变当前的工作目录,表示到子目录下执行子目录的Makefile,顶层Makefile中的export的变量还有make默认的变量是可以传递给子目录中的Makefile的;
  • -f选项:顶层Makefile使用make -f调用子目录中的文件(文件名可以随意,不一定用Makefile作为文件名,顶层Makefile中的export的变量也可以传递变量到底层目录,另外在命令行中加入变量赋值选项,将覆盖顶层Makefile中export的变量;

注意:在顶层Makefile中使用-f选项,例如make -f ./xxx/xx/build.mk 此时make命令的工作目录仍然是顶层目录,即CUDIR变量依然是./目录而不是./xxx/xx/目录。

一、Makefile规则

1.1 基本规则(注释规范)

一个简单的Makefile 主要的 5个部分 (显示规则, 隐晦规则, 变量定义, 文件指示, 注释),其样式如下:

复制代码
target:prerequisites
    command
    ...
    ...
=====================
目标 : 依赖文件
[tab键] 命令
      ...
      ...
复制代码

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

其中:

  • target: 是目标文件,可以是object file,也可以是执行文件,还可以是一个标签label。如果目标文件的更新时间晚于依赖文件的更新时间,则说明依赖文件没有改动,目标文件不需要重新编译。否则重新编译并更新目标。
  • prerequisites:即目标文件由哪些文件生成。如果依赖条件中存在不存在的依赖条件,则会寻找其它规则是否可以产生依赖条件。例如:规则一是生成目标 hello.elf需要使用到依赖条件 hello.o,但是 hello.o 不存在。则 Makefile 会寻找到一个生成 hello.o 的规则二并执行。
  • command:即通过执行该命令,由依赖文件生成目标文件,规则中的命令被传递给shell进行解析执行。注意每条命令前必须有且仅有一个 tab 保持缩进,这是语法要求。如果同一行有多条命令,命令间用分号分割。。

(1) 显示规则 : 说明如何生成一个或多个目标文件(包括 生成的文件, 文件的依赖文件, 生成的命令);

(2) 隐晦规则 : make的自动推导功能所执行的规则;

(3) 变量定义: Makefile中定义的变量;

(4) 文件指示 : Makefile中引用其他Makefile; 指定Makefile中有效部分; 定义一个多行命令;

(5) 注释: Makefile只有行注释 "#", 如果要使用或者输出"#"字符, 需要进行转义, "\#";

GNU make 的工作方式:

  1. 读入主Makefile (主Makefile中可以引用其他Makefile);
  2. 读入被include的其他Makefile;
  3. 初始化文件中的变量;
  4. 推导隐晦规则, 并分析所有规则;
  5. 为所有的目标文件创建依赖关系链;
  6. 根据依赖关系, 决定哪些目标要重新生成;
  7. 执行生成命令;

Makefile文件注释有以下几种方式:

  • 单行注释:makefile 把 # 字符后面的内容作为注释内容处理(shell、perl 脚本也是使用 # 字符作为注释符)
  • 多行注释:如果需要注释多行,在注释行的结尾加行反斜线(\),下一行也被注释,可以注释多行。

1.2 规则中的通配符

  • *   : 表示任意一个或多个字符;
  • ?    : 表示任意一个字符;
  • [...]: ex. [abcd] 表示a,b,c,d中任意一个字符, [^abcd]表示除a,b,c,d以外的字符, [0-9]表示 0~9中任意一个数字;
  • ~   :表示用户的home目录;
  • %:通配符,如%.o:%c,表示表示把所有的.c文件编译输出成.o文件。

1.3 路径搜索

当一个Makefile中涉及到大量源文件时(这些源文件和Makefile极有可能不在同一个目录中),这时, 最好将源文件的路径明确在Makefile中, 便于编译时查找。Makefile中有个特殊的变量VPATH就是完成这个功能的。指定了 VPATH 之后, 如果当前目录中没有找到相应文件或依赖的文件, Makefile会到VPATH 指定的路径中再去查找。

VPATH 使用方法:

  • vpath <directories>: 当前目录中找不到文件时, 就从<directories>中搜索;
  • vpath <pattern> <directories>:符合<pattern>格式的文件, 就从<directories>中搜索;
  • vpath <pattern>:清除符合<pattern>格式的文件搜索路径;
  • vpath:清除所有已经设置好的文件路径;
复制代码
# 示例1 - 当前目录中找不到文件时, 按顺序从 src目录 ../parent-dir目录中查找文件
VPATH src:../parent-dir   

# 示例2 - .h结尾的文件都从 ./header 目录中查找
VPATH %.h ./header

# 示例3 - 清除示例2中设置的规则
VPATH %.h

# 示例4 - 清除所有VPATH的设置
VPATH
复制代码

1.4 示例

我们以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   (目标,省略的话默认执行第一个目标):

编译并执行:

make
./hello.o

把hello.c文件复制一份,重命名为hello_copy.c。修改Makefile的内容如下:

ALL: $(obj)
$(obj):%.o: %.c
    gcc $< -o $@
.PHONY: clean
clean:
    rm -f $(obj)
  • 第一个target是ALL,而ALL由两个.o文件构成,对应的两个.c文件可以同时编译;
  • target可以是变量,例子中的obj是一个变量;
  • 这里加上了一个.PHONY、<@,究竟是啥呢?这个后面我们会介绍。

二、Makefile函数 

Makefile 中自带了一些函数, 利用这些函数可以简化 Makefile 的编写。

函数调用语法如下:

$(<function> <arguments>)
# 或者
${<function> <arguments>}
  • <function> 是函数名;
  • <arguments> 是函数参数;

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 变量。

示例:

复制代码
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下)。

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

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

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

示例:

# Makefile 内容
all:
    @echo $(filter %.o %.a,program.c program.o program.a)

执行命令:

# bash 中执行 make
$ make
program.o program.a

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

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

示例:

# Makefile 内容
all:
    @echo $(filter-out %.o %.a,program.c program.o program.a)

执行命令:

# bash 中执行 make
$ make
program.c

2.5 if函数

这里的if是个函数,它用于判断条件是否存在或者为true。 和后面将会介绍的条件判断不一样, 后面介绍的条件判断属于Makefile的关键字。

  • 语法:$(if <condition>,<then-part>)$(if <condition>,<then-part>,<else-part>)
  • 函数功能:用于条件判断。
  • 返回值:返回符合条件的部分的值。

示例:

# Makefile 内容
val := a
objects := $(if $(val),$(val).o,nothing)
no-objects := $(if $(no-val),$(val).o,nothing)

all:
    @echo $(objects)
    @echo $(no-objects)

执行命令:

# bash 中执行 make
$ make
a.o
nothing

2.6 创建新的参数化函数call

  • 语法:$(call <expression>,<parm1>,<parm2>,<parm3>...)
  • 函数功能:函数调用。
  • 返回值:返回调用的函数的执行结果。

示例:

# Makefile 内容
log = "====debug====" $(1) "====end===="

all:
    @echo $(call log,"正在 Make")

执行命令:

# bash 中执行 make
$ make
====debug==== 正在 Make ====end====

2.7 判断变量的来源函数orgin

  • 语法:$(origin <variable>)
  • 函数功能:判断变量的来源,返回值有以下类型:

类型

含义

undefined <variable> 没有定义过
default <variable> 是个默认的定义, 比如 CC 变量
environment <variable> 是个环境变量, 并且 make时没有使用 -e 参数
file <variable> 定义在Makefile中
command line <variable> 定义在命令行中
override <variable> 被 override 重新定义过
automatic <variable> 是自动化变量

 示例:

复制代码
# Makefile 内容
val-in-file := test-file
override val-override := test-override

all:
    @echo $(origin not-define)    # not-define 没有定义
    @echo $(origin CC)            # CC 是Makefile默认定义的变量
    @echo $(origin PATH)         # PATH 是 bash 环境变量
    @echo $(origin val-in-file)    # 此Makefile中定义的变量
    @echo $(origin val-in-cmd)    # 这个变量会加在 make 的参数中
    @echo $(origin val-override) # 此Makefile中定义的override变量
    @echo $(origin @)             # 自动变量, 具体前面的介绍
复制代码

执行命令:

复制代码
# bash 中执行 make
$ make val-in-cmd=val-cmd
undefined
default
environment
file
command line
override
automatic
复制代码

2.8 shell

  • 语法:$(shell <shell command>)
  • 函数功能:执行一个shell命令,作用和 `<shell command>` 一样, 是反引号。
  • 返回值:将shell命令的结果作为函数的返回。

2.9 去空格函数strip

  • 语法:$(strip <string>)
  • 函数功能:去掉 <string> 字符串中开头和结尾的空字符。
  • 返回值:被去掉空格的字符串值。

示例:

# Makefile 内容
VAL := "       aa  bb  cc "

all:
    @echo "去除空格前: " $(VAL)
    @echo "去除空格后: " $(strip $(VAL))

执行命令:

# bash 中执行 make
$ make
去除空格前:         aa  bb  cc 
去除空格后: aa bb cc

2.10 查找字符串函数findstring 

  • 语法 $(findstring <find>,<in>)、
  • 函数功能: 在字符串 <in> 中查找 <find> 字符串。
  • 返回值: 如果找到, 返回 <find> 字符串,  否则返回空字符串。

示例:

# Makefile 内容
VAL := "       aa  bb  cc "

all:
    @echo $(findstring aa,$(VAL))
    @echo $(findstring ab,$(VAL))

执行命令:

# bash 中执行 make
$ make
aa

2.11 更多函数

由于Makefile函数过多,就不一一介绍了,更多函数信息请移步此处

三、makefile内置变量

3.1 shell变量(CXX、CC等)

下面只列出一些C相关的:

变量名

含义

RM rm -f
AR ar
CC cc
CXX g++

示例:

复制代码
# Makefile 内容
all:
    @echo $(RM)
    @echo $(AR)
    @echo $(CC)
    @echo $(CXX)

# bash 中执行make, 显示各个变量的值
$ make
rm -f
ar
cc
g++
复制代码

3.2 自动变量($@、%<等)

Makefile 中很多时候通过自动变量来简化书写, 各个自动变量的含义如下:

自动变量

含义

$@ 目标集合
$% 当目标是函数库文件时, 表示其中的目标文件名
$< 第一个依赖目标. 如果依赖目标是多个, 逐个表示依赖目标
$? 比目标新的依赖目标的集合
$^ 所有依赖目标的集合, 会去除重复的依赖目标
$+ 所有依赖目标的集合, 不会去除重复的依赖目标
$* 这个是GNU make特有的, 其它的make不一定支持

示例:

复制代码
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 也设置为伪目标。 

4.3 Makefile命令前缀

我们知道在Makefile文件中是可以编写shell命令的,Makefile 中书写shell命令时可以加2种前缀 @ 和 -, 或者不用前缀。

3种格式的shell命令区别如下:

  • 不用前缀:输出执行的命令以及命令执行的结果, 出错的话停止执行;
  • 前缀 @ :关闭命令回显,只输出命令执行的结果, 出错的话停止执行;
  • 前缀 - :命令执行有错的话, 忽略错误, 继续执行;

不用前缀示例:

# Makefile 内容 (不用前缀)
all:
    echo "没有前缀"
    cat this_file_not_exist
    echo "错误之后的命令"       <-- 这条命令不会被执行

执行命令:

# bash中执行 make
$ make
echo "没有前缀"             <-- 命令本身显示出来
没有前缀                    <-- 命令执行结果显示出来
cat this_file_not_exist
cat: this_file_not_exist: No such file or directory
make: *** [all] Error 1

前缀 @ 示例:

# Makefile 内容 (前缀 @)
all:
    @echo "没有前缀"
    @cat this_file_not_exist
    @echo "错误之后的命令"       <-- 这条命令不会被执行

执行命令:

# bash中执行 make
$ make
没有前缀                         <-- 只有命令执行的结果, 不显示命令本身
cat: this_file_not_exist: No such file or directory
make: *** [all] Error 1

前缀 - 示例:

# Makefile 内容 (前缀 -)
all:
    -echo "没有前缀"
    -cat this_file_not_exist
    -echo "错误之后的命令"       <-- 这条命令会被执行

执行命令:

复制代码
# bash中执行 make
$ make
echo "没有前缀"             <-- 命令本身显示出来
没有前缀                    <-- 命令执行结果显示出来
cat this_file_not_exist
cat: this_file_not_exist: No such file or directory
make: [all] Error 1 (ignored)
echo "错误之后的命令"       <-- 出错之后的命令也会显示
错误之后的命令              <-- 出错之后的命令也会执行
复制代码

4.4 引用其它的Makefile

语法: include <filename>  (filename 可以包含通配符和路径)。

示例:

复制代码
# Makefile 内容
all:
    @echo "主 Makefile begin"
    @make other-all
    @echo "主 Makefile end"

include ./other/Makefile

# ./other/Makefile 内容
other-all:
    @echo "other makefile begin"
    @echo "other makefile end"
复制代码

执行命令:

# bash中执行 make
$ make
主 Makefile begin
make[1]: Entering directory `/path/to/makefile/other'
other makefile begin
other makefile end
make[1]: Leaving directory `/path/to/makefile/other'
主 Makefile end

亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。

日期姓名金额
2023-09-06*源19
2023-09-11*朝科88
2023-09-21*号5
2023-09-16*真60
2023-10-26*通9.9
2023-11-04*慎0.66
2023-11-24*恩0.01
2023-12-30I*B1
2024-01-28*兴20
2024-02-01QYing20
2024-02-11*督6
2024-02-18一*x1
2024-02-20c*l18.88
2024-01-01*I5
2024-04-08*程150
2024-04-18*超20
2024-04-26.*V30
2024-05-08D*W5
2024-05-29*辉20
2024-05-30*雄10
2024-06-08*:10
2024-06-23小狮子666
2024-06-28*s6.66
2024-06-29*炼1
2024-06-30*!1
2024-07-08*方20
2024-07-18A*16.66
2024-07-31*北12
2024-08-13*基1
2024-08-23n*s2
2024-09-02*源50
2024-09-04*J2
2024-09-06*强8.8
2024-09-09*波1
2024-09-10*口1
2024-09-10*波1
2024-09-12*波10
2024-09-18*明1.68
2024-09-26B*h10
2024-09-3010
2024-10-02M*i1
2024-10-14*朋10
2024-10-22*海10
2024-10-23*南10
2024-10-26*节6.66
2024-10-27*o5
2024-10-28W*F6.66
2024-10-29R*n6.66
2024-11-02*球6
2024-11-021*鑫6.66
2024-11-25*沙5
2024-11-29C*n2.88
posted @   大奥特曼打小怪兽  阅读(1038)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2018-05-30 第十九节,去噪自编码和栈式自编码
如果有任何技术小问题,欢迎大家交流沟通,共同进步

公告 & 打赏

>>

欢迎打赏支持我 ^_^

最新公告

程序项目代做,有需求私信(小程序、网站、爬虫、电路板设计、驱动、应用程序开发、毕设疑难问题处理等)。

了解更多

点击右上角即可分享
微信分享提示