Learning Makefile with PA
Warning
If someone is reading this blog, please be aware that the writer did not consider the experience of the other readers.
After all, the most important part is about writing things down for better memorization.
Preface
- Due to my laziness in PA1, I haven’t really read any Makefile in the ics project, which has caused many troubles in my following study.
- To me, Makefile is even horrible than the C programming language as it is literally completely new to me. (unlike C, at least I had learnt it in my college lessons)
- I believe that if I continue ignoring these Makefiles and indulging my fear, there will be more troubles waiting in the future.
- So I decided to take a thorough look at the the Makefiles in nemu.
BTW, PA provides a way of reading Makefiles in a ‘relatively concise’ way, which is rendering Makefiles as html:
1
2
3### *Get a more readable version of this Makefile* by `make html` (requires python-markdown)
html:
cat Makefile | sed 's/^\([^#]\)/ \1/g' | markdown_py > Makefile.htmlThe result looks like this:
Honestly, this seems worse as the browser does not provide syntax highlight for Makefile…
make run
- Let’s start simple: take a look at what is written inside in the Makefile under nemu root diretory.
Sanity Check
1 | # Sanity check |
- Generally speaking,
make
checks whether the directory defined in environment variableNEMU_HOME
containssrc/nemu-main.c
. - If not, throw an error message and quit the build process.
ifeq...endif
Pretty simple.
example:
1
2
3
4
5ifeq ("hello", "hello world")
@echo "EQUAL"
else
@echo "NOT EQUAL"
endif- Will echo:
NOT EQUAL
- Will echo:
wildcard
… But wildcard expansion does not normally take place when a variable is set, or inside the arguments of a function. If you want to do wildcard expansion in such places, you need to use the wildcard function.
Basically, this function is used to expand wildcard symbols.
example:
1
2wildcard:
@echo $(wildcard *.c)- Will echo the names of all files under current directly with an extension
.c
.
- Will echo the names of all files under current directly with an extension
error
- Pretty simple.
- example: No need
Include variables and rules generated by menuconfig
1 | -include $(NEMU_HOME)/include/config/auto.conf |
- Grasp config variables from other files.
include
When
make
processes an include directive, it suspends reading of the containing makefile and reads from each listed file in turn. When that is finished,make
resumes reading the makefile in which the directive appears.
- Pretty simple.
- example: No need
- NB:
If the specified name does not start with a slash (or a drive letter and colon when GNU Make is compiled with MS-DOS / MS-Windows path support), and the file is not found in the current directory, several other directories are searched.
First, any directories you have specified with the ‘-I’ or ‘–include-dir’ options are searched (see Summary of Options). Then the following directories (if they exist) are searched, in this order:
prefix/include
(normally/usr/local/include
1)/usr/gnu/include,
/usr/local/include,
/usr/include
.
The
.INCLUDE_DIRS
variable will contain the current list of directories that make will search for included files. See Other Special Variables.
About
-include
:If you want make to simply ignore a makefile which does not exist or cannot be remade, with no error message, use the -include directive instead of include, like this:
1
-include <filenames>
Function Definition
1 | remove_quote = $(patsubst "%",%,$(1)) |
Variables
About ?=
, see this chapter below
- Definition and usage of function
remove_quote
.
Function Calls
The call function is unique in that it can be used to create new parameterized functions. You can write a complex expression as the value of a variable, then use call to expand it with different values.
Users can define a custom function and store it in a variable, then call it with the
call
function.example:
1
2
3custom_concat = $(1)$(2)
func:
@echo $(call custom_concat,hello,world)- Will echo:
helloworld
- Will echo:
Text Functions
$(subst from,to,text)
:Performs a textual replacement on the text text: each occurrence of from is replaced by to. The result is substituted for the function call.
example:
1
$(subst ee,EE,feet on the street)
returns:
fEEt on the strEEt
$(patsubst pattern,replacement,text)
:Finds whitespace-separated words in text that match pattern and replaces them with replacement.
example:
1
$(patsubst %.c,%.o,x.c.c bar.c)
returns:
x.c.o bar.o
Setting Variables
To set a variable from the makefile, write a line starting with the variable name followed by one of the assignment operators
=
,:=
,::=
, or:::=
. Whatever follows the operator and any initial whitespace on the line becomes the value.
Variables defined with
=
are recursively expanded variables. Variables defined with:=
or::=
are simply expanded variables; these definitions can contain variable references which will be expanded before the definition is made. Variables defined with:::=
are immediately expanded variables. The different assignment operators are described in See The Two Flavors of Variables.
Recursively Expanded Variable Assignment
example:
1
objects = main.o foo.o bar.o utils.o
The example above is a simplest recursively expaneded variable
objects
. Nothing noticable.
example:
1
2
3
4
5foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:;echo $(foo)Assigning a variable through another variable.
Notice that, the variable called to assign another variable will be expanded whenever it’s value is assigned(substituted):
bar
is not assigned when we assign it’s value tofoo
. The value offoo
is assigned withHuh?
when it is expanded to$(bar)
which is assigned when it expanded to$(ugh)
.This recursive expansion happens at the time of use.
This way of assigning variables does not support self-appending, as it would cause an infinite loop in the variable expansion.
negative case:
1
2# would cause infinite loop
CFLAGS = $(CFLAGS) -O
Simply Expanded Variable Assignment
The value of a simply expanded variable is scanned once, expanding any references to other variables and functions, when the variable is defined. Once that expansion is complete the value of the variable is never expanded again: when the variable is used the value is copied verbatim as the expansion. If the value contained variable references the result of the expansion will contain their values as of the time this variable was defined.
This way of assigning variables is the most common is it works like variables in most programming languages, which means that it is more predictable.
example:
1
2
3x := foo
y := $(x) bar
x := later- After the assignment,
x
islater
,y
isfoo bar
- After the assignment,
- Note that
:=
and::=
are equivalent.
Immediately Expanded Variable Assignment
Another form of assignment allows for immediate expansion, but unlike simple assignment the resulting variable is recursive: it will be re-expanded again on every use. In order to avoid unexpected results, after the value is immediately expanded it will automatically be quoted: all instances of
$
in the value after expansion will be converted into$$.
This type of assignment uses the:::=
operator.
TODO:
This is a relatively new way of assigning variables. Actually, I don’t understand the explanation above(which is from the official document of GNU make), and I didn’t see any usage of such in the ics project.
:
As a result, I am leaving this as a TODO for future.
Conditional Assignment
There is another assignment operator for variables,
?=
. This is called a conditional variable assignment operator, because it only has an effect if the variable is not yet defined.
example:
1
FOO ?= bar
is equivalent to:
1
2
3ifeq ($(origin FOO), undefined)
FOO = bar
endifNote that a variable set to an empty value is still defined, so
?=
will not set that variable.
Shell Assignment
The shell assignment operator ‘!=’ can be used to execute a shell script and set a variable to its output. This operator first evaluates the right-hand side, then passes that result to the shell for execution.
example:
1
FILES != ls
is equivalent to:
1
FILES := $(shell ls)
Though I didn’t find this one used in the ics project as well, I put it here because I like this approach. lol
Pattern Rules
You define an implicit rule by writing a pattern rule. A pattern rule looks like an ordinary rule, except that its target contains the character ‘%’ (exactly one of them). The target is considered a pattern for matching file names; the ‘%’ can match any nonempty substring, while other characters match only themselves. The prerequisites likewise use ‘%’ to show how their names relate to the target name.
Thus, a pattern rule
%.o : %.c
says how to make any filestem.o
from another filestem.c
.
This is something quite like a super simple version of regular expression for matching files under the directory.
Example:
Rule that compiles
.c
files into.o
files:1
2%.o: %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@The recipe uses the automatic variables ‘$@’ and ‘$<’ to substitute the names of the target file and the source file in each case where the rule applies (see Automatic Variables down below).
Automatic Variables
Suppose you are writing a pattern rule to compile a ‘.c’ file into a ‘.o’ file: how do you write the ‘cc’ command so that it operates on the right source file name? You cannot write the name in the recipe, because the name is different each time the implicit rule is applied.
What you do is use a special feature of make, the automatic variables. These variables have values computed afresh for each rule that is executed, based on the target and prerequisites of the rule. In this example, you would use
$@
for the object file name and$<
for the source file name.
- Automatic variables could only be used within recipes.
- Common automatic variables table:
Symbol | Meaning |
---|---|
$@ |
The file name of the target of the rule. When there’re multiple targets, $@ is the name of whichever target caused the rule’s recipe to be run. |
$% |
The target member name when the target is an archive member. Is empty when the target is not an archive member.(See Using make to Update Archive Files) |
$< |
The name of the first prerequisite. If the target got its recipe from an implicit rule, this will be the first prerequisite added by the implicit rule (see Using Implicit Rules). |
$^ |
The names of all the prerequisites, with spaces between them. |
$? |
The names of all the prerequisites that are newer than the target, with spaces between them. |
… | … |
To Be Continued…
- Title: Learning Makefile with PA
- Author: Last
- Created at : 2024-09-29 16:47:02
- Link: https://blog.imlast.top/2024/09/29/makefile-in-pa/
- License: This work is licensed under CC BY-NC-SA 4.0.