跟我一起写 Makefile(整理版)

                  跟我一起写 Makefile
                    作者:陈皓
          (博客地址:http://blog.csdn.net/haoel/article/details/2886)

跟我一起写 Makefile

Part I, Overview

  What are makefiles? Maybe many Winodws programmers don't know this thing, because those Windows IDEs have done this work for you, but I think to be a good and professional programmer, you still need to understand makefile. It's like there are so many HTML editors now, but if you want to be a professional, you still have to understand the meaning of HTML tags. Especially for software compilation under Unix, you have to write makefile yourself. Whether you can write makefile or not, from one aspect shows whether a person has the ability to complete large-scale projects. Because the makefile is related to the compilation rules of the entire project. The source files in a project are not counted, and 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, and which files need to be re-compiled. Compilation, and even more complex functional operations, because the makefile is like a shell script, which can also execute the commands of the operating system. The benefit brought by makefile is - "automatic compilation". Once written, only one make command is needed, and the entire project is completely automatically compiled, which greatly improves the efficiency of software development. make is a command tool that explains the commands in the makefile. Generally speaking, most IDEs have this command, such as: make in Delphi, nmake in Visual C++, make in GNU under Linux. It can be seen that makefile has become a compilation method in engineering.

  There are relatively few articles about how to write makefiles now, which is why I want to write this article. Of course, the make of different manufacturers is different and has different syntax, but its essence is to make a fuss about "file dependencies". Here, I only talk about the make of GNU. My environment is RedHat Linux 8.0. The version of make is 3.80. After all, this make is the most widely used and the most used. And it is also the most compliant with the IEEE 1003.2-1992 standard (POSIX.2).

  In this document, we will use the source code of C/C++ as our basis, so it must involve some knowledge about C/C++ compilation. For this content, please refer to the relevant compiler documents. The default compilers here are GCC and CC under UNIX.

The second part, about the compilation and linking of the program

  Here, I would like to talk more about some specifications and methods of program compilation. Generally speaking, whether it is C, C++, or pascal, the source file must first be compiled into an intermediate code file, which is the .obj file under Windows, UNIX Below is the .o file, namely Object File, this action is called compile (compile). Then a large number of Object Files are synthesized into executable files. This action is called a link. When compiling, what the compiler needs is correct syntax and correct declaration of functions and variables. For the latter, usually you need to tell the compiler where the header file is located (the header file should only be a declaration, and the definition should be placed in a C/C++ file), as long as all the syntax is correct, the compiler can compile the intermediate target document. In general, each source file should correspond to an intermediate object file (O file or OBJ file). When linking, it is mainly to link functions and global variables, so we can use these intermediate object files (O files or OBJ files) to link our applications. The linker does not care about the source file where the function is located, only the intermediate object file (ObjectFile) of the function. Most of the time, because there are too many source files, there are too many intermediate object files generated by compilation, and the name of the intermediate object file needs to be clearly pointed out when linking, which is very inconvenient for compilation, so we need to give the intermediate object file a Package, under Windows, this kind of package is called "Library File", that is, a .lib file, and under UNIX, it is an Archive File, that is, a .a file.
  To sum up, the source file will first generate an intermediate object file, and then the intermediate object file will generate an execution file.At compile time, the compiler only checks the program syntax, and whether functions and variables are declared. If the function is not declared, the compiler will give a warning, but can generate Object File. When linking the program, the linker will search for the implementation of the function in all Object Files. If it cannot find it, it will report a link error code (Linker Error). Under VC, this error is generally: Link 2001 error, meaning that the linker could not find an implementation for the function. You need to specify the Object File of the function.
  Okay, let’s get down to business, GNU make has a lot of content, so let’s start.

The third part, Makefile introduction

  When the make command is executed, a Makefile is needed to tell the make command how to compile and link the program.
  First, we use an example to illustrate the writing rules of Makefile. In order to give everyone an interesting understanding. This example comes from the GNU make manual. In this example, our project has 8 C files and 3 header files. We need to write a Makefile to tell the make command how to compile and link these files. Our rules are:
 1) If the project has not been compiled, then all our C files must be compiled and linked.
 2) If some C files of this project are modified, then we only compile the modified C files and link the target program.
 3) If the header files of this project are changed, then we need to compile the C file that references these header files and link the target program.
  As long as our Makefile is written well enough, all of this can be done with only one make command. The make command will automatically and intelligently determine which files need to be recompiled according to the current file modification situation, so as to compile the required files by ourselves. files and linked target programs.

1. Makefile rules

Before talking about this Makefile, let us take a rough look at the rules of Makefile.
target ... : prerequisites ...
command
...
...
  target is also a target file, which can be an Object File or an execution file. It can also be a label (Label). For the feature of labels, it will be described in the subsequent chapter "Pseudo-targets". The prerequisites are the files or targets required to generate that target. command is the command that make needs to execute. (arbitrary shell command)

  This is a file dependency, that is, one or more target files of target depend on the files in prerequisites, and its generation rules are defined in command. To put it bluntly, if there is more than one file in the prerequisites that is newer than the target file, the command defined by command will be executed. This is the rule of Makefile. That is the core content of the Makefile.

  After all, the Makefile thing is just like this, it seems that this document of mine should be over. hehe. Not all, this is the main line and core of Makefile, but it is not enough to write a Makefile well, I will combine my work experience bit by bit later to show you slowly. There's more to it. :)

2. An example

  As mentioned above, if a project has 3 header files and 8 C files, in order to fulfill the three rules mentioned above, our Makefile should look like this.

edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o
main.o : main.c defs.h
	cc -c main.c
kbd.o : kbd.c defs.h command.h
	cc -c kbd.c
command.o : command.c defs.h command.h
	cc -c command.c
display.o : display.c defs.h buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

  A backslash (\) means a newline. This makes the Makefile easier to read. We can save this content in a file named "Makefile" or "makefile", and then directly enter the command "make" in this directory to generate the executable file edit. If you want to delete the executable file and all intermediate object files, then simply execute "make clean".
  In this makefile, the target file (target) includes: the executable file edit and the intermediate target file (*.o), and the dependent files (prerequisites) are those .c files and .h files after the colon. Each .o file has a set of dependent files, and these .o files are the dependent files of the executable file edit. The essence of the dependency relationship is to describe which files the target file is generated from, in other words, which files the target file is updated from.
  After the dependencies are defined, the subsequent line defines the operating system commands for how to generate the target file, which must start with a Tab key. Remember, make doesn't care how the command works, it just executes the defined command. make will compare the modification date of the targets file and the prerequisites file, if the date of the prerequisites file is newer than the date of the targets file, or if the target does not exist, then make will execute the subsequent defined commands.
  One thing to note here is that clean is not a file, it is just an action name, a bit like lable in C language, there is nothing after the colon, then make will not automatically find the dependencies of the file, It will not automatically execute the commands defined thereafter. To execute subsequent commands, it is necessary to clearly point out the name of the label after the make command. This method is very useful. We can define unused compilation or commands that have nothing to do with compilation in a makefile, such as program packaging, program backup, and so on.

3. How make works

In the default mode, that is, 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 find the first target file (target) in the file. In the above example, it will find the "edit" file and use this file as the final target file.
 3. If the edit file does not exist, or the file modification time of the later .o file that edit depends on is newer than the edit file, then it will execute the command defined later to generate the edit file.
 4. If the .o file that edit depends on also exists, then make will find the dependency whose target is the .o file in the current file, and if found, then generate the .o file according to that rule. (This is a bit like a stack process)
 5. Of course, your C file and H file exist, so make will generate the .o file, and then use the .o file to create the ultimate task of make, which is to execute the file edit up.

  This is the dependency of the entire make, and make will find the dependencies of the files layer by layer until the first target file is finally compiled. During the search process, if there is an error, such as the last dependent file cannot be found, then make will directly exit and report an error, but make will ignore the error of the defined command or the failure of compilation. make only cares about file dependencies, that is, if the file after the colon is still not there after I find the dependencies, then sorry, I won't work.
  Through the above analysis, we know that if clean is not directly or indirectly associated with the first target file, then the commands defined behind it will not be automatically executed, but we can show that it needs to be executed by make. That is, the command - "make clean" to clear all target files for recompilation.
  So in our programming, if this project has been compiled, when we modify one of the source files, such as file.c, then according to our dependencies, our target file.o will be recompiled (that is, in The command defined later in this dependency relationship), so the file.o file is also the latest, so the file modification time of file.o file is newer than edit, so edit will also be relinked (see edit target file for details commands defined later).
  And if we change "command.h", then kdb.o, command.o and files.o will be recompiled, and edit will be relinked.

4. Use variables in makefile

In the example above, let's first look at the rules for edit:

edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o insert.o search.o files.o utils.o

  We can see that the string of the [.o] file is repeated twice. If our project needs to add a new [.o] file, then we need to add it in two places (should be three places, and a place in clean). Of course, our makefile is not complicated, so it is not tiring to add it in two places, but if the makefile becomes complicated, then we may forget a place that needs to be added, and the compilation will fail. Therefore, for the easy maintenance of the makefile, we can use variables in the makefile. The variable of makefile is also a string, it may be better understood as a macro in C language.
  For example, we declare a variable called objects, OBJECTS, objs, OBJS, obj, or OBJ, anyway, as long as it can represent the obj file. We define it like this at the beginning of the makefile:

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o

  Therefore, we can conveniently use this variable in the form of "$(objects)" in our makefile, so our improved makefile becomes as follows:

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
main.o : main.c defs.h
	cc -c main.c
kbd.o : kbd.c defs.h command.h
	cc -c kbd.c
command.o : command.c defs.h command.h
	cc -c command.c
display.o : display.c defs.h buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit $(objects)

  So if a new .o file is added, we only need to simply modify the objects variable. I will talk about more topics about variables in the follow-up.

5. Let make automatically deduce

  GNU's make is very powerful, it can automatically deduce the commands behind files and file dependencies, so we don't need to write similar commands after each [.o] file, because our make will automatically recognize, And derive the command yourself.
  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 cc -c whatever.c will also be derived, so our makefile no longer needs to be so complicated. Our new makefile is out again.

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
	
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
	rm edit $(objects)

  This method is also the "obscure rule" of make. In the content of the above file, ".PHONY" means that clean is a pseudo-target file.
  Regarding the more detailed "obscure rules" and "pseudo-target files", I will tell you one by one later.

6. Alternative style makefile

  Now that our make can automatically deduce commands, I feel a little uncomfortable seeing the pile of [.o] and [.h] dependencies. There are so many repeated [.h], can you put them together, okay? , no problem, this is very easy for make, who told it to provide the function of automatically deriving commands and files? Let's take a look at the latest style of makefiles.

objects = main.o kbd.o command.o display.o insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

.PHONY : clean
clean :
	rm edit $(objects)

  This style makes our makefile very simple, but our file dependencies are a bit messy. You can not have it both ways. It also depends on your preferences. I don't like this style. One is that the dependencies of the files cannot be seen clearly, and the other is that if there are too many files and several new .o files need to be added, it will be unclear.

7. Rules for clearing target files

  Every Makefile should write a rule to clear object files (.o and executable files), which is not only convenient for recompilation, but also very helpful for keeping files clean. This is a "cultivation" (hehe, remember my "programming cultivation"). The general styles are:

clean:
	rm edit $(objects)

A more robust approach is to:

.PHONY : clean
clean :
	-rm edit $(objects)

  As mentioned earlier, .PHONY means that clean is a "pseudo-target". Adding a small minus sign in front of the rm command means that there may be problems with some files, but don't worry about it, and continue to do the following things. Of course, don't put clean rules at the beginning of the file, otherwise, this will become the default target of make, and I believe no one wants to do this. The unwritten rule is - "clean is always placed at the end of the file".
  The above is the overview of a makefile, and it is also the basis of the makefile. There are many details about the makefile below. Are you ready? Come when you're ready.

Part IV, Makefile Overview

1. What is in the Makefile?

Makefile mainly contains five things: explicit rules, implicit rules, variable definitions, file instructions and comments.
1. Explicit rules
  Explicit rules describe how to generate one or more target files. This is clearly indicated by the writer of the Makefile, the files to be generated, the dependencies of the files, and the generated commands.
2. Obscure rules.
  Since our make has the function of automatic deduction, the implicit rules allow us to write the Makefile in a rough and simple way, which is supported by make.
3. Definition of variables.
  In the Makefile, we need to define a series of variables, which are generally strings. This is a bit like a macro in C language. When the Makefile is executed, the variables in it will be expanded to the corresponding reference positions.
4. Document instructions.
  It includes three parts, one is to refer to another Makefile in a Makefile, just like the include in C language; the other is to specify the effective part in the Makefile according to certain circumstances, just like precompilation in C language #if is the same; there is also the definition of a multi-line command. About this part, I will tell in the following part.
5. Notes.
  There are only line comments in the Makefile, just like UNIX shell scripts, the comments use 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: "#".
  Finally, it is also worth mentioning that the commands in the Makefile must start with the [Tab] key.

Second, the file name of the Makefile

  默认的情况下,make 命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是 GNU 的 make 识别的。有另外一些 make 只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的 make 都支持“makefile”和“Makefile”这两种默认文件名。
  当 然 , 你 可 以 使 用 别 的 文 件 名 来 书 写 Makefile , 比 如 : “Make.Linux” ,“Make.Solaris”,“Make.AIX”等,如果要指定特定的 Makefile,你可以使用 make 的“-f”和“–file”参数,如:make -f Make.Linux 或 make --file Make.AIX。

三、引用其它的 Makefile

  在 Makefile 使用 include 关键字可以把别的 Makefile 包含进来,这很像 C 语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include 的语法是:

include <filename>

  filename 可以是当前操作系统 Shell 的文件模式(可以保含路径和通配符) 在 include前面可以有一些空字符,但是绝不能是[Tab]键开始。include 和可以用一个或多个空格隔开。举个例子,你有这样几个 Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了 e.mk 和 f.mk,那么,下面的语句:

