【一生一芯02】‘RTFSC’-Makefile构建总结

目录

1.makefile总结

1.1makefile基本规则

1.1.1makefile变量

1.1.2make自动推导

1.1.3清空目标文件

1.2makefile综述

1.2.1引用其他makefile

1.2.2makefile工作方式

1.3makefile规则书写

1.3.1通配符的使用

1.3.2文件搜寻 

1.3.3伪目标

 1.3.4多目标静态模式

1.4命令书写

1.4.1显示命令

1.4.2 命令执行

1.4.3命令出错 

1.4.4定义命令包

1.5使用变量 

1.5.1变量基础

 1.5.2变量中的变量

1.5.3变量值的替换

1.5.4追加变量值

1.6条件判断

1.7使用函数

1.7.1函数调用语法

 1.7.2字符串处理函数

1.7.3文件名操作函数

 1.7.4foreach函数

 1.7.5if函数

1.7.6call函数 

1.7.7shell函数

1.8make的运行

1.8.1检查规则

1.8.2make参数

1.9隐含规则

1.9.1使用隐含规则

1.9.2隐含规则使用变量 

1.9.3模式规则

1.9.4★自动化变量★

2.NEMU项目构建

3.AM项目构建

3.1AM-Makefile

3.1.1基础设置和检查

3.1.2一般编译目标

3.1.3常规编译标志

3.1.4特定架构配置

3.1.5编译选项

3.2riscv64-nemu.mk(ARCH)

3.3riscv64.mk(ISA)

3.4nemu.mk(PLATFORM)


1.makefile总结

以下内容参考《跟我一起写makefile》,仅作个人学习总结。

make 命令执行时,需要一个 Makefile 文件,以告诉 make 命令需要怎么样的去编译和链接程序。

1.1makefile基本规则

target...:prerequisites...
    command

target: 目标文件,Objectfile,执行文件,或者标签

prerequisites:生成target所需文件或目标

command:make执行的命令(任意shell命令)

1.1.1makefile变量

Makefile 中的变量其实就是 C/C++中的宏。

objects = main.0 kbd.o display.o insert.o #all .o file
edit: $(objects)
    cc -o edit $(objects) 

1.1.2make自动推导

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

1.1.3清空目标文件

所有生成的文件,都应当可以被清空,这有利于重编译和保持文件清洁。

.PHONY:clean
clean:
    rm edit $(objects)

1.2makefile综述

makefile主要包含五个东西:显式规则,隐式规则,变量定义,文件指示和注释

1.2.1引用其他makefile

makefile使用include关键字可以把别的makefile包含进来

include <filename>

 filename 可以是当前操作系统 Shell 的文件模式(可以保含路径和通配符) 在 include 前面可以有一些空字符,但是绝不能是[Tab]键开始。make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位置。

如果文件都没有指定绝对路径或是相对路径的话, make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:

1."-I"或"--include-dir"所指定目录

2.<prefix>/include中

1.2.2makefile工作方式

GNU 的 make 工作时的执行步骤入下:

1、读入所有的 Makefile。

2、读入被 include 的其它 Makefile。

3、初始化文件中的变量。

4、推导隐晦规则,并分析所有规则。

5、为所有的目标文件创建依赖关系链。

6、根据依赖关系,决定哪些目标要重新生成。

7、执行生成命令。

1.3makefile规则书写

targets : prerequisites 
    command 

或是这样: 
targets : prerequisites ; command 
    command 

1.3.1通配符的使用

~/:当前用户$HOME目录

*.c:表示所有后缀为c的文件

clean: 
    rm -f *.o

1.3.2文件搜寻 

在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当 make 需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是定义一个路径,让 make 在自动去找。

VPATH

VPATH = src:../headers

make 会优先在当前的目录中去找寻依赖文件和目标文件,找不到时会去VPATH中寻找,如上所示会按照"src"和"../headers"顺序去搜索。目录由“冒号”分隔。 

关键字vpath

1、vpath 为符合模式的文件指定搜索目录。

vpath <pattern> <directories>

2、vpath 清除符合模式的文件的搜索目录。

vpath <pattern> 

3、vpath 清除所有已被设置好了的文件搜索目录。

vpath

vapth 使用方法中的需要包含“%”字符。“%”的意思是匹配零或若干字符, 例如,“%.h”表示所有以“.h”结尾的文件。

vpath %.h ../headers

