Introduction to Make in Linux C

From WeChat Official Account: One Linux

Introduction to Make:

  • Make is an engineering manager, which, as the name suggests, is used to manage a large number of files.
  • The Make engineering manager is essentially an “automatic compilation manager“. The term “automatic” refers to its ability to automatically discover updated files based on file timestamps, thereby reducing the compilation workload. It performs a significant amount of compilation work by reading the contents of the Makefile.
  • ==Make will only compile the modified code files, rather than recompiling everything.==

Whether one can write a makefile indirectly indicates whether a person has the ability to complete large projects, as the makefile relates to the compilation rules of the entire project. The source files in a project are not counted; they are placed in several directories according to type, function, and module. 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 to be recompiled, and even perform more complex operations, as the makefile is like a Shell script, where operating system commands can also be executed. The benefit of the makefile is “automated compilation”; once written, a single make command can fully automate the compilation of the entire project, 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 dependency files required to create the target.
    • The commands that need to be executed to create each target.

==Note:== The command line must start with a “**==TAB==**” key; otherwise, the compilation error will be: *** missing separator. Stop.

For example:Introduction to Make in Linux C

Makefile Format:

target : dependency_files
	command

target // Target: target is an object file, which can be an Object File or an executable file. It can also be a label (Label).

dependency_files // Files required to generate the target: dependency_files are the files or targets needed 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 or multiple target files depend on the files in dependency_files, and the generation rules are defined in the command. **==In simpler terms, 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, which is also the core content of Makefile.==**

==[Note]==: When looking at someone else’s Makefile, you may encounter the following three variables:<span>$@</span>, <span>$^</span>, <span>$<</span> which represent:

These three are very important variables, representing:

**<span>$@ : Target file</span>, <span>$^ : All dependency files</span>, <span>$< : The first dependency file</span>**.

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 : Compiles 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 directly enter the command “make” in that directory to generate the executable file sunq. If you want to delete the executable file and all intermediate target files, you can simply execute “make clean”. In this makefile, ==the target file (target) includes: the executable file sunq and intermediate target files (*.o), and 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 the dependency files of the executable file sunq. The essence of the dependency relationship is to indicate which files generate the target file==, in other words, the target file is which files have been updated.

After defining the dependency relationship, the subsequent line defines the operating system command to generate the target file, **==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 make supports both “makefile” and “Makefile” as default filenames. You can use other filenames to write Makefile, such as: “Make.Linux”, “Make.Solaris”, “Make.AIX”, etc. If you want to specify a specific Makefile, you can use the “-f” and “–file” parameters of make, such as: **<span>make -f Make.Linux</span> or <span>make --file Make.AIX</span>**.

In the default way, that is, when we only enter the make command, then:

  1. make will look for a file named “Makefile” or “makefile” in the current directory.
  2. If found, it will look for the first target file in the file (in the above example, it will find the file “sunq” and take this file as the final target file).
  3. 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 commands defined later to generate the sunq file.
  4. If the .o files that sunq depends on do not exist, then make will look for the dependency of the target for the .o files in the current file; if found, it will generate the .o files according to that rule (this is somewhat like a stack process).
  5. Of course, your C files and H files exist, so make will generate the .o files, and then declare make’s ultimate task, which is the executable file sunq.

This is the entire dependency of make; make 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 does not care. Make only cares about file dependencies, that is, if after I have looked for the dependencies, the files after the colon are still not there, then sorry, I will not work.

The execution steps of make during work are as follows (other makes are similar):

  1. Read all Makefiles.

  2. Read other Makefiles that are included.

  3. Initialize the variables in the file.

  4. Infer implicit rules and analyze all rules.

  5. Create dependency chains for all target files.

  6. Decide which targets need to be regenerated based on dependencies.

  7. Execute the generation commands.

Steps 1-5 are the first stage, and steps 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 internally when that dependency is decided to be used.

2. Understanding Dependency Relationships in Makefile

Assuming the current project directory is object/, which contains six files: main.c, abc.c, xyz.c, abc.h, xyz.h, and Makefile. Where 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 as shown in the diagram.

Introduction to Make in Linux CThe 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. Writing Rules in 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 the Makefile==**, and other targets are derived from this target so ==make must know what your final target is==. Generally speaking, there may be many targets defined in the Makefile, but the target in the first rule will be established as the final target.==<span>If there are many targets in the first rule, then the first target will become the final target</span>==. 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

In this example, you should be quite familiar with it. As mentioned earlier, foo.o is our target, and 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:

  1. The dependency relationship of the file, foo.o depends on the files 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.

  2. 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 Usage of Makefile

Next, let’s do an example to learn how to write a Makefile

Write two C programsIntroduction to Make in Linux CIntroduction to Make in Linux CWrite a head.h header file to declare the above functionsIntroduction to Make in Linux C

Write a main programIntroduction to Make in Linux CIntroduction to Make in Linux C

If written in this way, if the files are modified, it will be very troublesome if there are too many files.