include foo.make *.mk $(bar)

等价于:

include foo.make a.mk b.mk c.mk e.mk f.mk

  make 命令开始时,会把找寻 include 所指出的其它 Makefile,并把其内容安置在当前的位。就好像 C/C++的#include 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:
  1、如果 make 执行时,有“-I”或“–include-dir”参数,那么 make 就会在这个参数所指定的目录下去寻找。
  2、如果目录/include(一般是:/usr/local/bin 或/usr/include)存在的话,make 也会去找。如果有文件没有找到的话,make 会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成 makefile 的读取,make 会再重试这些没有找到,或是不能读取的文件,如果还是不行,make 才会出现一条致命信息。如果你想让 make不理那些无法读取的文件,而继续执行,你可以在 include 前加一个减号“-”。
如: -include
  其表示,无论 include 过程中出现什么错误,都不要报错继续执行。和其它版本 make兼 容的相关命令是 sinclude,其作用和这一个是一样的。

四、环境变量 MAKEFILES

  If the environment variable MAKEFILES is defined in your current environment, then make will perform an action similar to include on the value in this variable. The values ​​in this variable are other Makefiles, separated by spaces. It's just that it is different from include, the "target" of the Makefile introduced from this environment variable will not work, and if the file defined in the environment variable finds an error, make will ignore it.
  But here I still suggest not to use this environment variable, because as long as this variable is defined, then when you use make, all Makefiles will be affected by it, which is definitely not what you want to see. I mention this here just to tell everyone that sometimes something strange happens in your Makefile, so you can check whether this variable is defined in the current environment.

5. How make works

The execution steps of GNU make are as follows: (I think other make is similar)
 1. Read all Makefiles.
 2. Read other Makefiles included.
 3. Initialize the variables in the file.
 4. Deduce implicit rules and analyze all rules.
 5. Create dependency chains for all object files.
 6. According to the dependencies, decide which targets should be regenerated.
 7. Execute the build command.
  Steps 1-5 are the first stage, and steps 6-7 are the second stage. In the first stage, if the defined variable is used, make will expand it at the used position. But make does not expand immediately. make uses a delaying tactic. If a variable appears in a dependency rule, then only when the dependency is determined to be used, the variable will be expanded inside it.
  Of course, you don't have to be clear about this way of working, but you will be more familiar with make if you know this way. With this foundation, the follow-up part will be easy to understand.

Part V, writing rules

  A rule consists of two parts, one for dependencies and one for methods that generate targets.
  In the Makefile, the order of the rules is very important, because there should only be one final goal in the Makefile, and other goals are all brought out by this goal, so be sure to let make know what your final goal is. In general, there may be many targets defined in the Makefile, but the target in the first rule will be established as the final target. If there are multiple targets in the first rule, then the first target becomes the final target. This is what make accomplishes.
  Ok, let's take a look at how to write the rules.

1. Examples of rules

foo.o : foo.c defs.h # foo 模块
	cc -c -g foo.c

  Seeing this example, everyone should be familiar with it. As mentioned earlier, 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" (start with the Tab key). This rule tells us two things:
  1. File dependencies, foo.o depends on the files foo.c and defs.h, if the file date of foo.c and defs.h is newer than the file date of foo.o , or foo.o does not exist, then the dependency occurs.
  2. If the foo.o file is generated (or updated). That is the cc command, which explains how to generate the file foo.o. (Of course the foo.c file includes the defs.h file)

2. The syntax of the rules

targets : prerequisites
command
or this:
targets : prerequisites ; command
command

  targets are filenames, separated by spaces, wildcards can be used. Generally speaking, our target is basically one file, but there may be multiple files.
  command is the command line. If it is not on the same line as "target: prerequisites", it must start with [Tab key]. If it is on the same line as prerequisites, it can be separated by a semicolon. (See above) The prerequisites are the files (or dependent targets) that the target depends on. If one of these files is newer than the target file, then the target is considered "out of date" and needs to be regenerated. This has already been said before.
  If the command is too long, you can use a backslash ('\') as a line break. make has no limit on how many characters can be on a line. The rules tell make two things, the dependencies of the files and how to make the object files.
  Generally speaking, make will execute commands with the standard UNIX shell, ie /bin/sh.

3. Use wildcards in rules

  If we want to define a series of similar files, we naturally think of using wildcards. make supports three wildcards: "*", "?" and "[…]". This is the same as the Unix B-Shell. The tilde ("~") character also has a special purpose in filenames. If it is "~/test", it means the test directory under the $HOME directory of the current user. And "~hchen/test" means the test directory under the home directory of user hchen. (These are all little knowledge under Unix, and make also supports it.) Under Windows or MS-DOS, the user does not have a home directory, so the directory pointed by the tilde depends on the environment variable "HOME".
  Wildcards replace your series of files, such as "*.c" means all files with the suffix c. One thing we need to pay attention to is that if there are wildcards in our file name, such as: "*", then we can use the escape character "\", such as "*" to represent the real "*" character instead of any length string.
  Well, let's look at a few examples first:

clean:
	rm -f *.o

  I won't say much about the above example, this is a wildcard supported by the operating system Shell. This is a wildcard in the command.

print: *.c
	lpr -p $?
	touch print

  The above example shows that wildcards can also be used in our rules, and the target print depends on all [.c] files. The "$?" is an automation variable, which I'll tell you about later.

objects = *.o

  The above example shows that wildcards can also be used in variables. Not that [ .o] will expand, no! The value of objects is " .o". Variables in Makefile are actually macros in C/C++. If you want to expand the wildcard in the variable, that is, let the value of objects be the set of all [.o] file names, then you can do this:

objects := $(wildcard *.o)

  This usage is indicated by the keyword "wildcard", which we will discuss later on in Makefile keywords.

4. File search

  In some large projects, there are a large number of source files. Our usual practice is to classify these many source files and store them in different directories. Therefore, when make needs to find the dependencies of the file, you can add the path before the file, but the best way is to tell make a path and let make find it automatically.
  The special variable "VPATH" in the Makefile completes this function. If this variable is not specified, make will only look for dependent files and target files in the current directory. If this variable is defined, then make will go to the specified directory to find the file if the current directory cannot be found.

VPATH = src:../headers

  The above definition specifies two directories, "src" and ".../headers", make will search in this order. Directories are separated by "colon". (Of course, the current directory is always the highest priority search place)
  Another way to set the file search path is to use the "vpath" keyword of make (note that it is all lowercase), this is not a variable, it is a make key Word, which is similar to the VPATH variable mentioned above, but it is more flexible. It can specify that different files are in different search directories. This is a very flexible feature. It can be used in three ways:

  • 1. vpath <pattern> <directories>
    specifies the search directory for files that match the pattern.

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

    3. vpath
    clears all the file search directories that have been set.

  The "%" character needs to be included in the vapth usage method. "%" means to match zero or several characters, for example, "%.h" means all files ending with ".h". specifies the filesets to search, and then specifies the directories to search for the filesets. 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)
  We can use the vpath statement continuously to specify different search strategies. If the same or repeated occurrences appear in consecutive vpath statements, then make will perform the search according to the order of the vpath statements. like:

vpath %.c foo
vpath % blish
vpath %.c bar

It means files ending in ".c", first in the "foo" directory, then in the "blish" directory, and finally in the "bar" directory.

vpath %.c foo:bar
vpath % blish

The above statement means that the files ending in ".c" are first in the "foo" directory, then in the "bar" directory, and finally in the "blish" directory.

5. False targets

  In one of the earliest examples, we mentioned a "clean" goal, which is a "pseudo-goal",

clean:
	rm *.o temp

  Just like "clean" in our previous example, since we generate many compiled files, we should also provide a "target" that cleans them up for a full recompilation. (Use this target with "make clean") Because, we don't generate the "clean" file. "Pseudo-target" is not a file, but a label. Since "pseudo-target" is not a file, make cannot generate its dependencies and decide whether to execute it. We can only make this "goal" work by explicitly specifying it. Of course, the name of the "false target" cannot be the same as the file name, otherwise it will lose the meaning of "false target".
  Of course, in order to avoid this situation with the same file name, we can use a special tag ".PHONY" to explicitly indicate that a target is a "pseudo-target", explaining to make that no matter whether there is this file, this target is " false target".

.PHONY : clean

  As long as there is this statement, no matter whether there is a "clean" file or not, the only way to run the "clean" target is "make clean". So the whole process can be written like this:

.PHONY: clean
clean:
	rm *.o temp

  Fake targets generally have no dependent files. However, we can also specify dependent files for pseudo-targets. False targets can also be used as "default targets", as long as they are placed first. An example is, if your Makefile needs to generate several executable files in one go, but you just want to simply type a make to finish, and all the target files are written in one Makefile, then you can use "pseudo-target" This feature:

all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
	cc -o prog1 prog1.o utils.o
prog2 : prog2.o
	cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
	cc -o prog3 prog3.o sort.o utils.o

  We know that the first target in the Makefile will be used as its default target. We declare an "all" pseudo-target that depends on the other three targets. Since the pseudo-target is always executed, the three targets it depends on are always not as new as the "all" target. Therefore, the rules for the other three goals will always be resolved. It also achieves our goal of generating multiple targets in one go. ".PHONY : all" declares the target "all" as a "pseudo-target".
  By the way, from the example above we can see that a target can also be a dependency. Therefore, false targets can also become dependencies. See the example below:

.PHONY: cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
	rm program
cleanobj :
	rm *.o
cleandiff :
	rm *.diff

"make cleanall" will clean all files to be cleaned. The two pseudo-targets "cleanobj" and "cleandiff" are a bit like "subroutines". We can enter the "make cleanall" and "make cleanobj" and "make cleandiff" commands to achieve the purpose of clearing different types of files.

6. Multi-objective

  There can be more than one target in the Makefile rules, and it supports multiple targets. It is possible that our multiple targets depend on one file at the same time, and the generated commands are roughly similar. So we can combine it. Of course, the execution commands of the generation rules of multiple targets are the same, which may cause us trouble, but fortunately, we can use an automation variable "$@" (about automation variables, which will be described later), this Variables represent the set of all targets in the current rule. This may be very abstract, so let's look at an example.

bigoutput littleoutput : text.g
	generate text.g -$(subst output,,$@) > $@

The above rules are equivalent to:

bigoutput : text.g
	generate text.g -big > bigoutput
littleoutput : text.g
	generate text.g -little > littleoutput

  Among them, the "$" in -$(subst output, $@) means to execute a Makefile function, the function name is subst, and the following are parameters. About functions, will be described later. The function here means to intercept the string, "$@" means the set of targets, just like an array, "$@" takes out the targets one by one, and sticks to the command.

Seven, static mode

  Static mode makes it easier to define multi-target rules and makes our rules more flexible and flexible. Let's take a look at the syntax first:
<targets ...>: : <prereq-patterns ...>
<commands>
...
targets defines a series of target files, which can have wildcards. is a collection of targets.
target-parrtern specifies the mode of targets, that is, the target set mode.
prereq-parrterns is the dependent mode of the target, it defines the target-dependent mode again for the mode formed by target-parrtern.

  It may still not be clear to describe these three things in this way, so let's give an example to illustrate. If our definition is "%.o", it means that all of our sets end with ".o", and if our definition is "%.c", it means that the formed target set is performed twice Definition, its calculation method is to take the "%" in the pattern (that is, remove the ending [.o]), and add the ending [.c] to it to form a new set.
  Therefore, there should be a "%" character in our "target mode" or "dependency mode". If there is a "%" in your file name, then you can escape it with a backslash "\" to indicate A real "%" character. See an example:

objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@

  In the above example, it is specified that our target is obtained from $object, "%.o" indicates all targets ending with ".o", that is, "foo.o bar.o", that is, the variable $object collection mode, and the dependency mode "%.c" takes the "%" of the mode "%.o", that is, "foo bar", and adds the suffix ".c" to it, so our dependency target is " foo.c bar.c". The "$<" and "$@" in the command are automation variables, "$<" means all dependent target sets (that is, "foo.c bar.c"), and "$@" means target sets (also is "foo.o bar.o"). Thus, the above rule expands to be equivalent to the following rule:

foo.o : foo.c
	$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
	$(CC) -c $(CFLAGS) bar.c -o bar.o

  Just imagine, if we have hundreds of "%.o", we can write a bunch of rules just by using this very simple "static pattern rule", which is really efficient. The usage of "static pattern rules" is very flexible, and if used well, it will be a very powerful function. Let's look at another example:

files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
	$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
	emacs -f batch-byte-compile $<

  $(filter %.o, $(files)) means to call the filter function of Makefile to filter the "$filter" set, as long as the pattern is "%.o". I don't need to say more about other content. This example demonstrates greater flexibility in Makefiles.

8. Automatically generate dependencies

  In the Makefile, our dependencies may need to include a series of header files. For example, if there is a sentence "#include "defs.h"" in our main.c, then our dependencies should be:

main.o : main.c defs.h

  However, if it is a relatively large project, you must know which C files contain which header files, and when you add or delete header files, you also need to carefully modify the Makefile, which is a very maintenance-free job. To avoid this heavy and error-prone thing, we can use a feature of C/C++ compilation. Most C/C++ compilers support a "-M" option, which automatically finds the header files included in the source file and generates a dependency. For example, if we execute the following command:

	cc -M main.c

Its output is:

main.o : main.c defs.h

  So the dependencies are automatically generated by the compiler, so that you don't have to manually write the dependencies of several files, but are automatically generated by the compiler. One thing to remind is that if you use the GNU C/C++ compiler, you have to use the "-MM" parameter, otherwise, the "-M" parameter will include some header files of the standard library.

	gcc -M main.c 

The output is:

