Detailed explanation of Makefile rules

Detailed explanation of Makefile rules

1 Introduction

GCC (GUN C Compiler) is the earliest C compiler developed and is still widely used today. It also adds compilation support for C++, Objective-C, Fortran, Ada, Go and other languages.

GCC is a compilation tool that uses CLI interaction and does not support a graphical interface, so all operations need to be completed through commands in the terminal. However, a complete source code is always divided into multiple files, and various controls and compilation options are required during compilation. You need to enter lengthy compilation commands in the terminal, and you can only enter one line of commands at a time. , and this process needs to be repeated every time a file is compiled, which is very laborious.

In order to make the compilation process easy and fast, and eliminate the trouble of using GCC to enter commands to compile step by step, Makefile appeared. No longer afraid of lengthy commands, just write GCC's various control and compilation options into the Makefile. Yes, just let Make parse the Makefile and then call GCC to perform compilation, which perfectly realizes compilation (build) automation.

2. Make

Before explaining Makefile in detail, let's first understand the Make tool. We know that Makefile is written to be used by Make. So what is Make? For the time being, Make can be understood as an interpreter, similar to the Python interpreter that interprets Python programs, but the program interpreted by Make is the Makefile file.

So what is the role of Make? Let's translate the Chinese meaning of Make, which means "making" or "manufacturing". Yes, the function of Make is to be able to produce files in a certain format.

If you need to use Make to create a plain text file hello.txt, you can execute the following command.

make hello.txt

However, Make will prompt the error "There is no rule for making hello.txt" because Make does not know how to make a hello.txt file that suits your wishes, nor does it know what content should be written in hello.txt.

make: *** No rule to make target 'hello.txt'.  Stop.

If the file content of hello.txt is the same as the content of temp.txt, that is, hello.txt is a copy of temp.txt (or hello.txt depends on temp.txt), then we can tell Make to just copy temp.txt and Name it hello.txt to get hello.txt that suits our needs, so we need to tell Make the following rules.

hello.txt: temp.txt
    cp temp.txt hello.txt

After executing the command make hello.txt, Make will confirm whether temp.txt already exists. If it exists, use the cp command to copy the temp.txt file and output it as a new file hello.txt.

Rules like this are written in the Makefile mentioned earlier, and Make relies on this file for construction.

experiment:

Prepare a temp.txt file with whatever content you want. Create a new file, copy the above rules into it, and save the file name as Makefile. Then we execute the make hello.txt command and get the following output. Check the local files and we really get the hello.txt file. The content is the same as the temp.txt we prepared.

make hello.txt
cp temp.txt hello.txt

Are you shocked? Think the above is pseudocode? In fact, it is completely executable without any problem. Make is not limited to processing C programs. As long as you provide it with rules, it can handle it for you.

Summarize:

Although Make can produce files in a certain format, Make itself does not know how to produce files in a certain format. Therefore, the role of Makefile is to instruct Make how to make files in a certain format, and when we write Makefile, we are equivalent to telling Make how to make files in a certain format.

Although Make can create files in a certain format, Make itself does not have the ability to create (or edit) files, so Make needs to rely on other tools that can edit, move or generate files to create files, such as calling GCC or text editing. The calling method is to execute the commands provided by these tools. The specific commands will not be discussed in detail here. Just know that Make can create files.

3. Detailed explanation of rules

As mentioned in the previous section, the build rules are all written in the Makefile, and naming the Makefile as Makefile or makefile does not require an extension. When executing the command in the terminal, makeMake will look for a file named Makefile or makefile in the current directory.

Of course, it can also be named by another name, such as config.txt, but -fthe name must be specified using the make command option.

make -f config.txt
make --file=config.txt

To learn to use Make to build programs, you must learn how to write Makefile files. Let's take an in-depth study of the rules for writing Makefiles.

3.1 Overall rules

Open a Makefile, such as the Linux source code Makefile. The overall look is very complicated, with all kinds of strange symbols. In fact, apart from the meaning of the symbols, complex Makefiles are composed of simple rules. The rules of Makefile The form is as follows.

<target> : <prerequisites>
[tab] <commands>