该语句表示,要求 make 在“../headers”目录下搜索所有以“.h”结尾的文件。(如果 某文件在当前目录没有找到的话)

1.3.3伪目标

“伪目标”并不是一个文件,只是一个标签, 由于“伪目标”不是文件,所以 make 无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。

伪目标最好不要和文件名重名,但可以用特殊标记".PHONY"显式指明伪目标,不管是否有该文件,该目标都是伪目标。

.PHONY: clean 
clean: 
    rm *.o temp 

伪目标可以指定依赖,如下所示可以实现“一键三连” 

all : prog1 prog2 prog3 
.PHONY : all 
 
prog1 : prog1.o utils.o 
    cc -o prog1 prog1.o utils.o 
 
prog2 : prog2.o 
    cc -o prog2 prog2.o 
 
prog3 : prog3.o sort.o utils.o 
    cc -o prog3 prog3.o sort.o utils.o 

 伪目标也可以成为依赖,如下所示可以实现“一键清空”

PHONY: cleanall cleanobj cleandiff 
 
cleanall : cleanobj cleandiff 
    rm program 
 
cleanobj : 
    rm *.o 
 
cleandiff : 
    rm *.diff 

 1.3.4多目标静态模式

<targets ...>: <target-pattern>: <prereq-patterns ...> 
    <commands>

targets 定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-parrtern 是指明了 targets 的模式,也就是的目标集模式。

prereq-parrterns 是目标的依赖模式,它对 target-parrtern 形成的模式再进行一次依赖目标的定义。

objects = foo.o bar.o 
 
all: $(objects) 
 
$(objects): %.o: %.c 
    $(CC) -c $(CFLAGS) $< -o $@

命令中的“$<”和“$@”是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)

当文件众多时,利用简单的“静态模式规则”可以高效的完成规则编写。

files = foo.elc bar.o lose.o 
 
$(filter %.o,$(files)): %.o: %.c 
$(CC) -c $(CFLAGS) $< -o $@ 
$(filter %.elc,$(files)): %.elc: %.el 
emacs -f batch-byte-compile $<

 $(filter %.o,$(files))表示调用 Makefile 的 filter 函数,过滤“$filter”集,只要其 中模式为“%.o”的内容。

1.4命令书写

每条命令的开头必须以[Tab]键开头,在命令行之间中的空格或是空行会被忽略。

make 的 命 令 默 认 是 被 “/bin/sh”——UNIX 的标准 Shell 解释执行的。

“#”是注释符,其后的本行字符都被注释。

1.4.1显示命令

通常,make 会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在 命令行前,那么,这个命令将不被 make 显示出来。

echo  git checkout ysyx-tracer
@echo git checkout ysyx-tracer

make执行时,加入参数"-n"或"--just-print",那么将只显示命令,不执行命令,可以利用这个功能调试Makefile。

而 make 参数“-s”或“--slient”则是全面禁止命令的显示。 

1.4.2 命令执行

当依赖目标新于目标时,也就是当规则的目标需要被更新时,make 会一条一条的执行 其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使 用分号分隔这两条命令。

exec: cd /home/hchen; pwd

1.4.3命令出错 

忽略命令的出错,我们可以在 Makefile 的命令行前加一个减号“-” (在 Tab 键之后),标记为不管命令出不出错都认为是成功的。

clean: 
    -rm -f *.o

给make 加上“-i”或是“--ignore-errors”参数,那么,Makefile 中所有命令都会忽略错误。

如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。

参数“-k”或是“--keep-going”的作用是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。

1.4.4定义命令包

如果 Makefile 中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一 个变量。定义这种命令序列的语法以“define”开始,以“endef”结束。

define run-yacc 
    yacc $(firstword $^) 
    mv y.tab.c $@ 
endef 

foo.c : foo.y 
    $(run-yacc) 

1.5使用变量 

在 Makefile 中的定义的变量,就像是 C/C++语言中的宏一样,他代表了一个文本字串,在 Makefile 中执行的时候其会自动原模原样地展开在所使用的地方。其与 C/C++所不同的是,你可以在 Makefile 中改变其值。在 Makefile 中,变量可以使用在“目标”,“依赖目标”,“命令”或是 Makefile 的其它部分中。

1.5.1变量基础

变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。

objects = program.o foo.o utils.o 
program : $(objects) 
    cc -o program $(objects) 
 
$(objects) : defs.h 

 1.5.2变量中的变量

