前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >芯片开发最常用的Makefile语法和功能

芯片开发最常用的Makefile语法和功能

作者头像
猫叔Rex
发布2024-05-03 14:01:29
720
发布2024-05-03 14:01:29
举报
文章被收录于专栏:科学计算科学计算

Why do Makefiles exist?

在软件或者芯片的开发中,一般都会用到Makefile,它是一个文本文件,其中包含有关如何编译和链接程序的指令。Makefile 由 make 工具使用,make 工具是一个自动化构建工具,可以根据 Makefile 中的指令自动执行编译和链接过程。

Makefile 在芯片开发中的主要作用包括:

  • 自动化编译过程:Makefile 可以指定要编译的源代码文件、编译器和编译选项。这使芯片开发人员能够轻松地编译整个项目,而无需手动执行每个编译步骤。
  • 管理依赖关系:Makefile 可以指定源文件和目标文件之间的依赖关系。这使 make 工具能够确定哪些文件需要重新编译,从而优化编译过程并节省时间。
  • 链接目标文件:Makefile 可以指定要链接的目标文件、链接器和链接选项。这使芯片开发人员能够轻松地将多个目标文件链接到一个可执行文件或库。
  • 执行测试和仿真:Makefile 可以包含执行测试和仿真脚本的规则。这使芯片开发人员能够自动化测试和仿真过程,从而提高开发效率。

从最简单的Makefile开始

Makefile的语法

代码语言:javascript
复制
targets: prerequisites
 command
 command
 command

其中,targets是我们要执行指令的名字,prerequisites是我们要执行指令的前置条件,command是我们要执行的指令。需要注意的是,command前面需要有tab键(多个tab也可以),但不能使用空格。

代码语言:javascript
复制
hello:
 echo "Hello, World"

执行make,结果如下:

img_v3_02ag_b4fe709d-466c-45ba-b806-f658188c249g

可以看到,在执行make后,它会先将我们要执行的指令打印出来,然后再执行该指令。

如果只想看我们的Makefile写的是不是正确,即把我们具体要执行的指令显示出来,而不想执行该指令,可以执行make -n: make hello -n,结果如下:

代码语言:javascript
复制
echo "Hello, World"

对于echo指令,如果只想看执行结果,而不想看回显的结果,可以使用@echo:

代码语言:javascript
复制
hello:
 @echo "Hello, World"

如果command前面是空格,那么执行后会提示:

代码语言:javascript
复制
*** missing separator.  Stop.

multiple targets

如果有多个targets,比如:

代码语言:javascript
复制
hello1:
 echo "Hello1, World"

hello2:
 echo "Hello2, World"

我们可以指定执行哪个target:make hello2 如果没有指定,执行了make,那默认就是执行第一个target。

行首行尾的空格

在 Makefile 中,行尾的空格不会被去除,而行首的空格则会被忽略。这意味着如果你需要创建一个包含单个空格的变量,直接在变量赋值时使用空格可能会因行首空格被忽略而导致无法达到预期效果。为了解决这个问题,你可以利用 $(nullstring) 这一特殊变量来确保正确创建含有单个空格的变量。

(nullstring) 是 Makefile 中的一个内置变量,其值为空字符串。通过在变量赋值时结合使用

(nullstring) 和空格,可以确保空格被当作有效字符包含在变量值内,而不被当作无意义的行首空格处理掉。

代码语言:javascript
复制
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

multiline

如果一行的内容太长,需要放到多行,那就使用\符号:

代码语言:javascript
复制
some_file: 
 echo This line is too long, so \
  it is broken up into multiple lines

.PHONY

在Makefile中,我们经常会在clean前面看到.PHONY:

代码语言:javascript
复制
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后就可以避免这种情况。

make clean

clean命令用于清理编译生成的文件,是一种很常用的指令,当然这个target名字也可以不叫clean,但一般都这么叫。

代码语言:javascript
复制
compile: 
 touch compile_files

run:
 touch run_files

clean:
 rm -f compile_files run_files

这样想删除编译或者执行过程中产生的文件,可以直接make clean。

一般来说,我们也会在编译前先将上次生成的文件先clean掉:

代码语言:javascript
复制
compile: clean
 touch compile_files

run:
 touch run_files

clean:
 rm -f compile_files run_files

这样在执行compile时,会先执行clean。

Makefile中的变量

Makefile中,变量只能是string类型,我们看下对变量的一些赋值操作,要区别'='、':='、'?='和'+='这四种赋值方式。

  • = (普通赋值): 使用等号(=)进行变量赋值时,定义的是一个递归扩展变量(recursively expanded variable)。这意味着在变量定义时,Makefile 不会立即展开其值,而是将其作为一个待处理的宏,直到该变量在后续规则或表达式中被引用时才进行展开。
  • := (立即赋值):使用双冒号加等号(:=)进行赋值时,定义的是一个简单扩展变量(simply expanded variable)。这种赋值方式会立即展开并替换所有在定义时已知的变量引用,不会保留任何待处理的宏。一旦赋值完成,变量的值就被固定下来,不再受后续变量定义的影响。
  • ?= (条件赋值): 使用问号加等号(?=)进行赋值时,只有在所定义的变量尚未被赋值(即未定义或其值为空)的情况下,才会为其赋予指定的值。如果变量已有非空值,则此次赋值操作会被忽略。
  • += (追加赋值):当你使用 += 对一个变量进行赋值时,新指定的值会被添加到该变量当前值的末尾,相当于在两个值之间进行了字符串拼接。这种操作尤其适用于需要累积或累加一系列相关值的场景,例如在构建过程中逐步收集编译选项、源文件列表、链接库路径等。