So the use of 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 MakefileIntroduction to Make in Linux CIntroduction to Make in Linux CIf I want to modify one of the C files, modify fun2.cIntroduction to Make in Linux CAfter modifying, when I make, I find that only fun2.c is re-generated as fun2.o, because fun2.o is newly generated, and test also needs to be newly generated.Introduction to Make in Linux CResultIntroduction to Make in Linux CMakefile will generate many intermediate files in the folder

Introduction to Make in Linux CWhen we need to clean up, we ==often use make-related commands to clean==, rather than deleting one by one with rm.

clean:
	 rm *.o test

Introduction to Make in Linux Cmake + target nameIntroduction to Make in Linux CIn this way, all intermediate files are cleaned upIntroduction to Make in Linux CPhony target: A file that will definitely be executed, even if it has the same nameIntroduction to Make in Linux CIf the name is the same, it is found that clean does not work, as it defaults to not being modified, so it does not work. How to avoid this problem?

Add .PHONY:command in the Makefile ==.PHONY: implies== “clean is a phony target file.

.PHONY:clean

Introduction to Make in Linux CThis way, it will not be delayed by the same name.Introduction to Make in Linux CRules for clearing target files should be written in every Makefile, which not only facilitates 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 issues, ignore them and continue with the following tasks. 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 would want that. The unwritten rule is—”clean is always placed at the end of the file”.

3. Understanding Dependency Relationships in Makefile

Assuming the current project directory is object/, which contains six files: main.c, abc.c, xyz.c, abc.h, xyz.h, and Makefile. Where 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 as shown in the diagram.

Introduction to Make in Linux CThe 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

To maintain the Makefile, we can use variables in the Makefile. The variables in the Makefile are essentially strings, which can be better understood as macros in C language.

In the above Makefile example:

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
.PHONY:clean
clean
	 rm *.o test

For example, we declare a variable called objects, which can represent obj files. We define it at the beginning of the Makefile:

objects = fun1.o fun2.o main.o

Then we can conveniently use this variable in our Makefile as “**<span>$(objects)</span>**”, 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 only 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 need to output information
  • Other things you want to do.

2.2 How to Define Variables:

Two ways to define variables

  • Recursive expansion method<span>VAR=var</span>
  • Simple method<span>VAR:=var</span>
  • Variable usage$(VAR)=========================
  • Use “thenuse$” to indicate
  • 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 methodVAR=var example:

foo = $(bar)
bar = $(ugh)
ugh = Huh?

The value ofisto check

Advantages: It can reference variables backward. Disadvantages: It cannot expand that variable.

For example, CFLAGS = $(CFLAGS)-0 will cause an infinite loop.

  • Simple methodVAR:=var
m := mm
x:=$(m)
y:= $(x) bar
x:=later
echo $(x) $(y)

For example, the value of m variable is mm, and the value of m is assigned to x.

(This variable method is more like C language)

==Using ?= to define variables==

dir :=/foo/bar
FOO?=bar
FOO is?

The ? means that if FOO has not been defined before, then the value of variable FOO will be “bar”; if FOO has been defined before, 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 is ar. AS is the name of the assembler, default is as.
-CC Name of the C compiler, default is cc. CPPC is the name of the preprocessor, default is $(CC) -E.
-CXX Name of the C++ compiler, default is g++.
-FC Name of the FORTRAN compiler, default is f77.
-RM Name of the file deletion program, default is rm -f.
  • Automatic Variables
– $* Name of the target file without extension.
-$+ All dependency files, separated by spaces, and in the order they appear, 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 unique target dependency files, separated by spaces.
-$% If the target is an archive member, this variable represents the name of the target’s archive member.

**<span>$@ : Target file</span>, <span>$^ : All dependency files</span>, <span>$< : The first dependency file</span>**. 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

Variable modification:

objects = fun1.o fun2.o main.o
CFLAGS=-c -Wall

test:$(objects)
	 gcc  $(objects) -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 automatically reads the environment variables that are currently defined in the system when it starts, and creates variables with the same name and value.
  • If the user defines a variable with the same name in the Makefile, then the user-defined variable will override the environment variable with the same name.

==Directly Running Make Options==

-C dir reads the Makefile in the specified directory.
-f file reads the file in the current directory as the 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 in the specified directory.

<span>make -C Makefile/</span> reads the makefile in the file.

Introduction to Make in Linux CIntroduction to Make in Linux C

-f : file reads the file in the current directory as the Makefile.

<span>make -f Refuel.debug</span><span>make -f Refuel.debug clean</span>

Then you can use Refuel.debug as a Makefile.

-i : Ignore all command execution errors If we write code and forget to write -o in <span>gcc -c -Wall fun2.c o $@</span>, we can use make -i, it will ignore small errors first, execute what can be executed normally in the code, and report the errors without executing them.

-n : Only print the commands to be executed, but do not execute these commands This does not actually execute the commands, but simulates executing the commands.Introduction to Make in Linux C

In U-Boot, we will see some kernel Makefiles, such as config.mk, which lists some variable declarations.Introduction to Make in Linux C

