一、Why do Makefiles exist?
在软件或者芯片的开发中,一般都会用到 Makefile。它是一个文本文件,其中包含有关如何编译和链接程序的指令。Makefile 由 make 工具使用,make 工具是一个自动化构建工具,可以根据 Makefile 中的指令自动执行编译和链接过程。
Makefile 在芯片开发中的主要作用包括:
-
自动化编译过程:Makefile 可以指定要编译的源代码文件、编译器和编译选项。这使芯片开发人员能够轻松地编译整个项目,而无需手动执行每个编译步骤。
-
管理依赖关系:Makefile 可以指定源文件和目标文件之间的依赖关系。这使 make 工具能够确定哪些文件需要重新编译,从而优化编译过程并节省时间。
-
链接目标文件:Makefile 可以指定要链接的目标文件、链接器和链接选项。这使芯片开发人员能够轻松地将多个目标文件链接到一个可执行文件或库。
-
执行测试和仿真:Makefile 可以包含执行测试和仿真脚本的规则。这使芯片开发人员能够自动化测试和仿真过程,从而提高开发效率。
二、从最简单的Makefile开始
targets: prerequisites
command
command
command
其中,targets是我们要执行指令的名字,prerequisites是我们要执行指令的前置条件,command是我们要执行的指令。需要注意的是,
command前面需要有tab键(多个tab也可以),但不能使用空格。
hello:
echo "Hello, World"
执行make,结果如下:
可以看到,在执行make后,它会先将我们要执行的指令打印出来,然后再执行该指令。
如果只想看我们的Makefile写的是不是正确,即把我们具体要执行的指令显示出来,而不想执行该指令,可以执行make -n: make hello -n,结果如下:
echo "Hello, World"
对于echo指令,如果只想看执行结果,而不想看回显的结果,可以使用@echo:
hello:
@echo "Hello, World"
如果command前面是空格,那么执行后会提示:
*** missing separator. Stop.
2、multiple targets
hello1:
echo "Hello1, World"
hello2:
echo "Hello2, World"
我们可以指定执行哪个target:make hello2
如果没有指定,执行了make,那默认就是执行第一个target。
3、行首行尾的空格
在 Makefile 中,行尾的空格不会被去除,而行首的空格则会被忽略。这意味着如果你需要创建一个包含单个空格的变量,直接在变量赋值时使用空格可能会因行首空格被忽略而导致无法达到预期效果。为了解决这个问题,你可以利用 $(nullstring) 这一特殊变量来确保正确创建含有单个空格的变量。
with_spaces = hello # with_spaces has many spaces after "hello"
after = $(with_spaces)there
nullstring =
space = $(nullstring) # Make a variable with a single space.
all:
echo "$(after)"
echo start"$(space)"end
运行结果如下:
4、multiline
如果一行的内容太长,需要放到多行,那就使用
\
符号:
some_file:
echo This line is too long, so \
it is broken up into multiple lines
5、.PHONY
在Makefile中,我们经常会在clean前面看到 .PHONY:
some_file:
touch some_file
touch clean
.PHONY: clean
clean:
rm -f some_file
rm -f clean
添加这的目的主要是避免免命令与目录下的文件名重复,如果目录下有个clean的文件,我们再执行make clean,Make工具会认为clean没有依赖文件,所以不会执行make clean,加上.PHONY后就可以避免这种情况。
6、make clean
clean命令用于清理编译生成的文件,是一种很常用的指令,当然这个target名字也可以不叫clean,但一般都这么叫。
compile:
touch compile_files
run:
touch run_files
clean:
rm -f compile_files run_files
这样想删除编译或者执行过程中产生的文件,可以直接make clean。
一般来说,我们也会在编译前先将上次生成的文件先clean掉:
compile: clean
touch compile_files
run:
touch run_files
clean:
rm -f compile_files run_files
三、Makefile中的变量
Makefile中,变量只能是string类型,我们看下对变量的一些赋值操作,要区别'='、':='、'?='和'+='这四种赋值方式。
-
= (普通赋值):使用等号(=)进行变量赋值时,定义的是一个递归扩展变量(recursively expanded variable)。这意味着在变量定义时,Makefile 不会立即展开其值,而是将其作为一个待处理的宏,直到该变量在后续规则或表达式中被引用时才进行展开。
-
:= (立即赋值):使用双冒号加等号(:=)进行赋值时,定义的是一个简单扩展变量(simply expanded variable)。这种赋值方式会立即展开并替换所有在定义时已知的变量引用,不会保留任何待处理的宏。一旦赋值完成,变量的值就被固定下来,不再受后续变量定义的影响。
-
?= (条件赋值
)
:
使用问号加等号(?=)进行赋值时,只有在所定义的变量尚未被赋值(即未定义或其值为空)的情况下,才会为其赋予指定的值。如果变量已有非空值,则此次赋值操作会被忽略。
-
+= (追加赋值
)
:
当你使用 += 对一个变量进行赋值时,新指定的值会被添加到该变量当前值的末尾,相当于在两个值之间进行了字符串拼接。这种操作尤其适用于需要累积或累加一系列相关值的场景,例如在构建过程中逐步收集编译选项、源文件列表、链接库路径等。
one = one ${later_variable}
two := two ${later_variable}
later_variable = later
all:
echo $(one)
echo $(two)
运行结果如下:
可以看到,later_variable是在后面定义的,因此one被应用时,才会被赋值later,而two是一开始就被赋值了,而此时later_variable还没有被赋值,因此,two中引用的later_variable是空的。
one = hello
one ?= will not be set
two ?= will be set
all:
echo $(one)
echo $(two)
执行make结果如下:
我们也可以在执行make时,就对其赋值:make all two=123,结果如下:
foo := start
foo += more
all:
echo $(foo)
结果如下:
start more
对于没有定义的变量,那就是空字符串。
all:
# Undefined variables are just empty strings!
echo $(nowhere)
我们上面讲到使用
?=
可以通过terminal执行make指令时指定变量的值,如果我们就是使用的
=
对变量赋值,还能通过terminal对这个变量重新赋值吗?
当然也可以,需要使用
override
这个关键字,我们执行:make all option_one=123,即可覆盖。
# Overrides command line arguments
override option_one = did_override
# Does not override command line arguments
option_two = not_override
all:
echo $(option_one)
echo $(option_two)
四、每一行都是单独的shell
在Makefile中,有一点需要特别注意,就是每一行都是一个单独的shell,在上一行定义的变量,下一行是无效的。对比下面两种makefile的写法:
all:
@export foo=hello
@echo $${foo}
写法二:
all:
@export foo=hello; echo $${foo}
第一种写法的echo内容不会打印出来,第二种写法的echo内容
hello
会打印出来。
这是因为第一种写法中,
export foo=hello
和
echo $${foo}
这两句话是在两个shell中运行的,因此第二行的shell中,无法获取到第一行定义的环境变量。
第二种写法中,在同一行中定义并获取,就可以正常打印。
all:
@export foo=hello \
echo $${foo}
五、$$符号的用法
上面我们使用了
echo $${foo}
,为什么要使用两个
$主要用于将makefile中引用转化为shell引用。
刚接触makefile时,会感觉到有些困惑。
首先需要明确的是,使用make命令执行makefile时并不是shell环境,当执行到makefile的某个操作时才会执行shell。
单独的
$表示引用shell命令中定义的变量的值。
foo = one two three
all:
for i in $(foo); do \
echo $i; \
end
通过make预处理后转为shell:
for i in one two three; do \
echo ; \
end
因为foo已经定义了,所以$(foo)就是one two three;而i变量没有在Makefile中定义,因此转化成了空。
foo = one two three
all:
for i in $(foo); do \
echo $$i; \
end
这是因为$$i命令被make翻译成了shell中的$i,而此时shell中的i的值就是one two three.
六、通配符
在Makefile中,*和%是都属于通配符。下面来看下他们的用法。
print: $(wildcard *.c)
ls -la $?
非常需要注意的是,使用*符号时,需要配合关键字wildcard一起来使用。
thing_wrong := *.o # Don't do this! '*' will not get expanded
thing_right := $(wildcard *.o)
all: one two three four
# Fails, because $(thing_wrong) is the string "*.o"
one: $(thing_wrong)
# Stays as *.o if there are no files that match this pattern :(
two: *.o
# Works as you would expect! In this case, it does nothing.
three: $(thing_right)
# Same as rule three
four: $(wildcard *.o)
%在编译c代码时经常用到,我们会在下面再具体说明。
七、运行时添加的指令
在运行Make时,可以使用下面的指令,来提高我们的调试效率:
-n
:将要执行的指令显示到terminal上,但不会执行,我们可以检查要执行的指令是否正确。
one:
false1
false2
@echo "this is one"
-i
:
可以将Make执行过程的所有错误都显示出现,否则只执行到第一个错误的地方就会停下来。
-
:
在Makefile文件中,通过
-
也实现
-i
的功能,但只对该行起效 。
将Makefile变成下面:
one:
-false1
-false2
@echo "this is one"
在执行make one后,也会显示跟
-i
同样的功能:
八、条件语句
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
检查变量是否为空: