Learning Makefile with PA

Last

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.html
  • The result looks like this:

    image.png

  • 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
2
3
4
# Sanity check
ifeq ($(wildcard $(NEMU_HOME)/src/nemu-main.c),)
$(error NEMU_HOME=$(NEMU_HOME) is not a NEMU repo)
endif
  • Generally speaking, make checks whether the directory defined in environment variable NEMU_HOME contains src/nemu-main.c.
  • If not, throw an error message and quit the build process.

ifeq...endif

  • Pretty simple.

  • example:

    1
    2
    3
    4
    5
    ifeq ("hello", "hello world")
    @echo "EQUAL"
    else
    @echo "NOT EQUAL"
    endif
    • Will echo: NOT EQUAL

wildcard

GNU make manual :

… 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
    2
    wildcard:
    @echo $(wildcard *.c)
    • Will echo the names of all files under current directly with an extension .c.

error

  • Pretty simple.
  • example: No need

Include variables and rules generated by menuconfig

1
2
-include $(NEMU_HOME)/include/config/auto.conf
-include $(NEMU_HOME)/include/config/auto.conf.cmd
  • Grasp config variables from other files.

include

GNU make manual :

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
2
3
4
5
6
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)

Variables

About ?=, see this chapter below

  • Definition and usage of function remove_quote.

Function Calls

GNU make manual :

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
    3
    custom_concat = $(1)$(2)
    func:
    @echo $(call custom_concat,hello,world)
    • Will echo: helloworld

Text Functions

GNU make manual

  • $(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

GNU make manual


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
    5
    foo = $(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 to foo. The value of foo is assigned with Huh? 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
    3
    x := foo
    y := $(x) bar
    x := later
    • After the assignment, x is later, y is foo bar

  • 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
    3
    ifeq ($(origin FOO), undefined)
    FOO = bar
    endif
  • Note 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

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.
Comments