5. Implicit Rules of Makefile

  • ==Implicit Rule 1:== Implicit rule for compiling C programs—allowing make to automatically infer.

It can automatically infer files and the commands behind the file dependencies, so we do not 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 of whatever.o. And the command cc -c whatever.c will also be inferred, so our makefile does not need to be written so complicated.

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 the “implicit rule” of make. In the above file content, “.PHONY” indicates that clean is a phony target file.

Summary:<span><n>.o</span>“‘s target dependency will be automatically inferred as “<span><n>.c</span>” and its generation command is “**<span>$(CC) -c $(CPPFLAGS) $(CFLAGS)</span>**”Introduction to Make in Linux C

  • Implicit Rule 2: Implicit rule for linking object files.

<span><n></span>” target depends on “<span><n>.o</span>” and runs the linking program to generate (usually “1d”). Its generation command is: “<span>$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)</span>. This rule is valid 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 ” all 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 (like x.c in the above example) associated with your target name (like x in the above example), then you better write your own generation rules; otherwise, the implicit rule will report an error.

fun1 : fun1.o  fun2.o  main.o

This way, it will not report an error.

Overview of Makefile

Makefile mainly includes five things:Explicit rules, implicit rules, variable definitions, file directives, and comments.

  1. Explicit Rules. Explicit rules specify how to generate one or more target files. This is clearly indicated by the writer of the Makefile, the files to be generated, the dependency files, and the commands for generation.

  2. Implicit Rules. Since our make has the function of automatic inference, implicit rules allow us to write Makefiles more roughly and simply, which is supported by make.

  3. Variable Definitions. In the 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 corresponding reference positions.

  4. File Directives. This includes three parts: one is to reference another Makefile in a Makefile, just like include in C language; the other is to specify valid parts of the Makefile based on certain conditions, just like the precompiled #if in C language; and the last is to define a multi-line command. I will discuss this part in subsequent sections.

  5. Comments. Makefile only has line comments, and like UNIX shell scripts, its comments are indicated by the “#” character, which is similar to “//” in C/C++. If you want to use the “#” character in your Makefile, you can escape it with a backslash, like: “#”.

Usage of VPATH

1. VPATH in Makefile

VPATH: Virtual Path

  • In large projects, there are a large number of source files. 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 way is to tell make a path so that make can automatically find==.

  • The special variable “VPATH” in the 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 directories when it cannot find them in the current directory.

  • VPATH = src:../headers

  • The above definition specifies two directories, “src” and “../headers”, and make will search in this order. The directories are separated by a “colon”. (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 that it is all lowercase). This is not a variable; it is a make keyword, which is 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 has three forms:

1. vpath < pattern > < directories >     
 //Specify search directories <directories> for files matching the pattern <pattern>.

2. vpath < pattern >                               
 //Clear the search directories for files matching the pattern <pattern>.

3.  vpath                                                 
 //Clear all previously set file search directories.

The < pattern> in the vpath usage method needs to include the “%” character. The “%” means to match zero or more characters, for example, “%.h” means all files ending with “.h”. The < pattern> specifies the set of files to search, while < directories> specifies the directories to search for that set of files. 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 a file is not found in the current directory).

You 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 according to the order of the vpath statements. For example:

vpath %.c foo

vpath % blish

vpath %.c bar

This indicates that for files ending with “.c”, first search in the “foo” directory, then in the “blish”, and finally in the “bar” directory.

vpath %.c foo:bar

vpath % blish, while the above statement indicates that for files ending with “.c”, first search in the “foo” directory, then in the “bar” directory, and finally in the “blish” directory.

Distributing programs in different paths. If programs are written in different directories, how to write the makefile without using VPATH?Introduction to Make in Linux C

Introduction to Make in Linux C
Insert image description here

Introduction to Make in Linux CIntroduction to Make in Linux C==How to delete unwanted intermediate files in different directories?== By using the command:<span>find ./ -name "*.o"</span>, find all .o files. We input the command:<span>find ./ -name "*.o" -exec rm {} \;</span>, meaning that I will take the found results to rm for deletion.Introduction to Make in Linux CIn this way, .o files will be deleted in different directories.

2. Usage of VPATH in Makefile

Introduction to Make in Linux CIntroduction to Make in Linux CIntroduction to Make in Linux C

Nested Makefiles

Each file has its own makefile, and makefiles call each other as sub-makefiles.Example:

We see that there are many directories and external<span>makefile</span>, in each directory there are<span>.c</span> programs and sub<span>makefile</span>Introduction to Make in Linux CIn the first directory f1, the sub-makefile generates f1.c to f1.o and places it in the OBJS_DIR obj.Introduction to Make in Linux CIntroduction to Make in Linux C

  • We notice that there is a line @echo $(SUBDIRS)

  • @(RM) is not a variable we defined; where does it come from? It is from make -f.

  • make -C $@

  • export CC OBJS BIN OBJS_DIR BIN_DIR: allows sub-makefiles to call as well.

— EOF —

Recommended ↓↓↓

Leave a Comment