The part before the colon in the first line is called "target", and the part after the colon is called "prerequisites". The second line must start with a tab key, followed by "commands".

"Target" is required and cannot be omitted. "Precondition" and "command" can be omitted, but at least one of them must exist (both cannot be omitted at the same time).

Each rule clarifies two things: what are the prerequisites for building the goal, and how to build the goal.

3.2 Target

A target constitutes a rule (each rule begins with "target"). The target is usually a file name, indicating the object to be built by the Make command, such as hello.txt in the previous example. The target can be one or more file names, and multiple file names are separated by spaces.

In addition to the file name, the target can also be the name of an operation. Such a target is called a "phony target".

For example, if the target is clean, which is a cleaning operation, then clean is a "pseudo target", which is used to clear some intermediate files. If executed in the terminal, the make cleanintermediate files can be cleared.

clean:
    rm -rf *.o

You see, Makefile cleverly uses "pseudo-targets" based on the "target" construction principle to implement the function that allows us to customize operation commands. But if the source code happens to have a file called clean, then the command corresponding to the pseudo target clean will not be executed. Because Make found that the clean file already existed, it thought there was no need to rebuild it, so it naturally stopped executing rm -rf *.othe command.

In order to avoid this situation, you can .PHONYexplicitly declare clean as a "pseudo target". After declaring clean as a "pseudo target", Make will no longer check whether there is a file named clean, and execute the corresponding command every time it runs. Declaration Here's how.

.PHONY: clean
clean:
    rm -rf *.o

So the "real goal" of make is to build a certain target object, and the "pseudo goal" of make is to perform a certain operation.

3.3 Prerequisites

Preconditions refer to the dependent files of the "target". The target needs to rely on preconditions to be generated. It is usually one or more file names. Multiple file names are separated by spaces. If "target" is not related to any other file, then the precondition does not need to list the file name.

Preconditions determine whether the "target" needs to be rebuilt. As long as any of the files listed in the precondition is updated (that is, the last modification time of the file listed in the precondition is newer than the last modification time of the target), or "target" does not exist, then "target" It needs to be rebuilt.

If any of the files listed in the preconditions does not exist, then Make needs to find and execute the rules that can generate the file in the Makefile. In other words, the missing file itself is also a "target" and needs to be first This "goal" must be built before it can be used to build the current "goal" (when building A, it is found that A needs to depend on B, so B must be built first before continuing to build A, which is referred to as recursive dependency generation).

hello.txt: temp.txt
    cp temp.txt hello.txt

Now look back at the previous example, hello.txt is the target, and the prerequisite for building the target is temp.txt. If temp.txt already exists in the current directory, make hello.txtit can run normally. Otherwise, temp.txt must be generated first and then hello.txt.

3.4 Commands

Commands indicate how to update the target file. They consist of one or more lines of Shell script commands. They are specific instructions for building the "target". The result of running the command is usually to generate the target file.

Note that the command must start with a tab key. Of course, the flexible Makefile allows you to replace the tab key with other symbols. The replacement method is to use the built-in variables of the Makefile to .RECIPEPREFIXdeclare new symbols, such as using >>to replace the tab key.

.RECIPEPREFIX=>>

clean:
>>rm -rf *.o

Building a target may require the use of multiple commands. Multiple commands generally need to be written in multiple lines for ease of reading. But one thing to note is that the Makefile will assign commands written on different lines to different Shell processes for execution. There is no inheritance relationship between these Shells (that is, there is no context connection, and the Shell process will exit immediately after running), which means Therefore, data interaction cannot be achieved between two adjacent commands.

Therefore, the echo command in the following Makefile rules cannot obtain the value of the variable var, because these two lines of commands are assigned to execute in different Shell processes.

variable_test:
    export var="Hello, World!"
    echo var=$${var}

Therefore, in order to realize data sharing between commands, the solution is to write multiple related commands in one line, and separate the commands with semicolons, as follows.

variable_test:
    export var="Hello, World!"; echo var=$${var}

Writing multiple commands in one line is not easy to read after all, so you still need to write them in separate lines. However, in order to make the multi-line commands achieve the effect of one line, you can use backslash escaping. After escaping, Make will think that these commands are \on the same line. You need to Executed in the same shell process.