main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
/usr/include/bits/sched.h /usr/include/libio.h \
/usr/include/_G_config.h /usr/include/wchar.h \
/usr/include/bits/wchar.h /usr/include/gconv.h \
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
/usr/include/bits/stdio_lim.h
gcc -MM main.c 

The output is then:

main.o: main.c defs.h

  So, how does this function of the compiler relate to our Makefile. Because in this way, our Makefile should also be regenerated according to these source files, so that Makefile itself depends on the source files? This function is not realistic, but we can have other means to achieve this function in a roundabout way. The GNU organization recommends that the compiler automatically generate dependencies for each source file into a file, and generate a "name.d" Makefile for each "name.c" file, [.d] file The dependencies of the corresponding [.c] files are stored in . Therefore, we can write out the dependencies between [.c] files and [.d] files, and let make automatically update or create [.d] files and include them in our main Makefile. In this way, we can It is now possible to automatically generate dependencies for each file.
  Here, we give a pattern rule to generate [.d] files:

%.d: %.c
@set -e; rm -f $@; \
	$(CC) -M $(CPPFLAGS) $< > $@.$$$$; \
	sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

  This rule means that all [.d] files depend on [.c] files, "rm -f $@" means to delete all targets, that is, [.d] files, the second line means , generate a dependent file for each dependent file "$<", that is, [.c] file, "$@" means the pattern "%.d" file, if there is a C file name.c, then "%" is "name", "$$$$" means a random number, the file generated in the second line may be "name.d.12345", the third line uses the sed command to make a replacement, please refer to the usage of the sed command See related usage documentation. The fourth line is to delete the temporary file.
All in all, what this mode needs to do is to add the dependencies of the [.d] file to the dependencies generated by the compiler, that is, to convert the dependencies:
main.o : main.c defs.h
into:
main.o main.d : main.c defs.h
  Therefore, our [.d] file will be automatically updated and automatically generated. Of course, you can also add not only dependencies to this [.d] file, but also generate The command can also be added together, so that each [.d] file contains a complete rule. Once we've done that, next, we'll put these auto-generated rules into our main Makefile. We can use the "include" command of Makefile to introduce other Makefile files (mentioned earlier), for example:

sources = foo.c bar.c
include $(sources:.c=.d)

  The ".c=.d" in "$(sources:.c=.d)" in the above statement means to make a replacement, replace all the strings of [.c] in the variable $(sources) with [ .d], I will describe the content of this "replacement" in more detail later. Of course, you have to pay attention to the order, because include loads files sequentially, and the target in the [.d] file loaded first will become the default target.

Part VI Writing Commands

The commands in each rule are the same as the command line of the operating system shell. make will execute commands one by one in order, and the beginning of each command must start with the [Tab] key, unless the command is immediately followed by a semicolon following a dependency rule. Spaces or blank lines between command lines are ignored, but if the space or blank line starts with the Tab key, then make considers it an empty command.
  We may use different shells under UNIX, but the make command is interpreted and executed by “/bin/sh”—the standard shell of UNIX by default. Unless you specify a different shell. In Makefile, "#" is a comment character, much like "//" in C/C++, and the following characters in this line are all commented.

1. Display commands

  Normally, make will output the command line it will execute to the screen before the command is executed. When we use the "@" character before the command line, then this command will not be displayed by make. The most representative example is that we use this function to display some information on the screen. like:

@echo 正在编译 XXX 模块......

When make is executed, the string "Compiling XXX module..." will be output, but the command will not be output. If there is no "@", then make will output:

echo 正在编译 XXX 模块......
正在编译 XXX 模块......

  If the make parameter "-n" or "–just-print" is brought in when make is executed, then it only displays the command, but does not execute the command. This function is very helpful for us to debug our Makefile and see the command we wrote It is what it looks like or in what order it is executed.
  The make parameter "-s" or "--slient" is to completely prohibit the display of commands.

2. Command execution

  When the dependent target is newer than the target, that is, when the target of the rule needs to be updated, make will execute the subsequent commands one by one. It should be noted that if you want to apply the result of the previous command to the next command, you should use a semicolon to separate the two commands. For example, your first command is the cd command, and you want the second command to be run on the basis of the cd, then you can't write these two commands on two lines, but you should write these two commands in on one line, separated by semicolons. Such as:
Example 1:

exec:
	cd /home/hchen
	pwd

Example two:

exec:
	cd /home/hchen; pwd

  When we execute "make exec", cd in the first example has no effect, pwd will print out the current Makefile directory, but in the second example, cd will work, pwd will print "/home/hchen ". make generally uses the system shell defined in the environment variable SHELL to execute commands. By default, it uses the standard UNIX shell—/bin/sh to execute commands. But it is a bit special under MS-DOS, because there is no SHELL environment variable under MS-DOS, of course you can also specify it. If you specify a UNIX-style directory form, first, make will search for the command interpreter in the path specified by SHELL, if it cannot find it, it will search for it in the current directory of the current drive letter, if it cannot find it again, It looks in all paths defined in the PATH environment variable. In MS-DOS, if the command interpreter you defined is not found, it will add suffixes such as ".exe", ".com", ".bat", ".sh" to your command interpreter.

3. Command error

  After each command is run, make will check the return code of each command. If the command returns successfully, then make will execute the next command. When all the commands in the rule return successfully, the rule is considered to be successfully completed. If a command in a rule fails (command exit code is non-zero), then make will terminate the execution of the current rule, which may terminate the execution of all rules.
  Sometimes, an error in a command does not mean it is wrong. For example, for the mkdir command, we must create a directory. If the directory does not exist, then mkdir will be executed successfully, and everything will be fine. If the directory exists, then an error will occur. The reason why we use mkdir means that there must be such a directory, so we don't want mkdir to make an error and terminate the running of the rule.
  In order to do this and ignore the error of the command, we can add a minus sign "-" (after the Tab key) before the command line of the Makefile to mark that the command is considered successful regardless of whether there is an error or not. like:

clean:
	-rm -f *.o

  Another global method is to add the "-i" or "-ignore-errors" parameter to make, then all commands in the Makefile will ignore errors. And if a rule targets ".IGNORE", then all commands in this rule will ignore errors. These are different levels of error prevention for commands, which you can set to your liking.
  Another make parameter to mention is "-k" or "--keep-going". This parameter means that if a command in a rule fails, then the execution of the rule will end, but Continue with other rules.

Fourth, nested execution make

In some large projects, we will put our different modules or source files of different functions in different directories, we can write a Makefile in each directory, which is beneficial to make our Makefile become It should be more concise, instead of writing everything in one Makefile, which will make it difficult to maintain our Makefile. This technique is of great benefit to our module compilation and segmented compilation.
  For example, we have a subdirectory called subdir, and there is a Makefile in this directory to specify the compilation rules for the files in this directory. Then the Makefile of our master control can be written like this:

subsystem:
	cd subdir && $(MAKE)

which is equivalent to:

subsystem:
	$(MAKE) -C subdir

  Defining the $(MAKE) macro variable means that maybe our make needs some parameters, so defining it as a variable is more convenient for maintenance. These two examples mean to enter the "subdir" directory first, and then execute the make command.
  We call this Makefile "Master Control Makefile". The variables of Master Control Makefile can be passed to the lower Makefile (if you show the statement), but will not overwrite the variables defined in the lower Makefile, unless "-e "parameter.
  If you want to pass variables to down-level Makefiles, then you can use declarations like this:

export <variable ...>

If you don't want certain variables to be passed to the lower-level Makefile, you can declare them like this:

unexport <variable ...>

Example 1:

export variable = value

which is equivalent to:

variable = value
export variable

which is equivalent to:

export variable := value

which is equivalent to:

variable := value
export variable

Example two:

export variable += value

which is equivalent to:

variable += value
export variable

  If you want to pass all the variables, then only one export will do. There is no need to follow anything, which means that all variables are passed.
  It should be noted that there are two variables, one is SHELL and the other is MAKEFLAGS, these two variables will always be passed to the lower Makefile whether you export or not, especially the MAKEFILES variable, which contains the parameter information of make. If there are make parameters or this variable is defined in the upper Makefile when we execute the "master control Makefile", then the MAKEFILES variable will be these parameters and will be passed to the lower Makefile, which is a system-level environment variable.
  However, several parameters in the make command are not passed down, they are "-C", "-f", "-h", "-o" and "-W" (details about Makefile parameters will be explained later ), if you don't want to pass parameters to the lower layer, then you can do this:

subsystem:
	cd subdir && $(MAKE) MAKEFLAGS=

  If you define the environment variable MAKEFLAGS, then you have to make sure that the options in it are used by everyone. If there are "-t", "-n", and "-q" parameters, there will be surprises for you The results may make you extremely panic.
  There is also a more useful parameter in "nested execution", "-w" or "--print-directory" will output some information during the make process, allowing you to see the current working directory. For example, if our subordinate make directory is "/home/hchen/gnu/make", if we use "make -w" to execute, then when entering this directory, we will see:

make: Entering directory `/home/hchen/gnu/make'.

And when we leave the directory after completing the lower level make, we will see:

make: Leaving directory `/home/hchen/gnu/make'

  When you use the "-C" parameter to specify the underlying Makefile of make, "-w" will be automatically turned on. If there is "-s" ("--slient") or "--no-print-directory" in the parameter, then "-w" is always invalid.

5. Define the command package

  If some identical command sequences appear in the Makefile , then we can define a variable for these identical command sequences. The syntax for defining such a sequence of commands begins with "define" and ends with "endef", as in:

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

  "run-yacc" is the name of the command package, which should not be the same as the variable in the Makefile. The two lines in "define" and "endef" are the command sequence. The first command in this command package is to run the Yacc program, because the Yacc program always generates a "y.tab.c" file, so the command in the second line is to change the name of this file. Let's put this command package into an example to see.

foo.c : foo.y
	$(run-yacc)

  We can see that to use this command package, we are like using variables. In the use of this command package, "$^" in the command package "run-yacc" is "foo.y", "$@" is "foo.c" (for this special variable starting with "$") , we will introduce later), when make executes the command package, each command in the command package will be executed independently in turn.

Part VII Using Variables

  The variable defined in the Makefile is like a macro in C/C++ language. It represents a text string, which will be automatically expanded in the place where it is used when it is executed in the Makefile. The difference with C/C++ is that you can change its value in Makefile. In Makefiles, variables can be used in "targets", "dependencies", "commands" or other parts of the Makefile.
  The name of the variable can contain characters, numbers, and underscores (it can start with a number), but it should not contain ":", "#", "=" or empty characters (spaces, carriage returns, etc.). Variables are case sensitive, "foo", "Foo" and "FOO" are three different variable names. The traditional Makefile variable names are named in all uppercase, but I recommend using variable names with uppercase and lowercase, such as: MakeFlags. This can avoid conflicts with system variables and unexpected things happen.
  Some variables are very strange strings, such as " <", "<", "<" , " @", etc. These are automation variables, which I'll cover later.

1. The basis of variables

  The variable needs to be given an initial value when it is declared, and the "$" symbol needs to be added before the variable name when it is used, but it is best to use parentheses "()" or braces "{}" to enclose the variable. If you're going to use a real "$" character, then you need to use "$$" for that.
  Variables can be used in many places, such as "targets", "dependencies", "commands" in rules, and new variables. Let's look at an example first:

objects = program.o foo.o utils.o
program : $(objects)
	cc -o program $(objects)
$(objects) : defs.h

Variables are expanded exactly where they are used, just like macros in C/C++, for example:

foo = c
prog.o : prog.$(foo)
	$(foo)$(foo) -$(foo) prog.$(foo)

After expansion, we get:

prog.o : prog.c
	cc -c prog.c

  Of course, don't do this in your Makefile, here is just an example to show how the variables in the Makefile really look like when they are used. It can be seen that it is a "substitution" principle.
  In addition, parentheses around variables are purely for safer use of this variable. In the above example, if you don't want to put parentheses around variables, that's okay, but I still strongly recommend that you put parentheses around variables.

2. Variables in variables

  When defining the value of a variable, we can use other variables to construct the value of the variable. In the Makefile, there are two ways to use variables to define the value of the variable.
  Let’s look at the first method first, which is to simply use the "=" sign. The left side of the "=" is the variable, and the right side is the value of the variable. The value of the variable on the right side can be defined anywhere in the file. That is to say, the variable on the right side does not have to be a defined value, it can also use the value defined later. like:

foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
	echo $(foo)

  Our execution of "make all" will print the value of the variable $(foo) as "Huh?" (the value of $(foo) is $(bar), the value of $(bar) is $(ugh), $(ugh) The value is "Huh?") It can be seen that variables can be defined using the following variables.
  This function has both good and bad points. The good thing is that we can push the real value of the variable to be defined later, such as:

CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar

  When "CFLAGS" is expanded in the command, it will be "-Ifoo -Ibar -O". But this form also has a bad place, that is, recursive definition, such as:

CFLAGS = $(CFLAGS) -O

or:

A = $(B)
B = $(A)

  This will make make fall into an infinite variable expansion process. Of course, our make is capable of detecting such a definition and will report an error. Also, if you use functions in variables, then this method will make our make run very slow, and what's worse, he will use the two make functions "wildcard" and "shell" to occur unpredictable mistake. Because you won't know how many times these two functions will be called.
  In order to avoid the above method, we can use another method in make to define variables with variables. This method uses the ":=" operator, such as:

x := foo
y := $(x) bar
x := later

which is equivalent to:

y := foo bar
x := later

It is worth mentioning that in this method, the previous variables cannot use the latter variables, only the previously defined variables can be used. If this is the case:

y := $(x) bar
x := foo

Then, the value of y is "bar", not "foo bar".
  The above are some relatively simple variables used, let us look at a complex example, which includes the use of make functions, conditional expressions and a system variable "MAKELEVEL":

