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 SCons, CMake, Bazel,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)