Introduction to Make:
-
Project Manager, as the name suggests, refers to managing multiple files -
The Make project manager is essentially an “automated compilation manager“. Here, “automated” means it can automatically detect updated files based on file timestamps to reduce compilation workload. It performs a large amount of compilation work by reading the contents of the Makefile. -
==Make will only compile the changed code files, rather than recompiling everything.==
Whether one can write a Makefile reflects, in a way, whether a person has the capability to complete large projects. The Makefile relates to the entire project’s compilation rules. The source files in a project are not counted; they are placed in several directories according to type, function, and module, and the Makefile defines a series of rules to specify:
Which files need to be compiled first, which files need to be compiled later, which files need recompilation, and even perform more complex operations, as the Makefile can execute operating system commands like a Shell script. The benefit of the Makefile is—”automated compilation”. Once written, only a make command is needed, and the entire project compiles automatically, greatly improving software development efficiency.
Basic Structure of Makefile:
-
The Makefile is the only configuration file read by Make. -
The target created by the make tool, usually an object file or executable file. -
The files that the target depends on (dependency_file). -
The commands that need to be executed when creating each target (command).
==Note:== The command line must start with a “**==TAB==**” key; otherwise, the compilation error will be: *** missing separator. Stop.
For example:
Makefile Format:
target : dependency_files
command
target // Target: target is an object file, it can be an Object File or an executable file. It can also be a label (Label).
dependency_files //Files needed to generate the target: dependency_files are the files or targets required to generate that target.
command is the command that make needs to execute (any Shell command).
This is a file dependency relationship, meaning that the target file depends on the files in dependency_files, and the generation rules are defined in command. **==Simply put, if there is more than one file in dependency_files that is newer than the target file, the command defined will be executed. This is the rule of Makefile, and also the core content of Makefile.==**
==[Note]==: When looking at someone else’s Makefile, you may encounter the following three variables: $@
, $^
, $<
represent:
These three are very important variables, representing the meanings:
**
$@: Target file
,$^: All dependency files
,$<: The first dependency file
**.
We will continue to explain the issue of these variables below.
More complex examples:
sunq:kang.o yul.o
gcc kang.o yul.o -o sunq
kang.o:kang.c kang.h
gcc -Wall -O -g -c kang.c -o kang.o
yul.o:yul.c yul.h
gcc -Wall -O -g -c yul.c -o yul.o
clean:
rm *.o test
Note: —Wall: Indicates that all useful warning messages from gcc are allowed. —c: Just compile without linking, generating target file “.o” —o file: Indicates that the output file is output to file.
We can save this content in a file named “Makefile” or “makefile”, and then enter the command “make” directly in that directory to generate the executable file sunq. If you want to delete the executable file and all intermediate target files, you just need to execute “make clean”.
In this Makefile, == the target files (target) include: executable file sunq and intermediate target files (*.o), the dependency files (prerequisites) are those .c files and .h files after the colon. Each .o file has a set of dependency files, and these .o files are also dependency files for the executable file sunq. The essence of the dependency relationship is to indicate which files generate the target file ==, in other words, which files have been updated.
After defining the dependency relationship, the subsequent line defines how to generate the target file using the operating system command, **== it must start with a Tab key ==**. Remember, make does not care how the command works; it only executes the defined command. Make will compare the modification dates of the targets and dependency files. If the modification date of the dependency_files is newer than that of the targets, or if the target does not exist, then make will execute the subsequent defined command.
1. How make Works
Most makes support both “makefile” and “Makefile” as default filenames, you can use other filenames to write Makefile,
For example: “Make.Linux”, “Make.Solaris”, “Make.AIX”, etc. If you want to specify a specific Makefile, you can use the make “-f” and “–file” parameters, such as: **make -f Make.Linux
or make --file Make.AIX
**.
In the default mode, that is, when we only enter the make command, then,
-
make will look for a file named “Makefile” or “makefile” in the current directory. -
If found, it will look for the first target file in the file (target), in the example above, it will find the file “sunq” and use this file as the final target file. -
If the sunq file does not exist, or if the modification time of the .o files that sunq depends on is newer than that of the sunq file, then it will execute the defined commands to generate the sunq file. -
If the .o files that sunq depends on do not exist, then make will look for the dependency of the target in the current file. If found, it will generate the .o files based on that rule (this is somewhat like a stack process). -
Of course, your C files and H files exist, so make will generate the .o files, and then declare make’s ultimate task with the .o files, which is the executable file sunq.
This is the entire dependency of make, which will look for the file dependencies layer by layer until it finally compiles the first target file. During the search process, if an error occurs, such as the last dependent file not being found, then make will exit directly and report an error, while for errors in the defined commands or unsuccessful compilations, make will not care.
Make only cares about file dependencies, that is, if after looking for dependencies, the files after the colon are still not present, then sorry, I will not work.
The execution steps of make during work are as follows: (I believe other makes are similar)
Read all Makefiles.
Read other Makefiles that are included.
Initialize variables in the file.
Infer implicit rules and analyze all rules.
Create dependency chains for all target files.
Decide which targets need to be regenerated based on dependencies.
Execute generation commands.
Steps 1-5 are the first stage, and 6-7 are the second stage. In the first stage, if the defined variables are used, then make will expand them at the point of use. However, make does not expand them immediately; it uses a delaying tactic. If a variable appears in the dependency rule, it will only be expanded when that dependency is decided to be used.
2. Understanding Dependencies in Makefile
Assuming the current project directory is object/, there are six files: main.c, abc.c, xyz.c, abc.h, xyz.h, and Makefile. Among them, main.c includes header files abc.h and xyz.h, abc.c includes header file abc.h, xyz.c includes header file xyz.h, and abc.h includes xyz.h. Their dependency relationships are shown in the diagram. The Makefile should be written like this (assuming the target is main):
main:main.o abc.o xyz.o
gcc main.o abc.o xyz.o -o main
main.o:main.c abc.h xyz.h
gcc -c main.c –o main.o -g
abc.o:abc.c abc.h xyz.h
gcc -c abc.c –o abc.o -g
xyz.o:xyz.c xyz.h
gcc -c xyz.c -o xyz.o -g
.PHONY:clean
clean:
rm main main.o abc.o xyz.o -f
3. Rules for Writing Makefile
Rules consist of two parts: == Dependency Relationships == and == Methods for Generating Targets ==.
In Makefile, the order of rules is very important because, **== there should only be one final target in Makefile ==**, and all other targets are derived from this target So == you must let make know what your final target is ==.
Generally speaking, there may be many targets defined in Makefile, but the target in the first rule will be established as the final target.==If there are many targets in the first rule, the first target will become the final target
==. What make accomplishes is this target. **
3.1 Example of a Rule
foo.o: foo.c defs.h # foo module
cc -c -g foo.c
Seeing this example, you should not be unfamiliar with it. The previous foo.o is our target, foo.c and defs.h are the source files that the target depends on, and there is only one command “cc -c -g foo.c” (starting with a Tab key). This rule tells us two things:
-
The dependency relationship, foo.o depends on foo.c and defs.h. If the dates of foo.c and defs.h are newer than that of foo.o, or if foo.o does not exist, then the dependency relationship occurs.
-
To generate (or update) the foo.o file. That is the cc command, which explains how to generate the foo.o file. (Of course, the foo.c file includes the defs.h file)
4. Basic Use of Makefile
Next, let’s do an example to learn how to write a Makefile
Write two C programs Write a head.h header file to declare the above functions
Write a main program
If written this way, if the Chinese files are modified, if there are too many files, it will be troublesome.
So using Makefile will bring great surprises.
Makefile
test:fun1.o fun2.o main.o
gcc fun1.o fun2.o main.o -o test
fun2.o:fun2.c
gcc -c -Wall fun2.c -o fun2.o
fun1.o:fun1.c
gcc -c -Wall fun1.c -o fun1.o
main.o:main.c
gcc -c -Wall main.c -o main.o
Internal process of Makefile If I want to change one of the C files, modify fun2.c
After modifying, make finds that only fun2.c is regenerated to fun2.o, because fun2.o is newly generated, it also needs to regenerate test.
Results
Makefile will generate many intermediate files in the folder
When we need to clean, we == often use make-related commands to clean ==, rather than deleting one by one with rm.
clean:
rm *.o test
make + target name
This way, all intermediate files are cleaned
Phony Target: A file that will definitely be executed, if it has the same name
If it has the same name, it is found that clean does not work, as it defaults that it has not been modified, so it does not work. How to avoid this problem?
In the Makefile, add .PHONY:command==.PHONY:
implies==“.PHONY” indicates that clean is a phony target file.
.PHONY:clean
This way, it will not be delayed by the same name.
Every Makefile should write a rule to clear target files (.o and executable files), which is not only convenient for recompilation but also helps keep files clean. The general style is:
clean:
rm edit $(objects)
A more robust approach is:
.PHONY : clean
clean :
-rm edit $(objects)
As mentioned earlier, .PHONY means that clean is a “phony target”. And adding a small minus sign before the rm command means that if some files have problems, don’t care, continue with the subsequent actions.
Of course, the clean rule should not be placed at the beginning of the file, otherwise, it will become the default target of make, and I believe no one wants this. The unwritten rule is—”clean is always placed at the end of the file”.
3. Understanding Dependencies in Makefile
Assuming the current project directory is object/, there are six files: main.c, abc.c, xyz.c, abc.h, xyz.h, and Makefile. Among them, main.c includes header files abc.h and xyz.h, abc.c includes header file abc.h, xyz.c includes header file xyz.h, and abc.h includes xyz.h. Their dependency relationships are shown in the diagram. The Makefile should be written like this (assuming the target is main):
main:main.o abc.o xyz.o
gcc main.o abc.o xyz.o -o main
main.o:main.c abc.h xyz.h
gcc -c main.c –o main.o -g
abc.o:abc.c abc.h xyz.h
gcc -c abc.c –o abc.o -g
xyz.o:xyz.c xyz.h
gcc -c xyz.c -o xyz.o -g
.PHONY:clean
clean:
rm main main.o abc.o xyz.o -f
4. Creating and Using Variables
For easier maintenance of Makefile, we can use variables in Makefile. Variables in Makefile are strings, which may be better understood as macros in C language.
In the above Makefile example:
objects = fun1.o fun2.o main.o
For instance, we declare a variable called objects, which can represent the obj files. We define it at the beginning of the Makefile:
objects = fun1.o fun2.o main.o
Thus, we can conveniently use this variable in our Makefile as “**$(objects)
**”, so our improved Makefile becomes:
objects = fun1.o fun2.o main.o
test:$(objects)
gcc fun1.o fun2.o main.o -o test
fun2.o:fun2.c
gcc -c -Wall fun2.c -o fun2.o
fun1.o:fun1.c
gcc -c -Wall fun1.c -o fun1.o
main.o:main.c
gcc -c -Wall main.c -o main.o
.PHONY:clean
clean:
rm *.o test
So if new .o files are added, we just need to modify the objects variable.
Let’s summarize briefly:
2.1 Purpose of Creating Variables:
To replace a text string:
Names of a series of files Parameters passed to the compiler Programs to be run Directories to search for source code Directories where you want to output information Other things you want to do.
2.2 How to Define Variables:
Two ways to define variables
Recursive expansion method VAR=var
Simple way VAR:=var
Variable usage $(VAR)
=========================Use “$” Similar to macros in programming languages
Let’s take another example:
sunq:kang.o yul.o
gcc kang.o yul.o -o sunq
kang.o:kang.c kang.h
gcc -Wall -O -g -c kang.c -o kang.o
yul.o:yul.c yul.h
gcc -Wall -O -g -c yul.c -o yul.o
.PHONY:clean
clean:
rm *.o test
Using variables to replace:
OBJS = kang.o yul.o
CC = gcc
CFLAGS = -Wall -O -g
sunq : $(OBJS)
$(CC) $(OBJS) -o sunq
kang.o : kang.c kang.h
$(CC) $(CFLAGS) -c kang.c -o kang.o
yul.o : yul.c yul.h
$(CC) $(CFLAGS) -c yul.c -o yul.o
.PHONY:clean
clean:
rm *.o test
-
Recursive expansion method VAR=var
example:
foo = $(bar)
bar = $(ugh)
ugh = Huh?
(foo) to check
Advantages: It allows backward referencing of variablesDisadvantages: Cannot expand that variable,
For example, CFLAGS = $(CFLAGS)-0 will cause an infinite loop
-
Simple way VAR:=var
m := mm
x:=$(m)
y:= $(x) bar
x:=later
echo $(x) $(y)
For example: the value of m variable is mm, m’s value is assigned to x
(This variable method is more like C language)
==Use ?= to define variables==
dir :=/foo/bar
FOO?=bar
FOO is?
? means that if FOO has not been defined before, then the value of variable FOO will be “bar”, if FOO has been defined earlier, then this statement will do nothing, which is equivalent to:
ifeq ($(origin FOO),undefined)
FOO=bar
endif
-
Adding values to variables
You can add new values to an already defined variable using +=
Main=hello.o hello-1.o
Main+=hello-2.o
-
Predefined Variables
-AR | Name of the library file maintenance program, default value is ar. AS is the name of the assembler, default value is as. |
-CC | Name of the C compiler, default value is cc. CPPC is the name of the preprocessor, default value is $(CC) -E. |
-CXX | Name of the C++ compiler, default value is g++. |
-FC | Name of the FORTRAN compiler, default value is f77. |
-RM | Name of the file deletion program, default value is rm -f. |
-
Automatic Variables
– $* | Name of the target file without extension. |
-$+ | All dependency files, separated by spaces, may include duplicate dependency files. |
-$< | Name of the first dependency file. |
-$? | All dependency files with timestamps later than the target file, separated by spaces. |
-$@ | Full name of the target file. |
-$^ | All non-duplicate target dependency files, separated by spaces. |
-$% | If the target is an archive member, this variable represents the member name of the target. |
**
$@: Target file
,$^: All dependency files
,$<: The first dependency file
**. These three variables are very common and important.
objects = fun1.o fun2.o main.o
test:$(objects)
gcc fun1.o fun2.o main.o -o test
fun2.o:$<
gcc $(CFLAGS) fun2.c -o $@
fun1.o:$<
gcc $(CFLAGS) fun1.c -o $@
main.o:$<
gcc $(CFLAGS) main.c -o $@
.PHONY:clean
clean:
rm *.o test
Environment Variables
-
make will automatically read the environment variables that are currently defined when starting, and will create variables with the same name and value. -
If the user defines variables with the same name in Makefile, then the user-defined variables will override the environment variables with the same name.
==Directly Running Make Options==
-C | dir reads the Makefile under the specified directory. |
-f | file reads the current directory’s file as Makefile. |
-i | Ignore all command execution errors. |
-I | dir specifies the directory where the included Makefile is located. |
-n | Only print the commands to be executed, but do not execute these commands. |
-p | Display the make variable database and implicit rules. |
-s | Do not display commands when executing commands. |
-w | If make changes directories during execution, print the current directory name. |
-C: dir reads the Makefile under the specified directory
make -C Makefile/
reads the Makefile in that directory.
-f: file reads the current directory’s file as Makefile
make -f Refuel.debug
make -f Refuel.debug clean
can use Refuel.debug as Makefile.
-i: Ignore all command execution errors If we write code and forget to -o in
gcc -c -Wall fun2.c o $@
, then we can usemake -i
, it will ignore small errors first, execute what can be executed in the code, report the errors, and not execute.
-n: Only print the commands to be executed, but do not execute these commands It does not actually execute the commands, but simulates executing the commands
In U-Boot, we will see some kernel Makefiles, such as config.mk, which lists some variable declarations
5. Implicit Rules of Makefile
-
==Implicit Rule 1:== Implicit rule for compiling C programs—let make automatically infer.
It can automatically infer files and the commands behind the file dependencies, so we don’t need to write similar commands after every [.o] file, because our make will automatically recognize and infer the commands.
As long as make sees a [.o] file, it will automatically add the [.c] file to the dependency relationship. If make finds a whatever.o, then whatever.c will be the dependency file for whatever.o. And cc -c whatever.c will also be inferred, so our Makefile no longer needs to be written so complicatedly.
objects = fun1.o fun2.o main.o
test:$(objects)
gcc $(objects) -o test
fun2.o:fun2.c
fun1.o:fun1.c
main.o:main.c
.PHONY:clean
clean:
rm *.o test
This method is also known as make’s “implicit rules”. In the above file content, “.PHONY” indicates that clean is a phony target file.
Summary: “<n>.o
” target’s dependency target will automatically infer to “<n>.c
“, and the generation command is “**$(CC) -c $(CPPFLAGS) $(CFLAGS)
**”
-
Implicit Rule 2: Implicit rule for linking object files.
“<n>
” target depends on “<n>.o
“, and runs the C compiler to run the linking program to generate (usually “1d”), the generation command is: “$(CC) $(LDFLAGS) <n>.0 $(LOADLIBES) $(LDLIBS)
“. This rule is effective for projects with only one source file, and also for multiple object files (generated from different source files). For example:
Rule:x : x.o y.o z.o and if “x.c”, “y.c”, and “z.c” exist, the implicit rule will execute the following commands: cc -c x.c -o x.o cc -c y.c -o y.o cc -c z.c -o z.o cc x.o y.o z.o -o x
If there is no source file (as in the example above x.c) related to your target name (as in the example above x), then you better write your own generation rules; otherwise, the implicit rules will report errors.
fun1 : fun1.o fun2.o main.o
This way, it won’t report errors.
Overview of Makefile
Makefile mainly includes five things: Explicit rules, implicit rules, variable definitions, file directives, and comments.
Explicit Rules. Explicit rules indicate how to generate one or more target files. This is explicitly pointed out by the writer of the Makefile, the files to be generated, the files that depend on, and the commands for generation.
Implicit Rules. Due to make’s automatic inference function, implicit rules allow us to write Makefile in a rough and simplified manner, which is supported by make.
Variable Definitions. In Makefile, we need to define a series of variables, which are generally strings. This is somewhat like macros in C language. When the Makefile is executed, the variables will be expanded to their respective reference positions.
File Directives. This includes three parts: one is to reference another Makefile in a Makefile, just like include in C language; another is to specify the valid part of the Makefile based on certain conditions, just like precompiled #if in C language; and the last is to define a multi-line command. I will discuss this part in the subsequent sections.
Comments. Makefile only has line comments, and like UNIX shell scripts, its comments are marked with the “#” character, which is like “//” in C/C++. If you want to use the “#” character in your Makefile, you can escape it with a backslash, such as: “#”.
Usage of VPATH
1. VPATH in Makefile
VPATH: Virtual Path
-
In large projects, there are a large number of source files, and our usual practice is to categorize these many source files and store them in different directories. Therefore, when make needs to find the dependency files, you can add the path before the file, but == the best method is to tell make a path and let make find it automatically ==.
-
The special variable “VPATH” in Makefile accomplishes this function. If this variable is not specified, make will only look for dependency files and target files in the current directory. If this variable is defined, then make will look for files in the specified directory when the current directory cannot be found.
-
VPATH = src:../headers
-
The above definition specifies two directories, “src” and “../headers”, make will search in this order. The directories are separated by colons (Of course, the current directory is always the highest priority search place).
Another way to set the file search path is to use make’s “vpath” keyword (note, it is all lowercase), which is not a variable, but a make keyword. This is quite similar to the VPATH variable mentioned above, but it is more flexible. It can specify different files in different search directories. This is a very flexible feature. Its usage method has three:
1. vpath < pattern> < directories>
//Specify the search directory <directories> for files matching the pattern <pattern>.
2. vpath < pattern>
//Clear the search directory for files matching the pattern <pattern>.
3. vpath
//Clear all search directories that have been set.
In the vpath usage method, < pattern> needs to contain the character “%”. “%” means to match zero or several characters. For example, “%.h” indicates all files ending with “.h”. < pattern> specifies the set of files to search, while < directories> specifies the directories to search for the file set. For example:
vpath %.h ../headers
This statement indicates that make is required to search for all files ending with “.h” in the “../headers” directory (if the file is not found in the current directory).
We can continuously use vpath statements to specify different search strategies. If the same < pattern> appears in consecutive vpath statements or is repeated, then make will execute the search in the order of the vpath statements. For example:
vpath %.c foo
vpath % blish
vpath %.c bar
This indicates that for files ending with “.c”, search in the “foo” directory first, then in “blish”, and finally in “bar”.
vpath %.c foo:bar
vpath % blish
And the above statement indicates that for files ending with “.c”, search in the “foo” directory first, then in the “bar” directory, and finally in the “blish” directory.
Distributing programs across different paths. If programs are written in different directories, how to write Makefile without using VPATH?

2. Usage of VPATH in Makefile
Nested Makefiles
Each file has its own makefile, and makefiles call each other as child makefilesExample:
We see many directories and external makefile
, with .c
programs and child makefile
in each directory.


-
We notice that there is a statement @echo $(SUBDIRS)
-
@(RM) is not a variable we defined ourselves, so where did it come from? It is from make -f
-
make -C $@
-
export CC OBJS BIN OBJS_DIR BIN_DIR: allows the child makefile to call as well.
– EOF –
Add the homepage’s WeChat, not only will you improve your Linux skills +1

The homepage will also share Linux-relatedtools,resources and selectedtechnical articles, and will share some interesting activities, job referrals and how to use technology for side projects from time to time.
Add a WeChat, open a window
1. A Guide to Makefile for Go Projects
2. Discussing Context Switching of Linux CPU
3. The Essence of High-Performance Service epoll in Linux is Really Not Simple
Did you gain something from reading this article? Please share it with more people
Recommend following “Linux Enthusiasts” to enhance Linux skills
Like and view is the biggest support ❤️