variable_test:
    export var="Hello, World!"; \
    echo var=$${var}

If you feel that placing a backslash after each command \is inelegant, the last option is to use Makefile's built-in command .ONESHELL:to declare rules, similar to the escaping effect of backslash.

.ONESHELL:
variable_test:
    export var="Hello, World!";
    echo var=$${var}

3.5 Summary

A Makefile generally defines multiple "targets", such as some "real targets" and some "pseudo targets". By default, executing makethe Make command will build the first "target" of the Makefile by default, and then gradually build the dependencies of the first target.

If you want Make to build a specified "target", you can makefollow the command with the name of the "target" to be built, for example , make hello.txtor make clean, if the target is a "real target", the Nubio object will be built, if the target is a "pseudo target", then perform some action.

4. Detailed explanation of commands

After the previous section, you are already familiar with the rules of Makefile. Let's explain the basic syntax of Makefile and familiarize yourself with some basic elements of Makefile.

4.1 Notes

Makefile comments use characters as comment identifiers in the same way as programming languages Python​​and scripts. That is , the content following the symbol will be understood by Make as a comment and will be ignored.shell##

# 这是注释语句
hello.txt: temp.txt # 这是注释语句
    cp temp.txt hello.txt # 这是注释语句

4.2 Echo

Before executing each command, Make will print the command to be executed to the terminal and then execute it. This is the echo mechanism.

echoing:
    echo "Hello, World!"

Execute make echoingthe command in the terminal, and you can get the following output in the terminal. It can be seen that Make outputs all the commands executed to the terminal.

make echoing
----------------------------------
echo "Hello, World!"
Hello, World!

Adding it in front of the command @can turn off the echo of the current command. Of course, it is generally only added in front of comments and pure display echo commands @to avoid echooutputting itself to the terminal.

echoing:
    @echo "Hello, World!

After adding before the echo command, @the echo command itself will no longer be output to the terminal, but only the content that needs to be output will be output.

make echoing
----------------------------------
Hello, World!

4.3 Wildcards

Wildcard characters (Wildcard) are used to specify a set of qualified file names. The wildcard characters of Makefile are consistent with Bash, mainly including asterisk *, question mark ?and .... For example, *.orepresents all .ofiles with suffix names. For example, wildcard characters have been used in the previous examples.

.PHONY: clean
clean:
    rm -rf *.o

4.4 Pattern matching

The Make command allows matching of file names in a manner similar to regular expressions. The main matching character used is the percent sign %. For example, assuming that there are two source code files ac and bc in the current directory, they need to be compiled into the corresponding object files ao and bo, and two rules need to be written.

a.o: a.c
    gcc -c a.c -o a.o

b.o: b.c
    gcc -c b.c -o b.o

If the project contains a large number of C files, it is obviously very cumbersome to do this, so for simplicity, %.o: %.cpattern matching can be used, and the effect is equivalent to the following writing. Therefore, using matching characters %, a large number of files of the same type can be constructed using only one Makefile rule (through pattern matching, only one rule can be implemented to compile all .cfiles into corresponding .ofiles).

When %appears in the target, %the value represented in the target determines %the value in the dependency. The usage
method is as follows:

a.o: a.c
b.o: b.c

In the pattern rules, at least it must be included in the target definition of the rule %, otherwise it is a general rule. In the target, it %means matching the file name, so %it means a non-empty string of any length.

4.5 Variables

Makefile, like other programming languages, supports defining variables. The advantage of defining variables is that for content that is used in a wide range, it can be replaced by a variable. When it is not modified, the content of the variable can be modified to uniformly modify the content that is used in a wide range.

The way to define variables in Makefile is as follows. Since Makefile is a script, unlike other programming languages, defining variables in Makefile does not require declaring the type of the variable (in fact, the values ​​saved by Makefile variables are all strings).

At the same time, Makefile variables are not variables in the true sense, but are more similar to macro definitions in C/C++. When executed, the variable content will be expanded as it is at the place of use.

hello = Hello, World!

hello:
    @echo $(hello)

When referencing a variable, the variable needs to be placed in $( )or ${}to get the value of the variable.