我们举例来详细说明一下:

代码语言:javascript
复制
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是空的。

代码语言:javascript
复制
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

+=没有什么歧义,就是简单的串接:

代码语言:javascript
复制
foo := start
foo += more

all: 
 echo $(foo)

结果如下:

代码语言:javascript
复制
start more

对于没有定义的变量,那就是空字符串。

代码语言:javascript
复制
all: 
 # Undefined variables are just empty strings!
 echo $(nowhere)

我们上面讲到使用 ?= 可以通过terminal执行make指令时指定变量的值,如果我们就是使用的=对变量赋值,还能通过terminal对这个变量重新赋值吗?

当然也可以,需要使用override这个关键字,我们执行:make all option_one=123,即可覆盖。

代码语言:javascript
复制
# 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的写法:

写法一:

代码语言:javascript
复制
all:
 @export foo=hello
 @echo $${foo}

写法二:

代码语言:javascript
复制
all:
 @export foo=hello; echo $${foo}

第一种写法的echo内容不会打印出来,第二种写法的echo内容hello会打印出来。

这是因为第一种写法中,export foo=helloecho $${foo}这两句话是在两个shell中运行的,因此第二行的shell中,无法获取到第一行定义的环境变量。

第二种写法中,在同一行中定义并获取,就可以正常打印。

当然也可以最第一行最后加反斜杠进行转义:

代码语言:javascript
复制
all:
 @export foo=hello \
 echo $${foo}

$$符号的用法

上面我们使用了echo ${foo},为什么要使用两个符号主要用于将makefile中引用转化为shell引用。

刚接触makefile时,会感觉到有些困惑。

首先需要明确的是,使用make命令执行makefile时并不是shell环境,当执行到makefile的某个操作时才会执行shell。

单独的

符号就是引用makefile中定义变量的值,

$表示引用shell命令中定义的变量的值。

代码语言:javascript
复制
foo = one two three
all:
 for i in $(foo); do \
  echo $i; \
 end

通过make预处理后转为shell:

代码语言:javascript
复制
for i in one two three; do \
 echo ; \
end

因为foo已经定义了,所以$(foo)就是one two three;而i变量没有在Makefile中定义,因此转化成了空。

如果改成下面的写法:

代码语言:javascript
复制
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文件:

代码语言:javascript
复制
print: $(wildcard *.c)
 ls -la $?

非常需要注意的是,使用*符号时,需要配合关键字wildcard一起来使用。

代码语言:javascript
复制
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内容如下:

代码语言:javascript
复制
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变成下面:

代码语言:javascript
复制
one:
 -false1
 -false2
 @echo "this is one"

在执行make one后,也会显示跟-i同样的功能:

img_v3_02ag_4045c7e9-0659-4780-9cd5-8d6f0eb3bacg

条件语句

Makefile中,也有条件语句:

代码语言:javascript
复制
foo = ok

all:
ifeq ($(foo), ok)
 echo "foo equals ok"
else
 echo "nope"
endif

检查变量是否为空:

代码语言:javascript
复制
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

判断一个变量是否被定义了:

代码语言:javascript
复制
bar =
foo = $(bar)

all:
ifdef foo
 echo "foo is defined"
endif
ifndef bar
 echo "but bar is not"
endif

Makefile中的函数

Makefile中有许多自带的函数供我们直接调用:

subst函数

代码语言:javascript
复制
bar := ${subst not,totally, "I am not superman"}
all: 
 @echo $(bar)

img_v3_02ag_b5df05ab-23b3-4d37-b604-c0a4ba98051g

代码语言:javascript
复制
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space),$(comma),$(foo))

all: 
 @echo $(bar)

执行结果为:

代码语言:javascript
复制
a,b,c
代码语言:javascript
复制
comma := ,
empty:=
space := $(empty) $(empty)
foo := a b c
bar := $(subst $(space), $(comma) , $(foo))

all: 
 @echo $(bar)

执行结果为:

代码语言:javascript
复制
, a , b , c

patsubst函数

代码语言:javascript
复制
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

foreach函数

代码语言:javascript
复制
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)

if函数

代码语言:javascript
复制
foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)

all:
 @echo $(foo)
 @echo $(bar)

call函数

代码语言:javascript
复制
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)

shell 函数

代码语言:javascript
复制
all: 
 @echo $(shell ls -la) # Very ugly because the newlines are gone!

平时用Makefile来做一些芯片方面的像vcs仿真编译这些工作,上面将的Makefile中的知识就大概够用了。但Makefile还有一个经常使用的场景,就是对C文件的编译。这些内容我们在下一篇中讲解。

本文中的很多例子来自:Makefile Tutorial By Example

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2024-05-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 傅里叶的猫 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Why do Makefiles exist?
  • 从最简单的Makefile开始
    • Makefile的语法
      • multiple targets
        • 行首行尾的空格
          • multiline
            • .PHONY
              • make clean
              • Makefile中的变量
              • 每一行都是单独的shell
              • $$符号的用法
              • 通配符
              • 运行时添加的指令
              • 条件语句
              • Makefile中的函数
                • subst函数
                  • patsubst函数
                    • foreach函数
                      • if函数
                        • call函数
                          • shell 函数
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档