make learning notes

Learn Makefiles

Make notes, organize some knowledge points you have learned and add your own understanding

Getting Started

Role of Makefiles

make is a build tool based on a project, and Makefile is its input. make follow the steps in the Makefile to build the project at a time.

In software development projects, makefile is used to determine which files need to be compiled, the dependencies between files, the commands to be executed, and execute them in turn according to the dependencies. Makefile manages dependencies on a file by file basis. The make command can parse the makefile and execute the corresponding shell commands according to the defined dependencies, so as to compile, link and other regular operations of the source file. Make can also perform only the operations affected by the files according to whether the files have changed (by comparing the modify time of the files). The make command itself cannot resolve file dependencies. Developers need to edit dependencies in makefile. Make itself is not responsible for compiling and other operations, but compiles source files by calling gcc, g + + and other commands.

Makefile has its own syntax and supports wildcards, pattern matching and embedded functions, so as to improve its flexibility and writing efficiency.

How to manage the dependencies in the following figure through Makefile?

contrast

Makefile is mainly used for the construction of c and c + + projects. It is also used for the construction of c and c + + projects SConsCMakeBazel,and Ninja . Similar to maven and gradle in java.

edition

View the version of make through make --version

machi@machiunbuntu:~$ make --version
GNU Make 4.2.1
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Makefile syntax

Makefile is composed of two rules. One rule is in the form of:

targets Target (one or more): prerequisites Premises (one or more)
   command
   command
   command
  • targets is the file name, separated by spaces. There is usually only one rule.

  • Command is a series of steps that need to be performed to generate target. Starts with a tab character.

    prerequisites are also file names, separated by spaces. In order to run the commands under the corresponding rule, these files must exist in advance. prerequisites are also called dependencies.

Start example

Makefile files are as follows:

some_file: other_file
    echo "This will run second, because it depends on other_file"
    touch some_file

other_file:
    echo "This will run first"
    touch other_file
    
clean:
    rm -f some_file other_file

Execute the make command directly. The first rule: some is executed by default_ File, because other_file does not exist, so other will be executed first_ file. other_ There is no dependency on file. After execution, execute some_file command.

You can also make some_file or other_file starts with the established rule.

clean is used to delete generated files, usually without dependencies.

variable

Variables can only be strings. Variables can be referenced in three ways: $VaR, $(VaR) and ${var}. However, the first one is not recommended. There may be recognition errors. The second and third are recommended. In addition, in order to distinguish from functions, the third one is recommended.

The following files look like arrays and are essentially space delimited strings.

files = file1 file2
some_file: $(files)
    echo "Look at this variable: " $(files)
    touch some_file

file1:
    touch file1
file2:
    touch file2

clean:
    rm -f file1 file2 some_file

Target Targets

The all target

If there are multiple targets to achieve a target, set the final target to all target

all: one two three

one:
    touch one
two:
    touch two
three:
    touch three

clean:
    rm -f one two three

Multiple targets

If a rule has multiple targets, each target will execute the premise judgment and command of the corresponding rule.

It can be understood that multiple targets are traversed and rules are executed from left to right in turn. For this traversal, Makefile uses $@ to represent the target name entered each time the rule is executed.

$@ see automatic variable for details.

all: f1.o f2.o

f1.o f2.o:
    echo $@
# Equivalent to:
# f1.o
#     echo $@
# f2.o
#     echo $@

Automatic Variables and Wildcards

* Wildcard

*And% are called wildcards in Makefile, but they are used in completely different scenarios* Used to search for matching file names under the file system. It is recommended to use * only under the wildcard function, otherwise there may be a problem that the matching fails and the wildcard string is returned directly.

Use the following example to illustrate the risks of using * directly.

wrong := *.o
  
target: ${wrong}
        echo $^

When files such as a.o and b.o that can be matched exist in the folder, a.o and b.o are returned as prerequisite, which can be executed normally. However, when there is no matching file, ${wrong} will directly return *. O as the prerequisite of the target and report an error.

right := $(wildcard *.c)

target: ${wrong}
        echo $^

When using the wildcard function, the matching fails and returns null directly.

How to print all matching files in the current directory is as follows:

Edit print target, because no file will be generated, so make print will be executed every time. Print the matching file as prerequisite.

print: $(wildcard *.c)
    ls -la  $<

% Wildcard

%It can be applied to a variety of scenarios, sometimes confusing.

  • For pattern matching, you can match one or more characters in a string. The matching part is called the trunk.
  • It is used to replace the pattern. You can extract the trunk on the matching in the string and replace it.
  • %It is commonly used in rule definitions and some specific functions.

Automatic Variables

Type make hello world

hello world: one two
    # Outputs "hey", since this is the first target
    echo $@

    # Outputs all prerequisites newer than the target
    echo $?

    # Outputs all prerequisites
    echo $^
    
	# Outputs first prerequisites
    echo $<

    touch $@

one:
    touch one

two:
    touch two

clean:
    rm -f hello world one two

Fancy Rules

Static Pattern Rules

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

Static pattern matching can be broken down into the following steps:

targets itself is a target list that satisfies a certain target pattern;

Traverse the targets and extract the matching trunk according to the target pattern;

Fill the trunk with the corresponding prereq patterns to generate the corresponding prerequisite for each target.

If static pattern matching is used, create rule s for each. c file and generate the corresponding. o file

objects = foo.o bar.o all.o
all: $(objects)

# Equivalent to 
# foo.o: foo.c
# bar.o: bar.c
# all.o: all.c
$(objects): %.o: %.c

all.c:
    echo "int main() { return 0; }" > all.c

%.c:
    touch $@

clean:
    rm -f *.c *.o all

Static Pattern Rules and Filter

obj_files = foo.result bar.o lose.o
src_files = foo.raw bar.c lose.c

all: $(obj_files)

# Obj through filter function_ files src_ Files variable for filtering
$(filter %.o,$(obj_files)): %.o: %.c
    echo "target: $@ prereq: $<"
$(filter %.result,$(obj_files)): %.result: %.raw
    echo "target: $@ prereq: $<" 

%.c %.raw:
    touch $@

clean:
    rm -f $(src_files)

Implicit Rules

For some commonly used rule s, make will be automatically added for us without our writing. For example:

Perhaps the most confusing part of make is the magic rules and variables that are made. Here's a list of implicit rules:

  • Compiling C program: n.o will automatically compile and generate from n.c through the command $(CC) -c $(CPPFLAGS) $(CFLAGS), without writing corresponding rules
  • Compiling C programs: n.o will automatically compile and generate from n.cc or c.cxx through the command $(CXX) -c $(CPPFLAGS) $(CFLAGS), without writing corresponding rules
  • Link a single target file: n will be automatically generated through the command $(CC) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS) link without writing corresponding rules

Important variables used by implicit rule s:

  • CC: Program for compiling C programs; default cc
  • CXX: Program for compiling C++ programs; default G++
  • CFLAGS: Extra flags to give to the C compiler
  • CXXFLAGS: Extra flags to give to the C++ compiler
  • CPPFLAGS: Extra flags to give to the C preprocessor
  • LDFLAGS: Extra flags to give to compilers when they are supposed to invoke the linker
CC = gcc # Flag for implicit rules
CFLAGS = -g # Flag for implicit rules. Turn on debug info

# Implicit rule #1: blah is built via the C linker implicit rule
# Implicit rule #2: blah.o is built via the C compilation implicit rule, because blah.c exists
blah: blah.o

blah.c:
    echo "int main() { return 0; }" > blah.c

clean:
    rm -f blah*

Pattern Rules

There are two common methods for pattern rules:

  • Custom implicit rules
  • Simplify static mode rules
# Schema rules for compiling arbitrary. c files to. o files
%.o : %.c
        $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
# There are no schema rules for how to schema in the premise, only empty. c files are created
%.c:
   touch $@

Double colon rules

It is allowed to create multiple rules for the same target. If it is a colon, the rules created later under the same target will overwrite the previous one.

all: blah

blah::
    echo "hello"

blah::
    echo "hello again"

Commands and execution

Echo / silence command echo / silencing

By default, the make command will print the currently executed command, and the current command will be cancelled by the prefix @ character.