4.6 Assignment operator

The variables were defined earlier, and the variables need to be assigned values ​​to function. Makefile defines four assignment operators for different scenarios, namely, , , . =These :=assignment ?=operators +=have different meanings.

(1) =Expand when the variable is executed, allowing recursive expansion. Make will expand the entire Makefile and then determine the value of the variable. In other words, the value of the variable will be the last value specified in the entire Makefile.

x = Hi
y = $(x) World
x = Hello
---
echo $(y)
Hello World

(2) :=Expand when the variable is defined. Indicates that the value of the variable is determined by its position in the makefile, rather than the final value after the entire Makefile is expanded.

x := Hi
y := $(x) World
x := Hello
---
echo $(y)
Hi World

(3) ?=The value is only set when the variable is empty.

(4) +=Append the value to the end of the variable.

4.7 Built-in variables

The Make command provides a series of built-in variables for cross-platform compatibility, for example, $(CC)pointing to the currently used compiler (such as gcc), $(MAKE)pointing to the currently used Make tool, and more built-in variables, which are not listed here. You can check Make official manual to find out.

hello:
    $(CC) hello.c -o hello.exe

4.8 Automatic variables

When I talked about pattern matching earlier, I mentioned that targets and dependencies are a series of files. Each time the pattern matching is parsed, it %represents a different target and dependency file, and the command is only one line. How to use one line of commands to get different targets and dependencies? The target corresponding to the dependency is generated in the dependency file? This requires the use of Makefile automatic variables.

$@Symbols similar to , are often seen in Makefiles $^. $<This symbol is called an automatic variable. Automatic variables are local variables with scope within the current rule (that is, automation variables should only appear in Makefile target rules). Using automatic variables helps simplify rule statements.

The so-called automated variables are variables that will automatically retrieve a series of files defined in the pattern one by one until all files that match the pattern are retrieved.

(1) $@refers to the current target, which is the target currently built by the Make command, for example, $@it represents the target hello.

hello: hello.o
    gcc hello.o -o hello

So the writing effect below is the same as the above.

hello: hello.o
    gcc hello.o -o $@

(2) $<refers to the first precondition. For example, if the rule is hello: ab, then $<it refers to a.

hello: a.o b.o
    gcc a.o b.o -o hello

So the writing effect below is the same as the above.

hello: a.o b.o
    gcc $< b.o -o hello

(3) $?refers to all preconditions that are newer than the target, separated by spaces. For example, the rule is hello: ab, and the timestamp of b is newer than the target hello, then $?it refers to b.

(4) $^refers to all preconditions, separated by spaces. For example, if the rule is hello: ab, then $^it refers to a and b.

hello: a.o b.o
    gcc a.o b.o -o hello

So the writing effect below is the same as the above.

hello: a.o b.o
    gcc $^ -o hello

(5) $*Refers to the matching part of the match character %. For example, if %it matches a in a.txt, $*it means a.


There are more automatic variables, which are not listed here. You can check the official manual of Make to learn more.

5. Advanced commands

In addition to the Make command itself, Makefile can also use Bash syntax (ie, Shell script syntax), such as using Shell syntax to complete judgments and loops, environment variable references, and so on.

The cross-use of the two syntaxes is also inelegant. For example, the Makefile allows the assignment operator =to contain spaces on both sides, but the Shell does not allow it. Otherwise, the Shell will also include spaces as values.

5.1 Judgment

Conditional statements can control Make to execute or ignore specific operations based on appropriate conditions. Unlike other programming languages, Makefile does not have comparison operators and is replaced by different comparison commands. Therefore, the following four comparison commands are required to achieve conditional comparison.

Order describe
ifeq Determine whether the parameters are not equal. If equal, it is true; if not, it is false.
ifneq Determine whether the parameters are not equal. If not equal, it is true; if equal, it is false.
ifdef Determine whether there is a value. If there is a value, it is true. If there is no value, it is false.
ifndef Determine whether there is a value. If there is no value, it is true. If there is a value, it is false.

The usage of the command ifeqis as follows, and the remaining three usages are similar.