ifeq (0,${MAKELEVEL})
	cur-dir := $(shell pwd)
	whoami := $(shell whoami)
	host-type := $(shell arch)
	MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

  Regarding conditional expressions and functions, we will talk about it later. For the system variable "MAKELEVEL", it means that if our make has a nested execution action (see the previous "Nested use of make"), then this variable It will record the calling level of our current Makefile.
  Here are two more things we need to know when defining variables. Please look at an example first. If we want to define a variable whose value is a space, then we can do it like this:

nullstring :=
space := $(nullstring) # end of the line

  nullstring is an Empty variable with nothing in it, and our space is a blank space. Because it is difficult to describe a space on the right side of the operator, the technique used here works very well. First, an Empty variable is used to mark the beginning of the value of the variable, and then the "#" comment is used to indicate the end of the variable definition. In this way , we can define a variable whose value is a space. Please pay attention to the use of "#" here. The feature of the comment "#" is worthy of our attention. If we define a variable like this:

dir := /foo/bar # directory to put the frobs in

  The value of the dir variable is "/foo/bar", followed by 4 spaces. If we use this variable to specify another directory - "$(dir)/file", then it will be over.
Another useful operator is "?=", first look at the example:

FOO ?= bar

  The implication is that if FOO has not been defined, then the value of the variable FOO is "bar", and if FOO was previously defined, then this statement will do nothing, which is equivalent to:

ifeq ($(origin FOO), undefined)
	FOO = bar
endif

3. Advanced usage of variables

  Here are two advanced usage methods of variables, the first one is the replacement of variable values. We can replace the common part in the variable, the format is "$(var:a=b)" or "${var:a=b}", which means, put all the variable "var" with "a The "a" at the end of the "string" is replaced with a "b" string. "End" here means "space" or "terminator". Let's look at an example:

foo := a.o b.o c.o
bar := $(foo:.o=.c)

In this example, we first define a "$(foo)" variable, and the second line means to replace all ".o" string "ends" in "$(foo)" with ".c" , so the value of our "$(bar)" is "ac" "bc" "cc". Another technique for variable substitution is defined in "static mode" (see previous section), such as:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)

This depends on the same pattern in the replaced string. The pattern must contain a "%" character. This example also makes the value of the $(bar) variable "ac" "bc" "cc". The second advanced usage is - "reuse the value of the variable as a variable". Let's look at an example first:

x = y
y = z
a := $($(x))

In this example, $(x) has the value "y", so $($(x)) is $(y), and $(a) ​​has the value "z". (Note, "x=y", not "x=$(y)") We can also use more levels:

x = y
y = z
z = u
a := $($($(x)))

The value of $(a) ​​here is "u", and the relevant derivation is left to the reader. Let's make it a little more complicated, using the first way of "using variables in variable definitions", and look at an example:

x = $(y)
y = z
z = Hello
a := $($(x))

Here ((( (x)) is replaced by $($(y)), because the value of $(y) is "z", so the final result is: a:=$(z), which is "Hello". To make it a little more complicated, let's add the function:

x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))

  In this example, "$($($(z)))" expands to "$($(y))", which again expands to "$($(subst1,2,$(x)))" . The value of $(x) is "variable1", the subst function replaces all "1" strings in "variable1" with "2" strings, so "variable1" becomes "variable2", and then takes its value, so , in the end, the value of $(a) ​​is the value of $(variable2) - "Hello". (Oh, it's not easy) In this way, you can use multiple variables to form a variable name, and then get its value:

first_second = Hello
a = first
b = second
all = $($a_$b)

Here "$a_$b" constitutes "first_second", so the value of $(all) is "Hello". Let's look at an example combining the first technique:

a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o
sources := $($(a1)_objects:.o=.c)

In this example, if the value of $(a1) is "a", then the value of $(sources) is "ac bc cc"; if the value of $(a1) is "1", then the value of $(sources) The value is "1.c 2.c 3.c". Here's another example of this technique used with "functions" and "conditional statements":

ifdef do_sort
	func := sort
else
	func := strip
endif
bar := a d b g q c
foo := $($(func) $(bar))

In this example, if "do_sort" is defined, then: foo := $(sort adbgqc), then the value of $(foo) is "abcdgq", and if "do_sort" is not defined, then: foo := $(sort ad bg qc), what is called is the strip function. Of course, the technique of "treating the value of a variable as a variable" can also be used on the left side of the operator:

dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
	lpr $($(dir)_sources)
endef

Three variables are defined in this example: "dir", "foo_sources" and "foo_print".

4. Add variable value

We can use the "+=" operator to append values ​​to variables, such as:

objects = main.o foo.o bar.o utils.o
objects += another.o

So, our $(objects) value becomes: "main.o foo.o bar.o utils.o another.o" (another.o is appended) Using the "+=" operator, it can be simulated as follows An example of this:

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

The difference is that it is more concise to use "+=". If the variable has not been defined before, then "+=" will automatically become "=", if there is a variable definition before, then "+=" will inherit the assignment symbol of the previous operation. If the previous operator is ":=", then "+=" will use ":=" as its assignment operator, such as:

variable := value
variable += more

Equivalent to:

variable := value
variable := $(variable) more

But if this is the case:

variable = value
variable += more

  Since the previous assignment symbol was "=", "+=" will also be assigned with "=", so it will not happen that the recursive definition of variables occurs, which is very bad, so make will automatically We solve this problem, we don't have to worry about this problem.

Five, override indicator

  If there is a variable that is usually set by the command line parameters of make, then the assignment to this variable in the Makefile will be ignored. If you want to set the value of such parameters in the Makefile, then you can use the "override" directive. Its syntax is:

override <variable> = <value>
override <variable> := <value>

#当然,你还可以追加:
override <variable> += <more text>

For multi-line variable definitions, we use the define indicator. Before the define indicator, the ovveride indicator can also be used, such as:

override define foo
bar
endef

Six, multi-line variables

  Another way to set the value of a variable is to use the define keyword. Using the define keyword to set the value of the variable can have newlines, which is beneficial to define a series of commands (the technique of "command package" we mentioned earlier is to use this keyword). The define indicator is followed by the name of the variable, and a new line defines the value of the variable, and the definition ends with the endef keyword. It works the same as the "=" operator. Variable values ​​can contain functions, commands, text, or other variables. Because the command needs to start with the [Tab] key, if the command variable you define with define does not start with the [Tab] key, then make will not consider it as a command. The following example shows the use of define:

define two-lines
	echo foo
	echo $(bar)
endef

Seven, environment variables

  The system environment variable when make is running can be loaded into the Makefile file when make starts running, but if this variable has been defined in the Makefile, or this variable is brought in by the make command line, then the value of the system environment variable will be is covered. (If make specifies the "-e" parameter, then the system environment variable will override the variable defined in the Makefile) Therefore, if we set the "CFLAGS" environment variable in the environment variable, then we can use it in all Makefiles this variable. This is of great benefit for us to use unified compilation parameters. If CFLAGS is defined in the Makefile, then this variable in the Makefile will be used, if not defined, the value of the system environment variable will be used, a unity of commonality and individuality, much like the characteristics of "global variables" and "local variables".
  When make is called nestedly (refer to the previous "Nested Calls" section), the variables defined in the upper Makefile will be passed to the lower Makefile in the form of system environment variables. Of course, by default only variables set via the command line are passed. However, if the variables defined in the file are to be passed to the underlying Makefile, they need to be declared with the export keyword. (See previous chapters)
  Of course, I don't recommend defining many variables in the system environment. In this way, when we execute different Makefiles, we have the same set of system variables, which may bring more troubles.

8. Target variable

  The variables defined in the Makefile we mentioned above are all "global variables", and we can access these variables throughout the file. Of course, except for "automation variables", automation variables of this type such as "$<" belong to "rule variables", and the value of such variables depends on the target of the rule and the definition of the dependent target. Of course, I can also set a local variable for a certain target. This variable is called "Target-specific Variable", which can have the same name as "Global Variable", because its scope of action is only in this rule and related rules. , so its value is only valid within the scope of the scope. It will not affect the value of global variables outside the rule chain. Its syntax is:

<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>

  <variable-assignment> can be various assignment expressions mentioned above, such as "=", ":=", "+=" or "?=". The second syntax is for variables brought in by the make command line, or system environment variables. This feature is very useful. When we set such a variable, this variable will be applied to all rules triggered by this goal. like:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
	$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
	$(CC) $(CFLAGS) prog.c
foo.o : foo.c
	$(CC) $(CFLAGS) foo.c
bar.o : bar.c
	$(CC) $(CFLAGS) bar.c

  In this example, regardless of the value of the global $(CFLAGS), the value of $(CFLAGS) is is "-g".

9. Pattern variables

  In GNU make, pattern-specific variables are also supported. Through the above target variables, we know that variables can be defined on a certain target. The advantage of mode variables is that we can give a "mode" and define variables on all targets that conform to this mode.
  We know that the "mode" of make generally contains at least one "%", so we can define target variables for all targets ending with [.o] in the following way:

%.o : CFLAGS = -O
#同样,模式变量的语法和“目标变量”一样:
<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>

  override is also for the variables passed in by the system environment, or the variables specified by the make command line.

Part VIII Judgment of Use Conditions

  Using conditional judgment, make can choose different execution branches according to different situations at runtime. Conditional expressions can compare the values ​​of variables, or compare the values ​​of variables and constants.

1. Examples

The following example determines whether the $(CC) variable is "gcc", and if so, compiles the target using GNU functions.

libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
	$(CC) -o foo $(objects) $(libs_for_gcc)
else
	$(CC) -o foo $(objects) $(normal_libs)
endif

  It can be seen that in the rule of the above example, the target "foo" can select different function libraries to compile the program according to the value of the variable "$(CC)".
  We can see three keywords from the example above: ifeq, else, and endif. ifeq means the beginning of a conditional statement, and specifies a conditional expression, the expression contains two parameters, separated by commas, and the expression is enclosed in parentheses. else indicates the case where the conditional expression is false. endif indicates the end of a conditional statement, and any conditional expression should end with endif. When our variable $(CC) has the value "gcc", the rules for target foo are:

foo: $(objects)
	$(CC) -o foo $(objects) $(libs_for_gcc)

And when our variable $(CC) has a value other than "gcc" (such as "cc"), the rule for target foo is:

foo: $(objects)
	$(CC) -o foo $(objects) $(normal_libs)

Of course, we can also write the above example more concisely:

libs_for_gcc = -lgnu
normal_libs =
ifeq ($(CC),gcc)
	libs=$(libs_for_gcc)
else
	libs=$(normal_libs)
endif
foo: $(objects)
	$(CC) -o foo $(objects) $(libs)

2. Grammar

The syntax for conditional expressions is:

<conditional-directive>
<text-if-true>
endif

as well as:

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

where it represents a conditional keyword, such as "ifeq". This keyword has four.
The first keyword is "ifeq" which we have seen before

ifeq (<arg1>, <arg2>)
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

Compares whether the values ​​of arguments "arg1" and "arg2" are the same. Of course, we can also use the make function in the parameters. like:

ifeq ($(strip $(foo)),)
	<text-if-empty>
endif

This example uses the "strip" function, if the return value of this function is empty (Empty), then <text-if-empty> will take effect.
The second condition keyword is "ifneq". The syntax is:

ifneq (<arg1>, <arg2>)
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

It compares whether the values ​​of the arguments "arg1" and "arg2" are the same, and returns true if they are different. Similar to "ifeq".
The third condition keyword is "ifdef". The syntax is:

ifdef <variable-name>

If the value of the variable is non-null, then the expression is true. Otherwise, the expression is false. Of course, it can also be the return value of a function. Note that ifdef only tests whether a variable has a value, it does not expand the variable to the current position. Let's look at two examples, Example 1:

bar =
foo = $(bar)
ifdef foo
	frobozz = yes
else
	frobozz = no
endif

Example two:

foo =
ifdef foo
	frobozz = yes
else
	frobozz = no
endif

In the first example, the "$(frobozz)" value is "yes", in the second it is "no".
The fourth condition keyword is "ifndef". Its syntax is:

ifndef <variable-name>

I won't say much about this, it means the opposite of "ifdef".
  On the <conditional-directive> line, extra spaces are allowed, but cannot start with the [Tab] key (otherwise it is considered a command). The comment character "#" is also safe. The same goes for "else" and "endif", as long as it doesn't start with the [Tab] key.
  Special attention is that make calculates the value of the conditional expression when reading the Makefile, and selects the statement according to the value of the conditional expression, so you'd better not put automation variables (such as "$@", etc.) into Conditional expressions, because automation variables are only available at runtime.
  Also, to avoid confusion, make does not allow the entire conditional statement to be split into two parts in different files.

Part 9 Using Functions

  In Makefile, functions can be used to process variables, so that our commands or rules are more flexible and intelligent. The functions supported by make are not too many, but it is enough for our operation. After the function is called, the return value of the function can be used as a variable.

1. Function call syntax

Function calls, much like the use of variables, are also marked with "$", and their syntax is as follows:

$(<function> <arguments>)

or

${<function> <arguments>}

  <function> is the function name, and there are not many functions supported by make. <arguments> is the parameters of the function, the parameters are separated by commas "," and the function name and parameters are separated by "space". A function call begins with "$", and the function name and parameters are enclosed in parentheses or curly braces. Feels like a variable, doesn't it? The parameters in the function can use variables. For the uniformity of style, it is better to use the same parentheses between functions and variables, such as "$(subst a,b,$(x))" instead of "$(subst a, b,${x})" form. Because unification will be clearer and reduce some unnecessary troubles. Let's look at an example:

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

  In this example, the value of $(comma) is a comma. $(space) uses $(empty) to define a space, the value of $(foo) is "abc", and the definition of $(bar) is used to call the function "subst", which is a replacement function. This function has three The first parameter is the string to be replaced, the second parameter is the replacement string, and the third parameter is the string to be replaced. This function is to replace the spaces in $(foo) with commas, so the value of $(bar) is "a,b,c".

2. String processing functions

1 、subst

$(subst <from>,<to>,<text>)

  • Name: String substitution function - subst.
  • Function: string The string in is replaced with .
  • Return: The function returns the replaced string.
  • Example:
$(subst ee,EE,feet on the street)

Replace "ee" in "feet on the street" with "EE", and the returned result is "fEEt on the strEEt".

2 、patsubst

$(patsubst <pattern>,<replacement>,<text>)

  • Name: pattern string substitution function -- patsubst.
  • Function: Find whether the words in <text> (words separated by "space", "tab" or "carriage return" and "line feed") match the pattern <pattern>, if they match, replace them with <replacement>. <pattern> can include the wildcard "%", which means a string of any length. If "%" is also included in <replacement>, the "%" in <replacement> will be the string represented by the "%" in the <replacement>. (You can use "\" to escape, use "%" to represent the real meaning of the "%" character).
  • Return: The function returns the replaced string.
  • Example:
$(patsubst %.c,%.o,x.c.c bar.c)

Replace the words in the string "xcc bar.c" that match the pattern [%.c] with [%.o], and the returned result is "xco bar.o"

  • Remarks: This is somewhat similar to the relevant knowledge we mentioned in the previous "Variables Chapter".
    For example: "$(var:<pattern>=<replacement>)" is equivalent to "$(patsubst <pattern>,<replacement>,$(var))"
    and "$(var: <suffix>=<replacement>) " is equivalent to "$(patsubst %<suffix>,%<replacement>,$(var))"

For example: objects = foo.o bar.o baz.o, then "$(objects:.o=.c)" is the same as "$(patsubst%.o,%.c,$(objects))" of.

3 、strip

$(strip <string>)

  • Name: remove spaces function - strip.
  • Function: Remove the empty characters at the beginning and end of the string.
  • Returns: Returns the string value with spaces removed.
  • Example:
$(strip a b c )

Strip the string "abc" to leading and trailing spaces, resulting in "abc".

4 、findstring

$(findstring <find>,<in>)

  • Name: Find string function - findstring.
  • Function: Find the <find> string in the string <in>.
  • Returns: <find> if found, otherwise an empty string.
  • Example:
$(findstring a,a b c)
$(findstring a,b c)

The first function returns the "a" string and the second returns the "" string (empty string)

5 、filter

$(filter <pattern…>,<text>)

  • Name: filter function - filter.
  • Function: Filter the words in the <text> string with the <pattern> pattern, and keep the words that match the pattern <pattern>. There can be multiple patterns.
  • Return: Returns a string matching the pattern <pattern>.
  • Example:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
	cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))