all: 
    @echo "This make line will not be printed"
    echo "But this will"

Command Execution

The same command line will be executed in the same shell. Different line directories open a new shell.

all: 
    cd ..
    echo `pwd`

    cd ..;echo `pwd`

    cd ..; \
    echo `pwd`

Default shell Default Shell

The default shell is / bin/sh. How can I modify it.

SHELL=/bin/bash

cool:
    echo "Hello from bash"

Exception handling Error handling with -k, -i, and-

The Add -k make command parameter continues to run when an error is encountered, so as to view all errors at once.

Add a - do not catch this line of command exception before adding a command

The Add -i make command parameter continues to run when an error is encountered. Even if the previous dependencies fail, the subsequent jobs will pretend that all the previous dependencies are successful.

all: fail success something_wrong

all success:
        @echo $@

fail:
        exit 1
        @echo $@

something_wrong:
        -false
        @echo $@

Interrupt or kill make Interrupting or killing make

ctrl+c kill make will delete the newly generated target file.

make Recursive use of make

Call makefile recursively and use a specific $(MAKE) instead of make, because $(MAKE) will pass in the make flag and will not be affected.

new_contents = "hello:\n\ttouch inside_file"
all:
    mkdir -p subdir
    printf $(new_contents) | sed -e 's/^ //' > subdir/makefile
    cd subdir && $(MAKE)

clean:
    rm -rf subdir

Use export for recursive make

export outputs a variable so that the sub make command can use this variable.

Note: export is just formally the same as shell export. In fact, there is no relationship between the two.

new_contents = "hello:\n\\techo \$$(cooly)"

all:
    mkdir -p subdir
    echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
    @echo "---MAKEFILE CONTENTS---"
    @cd subdir && cat makefile
    @echo "---END MAKEFILE CONTENTS---"
    cd subdir && $(MAKE)

# Note that variables and exports. They are set/affected globally.
cooly = "The subdirectory can see me!"
export cooly
# This would nullify the line above: unexport cooly

clean:
    rm -rf subdir

When variables need to be run in the shell, they also need to be output through export.

one=this will only work locally
export two=we can run subcommands with this

all: 
    @echo $(one)
    @echo $$one
    @echo $(two)
    @echo $$two

.EXPORT_ALL_VARIABLES outputs all variables.

.EXPORT_ALL_VARIABLES:
new_contents = "hello:\n\techo \$$(cooly)"

cooly = "The subdirectory can see me!"
# This would nullify the line above: unexport cooly

all:
    mkdir -p subdir
    echo $(new_contents) | sed -e 's/^ //' > subdir/makefile
    @echo "---MAKEFILE CONTENTS---"
    @cd subdir && cat makefile
    @echo "---END MAKEFILE CONTENTS---"
    cd subdir && $(MAKE)

clean:
    rm -rf subdir

Arguments to make

https://www.gnu.org/software/make/manual/make.html

Variables Pt. 2

Style and modification

Variables have two styles:

  • Recursive (=) - find the value of a variable only when the variable is called, not when the variable is defined.
  • Simple extensions (: =) - like normal programming languages - extend only variables that have been defined.
# Recursive variables print "later"
one = one ${later_variable}
# Simple extensions do not print "later"
two := two ${later_variable}

later_variable = later

all: 
    echo $(one)
    echo $(two)

Simple extensions (: =) allow concatenation of variables. Recursive definitions will report infinite loop errors.

one = hello
# By defining one as a simple extension (: =), self splicing can be handled
one := ${one} there

all: 
    echo $(one)

?= It only takes effect when the variable is assigned

one = hello
one ?= will not be set
two ?= will be set

all: 
    echo $(one)
    echo $(two)

Spaces at the end of a line are not deleted, but those before the line are deleted. Use $(nullstring) when you need to create a single space variable

with_spaces = hello   # There are many spaces after "hello"
after = $(with_spaces)there

nullstring =
space = $(nullstring) # The variable is a space

all: 
    echo "$(after)"
    echo start"$(space)"end

An undefined variable is essentially an empty string

all: 
    echo $(nowhere)

Use + = append

foo := start
foo += more

all: 
    echo $(foo)