ifeq (0,${MAKELEVEL}) 
cur-dir := $(shell pwd) 
whoami := $(shell whoami) 
host-type := $(shell arch) 
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami} 
endif 

1.5.3变量值的替换

我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以 “a”字串“结尾”的“a”替换成“b”字串。

foo := a.o b.o c.o 
bar := $(foo:.o=.c) 

1.5.4追加变量值

objects = main.o foo.o bar.o utils.o 
objects += another.o 

如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=” 会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符。

1.6条件判断

使用条件判断,可以让 make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。

<conditional-directive> 
<text-if-true> 
endif 

 其中<conditional-directive>表示条件关键字,共有四个:"ifeq","ifneq","ifdef","ifndef"。

1.ifeq

ifeq (<arg1>, <arg2>)

比较参数“arg1”和“arg2”值是否相同,相同为真。 

ifeq ($(strip $(foo)),) 
<text-if-empty> 
endif 

这个示例中使用了“strip”函数,如果这个函数的返回值是空( Empty )那么就生效。

2.ifneq

ifneq (<arg1>, <arg2>) 

和ifeq类似,“arg1”和“arg2”不同时为真。

3.ifdef

ifdef <variable-name> 

如果变量 的值非空,那到表达式为真。否则,表达式为假。当然,同样可以是一个函数的返回值。ifdef只是测试一个变量是否有值,其并不会把变量扩展到当前位置。

4.ifndef

ifndef <variable-name> 

在<conditional-directive>这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)而注释符“#”同样也是安全的。“else”和“endif”也 一样,只要不是以[Tab]键开始就行了。

1.7使用函数

在Makefile 中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make 所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。

1.7.1函数调用语法

$(<function> <arguments>) 

 function是函数名,arguments是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。

comma:= , 
empty:= 
space:= $(empty) $(empty) 
foo:= a b c 
bar:= $(subst $(space),$(comma),$(foo)) 

 1.7.2字符串处理函数

1.subset

$(subst <from>,<to>,<text>) 

 把字串<text>中的<from>字符串替换成<to>,并返回替换过后的字符串。

2.patsubsat

$(patsubst <pattern>,<replacement>,<text>) 

3.strip

$(strip <string>) 

去掉<string>字串中开头和结尾的空字符,并返回字串。 

4.findstring

$(findstring <find>,<in>)

在字串<in>中查找<find>字串,如果找到返回<find>,否则返回空字符串 

5.filter

$(filter <pattern...>,<text>) 

以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词,并返回符合模式<pattern>的字串。可以有多个模式。  

6.filter-out

$(filter-out <pattern...>,<text>) 

7.sort

$(sort <list>) 

8.word

$(word <n>,<text>) 

9.wordlist

$(wordlist <s>,<e>,<text>) 

10.words

$(words <text>) 

11.firstword

$(firstword <text>) 

12.组合使用举例:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

1.7.3文件名操作函数

1.dir

$(dir <names...>) 

2.notdir

$(notdir <names...>)

 3.suffix

$(suffix <names...>) 

4.basename

$(basename <names...>) 

 5.addsuffix

$(addsuffix <suffix>,<names...>) 

6.addprefix

$(addprefix <prefix>,<names...>) 

7. join

$(join <list1>,<list2>) 

 1.7.4foreach函数

$(foreach <var>,<list>,<text>) 

这个函数的意思是,把参数中的单词逐一取出放到参数所指定的变量中,然后再执行所包含的表达式。每一次会返回一个字符串,循环过程中,的所返回的每个字符串会以空格分隔,最后当整个循环结束时,所返回的每个字符串所组成的整个字符串(以空格分隔)是foreach函数的返回值。  

names := a b c d 
files := $(foreach n,$(names),$(n).o) 
#将返回“a.o b.o c.o d.o”

 1.7.5if函数

$(if <condition>,<then-part>) 
#或者
$(if <condition>,<then-part>,<else-part>) 

1.7.6call函数 

类似printf的用法

$(call <expression>,<parm1>,<parm2>,<parm3>...) 
reverse = $(1) $(2) 
foo = $(call reverse,a,b) 

foo的返回值将是“a,b”,参数顺序可以自定 

1.7.7shell函数

contents := $(shell cat foo) 
files := $(shell echo *.c) 

1.8make的运行

make 命令执行后有三个退出码:

0 - 表示成功执行。

1 - 如果 make 运行时出现任何错误,其返回 1。

