MakeFile总结

Makefile核心语法详解

本文总结自陈皓老师《跟我一起写MakeFile》

Makefile的基本概念

Makefile是一种用于自动化构建程序的工具,主要用于管理源代码的编译和链接过程。它依赖于规则(rules)来定义文件的依赖关系及生成方式,通常用于C/C++项目,但也支持其他语言。

Makefile的核心语法

Makefile由一系列规则(rules)组成,每条规则包含目标(target)、依赖(prerequisites)和命令(commands)。基本格式如下:

target: prerequisites  
    commands

目标通常是生成的文件(如可执行文件或目标文件),依赖是生成目标所需的文件,命令是实际执行的Shell命令。

hello:hello.o
	gcc hello.o -o hello
	
hello.o:hello.S
	gcc -c hello.S -o hello.o
	
hello.S:hello.i
	gcc -S hello.i -o hello.S
	
hello.i:hello.c
	gcc -E hello.c -o hello.i
注:越是接近目标文件的命令,就越是要写在前面。因为程序是按照递归的方式进行依赖文件查找的,看到第一行有一个没见过的依赖文件,就往下一行进行查找,以此类推。

Makefile的变量与宏

Makefile支持变量定义,以提高灵活性和可维护性。变量可以通过=:=?=+=定义,例如:

= 操作符(递归展开)

在 Makefile 中,= 是递归展开赋值操作符。通过 = 赋值的变量会在每次被引用时动态展开。这意味着变量的值可以包含其他变量的引用,而这些引用的变量会在最终使用时才被展开。递归展开可能会导致变量值在后续被修改时影响之前引用的结果。

VAR_A = $(VAR_B)
VAR_B = Hello
all:
    @echo $(VAR_A)  # 输出 Hello,因为 VAR_B 的值在最终使用时才展开

:= 操作符(简单展开)

:= 是简单展开赋值操作符。与 = 不同,:= 会在变量定义时就立即展开右侧的表达式,后续对变量的引用不会重新展开。因此,:= 更适合用于需要固定值的场景,避免递归展开带来的不确定性。

VAR_B = World
VAR_A := $(VAR_B)
VAR_B = Hello
all:
    @echo $(VAR_A)  # 输出 World,因为 VAR_A 的值在定义时就已经固定

?= 操作符(条件赋值)

?= 是条件赋值操作符。只有当变量未被定义时,才会将右侧的值赋给变量。如果变量已经被定义(即使为空值),?= 不会覆盖原有值。这种操作符常用于为变量提供默认值。

VAR_A ?= Default
all:
    @echo $(VAR_A)  # 输出 Default
VAR_A = Override
all:
    @echo $(VAR_A)  # 输出 Override

+= 操作符(追加赋值)

+= 是追加赋值操作符。它用于在变量已有的值后追加新内容,类似于字符串拼接。如果变量之前未被定义,+= 的行为与 = 相同。追加的内容可以是字符串或空格分隔的列表。

VAR_A = Hello
VAR_A += World
all:
    @echo $(VAR_A)  # 输出 Hello World

在代码或脚本中,-include <filename> 通常用于指示编译器或解释器在遇到错误时继续执行而不中断。这种语法常见于 Makefile 或其他构建系统中。

在 Makefile 中添加 -include

如果需要在 Makefile 中包含文件并忽略错误,可以在合适的位置加入:

-include filename.mk

这样,即使 filename.mk 不存在或包含错误,Makefile 仍会继续执行。

通配符概述

Makefile中的通配符用于匹配文件名或路径,简化文件操作和规则定义。主要包括以下几种通配符:

*(星号)

匹配任意长度的字符(包括零个字符),通常用于文件名或扩展名匹配。

  • 示例:*.c 匹配当前目录下所有 .c 文件。
  • 注意:不匹配以 . 开头的隐藏文件(如 .gitignore),除非显式指定(如 .*)。

?(问号)

匹配任意单个字符。

  • 示例:file?.txt 匹配 file1.txtfileA.txt,但不匹配 file10.txt
  • 用途:精确匹配文件名中某个位置的单个字符。

[...](字符集)

匹配方括号内任意一个字符。支持范围表示(如 [0-9])和否定(如 [^a-z])。

  • 示例:file[1-3].txt 匹配 file1.txtfile2.txtfile3.txt
  • 否定示例:file[^0-9].txt 匹配非数字结尾的文件名(如 fileA.txt)。

%(百分号)