String substitution is also a common and efficient way to modify variables. see Text Functions and Filename Functions.

Command line parameters and overrides

override the variables passed in from the command line. Run the make command make option_one=hi option_two=you

# Override command line parameters
override option_one = did_override
# Do not overwrite command line parameters
option_two = not_override
all: 
    echo $(option_one)
    echo $(option_two)

Command list and definitions

Definition is actually a list of commands. Independent of the equation. Note that this is different from a series of commands separated by semicolons, because each command runs in a separate shell.

one = export blah="I was set!"; echo $$blah

define two
export blah=set
echo $$blah
endef

# One and two are different.

all: 
    @echo "This prints 'I was set'"
    @$(one)
    @echo "This does not print 'I was set' because each command runs in a separate shell"
    @$(two)

Specifies the variable for the target

Variables can be assigned to specific targets

all: one = cool

all: 
    echo one is defined: $(one)

other:
    echo one is nothing: $(one)

Specifies the variables for the schema

Variables can be assigned to the target of a particular pattern

%.c: one = cool

blah.c: 
    echo one is defined: $(one)

other:
    echo one is nothing: $(one)

Conditional statement Makefiles

if/else

foo = ok

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

Check whether the variable is empty

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

Check whether the variable is defined

ifdef does not extend the reference of variables; Only check whether it is defined.

bar =
foo = $(bar)

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

$(makeflags)

This example shows you how to test make flags with findstring and MAKEFLAGS. Run this example with make -i to see it print out the echo statement.

bar =
foo = $(bar)

all:
# Search for the "-i" flag. MAKEFLAGS is just a list of single characters, one per flag. So look for "i" in this case.
ifneq (,$(findstring i, $(MAKEFLAGS)))
    echo "i was passed to MAKEFLAGS"
endif

function

Let's show some functions first

Function is mainly used for text processing. Call the function in the form of $(fn, arguments) or ${fn, arguments}. You can write your own functions through the call built-in function. Make itself has many built-in functions.

bar := ${subst not, totally, "I am not superman"}
all: 
    @echo $(bar)

If you need to replace spaces or commas, use variables

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

all: 
    @echo $(bar)

Parameters other than the first parameter cannot contain spaces. Otherwise, spaces will be treated as part of the string.

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

all: 
    # Output ", a, B, C". Note the addition of spaces
    @echo $(bar)

String substitution

$(patsubst pattern,replacement,text) :

"Find space delimited words in the text that match the pattern and replace them with replacement characters. The pattern may contain a wildcard '%' that matches any number of characters in the word. If the replacement character also contains'% ', the'% 'will be replaced with the corresponding part of the matched string. Only the first'% 'in the pattern will be matched and replaced according to this rule; the subsequent'% ' It won't change. "( GNU docs)

Abbreviation $(text:pattern=replacement).

If only the suffix is replaced, the abbreviation is $(text:suffix=replacement). The% wildcard is not required.

Main: do not add extra spaces when using abbreviations. Spaces will be used as search or replacement content.

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)

foreach function

$(foreach, var, list, text). Convert a series of words separated by spaces into another. var is set to the words in the list, and text expands each word.
The following example adds an exclamation point to each word.

foo := who are you
# For each "word" in foo, add an exclamation point after word and output it.
bar := $(foreach wrd,$(foo),$(wrd)!)

all:
    # Output is "who! are! you!"
    @echo $(bar)

if function

if when checking the first parameter, it is not empty. Run the second parameter, otherwise run the third parameter.

foo := $(if this-is-not-empty,then!,else!)
empty :=
bar := $(if $(empty),then!,else!)

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

call function

Make supports the creation of basic functions. You can "define" a function by creating a variable, but you need to use parameters through $(0), $(1), etc. then you can call custom functions through the call function. The call syntax is $(call variable,param,param). $(0) is the variable name, and $(1), $(2) and so on represent parameters

sweet_new_fn = Variable Name: $(0) First: $(1) Second: $(2) Empty Variable: $(3)

all:
    # Output "variable name: sweet_new_fn (variable name) first: go (first parameter) second: Tigers (second parameter) Empty Variable: (no third parameter is entered)"
    @echo $(call sweet_new_fn, go, tigers)

shell function

Call the function in the shell, but replace the newline with a space!

all: 
    @echo $(shell ls -la) # Newlines are replaced with spaces

Other Features

Include Makefiles

The include directive tells make to read one or more other makefiles. It's a line in the makefile makefile that looks like this:

include filenames...

This is particularly useful when you use compiler flags like -M that create Makefiles based on the source. For example, if some c files includes a header, that header will be added to a Makefile that's written by gcc. I talk about this more in the Makefile Cookbook

The vpath Directive

Use vpath to specify where some set of prerequisites exist. The format is vpath <pattern> <directories, space/colon separated>
<pattern> can have a %, which matches any zero or more characters.
You can also do this globallyish with the variable VPATH

vpath %.h ../headers ../other-directory

some_binary: ../headers blah.h
    touch some_binary

../headers:
    mkdir ../headers

blah.h:
    touch ../headers/blah.h

clean:
    rm -rf ../headers
    rm -f some_binary

Multiline

The backslash ("") character gives us the ability to use multiple lines when the commands are too long

some_file: 
    echo This line is too long, so \
        it is broken up into multiple lines

.phony

Adding .PHONY to a target will prevent make from confusing the phony target with a file name. In this example, if the file clean is created, make clean will still be run. .PHONY is great to use, but I'll skip it in the rest of the examples for simplicity.

some_file:
    touch some_file
    touch clean

.PHONY: clean
clean:
    rm -f some_file
    rm -f clean

.delete_on_error

The make tool will stop running a rule (and will propogate back to prerequisites) if a command returns a nonzero exit status.
DELETE_ON_ERROR will delete the target of a rule if the rule fails in this manner. This will happen for all targets, not just the one it is before like PHONY. It's a good idea to always use this, even though make does not for historical reasons.

.DELETE_ON_ERROR:
all: one two

one:
    touch one
    false

two:
    touch two
    false

Makefile Cookbook

Let's go through a really juicy Make example that works well for medium sized projects.

The neat thing about this makefile is it automatically determines dependencies for you. All you have to do is put your C/C++ files in the src/ folder.

# Thanks to Job Vranish (https://spin.atomicobject.com/2016/08/26/makefile-c-projects/)
TARGET_EXEC := final_program

BUILD_DIR := ./build
SRC_DIRS := ./src

# Find all the C and C++ files we want to compile
# Note the single quotes around the * expressions. Make will incorrectly expand these otherwise.
SRCS := $(shell find $(SRC_DIRS) -name '*.cpp' -or -name '*.c' -or -name '*.s')

# String substitution for every C/C++ file.
# As an example, hello.cpp turns into ./build/hello.cpp.o
OBJS := $(SRCS:%=$(BUILD_DIR)/%.o)

# String substitution (suffix version without %).
# As an example, ./build/hello.cpp.o turns into ./build/hello.cpp.d
DEPS := $(OBJS:.o=.d)

# Every folder in ./src will need to be passed to GCC so that it can find header files
INC_DIRS := $(shell find $(SRC_DIRS) -type d)
# Add a prefix to INC_DIRS. So moduleA would become -ImoduleA. GCC understands this -I flag
INC_FLAGS := $(addprefix -I,$(INC_DIRS))

# The -MMD and -MP flags together generate Makefiles for us!
# These files will have .d instead of .o as the output.
CPPFLAGS := $(INC_FLAGS) -MMD -MP

# The final build step.
$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)
    $(CC) $(OBJS) -o $@ $(LDFLAGS)

# Build step for C source
$(BUILD_DIR)/%.c.o: %.c
    mkdir -p $(dir $@)
    $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

# Build step for C++ source
$(BUILD_DIR)/%.cpp.o: %.cpp
    mkdir -p $(dir $@)
    $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@


.PHONY: clean
clean:
    rm -r $(BUILD_DIR)

# Include the .d makefiles. The - at the front suppresses the errors of missing
# Makefiles. Initially, all the .d files will be missing, and we don't want those
# errors to show up.
-include $(DEPS)

Keywords: Java Back-end

Added by zapa on Fri, 26 Nov 2021 17:19:17 +0200