2 - 如果你使用了 make 的“-q”选项,并且 make 使得一些目标不需要更新,那么返回 2。

1.8.1检查规则

-n 打印命令单步执行。

-t 将目标文件时间更新,但不更改目标文件。

-q 寻找目标。目标存在将不输出信息,目标不存在会打印出错信息。

-W <file> 指定一个文件,make会根据规则推导依赖于这个文件的命令,配合-n使用可以查看依赖该文件发生的规则命令。

1.8.2make参数

-B 将所有目标都更新。

-C 指定读取makefile的目录。

-qp 只输出信息不执行。

-p -f /dev/null 输出makefile文件的文件名和行号。

1.9隐含规则

“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,你根本就不用写出来,make会自动推导出这种规则,并生成我们需要的[.o]文件。

1.9.1使用隐含规则

foo : foo.o bar.o 
    cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

例子中,make的隐含规则会自动推导foo.o bar.o的依赖目标和生成命令。 即省略一下的内容,可以由make隐含规则自动推导。

foo.o : foo.c 
    cc –c foo.c $(CFLAGS) 
bar.o : bar.c 
    cc –c bar.c $(CFLAGS) 

1.9.2隐含规则使用变量 

编译C程序的隐含规则的命令是“$(CC)  –c  $(CFLAGS)  $(CPPFLAGS)”。Make默认的编译命令是“cc”,如果把变量“$(CC)”重定义成“gcc”,把变量“$(CFLAGS)”重定义成“-g”,那么,隐含规则中的命令全部会以“gcc –c -g $(CPPFLAGS)”的样子来执行了。

1.关于命令的变量

CC:C语言编译程序。默认命令是“cc”。

CXX:C++语言编译程序。默认命令是“g++”。

2.关于命令参数的变量

如果没有指明其默认值,那么其默认值都是空。

CFLAGS:C语言编译器参数。

CXXFLAGS:C++语言编译器参数。

CPPFLAGS:C预处理器参数。

LDFLAGS:链接器参数。

1.9.3模式规则

%.o : %.c 
    $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

这个例子中,会把所有所有[.c]文件都编译成[.o]文件。

1.9.4★自动化变量★

所谓自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

$@:表示规则中的目标文件集。

$%:仅当目标是函数库文件中,表示规则中的目标成员名。

$<:依赖目标中的第一个目标名字。

$?:所有比目标新的依赖目标的集合。以空格分隔。

2.NEMU项目构建

2.1 NEMU-Makefile

ifeq ($(wildcard $(NEMU_HOME)/src/nemu-main.c),)
  $(error NEMU_HOME=$(NEMU_HOME) is not a NEMU repo)
endif

这两行代码用于检查 NEMU_HOME 变量是否为有效的 NEMU 项目目录。具体来说,通过 wildcard 命令查找 $(NEMU_HOME)/src/nemu-main.c 文件是否存在,如果不存在,则通过 error 命令打印错误信息。

# Include variables and rules generated by menuconfig
-include $(NEMU_HOME)/include/config/auto.conf
-include $(NEMU_HOME)/include/config/auto.conf.cmd

这两行代码用于引入 menuconfig 生成的配置变量和规则。menuconfig 是一个配置工具,可以通过图形界面方便地配置项目的编译选项,并生成自动化脚本。

remove_quote = $(patsubst "%",%,$(1))

# Extract variabls from menuconfig
GUEST_ISA ?= $(call remove_quote,$(CONFIG_ISA))
ENGINE ?= $(call remove_quote,$(CONFIG_ENGINE))
NAME    = $(GUEST_ISA)-nemu-$(ENGINE)

这段代码用于设置 GUEST_ISAENGINE 变量,分别基于 CONFIG_ISACONFIG_ENGINE 变量。 remove_quote 函数用于去除可能存在的引号。然后,NAME 变量被设置为 GUEST_ISAENGINE 的组合,中间用短横线连接。例如,如果 GUEST_ISA 设置为 x86ENGINE 设置为 qemu,则 NAME 将设置为 x86-nemu-qemu

?= 运算符用于为变量设置默认值。在这种情况下,如果 GUEST_ISAENGINE 变量尚未设置,它们将被设置为 CONFIG_ISACONFIG_ENGINE 变量的值,而不是保留原来的值。如果 GUEST_ISAENGINE 变量已经设置,则它们的值不会更改。这使得 Makefile 可以在这些变量没有指定其他值的情况下使用默认值。