Makefile特有的模式匹配通配符,常用于规则定义和函数。

  • 示例规则:
    %.o: %.c
        $(CC) -c $^ -o $@
    
    
    %.o : 任意的.o文件
    %.c : 任意的.c文件
    *.o :所有的.o文件
    
    $^ :所有依赖文件
    $@ :所有目标文件
    $< :所有依赖文件的第一个文件
  • 表示将任意 .c 文件编译为同名 .o 文件。
  • Makefile 中$^:表示所有依赖文件的列表,通常用于规则中。例如:
    target: dep1 dep2 dep3
        command $^   # 等价于 dep1 dep2 dep3
  • Makefile 中$@:表示当前规则的目标文件名。例如:
    output.txt: input.txt
        cp $< $@   # 等价于 cp input.txt output.txt
  • Makefile 中$<:表示第一个依赖文件。例如:
    output.txt: input.txt config.txt
        cat $< > $@   # 等价于 cat input.txt > output.txt

~(波浪号)

匹配用户的主目录路径。

  • 示例:~/project 展开为 /home/username/project
  • 特殊形式:~user 匹配指定用户的主目录(如 ~root)。

{}(花括号)

生成多个可能的匹配项(逗号分隔),不是所有Make版本都支持。

  • 示例:file.{c,o} 展开为 file.c file.o
  • 用途:简化重复模式的列举。

条件判断

Makefile支持条件判断(如ifeqifneq、ifdef)和内置函数(如$(shell ...)$(foreach ...)),用于实现动态逻辑。例如:

ifeq (是否相同)

libs_for_gcc = -lgnu 
normal_libs = 
foo: $(objects) 
ifeq ($(CC),gcc) 
$(CC) -o foo $(objects) $(libs_for_gcc) 
else 
$(CC) -o foo $(objects) $(normal_libs) 
endif

我们可以从上面的示例中看到三个关键字:ifeq、else 和 endif。ifeq 的意思表示条
件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括
号括起。else 表示条件表达式为假的情况。endif 表示一个条件语句的结束,任何一个条件
表达式都应该以 endif 结束。 

当我们的变量$(CC)值是“gcc”时,目标 foo 的规则是: 
 
foo: $(objects) 
$(CC) -o foo $(objects) $(libs_for_gcc) 
 
而当我们的变量$(CC)值不是“gcc”时(比如“cc”),目标 foo 的规则是: 
 
foo: $(objects) 
$(CC) -o foo $(objects) $(normal_libs)
ifdef(是否定义)

示例一: 
bar = 
foo = $(bar) 
ifdef foo 
frobozz = yes 
else 
frobozz = no 
endif 
 
示例二: 
foo = 
ifdef foo 
frobozz = yes 
else 
frobozz = no 
endif 

第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。 

函数(字符串操作)

$(subst <from>,<to>,<text>) 
 
名称:字符串替换函数——subst。 
功能:把字串<text>中的<from>字符串替换成<to>。 
返回:函数返回被替换过后的字符串。 
 
示例:
$(subst ee,EE,feet on the street)

把“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the 
strEEt”。 
$(strip <string>) 
 
名称:去空格函数——strip。 
功能:去掉<string>字串中开头和结尾的空字符。 
返回:返回被去掉空格的字符串值。 

示例: 
$(strip a b c ) 
把字串“a b c ”去到开头和结尾的空格,结果是“a b c”。 
findstring 
$(findstring <find>,<in>) 
 
名称:查找字符串函数——findstring。 
功能:在字串<in>中查找<find>字串。 
返回:如果找到,那么返回<find>,否则返回空字符串。 
示例: 
$(findstring a,a b c) 
$(findstring a,b c) 
第一个函数返回“a”字符串,第二个返回“”字符串(空字符串) 
$(filter <pattern...>,<text>) 
名称:过滤函数——filter。 
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以
有多个模式。 
返回:返回符合模式<pattern>的字串。 
示例: 
sources := foo.c bar.c baz.s ugh.h 
foo: $(sources) 
cc $(filter %.c %.s,$(sources)) -o foo 
$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。 

函数(文件名操作) 

dir 
$(dir <names...>) 
名称:取目录函数——dir。 
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前
的部分。如果没有反斜杠,那么返回“./”。 
返回:返回文件名序列<names>的目录部分。 
示例: $(dir src/foo.c hacks)返回值是“src/ ./”。
suffix 
$(suffix <names...>) 
 
名称:取后缀函数——suffix。 
功能:从文件名序列<names>中取出各个文件名的后缀。 
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。 
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。 
basename 
$(basename <names...>) 
 