The returned value is "foo.c bar.c baz.s".

6 、filter-out

$(filter-out <pattern…>,<text>)

  • Name: anti-filter function - filter-out.
  • Function: Filter the words in the <text> string with the <pattern> pattern, and remove the words that match the pattern <pattern>. There can be multiple patterns.
  • Return: Returns a string that does not match the pattern <pattern>.
  • Example:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
	$(filter-out \$(mains),\$(objects)) 

The return value is "foo.o bar.o".

7 、sort

$(sort <list>)

  • Name: Sort function - sort.
  • Function: Sort the words in the string (in ascending order).
  • Returns: Returns the sorted string.
  • Example: $(sort foo bar lose) returns "bar foo lose".
  • Remarks: The sort function will remove the same words.

8 、word

$(word <n>,<text>)

  • Name: take the word function - word.
  • Function: take a string The first word in . (From the beginning)
  • Returns: return string The first word in . if than If the number of words in is greater, return an empty string.
  • Example: $(word 2, foo bar baz) returns "bar".

9、wordlist

$(wordlist <s>,<e>,<text>)

  • Name: get the word string function - wordlist.
  • Function: from string Take the word string starting from <s> to <e>. <s> and <e> are a number.
  • Return: Return the word string from <s> to <e> in the string <text>. If <s> is greater than the number of words in <text>, return an empty string. If <e> is greater than the number of words in <text>, then return the word string starting from <s> and ending in <text>.
  • Example: $(wordlist 2, 3, foo bar baz) returns "bar baz".

10 、words

$(words <text>)

  • Name: word count function - words.
  • Function: statistics The number of words in the string.
  • return: return the number of words in .
  • Example: $(words, foo bar baz) returns "3".
  • Remarks: If we want to take In the last word, we can do this: $(word $(words ), )。

11 、firstword

$(firstword <text>)

  • Name: first word function - firstword.
  • Function: take a string The first word in .
  • Returns: return string the first word of .
  • Example: $(firstword foo bar) returns "foo".
  • Note: This function can be implemented with the word function: $(word 1, )。

12. String function instance

    The above are all string manipulation functions. If they are used in combination, more complex functions can be completed. Here, give an example of practical application. We know that make uses the "VPATH" variable to specify the search path for "dependency files". Therefore, we can use this search path to specify the compiler's search path parameter CFLAGS for header files,
such as:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

    If our "$(VPATH)" value is "src:.../headers", then "$(patsubst%,-I%,$(subst :, ,$(VPATH)))" will return "-Isrc -I .../headers", which is exactly the parameter that cc or gcc searches for the header file path.

Three, the file name operation function

The functions we will introduce below mainly deal with file names. Each function's argument string is treated as a filename or sequence of filenames.

1 、dir

$(dir <names…>)

  • Name: take the directory function - dir.
  • Function: Take out the directory part from the file name sequence <names>. The directory part is the part before the last backslash ("/"). If there is no backslash, "./" is returned.
  • Returns: Returns the directory portion of the filename sequence <names>.
  • Example: $(dir src/foo.c hacks) returns "src/ ./".

2 、notdir

$(notdir <names…>)

  • Name: get file function - notdir.
  • Function: Take out the non-directory part from the file name sequence <names>. The non-directory part is the part after the last backslash ("/").
  • Returns: Returns the non-directory part of the filename sequence <names>.
  • Example: $(notdir src/foo.c hacks) returns "foo.c hacks".

3 、suffix

$(suffix <names…>)

  • Name: take the suffix function - suffix.
  • Function: Take out the suffix of each file name from the file name sequence <names>.
  • Return: Return the suffix sequence of the file name sequence <names>, or an empty string if the file has no suffix.
  • Example: $(suffix src/foo.c src-1.0/bar.c hacks) returns ".c .c".

4, basename

$(basename <names…>)

  • Name: Prefix function - basename.
  • Function: Take out the prefix part of each file name from the file name sequence <names>.
  • Return: Returns the prefix sequence of the file name sequence <names>, or an empty string if the file has no prefix.
  • Example: $(basename src/foo.c src-1.0/bar.c hacks) returns "src/foo src-1.0/bar hacks".

5 、addsuffix

$(addsuffix<suffix>,<names...>)

  • Name: add suffix function - addsuffix.
  • Function: Add the suffix <suffix> to each word in <names>.
  • Return: Returns the sequence of file names with suffixes added.
  • Example: $(addsuffix .c,foo bar) returns "foo.c bar.c".

6 、addprefix

$(addprefix <prefix>,<names…>)

  • Name: Prefix function -- addprefix.
  • Function: Add the prefix <prefix> to each word in <names>.
  • Returns: Returns the prefixed sequence of filenames.
  • Example: $(addprefix src/,foo bar) returns "src/foo src/bar".

7 、join

$(join <list1>,<list2>)

  • Name: Connection function - join.
  • Function: Add the words in <list2> to the back of the words in <list1>. If the number of words in <list1> is more than that in <list2>, then the extra words in <list1> will remain the same. If <list2> has more words than <list1>, then the extra words in <list2> will be copied to <list2>.
  • Return: Returns the string after connection.
  • Example: $(join aaa bbb , 111 222 333) returns "aaa111 bbb222 333".

Four, foreach function

    The foreach function is very different from other functions. Because this function is used for looping, the foreach function in the Makefile is almost modeled after the for statement in the Unix standard shell (/bin/sh), or the foreach statement in the C-Shell (/bin/csh). constructed. Its syntax is:

$(foreach <var>,<list>,<text>)

    The meaning of this function is to take out the words in the parameter one by one and put them into the variable specified by the parameter <var>, and then execute the expression contained in <text>. Each time <text> will return a string, during the loop, each string returned by <text> will be separated by spaces, and finally when the entire loop ends, each string returned by <text> will consist of The entire string of (separated by spaces) will be the return value of the foreach function.
    Therefore, <var> is preferably a variable name, which can be an expression, and <var> is generally used in <text> to enumerate the words in sequence. for example:

names := a b c d
files := $(foreach n,$(names),$(n).o)

    In the above example, the words in $(name) will be taken out one by one and stored in the variable "n". "$(n).o" calculates a value based on "$(n)" each time, and these values ​​start with separated by spaces, and finally returned as the foreach function, so the value of $(files) is "ao bo co do".
    Note that the <var> parameter in foreach is a temporary local variable. After the foreach function is executed, the variable of the parameter <var> will not work, and its scope is only in the foreach function.

Five, if function

    The if function is very similar to the conditional statement supported by GNU's make - ifeq (see the previous chapter), the syntax of the if function is:

$(if <condition>,<then-part>)

or

$(if <condition>,<then-part>,<else-part>)

    An if function can have an "else" part, or not. That is, the parameters of the if function can be two or three. The <condition> parameter is an if expression, if it returns a non-empty string, then this expression is equivalent to returning true, so <then-part> will be calculated, otherwise <else-part> will be calculated .
    And the return value of the if function is, if <condition> is true (non-empty string), that <then-part> will be the return value of the entire function, if <condition> is false (empty string), then <else -part> will be the return value of the entire function, if <else-part> is not defined at this time, then the entire function returns an empty string.
    So, only one of <then-part> and <else-part> will be calculated.

Six, call function

    The call function is the only one that can be used to create new parameterizations. You can write a very complex expression, in this expression, you can define many parameters, and then you can use the call function to pass parameters to this expression. Its syntax is:

$(call <expression>,<parm1>,<parm2>,<parm3>...)

    When make executes this function, the variables in the <expression> parameter, such as $(1), $(2), $(3), etc., will be replaced by the parameters <parm1>, <parm2>, <parm3> in turn. And the return value of <expression> is the return value of the call function. For example:

reverse = $(1) $(2)
foo = $(call reverse,a,b)

Then, the value of foo is "ab". Of course, the order of parameters can be customized, not necessarily sequential, such as:

reverse = $(2) $(1)
foo = $(call reverse,a,b)

The value of foo at this time is "ba".

Seven, origin function

    The origin function is not like other functions, he does not manipulate the value of the variable, he just tells you where the variable comes from? Its syntax is:

$(origin <variable>)

    Note that <variable> is the name of the variable, not a reference. So you better not use "$" character in <variable>. The origin function will tell you the "birth situation" of this variable with its return value. Below is the return value of the origin function:

“undefined”

    If <variable> has never been defined, the origin function returns the value "undefined".

“default”

    If <variable> is a default definition, such as the variable "CC", we will describe this kind of variable later. environment" If <variable> is an environment variable, and the "-e" parameter is not turned on when the Makefile is executed.

“file”

    If this variable is defined in Makefile.

“command line”

    If <variable> this variable is defined by the command line.

“override”

    If <variable> is redefined by the override directive.

“automatic”

    If <variable> is an automation variable in a command run. About automation variables will be described later. This information is very useful for us to write Makefile, for example, suppose we have a Makefile which includes a definition file Make.def, a variable "bletch" is defined in Make.def, and there is also an environment variable in our environment "bletch", at this time, we want to judge, if the variable comes from the environment, then we redefine it, if it comes from non-environment such as Make.def or command line, then we don't redefine it. In our Makefile, we can write:

ifdef bletch
	ifeq "$(origin bletch)" "environment"
		bletch = barf, gag, etc.
	endif
endif

    Of course, you might say, isn't it possible to redefine variables in the environment by using the override keyword? Why is such a step needed? Yes, we can use override to achieve this effect, but override is too rough, it will also overwrite the variables defined from the command line, and we only want to redefine the variables passed from the environment, not from the command line. here.

Eight, shell function

    Shell functions are not like other functions. As the name suggests, its parameters should be the commands of the operating system Shell. It has the same function as the backtick "`". That is, shell functions return the output of executing operating system commands as functions. Therefore, we can use operating system commands and string processing commands such as awk, sed, etc. to generate a variable, such as:

contents := $(shell cat foo)
files := $(shell echo *.c)

    Note that this function will generate a new Shell program to execute commands, so you should pay attention to its running performance. If you have some complicated rules in your Makefile and use this function a lot, it will be harmful to your system performance. . In particular, the cryptic rules of the Makefile may cause your shell functions to be executed much more often than you think.

Nine, control the function of make

    make provides some functions to control the operation of make. Usually, you need to detect some runtime information when running the Makefile, and decide based on this information whether you want make to continue or stop.

1 、error

$(error <text ...>)
    produces a fatal error, where <text ...> is the error message. Note that the error function does not generate an error message as soon as it is used, so if you define it in a variable and use this variable in subsequent scripts, it is also possible. For example:
example one:

ifdef ERROR_001
	$(error error is $(ERROR_001))
endif

Example two:

ERR = $(error found an error!)
.PHONY: err
err: ; $(ERR)

Example 1 will generate an error call when the variable ERROR_001 is defined, and Example 2 will
generate an error call when the directory err is executed.

2 、warning

$(warning <text ...>)
    This function is very similar to the error function, except that it does not make make exit, but only outputs a warning message, and make continues to execute.

The tenth part of the operation of make

    Generally speaking, the easiest way is to directly enter the make command on the command line, and the make command will find the makefile in the current directory to execute, and everything is automatic. But sometimes you may only want make to recompile certain files, not the entire project, and sometimes you have several sets of compilation rules, and you want to use different compilation rules at different times. This chapter describes how to use the make command.