all:
ifeq ($(CC), gcc)
    @echo GCC Compiler
else
    @echo Other Compiler
endif

5.2 Loop

The for loop syntax of Makefile is as follows, where is @only used to turn off command echo, and $$actually means $that it is used to reference Shell variables in the Shell process (because in Shell, $will be used as an escape symbol), and $aor $(a)means to reference Makefile variables.

a := 123 123 123

testfor:
    @for var in $(a); do \
        echo $${var}; \
    done

In addition to the for loop, Makefile also supports the foreach statement, which is a built-in function of Makefile (it doesn’t matter if you don’t understand it now, it will be discussed in the next section), and the syntax is as follows.

a := 123 123 123

testfor:
    @$(foreach var, $(a),\
        echo $(var); \
    )

5.3 Functions

Makefile can also use functions. For Makefile, a function is a special instruction. The function can accept parameters and return a value in the following format.

$(function arguments) 或 ${function arguments}

pwdThe functions in the Makefile have been defined by Make and do not support our custom functions. We can use them directly. For example, the function can be used to execute Shell commands in the Makefile. Here, the function is used to execute a command that can print out the working directory in the terminal. .

PATH := $(shell pwd)

Make does not provide too many functions, but they are enough for us to use. In the next section, we will take a general look at the uses of the functions (built-in functions) provided by Make.

6. Built-in functions

Makefile officially provides a series of built-in functions suitable for processing various types of files. The function itself is a kind of encapsulation of the operation process. Using these functions can simplify some complex operations.

6.1 Shell functions

The shell function is used to execute shell commands.

srcfiles := $(shell echo src/{00..99}.txt)

6.2 Wildcard function

Wildcards %can only be used in target rules, and they will be expanded only in target rules. If wildcards are not automatically expanded when variables are defined and functions are used, the function wildcard must be used at this time.

You can use it to replace Bash's wildcard characters in a Makefile *, as well as wildcard characters %, and return a list of file names that match the pattern.

$(wildcard pattern...)

For example, match all .txt files in the src directory.

srcfiles := $(wildcard src/*.txt)

6.3 Subst function

The subst function is used to complete text replacement (string replacement), replacing from in the text with to. The format is as follows.

$(subst from,to,text)

For example, replace all "ee" contained in the string "feet on the street" with "EE".

$(subst ee,EE,feet on the street)

See the example below.

comma:= ,
empty:=
# space变量用两个空变量作为标识符,当中是一个空格
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))
# bar is now `a,b,c'.

6.4 Patsubst function

The patsubst function is used for pattern matching replacement. It replaces the part of the text that matches the pattern with replacement. The format is as follows.

$(patsubst pattern,replacement,text)

Pattern can use wildcards %to represent strings of any length, and the function return value is the replaced
string. If replacement is also included %, then replacement will be the string represented by %the one in pattern .%

For example, replace all strings matching "%.c" in the string "ac bc cc" with "%.o", and the string after the replacement is "ao bo co".

$(patsubst %.c,%.o,a.c b.c c.c)

6.5 Foreach function

The foreach function is used to complete the loop. The meaning of this function is to take out the words in the parameter list one by one and put them into the parameter var, and then execute the expression contained in text. every time will return a string. During the loop, each string contained in text will be separated by spaces. Finally, when the entire loop ends, the entire string composed of each string returned by text will be The return value of the function foreach function.

$(foreach var, list,text)

6.6 Dir/Notdir function

The function dir is used to obtain the directory. This function is used to extract the directory part from the file name sequence names. The return value is the directory part of the file name sequence names.

$(dir names...)

For example, extract the directory part of the file "/src/hello.c", which is "/src".

$(dir </src/hello.c>)

The function notdir looks at the name and knows to remove the directory part of the file, which has the opposite effect.

$(notdir names...)

For example, extract the file name part of the file "/src/hello.c", which is "hello.c".

$(notdir </src/hello.c>)

7. Summary

To summarize, writing Makefile requires familiarity with the compilation process stages and the use of GCC commands. If you still don’t understand, you can read this article of mine:

Detailed explanation of GCC commands

Guess you like

Origin blog.csdn.net/jf_52001760/article/details/131277015