名称:取前缀函数——basename。 
功能:从文件名序列<names>中取出各个文件名的前缀部分。 
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。 
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/b 
ar hacks”。
shell 函数 
shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统 Shell 的命令。
它和反引号“`”是相同的功能。这就是说,shell 函数把执行操作系统命令后的输出作为
函数返回。于是,我们可以用操作系统命令以及字符串处理命令 awk,sed 等等命令来生成
一个变量,如: 
contents := $(shell cat foo) 
files := $(shell echo *.c) 
注意,这个函数会新生成一个 Shell 程序来执行命令,所以你要注意其运行性能,如果你的
Makefile 中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有
害的。特别是 Makefile 的隐晦的规则可能会让你的 shell 函数执行的次数比你想像的多得
多。
foreach 函数 
foreach 函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile
中的 foreach 函数几乎是仿照于 Unix 标准 Shell(/bin/sh)中的 for 语句,或是 C-Shell
(/bin/csh)中的 foreach 语句而构建的。它的语法是: 
$(foreach <var>,<list>,<text>) 
这个函数的意思是,把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,
然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>
的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串
所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。 
 
所以,<var>最好是一个变量名,<list>可以是一个表达式,而<text>中一般会使用<var>
这个参数来依次枚举<list>中的单词。举个例子: 
names := a b c d 
files := $(foreach n,$(names),$(n).o) 
上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次
根据“$(n)”计算出一个值,这些值以空格分隔,最后作为 foreach 函数的返回,所以,
$(files)的值是“a.o b.o c.o d.o”。 
 
注意,foreach 中的<var>参数是一个临时的局部变量,foreach 函数执行完后,参数
<var>的变量将不在作用,其作用域只在 foreach 函数当中。 

隐晦规则

make 有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书 写 Makefile,这是由 make 所支持的。

make 很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必
要去在每一个[.o]文件后都写上类似的命令,因为,我们的 make 会自动识别,并自己推导
命令。 
 
只要 make 看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果 make
找到一个 whatever.o,那么 whatever.c,就会是 whatever.o 的依赖文件。并且 cc -c 
whatever.c 也会被推导出来,于是,我们的 makefile 再也不用写得这么复杂。我们的是新
的 makefile 又出炉了

objects = main.o kbd.o command.o display.o \ 
insert.o search.o files.o utils.o 
 
edit : $(objects) 
cc -o edit $(objects) 
 
main.o : defs.h 
kbd.o : defs.h command.h 
command.o : defs.h command.h 
display.o : defs.h buffer.h 
insert.o : defs.h buffer.h 
search.o : defs.h buffer.h 
files.o : defs.h buffer.h command.h 
utils.o : defs.h 
 
.PHONY : clean 
clean : 
rm edit $(objects) 

这种方法,也就是 make 的“隐晦规则”。上面文件内容中,“.PHONY”表示,clean
是个伪目标文件。

常见问题与调试技巧  

Makefile常见错误包括缩进错误(命令必须用Tab开头)、变量未定义或路径问题。调试时可使用--debug选项或添加$(info ...)打印变量值。

现代替代方案与总结

尽管Makefile仍被广泛使用,现代工具如CMake、Bazel和Ninja提供了更高级的抽象和跨平台支持。了解Makefile的原理有助于掌握这些工具的核心思想。

"Mstar Bin Tool"是一款专门针对Mstar系列芯片开发的固件处理软件,主要用于智能电视及相关电子设备的系统维护与深度定制。该工具包特别标注了"LETV USB SCRIPT"模块,表明其对乐视品牌设备具有兼容性,能够通过USB通信协议执行固件读写操作。作为一款专业的固件编辑器,它允许技术人员对Mstar芯片的底层二进制文件进行解析、修改与重构,从而实现系统功能的调整、性能优化或故障修复。 工具包中的核心组件包括固件编译环境、设备通信脚本、操作界面及技术文档等。其中"letv_usb_script"是一套针对乐视设备的自动化操作程序,可指导用户完成固件烧录全过程。而"mstar_bin"模块则专门处理芯片的二进制数据文件,支持固件版本的升级、降级或个性化定制。工具采用7-Zip压缩格式封装,用户需先使用解压软件提取文件内容。 操作前需确认目标设备采用Mstar芯片架构并具备完好的USB接口。建议预先备份设备原始固件作为恢复保障。通过编辑器修改固件参数时,可调整系统配置、增删功能模块或修复已知缺陷。执行刷机操作时需严格遵循脚本指示的步骤顺序,保持设备供电稳定,避免中断导致硬件损坏。该工具适用于具备嵌入式系统知识的开发人员或高级用户,在进行设备定制化开发、系统调试或维护修复时使用。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值