?= += 都是 make 的变量定义运算符。

  • ?= 的意思是,如果变量之前没有被定义过,就将其定义为后面的值,否则,保留之前的值。这个运算符可以用来定义默认值。
  • += 的意思是将变量的值和后面的值进行拼接。这个运算符可以用来往变量添加额外的值。
# Include all filelist.mk to merge file lists
FILELIST_MK = $(shell find ./src -name "filelist.mk")
include $(FILELIST_MK)

# Filter out directories and files in blacklist to obtain the final set of source files
DIRS-BLACKLIST-y += $(DIRS-BLACKLIST)
SRCS-BLACKLIST-y += $(SRCS-BLACKLIST) $(shell find $(DIRS-BLACKLIST-y) -name "*.c")
SRCS-y += $(shell find $(DIRS-y) -name "*.c")
SRCS = $(filter-out $(SRCS-BLACKLIST-y),$(SRCS-y))

这一段代码用于提取源文件列表。

首先,它会查找所有名为 filelist.mk 的文件,并将它们包含到当前文件中。filelist.mk 文件中包含了一些变量,用于指定要编译的源文件。

然后,它会定义一些黑名单变量,用于指定不需要编译的目录和文件。它会先找出所有在黑名单中的目录,并将所有在这些目录中的 .c 文件都加入黑名单。

最后,它会找出所有需要编译的目录,并从中提取所有 .c 文件,最终将它们筛送出黑名单中的文件,得到最终的源文件列表。

在这段代码中,筛选文件列表的过程使用了 filter-out 函数。这个函数会将第二个参数中不在第一个参数中出现过的项目返回。

# Extract compiler and options from menuconfig
CC = $(call remove_quote,$(CONFIG_CC))
CFLAGS_BUILD += $(call remove_quote,$(CONFIG_CC_OPT))
CFLAGS_BUILD += $(if $(CONFIG_CC_LTO),-flto,)
CFLAGS_BUILD += $(if $(CONFIG_CC_DEBUG),-Og -ggdb3,)
CFLAGS_BUILD += $(if $(CONFIG_CC_ASAN),-fsanitize=address,)
CFLAGS_TRACE += -DITRACE_COND=$(if $(CONFIG_ITRACE_COND),$(call remove_quote,$(CONFIG_ITRACE_COND)),true)
CFLAGS  += $(CFLAGS_BUILD) $(CFLAGS_TRACE) -D__GUEST_ISA__=$(GUEST_ISA)
LDFLAGS += $(CFLAGS_BUILD)

这段代码用于从菜单配置中提取编译器和选项,并设置相应的编译器变量。它首先设置 CC 变量为 CONFIG_CC 的值,并使用 remove_quote 函数去除可能存在的引号。然后,它检查了菜单配置中的各种选项,并根据它们来设置 CFLAGS_BUILDCFLAGS_TRACE 变量。最后,它将这些变量用于设置 CFLAGSLDFLAGS 变量,以便在编译时使用它们。这些变量包含了编译器和选项,用于编译程序。

# Include rules for menuconfig
include $(NEMU_HOME)/scripts/config.mk

ifdef CONFIG_TARGET_AM
include $(AM_HOME)/Makefile
LINKAGE += $(ARCHIVES)
else
# Include rules to build NEMU
include $(NEMU_HOME)/scripts/native.mk
endif

这段代码包括了其他 Makefile 中的规则,用于构建 NEMU 程序。include $(NEMU_HOME)/scripts/config.mk 语句包括了 config.mk Makefile 中的规则,用于处理菜单配置选项。ifdef 语句检查 CONFIG_TARGET_AM 变量是否已定义。如果是,则执行 include $(AM_HOME)/Makefile 语句,它包括了 AM_HOME 目录中的 Makefile 中的规则。然后将 LINKAGE 变量附加上 ARCHIVES 变量。如果 CONFIG_TARGET_AM 未定义,则执行 include $(NEMU_HOME)/scripts/native.mk 语句,它包括了 native.mk Makefile 中的规则,用于构建 NEMU 程序。这些规则用于编译程序并链接所有必要的对象文件,以创建最终的可执行文件。

3.AM项目构建

3.1AM-Makefile

AM的框架代码提供了一种可读性更强的markdown版本,可以通过运行指令“make html”获得。

按照官方提供的markdown模式,Makefile文件总共有6个部分。

3.1.1基础设置和检查