1. The exit code of make

There are three exit codes after the make command is executed:

  • 0 - indicates successful execution.
  • 1 - If make runs with any errors, it returns 1.
  • 2 - Returns 2 if you used the "-q" option to make and make made some targets unnecessary to update.

The relevant parameters of Make will be described in subsequent chapters.

2. Specified Makefile

As we said before, the rule for GNU make to find the default Makefile is to find three files in the current directory—"GNUmakefile", "makefile" and "Makefile". It looks for these three files in order, and once found, it starts to read the file and execute it.
    Currently, we can also specify a Makefile with a special name to the make command. To achieve this function, we need to use the "-f" or "-file" parameter of make (the "-makefile" parameter is also fine). For example, we have a makefile whose name is "hchen.mk", then we can make make execute this file like this:

make –f hchen.mk

    If you use the "-f" parameter more than once on the make command line, then all specified makefiles will be concatenated and passed to make for execution.

3. Specify the target

    Generally speaking, the final target of make is the first target in the makefile, and other targets are generally derived from this target. This is the default behavior of make. Of course, generally speaking, the first target in your makefile is composed of many targets, and you can instruct make to complete the target you specify. It is very simple to achieve this goal, just follow the make command with the name of the target (such as the "make clean" form mentioned above). Any
    target in the makefile can be specified as the ultimate target, but except for Targets starting with "-" or containing "=", because targets with these characters will be parsed into command line parameters or variables. Even a target that is not explicitly written by us can become the ultimate target of make, that is, as long as make can find its implicit rule derivation rules, then this implicit target can also be designated as the ultimate target.
    There is a make environment variable called "MAKECMDGOALS", which will store a list of the ultimate goals you specify. If you do not specify a goal on the command line, then this variable will be empty. This variable allows you to use it in some special situations. Like the following example:

sources = foo.c bar.c
ifneq ( $(MAKECMDGOALS),clean)
	include $(sources:.c=.d)
endif

    Based on the above example, as long as the command we enter is not "make clean", then the makefile will automatically include the two makefiles "foo.d" and "bar.d".
    Using the method of specifying the ultimate goal can easily allow us to compile our program, such as the following example:

.PHONY: all
all: prog1 prog2 prog3 prog4

    From this example, we can see that there are four programs that need to be compiled in this makefile - "prog1", "prog2", "prog3" and "prog4", we can use the "make all" command to compile all target (if you set all as the first target, then just execute "make"), we can also use "make prog2" to compile the target "prog2" alone.
    Since make can specify all the targets in the makefile, it also includes "pseudo-targets", so we can use this property to make our makefile accomplish different things according to the specified targets. In the Unix world, when software is released, especially when open source software such as GNU is released, its makefile includes functions such as compilation, installation, and packaging. We can refer to this rule to write the targets in our makefile.

“all”

    This pseudo-target is the target of all targets, and its function is generally to compile all targets.

“clean”

    The function of this pseudo-target is to delete all files created by make.

“install”

    This pseudo-target function is to install the compiled program, in fact, it is to copy the target execution file to the specified target.

“print”

    The function of this pseudo-target is to instantiate changed source files.

“tar”

    The pseudo-target function is to package the source program for backup. That is a tar file.

“dist”

    The function of this pseudo-target is to create a compressed file, usually a tar file compressed into a Z file. or gz files.

“TAGS”

    This pseudo-target function is to update all targets ready for a full recompilation.

"check" and "test"

    These two pseudo-targets are generally used to test the flow of makefiles.
    Of course, it is not necessary to write such a goal in the makefile of a project. These things are all GNU things, but I think that GNU must have its merits in making these things (wait for your program files under UNIX for a long time) You will find these functions very useful), here is just an explanation, if you want to write this function, it is best to use this name to name your target, so that it is more standardized, the benefit of the specification is - no need to explain, Everyone understands. And if you have these functions in your makefile, one is very practical, and the other is that your makefile can look very professional (not the kind of work for beginners).

4. Inspection rules

    Sometimes, we don't want the rules in our makefile to be executed, we just want to check our commands, or the sequence of execution. So we can use the following parameters of the make command:
"-n"
"–just-print"
"–dry-run"
"–recon"
    does not execute parameters, these parameters are just print commands, regardless of whether the target is updated, the rules and associated The commands under the rules are printed, but not executed. These parameters are very useful for us to debug the makefile.

The "-t"
"-touch"
    parameter means to update the time of the target file, but not to change the target file. That is to say, make pretends to compile the target, but it is not a real compiled target, it just turns the target into a compiled state.

    The behavior of the "-q" "-question" parameter is to find the target. That is to say, if the target exists, it will not output anything, and of course it will not perform compilation. If the target does not exist, it will print
out
An error message.

"-W"
"--what-if="
"--assume-new="
"--new-file="
    This parameter needs to specify a file. Generally, it is a source file (or dependent file). Make will run the commands that depend on this file according to rule deduction. Generally speaking, it can be used with the "-n" parameter to view the rule commands that occur in this dependent file.

    Another very interesting usage is to combine "-p" and "-v" to output information when the makefile is executed (this will be described later).

Five, make parameters

    Listed below are all parameter definitions for GNU make version 3.80. Other versions and manufacturers' make are similar, but for specific parameters of other manufacturers' make, please refer to their respective product documentation.

The function of the two parameters "-b" and
"-m"
    is to ignore the compatibility with other versions of make.

"-B"
"--always-make"
    thinks that all targets need to be updated (recompiled).

"-C <dir>"
"-directory=<dir>"
    specifies the directory to read the makefile from. If there are multiple "-C" parameters, the interpretation of make is that the latter path uses the previous path as a relative path, and the last directory as the specified directory. For example: "make -C ~hchen/test -C prog" is equivalent to "make -C ~hchen/test/prog".

“—debug[=]”
输出 make 的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
a —— 也就是 all,输出所有的调试信息。(会非常的多)
b —— 也就是 basic,只输出简单的调试信息。即输出不需要重编译的目标。
v —— 也就是 verbose,在 b 选项的级别之上。输出的信息包括哪个 makefile 被解析,不需要被重编译的依赖文件(或是依赖目标)等。
i —— 也就是 implicit,输出所以的隐含规则。
j —— 也就是 jobs,输出执行规则中命令的详细信息,如命令的 PID、返回码等。
m —— 也就是 makefile,输出 make 读取 makefile,更新 makefile,执行 makefile 的信
息。

“-d”
相当于“–debug=a”。

“-e”
“–environment-overrides”
指明环境变量的值覆盖 makefile 中定义的变量的值。

“-f=”
“–file=”
“–makefile=”
指定需要执行的 makefile。

“-h”
“–help”
显示帮助信息。

“-i”
“–ignore-errors”
在执行时忽略所有的错误。

"-I <dir>"
"--include-dir=<dir>"
specifies a search target to be included in the makefile. Multiple "-I" parameters can be used to specify multiple directories.

"-j []"
"-jobs[=]"
    refers to the number of commands to run at the same time. Without this parameter, make runs as many commands as it can run. If there is more than one "-j" parameter, only the last "-j" is valid. (Note that this parameter is useless in MS-DOS)

"-k"
"--keep-going"
error does not stop running. If generating a target fails, the targets that depend on it will not be executed.

"-l"
"--load-average[=<load]"
"--max-load[=]"
specifies the load of the make command.

"-n"
"--just-print"
"--dry-run"
"--recon"
only outputs the command sequence during execution, but does not execute it.

"-o"
"--old-file="
"--assume-old="
Do not regenerate the specified ones, even if this target's dependent files are newer than it.

"-p"
"--print-data-base"
    outputs all data in the makefile, including all rules and variables. This parameter will make a simple makefile output a bunch of information. If you just want to output information without executing the makefile, you can use the "make -qp" command. If you want to see the preset variables and rules before executing the makefile, you can use "make -p -f /dev/null". The information output by this parameter will include the file name and line number of your makefile, so it will be very useful to use this parameter to debug your makefile, especially when your environment variables are very complicated.

"-q"
"--question"
do not run the command, nor output. Just checks if the specified target needs to be updated. If it is 0, it means to update, if it is 2, it means there is an error.

"-r"
"--no-builtin-rules"
prohibits make from using any implicit rules.

"-R"
"--no-builtin-variabes"
prohibits make from using any implicit rules acting on variables.

"-s"
"--silent"
"--quiet"
does not print the output of the command while the command is running.

"-S"
"--no-keep-going"
"--stop"
cancels the effect of the "-k" option. Because sometimes, make options are inherited from the environment variable "MAKEFLAGS". So you can use this parameter on the command line to invalidate the "-k" option in the environment variable.

"-t"
"–touch"
is equivalent to the UNIX touch command, it just changes the modification date of the target to the latest, that is, prevents the command that generates the target from running.

"-v"
"--version"
outputs information about make such as the version and copyright of the make program.

"-w"
"--print-directory"
outputs information before and after running the makefile. This parameter is useful for tracing nested calls to make.

"--no-print-directory"
disables the "-w" option.

"-W"
"--what-if="
"--new-file="
"--assume-file="
assumes that the target needs to be updated, if used with the "-n" option, then this parameter will output the run when the target is updated action. If there is no "-n", it is like running the UNIX "touch" command, making the modification time of the current time.

"--warn-undefined-variables"
As long as make finds undefined variables, it will output a warning message.

Part XI Implied Rules

    When we use Makefile, there are some things that we will use frequently, and the frequency of use is very high. For example, we compile the source program of C/C++ as an intermediate target file ([.o] file under Unix, [. obj] file). This chapter is about some "implicit" rules in the Makefile, which are agreed earlier and do not need us to write out.

    The "implicit rule" is a kind of convention, and make will run tacitly according to this "convention", even if there is no such rule written in our Makefile. For example, you don't need to write the rule of compiling [.c] files into [.o] files, make will automatically deduce this rule and generate the [.o] files we need.

    "Implicit rules" will use some of our system variables, and we can change the values ​​of these system variables to customize the runtime parameters of the implicit rules. For example, the system variable "CFLAGS" can control the compiler parameters at compile time.

    We can also write our own implicit rules by way of "pattern rules". Using "suffix rules" to define implicit rules has many limitations. Using "pattern rules" will be more intelligent and clear, but "suffix rules" can be used to ensure the compatibility of our Makefile.

    We understand the "implicit rules", which can serve us better, and let us know some "conventional" things, so as not to cause some inexplicable things to appear when we run the Makefile. Of course, everything is contradictory. Water can carry a boat, but it can also overturn it. Therefore, sometimes "implicit rules" will cause us a lot of trouble. Only by understanding it can we use it better.

1. Use implicit rules

    If you want to use implicit rules to generate the goal you need, all you need to do is not write the rules for this goal. Then, make will try to automatically deduce the rules and commands for generating this target. If make can automatically deduce the rules and commands for generating this target, then this behavior is the automatic deduction of implicit rules. Of course, implicit rules are things agreed upon by make in advance. For example, we have the following Makefile:

foo : foo.o bar.o
	cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

    We can notice that the rules and commands for how to generate the two targets foo.o and bar.o are not written in this Makefile. Because the "implicit rules" function of make will automatically deduce the dependent targets and generation commands of these two targets for us.
    make will look for available rules in its own "implicit rules" library, and if found, it will be used. If not found, an error will be reported. In the above example, the implicit rule of the make call is to set the dependent file of the [.o] target to [.c], and use the C compilation command "cc –c $(CFLAGS) [.c] " to generate [.o] targets. That is, there is absolutely no need for us to write down the following two rules:

foo.o : foo.c
	cc –c foo.c $(CFLAGS)
bar.o : bar.c
	cc –c bar.c $(CFLAGS)

    Because, this is already a "promised" thing, make and we have agreed on the rules for generating [.o] files with the C compiler "cc", which is the implicit rule. Of course, if we write our own rules for [.o] files, then make will not automatically deduce and call the implicit rules, it will faithfully execute according to the rules we wrote.
    Also, in make's "implicit rule library", each implicit rule has its order in the library, and the higher the front, the more frequently used it is. Therefore, this will cause us that sometimes even if we explicitly specify the target dependency, make will not care. Like the following rule (without commands):

foo.o : foo.p

    Depending on the file "foo.p" (the source file of the Pascal program) may become meaningless. If the "foo.c" file exists in the directory, then our implicit rules will also take effect, and the C compiler will be called through "foo.c" to generate the foo.o file. Because in the implicit rules, the Pascal rules appear after the C rules, so make will not look for the next rule when it finds the C rules that can generate foo.o. If you really don't want any implicit rule derivation, then you don't just write "dependency rules" without commands.

2. List of implicit rules

    Here we will talk about all the preset (that is, make built-in) implicit rules. If we don't explicitly write down the rules, then make will look for the required rules and commands in these rules. Of course, we can also use the parameter "-r" or "--no-builtin-rules" option of make to cancel all the preset implicit rules.
    Even if we specify the "-r" parameter, some implicit rules will still take effect, because many implicit rules are defined using "suffix rules", so as long as there are "suffix list" in the implicit rules " (that is, the dependent target defined by the system in target.SUFFIXES), then the implicit rule will take effect. The default suffix list is: .out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S , .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el. The specific details will be described later.
    Let's take a look at the commonly used implicit rules first.

1. Implicit rules for compiling C programs

    The dependent target of a target of "<n>.o" is automatically deduced as ".c", and its build command is:

$(CC) –c $(CPPFLAGS) $(CFLAGS)

2. Implicit rules for compiling C++ programs

    The dependent target of the "<n>.o" target will be automatically deduced as "<n>.cc" or "<n>.C", and its build command is:

$(CXX) –c $(CPPFLAGS) $(CFLAGS)

(It is recommended to use ".cc" as the suffix of C++ source files instead of ".C")

