在软件或者芯片的开发中,一般都会用到Makefile,它是一个文本文件,其中包含有关如何编译和链接程序的指令。Makefile 由 make 工具使用,make 工具是一个自动化构建工具,可以根据 Makefile 中的指令自动执行编译和链接过程。
Makefile 在芯片开发中的主要作用包括:
targets: prerequisites
command
command
command
其中,targets是我们要执行指令的名字,prerequisites是我们要执行指令的前置条件,command是我们要执行的指令。需要注意的是,command前面需要有tab键(多个tab也可以),但不能使用空格。
hello:
echo "Hello, World"
执行make,结果如下:
img_v3_02ag_b4fe709d-466c-45ba-b806-f658188c249g
可以看到,在执行make后,它会先将我们要执行的指令打印出来,然后再执行该指令。
如果只想看我们的Makefile写的是不是正确,即把我们具体要执行的指令显示出来,而不想执行该指令,可以执行make -n: make hello -n,结果如下:
echo "Hello, World"
对于echo指令,如果只想看执行结果,而不想看回显的结果,可以使用@echo:
hello:
@echo "Hello, World"
如果command前面是空格,那么执行后会提示:
*** missing separator. Stop.
如果有多个targets,比如:
hello1:
echo "Hello1, World"
hello2:
echo "Hello2, World"
我们可以指定执行哪个target:make hello2 如果没有指定,执行了make,那默认就是执行第一个target。
在 Makefile 中,行尾的空格不会被去除,而行首的空格则会被忽略。这意味着如果你需要创建一个包含单个空格的变量,直接在变量赋值时使用空格可能会因行首空格被忽略而导致无法达到预期效果。为了解决这个问题,你可以利用 $(nullstring) 这一特殊变量来确保正确创建含有单个空格的变量。
(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
运行结果如下:
img_v3_02ag_79bd0979-b137-43c1-acef-01c2d6ec023g
如果一行的内容太长,需要放到多行,那就使用\
符号:
some_file:
echo This line is too long, so \
it is broken up into multiple lines
在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后就可以避免这种情况。
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
这样在执行compile时,会先执行clean。
Makefile中,变量只能是string类型,我们看下对变量的一些赋值操作,要区别'='、':='、'?='和'+='这四种赋值方式。
我们举例来详细说明一下:
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结果如下:
img_v3_02ag_381dccbe-57a9-4ec0-9d9b-0365c73f5bdg
我们也可以在执行make时,就对其赋值:make all two=123,结果如下:
img_v3_02ag_37decf44-1e95-4242-9e79-5fb455f1ab4g
+=没有什么歧义,就是简单的串接:
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)
在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
就可以正常打印:one two three。
这是因为$i命令被make翻译成了shell中的i,而此时shell中的i的值就是one two three.
在Makefile中,*和%是都属于通配符。下面来看下他们的用法。
打印所有的c文件:
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上,但不会执行,我们可以检查要执行的指令是否正确。
Makefile内容如下:
one:
false1
false2
@echo "this is one"
img_v3_02ag_424cfa77-95ec-4014-9553-3dc43d8d733g
-i
:可以将Make执行过程的所有错误都显示出现,否则只执行到第一个错误的地方就会停下来。
img_v3_02ag_e760c449-64be-4ba4-9ec5-cb63dd5b662g
-
:在Makefile文件中,通过-
也实现-i
的功能,但只对该行起效 。将Makefile变成下面:
one:
-false1
-false2
@echo "this is one"
在执行make one后,也会显示跟-i
同样的功能:
img_v3_02ag_4045c7e9-0659-4780-9cd5-8d6f0eb3bacg
Makefile中,也有条件语句:
foo = ok
all:
ifeq ($(foo), ok)
echo "foo equals ok"
else
echo "nope"
endif
检查变量是否为空:
nullstring =
foo = $(nullstring) # end of line; there is a space here
all:
ifeq ($(strip $(foo)),)
echo "foo is empty after being stripped"
endif
ifeq ($(nullstring),)
echo "nullstring doesn't even have spaces"
endif
判断一个变量是否被定义了:
bar =
foo = $(bar)
all:
ifdef foo
echo "foo is defined"
endif
ifndef bar
echo "but bar is not"
endif
Makefile中有许多自带的函数供我们直接调用:
bar := ${subst not,totally, "I am not superman"}
all:
@echo $(bar)
img_v3_02ag_b5df05ab-23b3-4d37-b604-c0a4ba98051g
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))
all:
@echo $(bar)
执行结果为:
a,b,c
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))
all:
@echo $(bar)
执行结果为:
, a , b , c
foo := a.o b.o l.a c.o
one := $(patsubst %.o,%.c,$(foo))
# This is a shorthand for the above
two := $(foo:%.o=%.c)
# This is the suffix-only shorthand, and is also equivalent to the above.
three := $(foo:.o=.c)
all:
@echo $(one)
@echo $(two)
@echo $(three)
执行结果为:
img_v3_02ag_acca012c-0537-4f6a-9b6e-c3a8d3e00e4g
foo := who are you
# For each "word" in foo, output that same word with an exclamation after
bar := $(foreach wrd,$(foo),$(wrd)!)
all:
# Output is "who! are! you!"
@echo $(bar)
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)
all:
@echo $(foo)
@echo $(bar)
sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)
all:
# Outputs "Variable Name: sweet_new_fn First: go Second: tigers Empty Variable:"
@echo $(call sweet_new_fn, go, tigers)
all:
@echo $(shell ls -la) # Very ugly because the newlines are gone!
平时用Makefile来做一些芯片方面的像vcs仿真编译这些工作,上面将的Makefile中的知识就大概够用了。但Makefile还有一个经常使用的场景,就是对C文件的编译。这些内容我们在下一篇中讲解。
本文中的很多例子来自:Makefile Tutorial By Example