### Default to create a bare-metal kernel image
ifeq ($(MAKECMDGOALS),)
  MAKECMDGOALS  = image
  .DEFAULT_GOAL = image
endif

创建裸机内核映像。MAKECMDGOALS为空时,设置为image。

### Override checks when `make clean/clean-all/html`
ifeq ($(findstring $(MAKECMDGOALS),clean|clean-all|html),)

利用findstring函数检查参数MAKECMDGOALS中是否包含clean,clean-all,html,如果则返回值不为空,ifeq为假,后续内容不执行。

### Check: environment variable `$ARCH` must be in the supported list
ARCHS = $(basename $(notdir $(shell ls $(AM_HOME)/scripts/*.mk)))
ifeq ($(filter $(ARCHS), $(ARCH)), )
  $(error Expected $$ARCH in {$(ARCHS)}, Got "$(ARCH)")
endif

通过shell指令获得$(AM_HOME)/scripts/目录下所有[.mk]文件,用notdir函数去掉目录部分,用basename函数去掉后缀,环境变量ARCHS即为所有mk文件的文件名合集。再利用filter函数,如果ARCH变量不在ARCHS列表中,将报error。

(ARCH即为编译时所使用的指令集架构,包含了native,spike,(x86/mips32/riscv32/riscv64)-nemu,riscv64-npc,(x86/x86_64)-qemu)

### Extract instruction set architecture (`ISA`) and platform from `$ARCH`. Example: `ARCH=x86_64-qemu -> ISA=x86_64; PLATFORM=qemu`
ARCH_SPLIT = $(subst -, ,$(ARCH))
ISA        = $(word 1,$(ARCH_SPLIT))
PLATFORM   = $(word 2,$(ARCH_SPLIT))

提取出ARCH中的各个关键字。首先用subst函数将ARCH中所有 '-' 替换成空格。再使用word函数,将架构和平台分别给到变量ISA和PLATFORM。(根据注释中给出的例子可以很直观了解)

3.1.2一般编译目标

### Create the destination directory (`build/$ARCH`)
WORK_DIR  = $(shell pwd)
DST_DIR   = $(WORK_DIR)/build/$(ARCH)
$(shell mkdir -p $(DST_DIR))

shell命令pwd为打印当前目录,将目录传入参数WORK_DIR,在当前目录下创建build/$(ARCH)作为目标目录,再调用shell命令mkdir创建名为DST_DIR的目录。

### Compilation targets (a binary image or archive)
IMAGE_REL = build/$(NAME)-$(ARCH)
IMAGE     = $(abspath $(IMAGE_REL))
ARCHIVE   = $(WORK_DIR)/build/$(NAME)-$(ARCH).a

abspath函数将IMAGE_REL中的各路径转换成绝对路径,并将转换后的结果返回。

### Collect the files to be linked: object files (`.o`) and libraries (`.a`)
OBJS      = $(addprefix $(DST_DIR)/, $(addsuffix .o, $(basename $(SRCS))))
LIBS     := $(sort $(LIBS) am klib) # lazy evaluation ("=") causes infinite recursions
LINKAGE   = $(OBJS) \
  $(addsuffix -$(ARCH).a, $(join \
    $(addsuffix /build/, $(addprefix $(AM_HOME)/, $(LIBS))), \
    $(LIBS) ))

将变量SRCS里所有文件后缀去掉,使用addsuffix函数添加[.d]后缀。sort函数将LIBS内容重新排序。

在变量LINKAGE中,首先用addprefix、addsuffix函数为变量LIBS添加前后缀。再用join函数将两个字串连接,再添加后缀ARCH.a

3.1.3常规编译标志

### (Cross) compilers, e.g., mips-linux-gnu-g++
AS        = $(CROSS_COMPILE)gcc
CC        = $(CROSS_COMPILE)gcc
CXX       = $(CROSS_COMPILE)g++
LD        = $(CROSS_COMPILE)ld
OBJDUMP   = $(CROSS_COMPILE)objdump
OBJCOPY   = $(CROSS_COMPILE)objcopy
READELF   = $(CROSS_COMPILE)readelf

 交叉编译参数选项。

### Compilation flags
INC_PATH += $(WORK_DIR)/include $(addsuffix /include/, $(addprefix $(AM_HOME)/, $(LIBS)))
INCFLAGS += $(addprefix -I, $(INC_PATH))

CFLAGS   += -O2 -MMD -Wall -Werror $(INCFLAGS) \
            -D__ISA__=\"$(ISA)\" -D__ISA_$(shell echo $(ISA) | tr a-z A-Z)__ \
            -D__ARCH__=$(ARCH) -D__ARCH_$(shell echo $(ARCH) | tr a-z A-Z | tr - _) \
            -D__PLATFORM__=$(PLATFORM) -D__PLATFORM_$(shell echo $(PLATFORM) | tr a-z A-Z | tr - _) \
            -DARCH_H=\"arch/$(ARCH).h\" \
            -fno-asynchronous-unwind-tables -fno-builtin -fno-stack-protector \
            -Wno-main -U_FORTIFY_SOURCE
CXXFLAGS +=  $(CFLAGS) -ffreestanding -fno-rtti -fno-exceptions
ASFLAGS  += -MMD $(INCFLAGS)

tr函数为shell命令行中字符串替换函数,"tr a-z A-Z"即为将所有小写替换为大写字符。

3.1.4特定架构配置

-include $(AM_HOME)/scripts/$(ARCH).mk

引用scripts中的makefile文件,‘-’的作用是,当所要包含的文件不存在时不提示错误同时make也不会退出继续进行。

### Fall back to native gcc/binutils if there is no cross compiler
ifeq ($(wildcard $(shell which $(CC))),)
  $(info #  $(CC) not found; fall back to default gcc and binutils)
  CROSS_COMPILE :=
endif

检查交叉编译器,如果没有将跳转回native。

3.1.5编译选项

### Rule (compile): a single `.c` -> `.o` (gcc)
$(DST_DIR)/%.o: %.c
	@mkdir -p $(dir $@) && echo + CC $<
	@$(CC) -std=gnu11 $(CFLAGS) -c -o $@ $(realpath $<)

&&是左边返回0(执行成功)再执行右边,和编程语言的逻辑且比较像。

||是左边返回非0(执行失败)再执行右边,和编程语言的逻辑或比较像。

3.2riscv64-nemu.mk(ARCH)

include $(AM_HOME)/scripts/isa/riscv64.mk
include $(AM_HOME)/scripts/platform/nemu.mk
CFLAGS  += -DISA_H=\"riscv/riscv.h\"

AM_SRCS += riscv/nemu/start.S \
           riscv/nemu/cte.c \
           riscv/nemu/trap.S \
           riscv/nemu/vme.c

AM-Makefile中会调用该文件。

3.3riscv64.mk(ISA)

CROSS_COMPILE := riscv64-linux-gnu-
COMMON_FLAGS  := -fno-pic -march=rv64g -mcmodel=medany -mstrict-align
CFLAGS        += $(COMMON_FLAGS) -static
ASFLAGS       += $(COMMON_FLAGS) -O0
LDFLAGS       += -melf64lriscv

3.4nemu.mk(PLATFORM)

AM_SRCS := platform/nemu/trm.c \
           platform/nemu/ioe/ioe.c \
           platform/nemu/ioe/timer.c \
           platform/nemu/ioe/input.c \
           platform/nemu/ioe/gpu.c \
           platform/nemu/ioe/audio.c \
           platform/nemu/ioe/disk.c \
           platform/nemu/mpe.c

CFLAGS    += -fdata-sections -ffunction-sections
LDFLAGS   += -T $(AM_HOME)/scripts/linker.ld \
             --defsym=_pmem_start=0x80000000 --defsym=_entry_offset=0x0
LDFLAGS   += --gc-sections -e _start
NEMUFLAGS += -l $(shell dirname $(IMAGE).elf)/nemu-log.txt

CFLAGS += -DMAINARGS=\"$(mainargs)\"
CFLAGS += -I$(AM_HOME)/am/src/platform/nemu/include
.PHONY: $(AM_HOME)/am/src/platform/nemu/trm.c

image: $(IMAGE).elf
	@$(OBJDUMP) -d $(IMAGE).elf > $(IMAGE).txt
	@echo + OBJCOPY "->" $(IMAGE_REL).bin
	@$(OBJCOPY) -S --set-section-flags .bss=alloc,contents -O binary $(IMAGE).elf $(IMAGE).bin

run: image
	$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) run ARGS="$(NEMUFLAGS)" IMG=$(IMAGE).bin

gdb: image
	$(MAKE) -C $(NEMU_HOME) ISA=$(ISA) gdb ARGS="$(NEMUFLAGS)" IMG=$(IMAGE).bin

  • 7
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值