3. Implicit rules for compiling Pascal programs

    The dependent target for a target of "<n>.o" is automatically deduced as "<n>.p", and its build command is

$(PC) –c $(PFLAGS)

4. Implicit rules for compiling Fortran/Ratfor programs

    The dependent target for a target of "<n>.o" is automatically deduced as "<n>.r" or "<n>.F" or "<n>.f", and its build command is:

.f $(FC) –c $(FFLAGS)
.F $(FC) –c $(FFLAGS) $(CPPFLAGS)
.f $(FC) –c $(FFLAGS) $(RFLAGS)

5. Implicit rules for preprocessing Fortran/Ratfor programs

    Dependent targets for targets of ".f" are automatically deduced as ".r" or ".F". This rule simply converts a Ratfor or Fortran program with preprocessing into a standard Fortran program. The command it uses is:

.F $(FC) –F $(CPPFLAGS) $(FFLAGS)
.r $(FC) –F $(FFLAGS) $(RFLAGS)

6. Implicit rules for compiling Modula-2 programs

    The dependent target for a target of ".sym" is automatically deduced as "<n>.def", and its build command is:

$(M2C) $(M2FLAGS) $(DEFFLAGS)

    The dependent target for a target of "<no>" is automatically deduced as "<n>.mod", and its build command is:

$(M2C)  $(M2FLAGS)  $(MODFLAGS)

7. Implicit rules for assembly and assembly preprocessing

    The dependent target of the target of "<n>.o" will be automatically deduced as "<n>.s", the compiled product "as" is used by default, and its build command is:

$(AS) $(ASFLAGS)

    The dependent target of a target of "<n>.s" is automatically deduced as "<n>.S", which uses the C precompiler "cpp" by default, and its build command is:

$(AS) $(ASFLAGS)

8. Implicit rules for linking Object files

    The "<n>" target depends on "<n>.o", which is generated by running the linker (usually "ld") by running the C compiler, and the build command is:

$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)

    This rule is valid for projects with only one source file, and it is also valid for multiple Object files (generated from different source files). For example the following rules:

x : y.o z.o

And when "xc", "yc" and "zc" 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
rm -f x.o
rm -f y.o
rm -f z.o

    If there is no source file (such as xc in the above example) associated with your target name (such as x in the above example), then you'd better write your own generation rules, otherwise the implicit rules will report an error.

9. Implicit rules for Yacc C programs

    The dependent file of "<n>.c" is automatically deduced as "ny" (the file generated by Yacc), and its generation command is:

$(YACC) $(YFALGS)

("Yacc" is a syntax analyzer, please see the relevant information for its details)

10. Implicit rules for Lex C programs

    The dependent file of "<n>.c" is automatically deduced as "nl" (the file generated by Lex), and its generation command is:

$(LEX) $(LFALGS)

(For details about "Lex", please refer to the relevant information)

11. Implicit rules when Lex Ratfor programs

    The dependent file of "<n>.r" is automatically deduced as "nl" (the file generated by Lex), and its generation command is:

$(LEX) $(LFALGS)

12. Implicit rules for creating Lint libraries from C programs, Yacc files, or Lex files

    The dependent file of "<n>.ln" (the file generated by lint) is automatically deduced as "nc", and its generation command is:

$(LINT) $(LINTFALGS) $(CPPFLAGS) -i

The same rule applies to ".y" and ".l".

3. Variables used by implicit rules

    In the commands in the implicit rules, some pre-set variables are basically used. You can change the value of these variables in your makefile, or pass these values ​​​​in the make command line, or set these values ​​​​in your environment variables, no matter what, as long as these specific variables are set, Then it acts on the implicit rules. Of course, you can also use the "-R" or "--no-builtin-variables" parameter of make to cancel the effect of the variables you define on the implicit rules.

    For example, the command for the first implicit rule - the one that compiles a C program is:

$(CC) –c $(CFLAGS) $(CPPFLAGS)

    The default compilation command of Make is "cc". If you redefine the variable "$(CC)" to "gcc" and the variable "$(CFLAGS)" to "-g", then the implicit rule All commands will be executed in the form of "gcc –c -g $(CPPFLAGS)".
    We can divide the variables used in implicit rules into two types: one is command-related, such as "CC"; the other is parameter-related, such as "CFLAGS". The following variables are used in all implicit rules:

1. About the variables of the command.

  • AR
    function library packager. The default command is "ar".
  • AS
    assembly language compiler. The default command is "as".
  • CC
    C language compiler. The default command is "cc".
  • CXX
    C++ language compiler. The default command is "g++".
  • CO
    extends the file program from the RCS file. The default command is "co".
  • CPP
    Preprocessor for C programs (output is standard output). The default command is "$(CC) -E".

  • Compilers and preprocessors for FC Fortran and Ratfor. The default command is "f77".
  • GET
    program to expand files from SCCS files. The default command is "get".
    LEX
    Lex method parser program (for C or Ratfor). The default command is "lex".
  • PC
    Pascal language compiler. The default command is "pc".
  • YACC
    Yacc grammar analyzer (for C programs). The default command is "yacc".
  • YACCR
    Yacc grammar analyzer (for Ratfor programs). The default command is "yacc -r".
  • MAKEINFO
    Convert Texinfo source files (.texi) to Info files program. The default command is "makeinfo".
  • TEX
    A program that creates TeX DVI files from TeX source files. The default command is "tex".
  • TEXI2DVI
    Program to create TeX DVI files from Texinfo source files. The default command is "texi2dvi".
  • WEAVE
    is a program for converting the Web to TeX. The default command is "weave".
  • CWEAVE
    converts C Web to TeX program. The default command is "cweave".
  • TANGLE
    is a program for converting Web to Pascal language. The default command is "tangle".
  • CTANGLE
    converts C Web to C. The default command is "ctangle".
  • RM
    delete file command. The default command is "rm -f".

2. Variables about command parameters

    The following variables are parameters related to the above commands. If no default value is specified, then its default value is empty.

  • ARFLAGS
    Arguments for the library packer AR command. The default value is "rv".
  • ASFLAGS
    Assembly language compiler parameters. (when explicitly calling ".s" or ".S" files).
  • CFLAGS
    C compiler parameters.
  • CXXFLAGS
    C++ language compiler parameters.
  • COFLAGS
    RCS command parameter.
  • CPPFLAGS
    C preprocessor parameters. (C and Fortran compilers also use it).
  • FFLAGS
    Fortran language compiler parameters.
  • GFLAGS
    SCCS "get" program parameters.
  • LDFLAGS
    linker parameter. (eg: "ld")
  • LFLAGS
    Lex grammar analyzer parameters.
  • PFLAGS
    Pascal language compiler parameters.
  • RFLAGS
    Fortran compiler argument for the Ratfor program.
  • YFLAGS
    Yacc grammar analyzer parameters.

4. Chain of implicit rules

    Sometimes, a goal may be affected by a series of implicit rules. For example, a [.o] file may be generated by Yacc's [.y] file first into [.c], and then generated by the C compiler. We call this series of implicit rules "implicit rule chain".

    In the above example, if the file [.c] exists, then the implicit rules of the C compiler are directly invoked. If there is no [.c] file, but there is a [.y] file, then the implicit rules of Yacc Will be called to generate a [.c] file. Then, call the implicit rule of C compilation and finally generate [.o] file from [.c] to achieve the goal.

    We call this [.c] file (or target) an intermediate target. No matter what, make will try to automatically deduce all the methods to generate the target. No matter how many intermediate targets there are, it will persistently analyze all the implicit rules and the rules you wrote, and strive to achieve the target. So, sometimes, it may make you wonder, why is my target generated like this? Why is my makefile going crazy?

    By default, for intermediate targets, it differs from normal targets in two ways: The first difference is that intermediate rules are triggered unless the intermediate target does not exist. The second difference is that as long as the target is successfully generated, the generated intermediate target file will be deleted with "rm -f" during the process of generating the final target.

    In general, a file specified by a makefile as a target or as a dependent target cannot be used as an intermediary. However, you can explicitly state that a file or target is an intermediate target, and you can use the pseudo-target ".INTERMEDIATE" to force the declaration. (eg: .INTERMEDIATE: mid )

    You can also prevent make from automatically removing intermediate targets. To do this, you can use the pseudo-target ".SECONDARY" to force declarations (eg: .SECONDARY : sec). You can also specify your target as a pattern (such as: %.o) as a dependent target of the pseudo-target ".PRECIOUS" to save the intermediate files generated by the implicit rules.

    In the "implicit rule chain", the same target is prohibited from appearing twice or more than twice, so as to prevent infinite recursion when make is automatically deduced.

    Make optimizes some special implicit rules without generating intermediate files. For example, to generate the target program "foo" from the file "foo.c", logically, make will compile and generate the intermediate file "foo.o", and then link it into "foo", but in actual situations, this action can be replaced by a The "cc" command completes (cc -o foo foo.c), so the optimized rules do not generate intermediate files.

5. Define pattern rules

    You can use pattern rules to define an implicit rule. A pattern rule is just like a regular rule, except that in the rule, the target definition needs to have "%" characters. "%" means one or more arbitrary characters. "%" can also be used in the dependent target, but the value of "%" in the dependent target depends on its target.

    One thing to note is that the expansion of "%" occurs after the expansion of variables and functions, and the expansion of variables and functions occurs when make loads the Makefile, while the "%" in the pattern rules occurs at runtime.

1. Introduction to pattern rules

    In a pattern rule, at least "%" must be included in the target definition of the rule, otherwise, it is a general rule. The "%" definition in the target means a match on the file name, and "%" means a non-empty string of any length. For example: "%.c" means the file name ending with ".c" (the length of the file name is at least 3), "s.%.c" means the file starting with "s." and ending with ".c" name (the length of the filename must be at least 5).

    If "%" is defined in the target, then the value of "%" in the target determines the value of "%" in the dependent target, that is, the "%" of the pattern in the target determines the value of "%" in the dependent target "Look. For example, there is a pattern rule as follows:

%.o : %.c ; <command ......>

    Its meaning is to point out how to generate the corresponding [.o] file rules from all [.c] files. If the target to generate is "ao bo", then "%c" is "ac bc".
    Once the "%" pattern in the dependent target is determined, then make will be required to match all the file names in the current directory. Once found, make will execute the command under the rule, so in the pattern rule, the target may be multiple Specifically, if a pattern matches more than one target, make will generate all of the pattern targets. At this point, what make cares about is the dependent filename and the command to generate the target.

2. Example of pattern rules

The following example shows that all [.c] files are compiled into [.o] files.

%.o : %.c
	$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

    Among them, "$@" represents one by one value of all targets, and "$<" represents one by one value of all dependent targets. We call these strange variables "automatic variables", which will be described in detail later. In the following example, two targets are schema:

%.tab.c %.tab.h: %.y
bison -d $<

    This rule tells make to execute all [.y] files with "bison -d <n>.y", and then generate "<n>.tab.c" and "<n>.tab.h" files. (where "<n>" represents an arbitrary string). If our executable program "foo" depends on the files "parse.tab.o" and "scan.o", and the file "scan.o" depends on the file "parse.tab.h", if the "parse.y" file is updated, then according to the above rules, "bison -d parse.y" will be executed once, so the dependency files of "parse.tab.o" and "scan.o" will be complete. (Assume, "parse.tab.o" is generated from "parse.tab.c", and "scan.o" is generated from "scan.c", and "foo" is generated from "parse.tab.o" and "scan. o" link is generated, and the dependencies between foo and its [.o] files are also written, then all targets will be satisfied)

3. Automatic variables

    In the above pattern rules, the target and dependent files are a series of files, so how do we write a command to complete the generation of corresponding targets from different dependent files? Because every time the pattern rules are parsed, there will be different targets and dependent files.
    Automated variables do just that. We have already mentioned automation variables in the front, I believe you have a perceptual understanding of it here. The so-called automatic variable means that this variable will automatically take out a series of files defined in the pattern one by one until all the files matching the pattern are taken out. Such automation variables should only appear in the commands of a rule.

Below are all automation variables and their descriptions:

  • $@
    indicates the target file set in the rule. In the pattern rule, if there are multiple targets, then "$@" is the set that matches the pattern definition in the target.
  • $%
    indicates the target member name in the rule only if the target is in a function library file. For example, if a target is "foo.a(bar.o)", then "$%" is "bar.o" and "$@" is "foo.a". If the target is not a function library file ([.a] under Unix, [.lib] under Windows), then its value is empty.
  • $<
    The first target name in the dependent target. If the dependency target is defined with a pattern (ie "%"), then "$<" will be a list of files matching the pattern. Note that they are taken out one by one.
  • $?
    A space-separated collection of all dependent targets newer than the target.
  • $^
    A collection of all dependent targets. separated by spaces. If there are multiple duplicates in the dependent target, then this variable will remove the duplicate dependent target and keep only one copy.
  • $+
    This variable is very similar to "$^", and it is also a collection of all dependent targets. It's just that it doesn't remove duplicate dependency targets.
  • $*
    This variable represents "%" and the part before it in the target pattern. If the target is "dir/a.foo.b", and the target's pattern is "a.%.b", then the value of "$*" is "dir/a.foo". If there is no pattern definition in the target, then "$*" cannot be deduced, but if the suffix of the target file is recognized by make, then "$*" is the part except the suffix. For example: if the target is "foo.c", because ".c" is a suffix recognized by make, the value of "$*" is "foo". This feature is GNU make, it is very likely not compatible with other versions of make, so you should try to avoid using "$*", except in implicit rules or static mode. If the suffix in the target is not recognized by make, then "$*" is null.
        "$?" is useful in explicit rules when you want to operate only on updated dependent files, for example, suppose there is a function library file called "lib", which is updated by several other object files. Then the more efficient Makefile rules for packaging object files are:
lib : foo.o bar.o lose.o win.o
ar r lib $?

    Among the automatic variables listed above. Four variables ($@, $<, $%, $*) will only have one file when expanded, while the value of the other three is a list of files. These seven automation variables can also get the directory name of the file or the file name that matches the pattern in the current directory, just need to match the word "D" or "F". This is a feature of older versions of GNU make, in newer versions we can do it with the functions "dir" or "notdir". "D" means Directory, which is a directory, and "F" means File, which is a file.

The following is the meaning of adding "D" or "F" to the above seven variables:

  • $(@D)
    means the directory part of "$@" (not ending with a slash), if "$@" value is "dir/foo.o", then "$(@D)" is "dir", And if "$@" does not contain a slash, its value is "." (current directory).

  • $(@F)
    represents the file part of "$@", if "$@" value is "dir/foo.o", then "$(@F)" is "foo.o", "$(@F) "Equivalent to the function "$(notdir $@)".

  • $(*D)
    $(*F)
    is the same as the above, and also takes the directory part and file part of the file. For the example above, "$(*D)" returns "dir" and "$(*F)" returns "foo"

  • $(%D)
    $(%F)
    represent the directory part and file part of the function package file member respectively. This is useful for objects of the form "archive(member)" where "member" contains different directories.

  • $(<D)
    $(<F)
    respectively represent the directory part and file part of the dependent file.

  • $(^D)
    $(^F)
    represent the directory part and file part of all dependent files, respectively. (no same)

  • $(+D)
    $(+F)
    respectively represent the directory part and file part of all dependent files. (can have the same)

  • $(?D)
    $(?F)
    represent the directory part and file part of the updated dependent file, respectively.

    The last thing I want to remind is that for "$<", in order to avoid unnecessary trouble, we'd better add parentheses to the specific character after $, for example, "$(<)" is better than "$ <"Better.
    It should also be noted that these variables are only used in rule commands, and are generally "explicit rules" and "static pattern rules" (see the previous chapter "Writing Rules"). It has no meaning in implicit rules.

4. Pattern matching

    In general, a target's pattern has a "%" with a prefix or suffix, or just a "%" without a prefix or suffix. Because "%" represents one or more characters, so in the defined pattern, we call the content matched by "%" "stem", for example, in the file "test.c" matched by "%.c" "test" is "stem". Because when there is "%" in both the target and the dependent target, the "stem" of the dependent target will be passed to the target as the "stem" in the target.
    When a pattern matches a file that contains a slash (actually, it does not often contain it), then when the pattern is matched, the directory part will be removed first, and then the match will be performed. After success, the directory will be added back. We need to know this step when doing the "stem" transfer. For example, if there is a pattern "e%t", the file "src/eat" matches the pattern, so "src/a" is its "stem", if this pattern is defined in the dependent target, and the target that is dependent on this pattern There is another pattern "c%r" in , then the target is "src/car". ("stem" is passed)

5. Overload built-in implicit rules

    You can override the built-in implicit rules (or define a completely new one), for example you can restructure commands differently from the built-in implicit rules, such as:

%.o : %.c
$(CC) -c $(CPPFLAGS) $(CFLAGS) -D$(date)

    You can cancel the built-in implicit rules, as long as you don't write commands later. like:

%.o : %.s

    Likewise, you can redefine an entirely new implicit rule, whose position in the implicit rule depends on where you write the rule. The forward position is forward.

6. Old-fashioned "suffix rules"

    Suffix rules are an older way of defining implicit rules. Suffix rules are gradually replaced by pattern rules. Because pattern rules are stronger and clearer. For compatibility with older Makefiles, GNU make is also compatible with these things. There are two types of suffix rules: "double suffix" and "single suffix".
    The double suffix rule defines a pair of suffixes: the suffix of the target file and the suffix of the dependent target (source file). For example, ".co" is equivalent to "%o : %c". A single suffix rule defines only one suffix, which is the suffix of the source file. For example, ".c" is equivalent to "%:%.c".
    The suffixes defined in the suffix rules should be recognized by make. If a suffix is ​​recognized by make, then this rule is a single suffix rule, and if two suffixes connected together are recognized by make, it is a double suffix rule. For example: ".c" and ".o" are known to make. Therefore, if you define a rule as ".co", then it is a double-suffix rule, which means that ".c" is the suffix of the source file, and ".o" is the suffix of the object file. For example:

.c.o:
	$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

Suffix rules do not allow any dependent files. If there are dependent files, it is not a suffix rule. Those suffixes are all considered as file names, such as:

.c.o: foo.h
	$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

This example, that is, the file ".co" depends on the file "foo.h", instead of what we want:

%.o: %.c foo.h
	$(CC) -c $(CFLAGS) $(CPPFLAGS) -o $@ $<

In the suffix rule, if there is no order, it is meaningless. Because he will not remove the built-in implicit rules. And to let make know some specific suffixes, we can use the pseudo-target ".SUFFIXES" to define or delete, such as:

.SUFFIXES: .hack .win

Add the suffixes .hack and .win to the end of the list of suffixes.

.SUFFIXES: # 删除默认的后缀
.SUFFIXES: .c .o .h # 定义自己的后缀

Clear the default suffix first, and then define your own suffix list.
    The make parameter "-r" or "-no-builtin-rules" will also be used to make the default suffix list empty. The variable "SUFFIXE" is used to define the default suffix list, you can use ".SUFFIXES" to change the suffix list, but please do not change the value of the variable "SUFFIXE".

7. Implicit rule search algorithm

    For example, we have a target named T. The following is the algorithm for searching the rules for the target T. Please note that in the following, we do not mention suffix rules because all suffix rules will be converted into pattern rules when the Makefile is loaded into memory. If the target is the function library file mode of "archive(member)", then this algorithm will be run twice, the first time is to find the target T, if it is not found, then enter the second time, and the second time will use "member" as Do T to search.

  • 1. Separate the directory part of T. Called D, while the rest is called N. (e.g. if T is "src/foo.o", then D is "src/" and N is "foo.o")
  • 2. Create a list of all pattern rules matching T or N.
  • 3. If there is a pattern matching all files in the pattern rule list, such as "%", then remove other patterns from the list.
  • 4. Remove the rules without commands in the list.
  • 5. For the first pattern rule in the list:
    • 1) Deduce its "stem" S, S should be T or N matching the non-empty part of "%" in the pattern.
    • 2) Calculate dependent files. Replace all "%" in dependency files with "stem"s. If the target pattern does not contain a slash character, add D to the beginning of the first dependent file.
    • 3) Test whether all dependent files exist or should exist. (If a file is defined as the target file of another rule, or a dependent file of an explicit rule, then this file is called "supposed to exist")
    • 4) If all dependent files exist or should exist, or there are no dependent files. Then this rule will be adopted and exit the algorithm.
  • 6. If after step 5, no pattern rule is found, then do a further search. For the first pattern rule present in the list:
    • 1) If the rule is a terminating rule, ignore it and move on to the next pattern rule.
    • 2) Calculate dependent files. (same as step 5)
    • 3) Test whether all dependent files exist or should exist.
    • 4) For a non-existing dependent file, call this algorithm recursively to find out whether it can be found by the implicit rule.
    • 5) If all dependent files exist or should exist, or if there are no dependent files at all. Then this rule is adopted and the algorithm is exited.
  • 7. If there is no implicit rule available, check the ".DEFAULT" rule. If there is, use it and use the ".DEFAULT" command for T.
    Once the rule is found, its equivalent command is executed, and at this point, the value of our automation variable is generated.

The twelfth part uses make to update the function library file

    The function library file is also the packaging file for the Object file (intermediate file for program compilation). Under Unix, the packaging work is usually done by the command "ar".

1. Members of the function library file

    A function library file consists of multiple files. You can specify library files and their components in the following format: archive(member) This is not a command, but a definition of targets and dependencies. Generally speaking, this usage is basically for the "ar" command to serve. like:

foolib(hack.o) : hack.o
ar cr foolib hack.o

If you want to specify multiple members, separate them with spaces, such as:

foolib(hack.o kludge.o)

which is equivalent to:

foolib(hack.o) foolib(kludge.o)

You can also use the shell's file wildcards to define, such as:

foolib(*.o)

2. Implicit rules for library members

    A special feature when make searches for an implicit rule for a target is that it turns the target into "(m)" if the target is of the form "a(m)". So, if our member is a pattern definition of "%.o", and if we call the Makefile with the form "make foo.a(bar.o)", the implicit rule will look for the rule of "bar.o" , if no rule for bar.o is defined, then the built-in implicit rule will take effect, and make will search for the bar.c file to generate bar.o. If it can find it, the commands executed by make are roughly as follows:

cc -c bar.c -o bar.o
ar r foo.a bar.o
rm -f bar.o

    Another variable to pay attention to is "$%", which is the automation variable of the exclusive function library file. For its description, please refer to the "Automation Variables" section.

3. Suffix rules for function library files

You can use "suffix rules" and "implicit rules" to generate function library package files, such as:

.c.a:
	$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
	$(AR) r $@ $*.o
	$(RM) $*.o

which is equivalent to:

(%.o) : %.c
	$(CC) $(CFLAGS) $(CPPFLAGS) -c $< -o $*.o
	$(AR) r $@ $*.o
	$(RM) $*.o

4. Matters needing attention

    Please be careful to use make's parallel mechanism ("-j" parameter) when generating function library package files. If multiple ar commands run on the same function library package file at the same time, it is very likely to damage the function library file. Therefore, in future versions of make, a mechanism should be provided to prevent parallel operations from occurring on function package files.
    But for now, you should try not to use the "-j" parameter.

Part Thirteen Afterword

    It's finally time to write the conclusion. The above is basically all the details of the Makefile of GNU make. The make of other manufacturers is basically like this. No matter what kind of make, it is based on file dependencies, and basically follows a standard. 80% of the technical details in this document are applicable to any make. I guess the content of the "function" chapter may not be supported by other make. As for the implicit rules, I think different makes will have different implementations , I don’t have the energy to check the difference between GNU’s make and VC’s nmake, BCB’s make, or other make under UNIX. make, formerly on SCO Unix and IBM's AIX, is now available under Linux, Solaris, HP-UX, AIX, Alpha, and a little more under Linux and Solaris. However, I can be sure that the make under Unix almost uses the make and cc/gcc compiler developed by Richard Stallman no matter what platform it is, and it is basically GNU make (all GNU stuff is installed on all UNIX machines, so there are more programs using GNU). GNU stuff is still very good, especially after using it deeply, I feel more and more powerful of GNU software, and I feel more and more "lethal" of GNU in the operating system (mainly Unix, even Windows).

    For all the details of make mentioned above, we can not only use the tool make to compile our program, but also use make to complete other tasks, because the commands in the rules can be commands under any shell, so under Unix , you don't have to just use the compiler of the programming language, you can also write other commands in the Makefile, such as: tar, awk, mail, sed, cvs, compress, ls, rm, yacc, rpm, ftp...etc , etc., to complete various functions such as "program packaging", "program backup", "making program installation package", "submitting code", "using program template", "merging files", etc., file operation, file management , programming development design, or some other whimsical things. For example, when writing a bank transaction program, because the bank's transaction program is basically the same, I saw someone write some general program templates for transactions. In a file, use some strange strings such as "@@@N, ###N" to mark some positions in these files, and then when writing transactions, you only need to write specific processing according to a specific rule, and finally in When making, use awk and sed to replace "@@@N, ###N" and other strings in the template with specific programs to form a C file and then compile it. This action is very similar to the "extended C" language of the database (that is, to execute SQL statements in the form of "EXEC SQL" in C language, before compiling with cc/gcc, you need to use the translation program of "extended C", such as cpre, put which translates to Standard C). If you have some more elegant ways to use make, please remember to tell me.

    Looking back at the entire document, I can't help but remember that when I first started developing under Unix a few years ago, when someone asked me if I could write akefile, my eyes were straightened, and I didn't know what I was talking about. At first, when I saw other people input "!make" after writing a program in vi, I thought it was a function of vi. Later, I found out that there was a Makefile doing the trick, so I searched online. At that time, I didn't want to read English, and found that There is no Chinese document to introduce Makefile at all. I have to read Makefile written by others and accumulate a little knowledge by myself, but in many places I don't know why. Later, I started to develop product software under UNIX. I saw a large project with 400 man-years and nearly 2 million lines of code, and found that it would be a terrible thing to compile such a huge project without Makefile. So I made up my mind and read a bunch of English documents desperately, only to feel that I have mastered it. But I found that there are still very few articles introducing Makefile on the Internet, so I want to write such an article and share it with everyone, hoping to be helpful to everyone.

    Now I have finally finished writing. Looking at the creation time of the document, this technical document has been written for more than two months. I found that it is one thing to know, but it is another thing to write it down and tell others. Moreover, there is less and less time to study technical details, so when writing, I found it difficult to explain some details. Be rigorous and concise, and it is not very clear what to talk about first, so it is completed by referring to the materials and topic outlines on some foreign sites, as well as the language style of some technical books. The outline of the entire document is written based on the outline of the GNU Makefile technical manual, combined with my own work experience and my own learning process. Because I have never written such a long and detailed document, there must be many expression problems, language ambiguities or errors. Because of this, I am eagerly waiting for your testimonials and suggestions, as well as any feedback.

    Finally, use this preface to introduce yourself. I am currently engaged in software development under all Unix platforms, mainly system product software for distributed computing/grid computing, and I am very interested in the next generation of computer revolution - grid computing, for distributed computing , P2P, Web Service, and J2EE technical directions are also very interested. At the same time, I also have some experience in project implementation, team management, and project management. Talk to me a lot.

    I welcome any form of communication, whether it's discussing technology or management, or anything else. I don't care about politics and entertainment news, I like everything else as long as it is positive.

Guess you like

Origin blog.csdn.net/yangjia_cheng/article/details/111600852