GNU Make Guide
Translation: Ha
Translator Press: This article is an article introducing GNU Make. After reading, readers should basically master the use of Make. Make is a tool that you want to program on UNIX (of course also include Linux) systems. If you don't use Make in the program you write, you will explain that the program you write is just a personal exercise program, does not have any practical value. Maybe this is a bit biased, but make it really uses in any slightly scale program. I hope this article can provide a good useful information for China's UNIX programming beginners. In addition to installing red hats, China Linux users should try to write some useful procedures. Personal idea, everyone reference.
C-Scene topic # 2
Multi-file project and GNU Make tool
Author: Qiaozhi Fu special (Goerge Foot)
Email: george.foot@meton.ox.ac.uk
Occupation: Student At Merton College, Oxford University, England
Occupation: Student, Merton College, Oxford City University, England
IRC anonymity: gfoot
Refuse to commit: Author's damage caused by anything (you have or don't have actual, abstract, or virtual). All damage is your own responsibility, but nothing to do with me.
Ownership: The "Multi-File Project" part belongs to the author's property, copyright belongs to Georget Fug, from May to July, 1997. Other part belongs to CScene property, copyright CScene in 1997, all copyrights. The distribution, part or all of this CScene article shall be handled in accordance with all other CSCENE articles.
0) Introduction
~~~~~~~~~~~~~~~
This article will first introduce why you want to separate your C source code into several reasonable independent files, when you need to be divided, how can you be able to divide? Then I will tell you how GNU Make automates your compilation and connection steps. For users of other Make tools, although appropriate adjustments are made when using other similar tools, the content of this article is still very useful. If you have doubts about your own programming tool, you can try it in actually, but please read the user manual first.
1) Multi-file project
~~~~~~~~~~~~~~~~~~~~~~
1.1 Why use them?
First, there are the benefits of multi-file projects?
They seem to make things more complicated. Also wants the header file, but also the extern declaration, and if you need to find a file, you have to search in more files.
But in fact, we have a strong reason to support us to break up a project into small pieces. When you change a line of code, the compiler needs to be re-compiled to generate a new executable. But if your project is separated in a few small files, when you change one of the files, the object file of the other source files already exists, so there is no reason to recompile them. What you need to do is just that reproduces the file that is changed, and then reconnects all the target files. In a large project, this means that it is a simple adjustment from a long (minutes from a few hours) to recoib to more than ten and twenty seconds.
As long as the basic planning, a project is broken down into multiple small files to make it easier to find a code. Very simple, you decompose your code into a different file according to the role of the code. When you want to see a code, you can know accurately in that file to look for it.
Generating a package from a lot of target files (library) is much better than from a single big target file. Of course, this is actually a advantage is determined by the system you use. However, when a package is connected to a program using GCC / LD (a GNU C Compilation / Connector), in the process of the connection, it will try to not use the part that is not used. But each time you can only remove a complete target file from the package. So if you refer to any of the symbols in a single target file, then this target file will be connected in. If a package is very fully decomposed, then after the connection, the obtained executable is much smaller than the files connected to the package consisting of a large target file. Because your program is very modular, the sharing part between files is minimized, then there are many benefits - it can be easily tracked into bugs, which often can be used in other projects, At the same time, others can easily understand what your code is doing. Of course, there are many other benefits ...
1.2 When to decompose your project
Obviously, it is unreasonable to decompose anything. Simple programs like "World, you are good" are not divided, because there is nothing to be able to divide. Decompose the decomposition of small procedures for testing is also meaningless. But in general, when the decomposition project helps layout, development and readability, I will take it. In most cases, this is applicable. (So-called "world, you are good", both 'Hello World', just an example program that introduces a programming language, which will display a line 'Hello World' on the screen.) Is the easiest program.)
If you need to develop a considerable project, you should consider how you will implement it, and generate a few files (with the appropriate name) to put your code. Of course, in the process of your project development, you can build new files, but if you do this, you may change the original idea, you should think about whether it needs to be adjusted for the overall structure.
For medium-sized projects, you can certainly use the above skills, but you can also start entering your code, when your code is more difficult to manage, decompose them into different files. But in my experience, I will form a probably scheme in my mind, and try to follow it, or during development, as needed, it will make development easier.
1.3 How to Decompose Project
First, this is exactly my personal opinion, you can (maybe you really will?) Do other ways. This will touch the problem of coding style, and everyone has never stopped the debate on this issue. Here I just give my own favorite practices (while giving this reason):
i) Do not point to multiple source files with a header file (exception: the HEADER file for the package). The way to define a source code file is more efficient and easier to find. Otherwise, changing the structure of a source file (and its header file) must recompile several files.
II) If you can, you can use more than one header file to point to the same source code file. Sometimes the function prototype, type definition, and so on, which cannot be invoked, and it is very useful from their C source file files. Using a Header file to install public symbols, use another private symbol that if you change the internal structure of this source file, you can just recompile it without having to recompile other source files that use its public header file. .
III) Do not repeat the definition information in multiple header files. If necessary, in one of the header files #include one, but do not repeat the same Header information twice. The reason is if you change this information later, you only need to change it once, do not search and change another repetition. IV) In each source file, #include states all Header files that declare the symbols in the source file file. In this way, the contradictions you make in some functions in the source file and the Header file can be discovered by the compiler.
1.4 Notes for common mistakes
a) Confligence (Identifier contradictions in the source file: In C, the default state of variables and functions is public. Therefore, any C source file file can reference universal (GLOBAL) functions and general variables existing in other source code archives, making this file without the declaration or prototype of the variable or function. So you must guarantee that you can't use the same symbol name in different two files, otherwise there will be an incorrect connection or a warning when compiling.
A method of avoiding such an error is to add a prefix with its source file before the public symbol. For example: All functions in GFX.c are plus prefix "GFX_". If you are very careful to decompose your program, use meaningful function names, and not excessive common variables, of course, this is not a problem.
To prevent a symbol from being seen outside of its defined source file, you can add keyword "static" before it definition. This is useful to use only simple functions that other files use only in one file.
b) Multi-defined symbols: The Header file will be replaced with the location of the #include in your source file. Therefore, if the Header file is included in #include to more than one source file, all definitions in this header file will appear in each related source code file. This will cause the symbols in which they are defined over, thus connecting the connection error (see).
Workaround: Do not define variables in the HEADER file. You only need to declare them in the Header file and then define them in the appropriate C source file (should be included in the Header file). For beginners, definitions and statements are easily confused. The role of the declaration is to tell the compiler that the symbols they have declared should exist and have the type specified. However, it does not make the compiler allocates storage space. The definition is required to require a compiler to allocate storage space. When making a statement instead of being defined, put a keyword "extern" before the declaration.
For example, we have a variable called "counter". If we want it to become public, we define it in a source code (in one inside): "int counter;", then in the relevant header file declaration It: "Extern Int Counter;".
The function prototype is implied with Extern, so you don't need to worry about this problem.
c) Repeat definition, repeat declaration, contradictory type:
Please consider if there are two files A.H and B.h in a C source file, and A.h is also #include B.H file (the reason is that B.H file defines some A.h needs type), what happens? At this time, the C source code file #include has b.h twice. So each of the #define in B.H has occurred twice, and each declaration has occurred twice, and so on. In theory, because they are exactly the same copy, there should be no problems, but in practical applications, this is not conforming to C's grammar, which may occur when compiling, or at least warning. The solution is to determine that every Header file is only included in any source file file. We generally use preprocessors to achieve this purpose. When we enter every Header file, we have a giant command for this Header file #define. We really use the body file of the Header file only if this giant intention is not defined. In practical applications, as long as we put the following one code in each Header file:
#ifndef filename_h
#define filename_h
Then put the following code last:
#ENDIF
Instead of using the Header file file instead of the above filename_h, use the bottom line instead of the point in the file. Some people like to make comments in #ENDIF to remind them that this #ndif refers to what. E.g:
#ENDIF / * #ifndef filename_h * /
I personally don't have this habit, because this is actually very obvious. Of course, this is only different in the style of each person, and there is no harm.
You only need to add this skill in the header file with compilation errors, but there is no loss in all HEADER files, which is a good habit.
1.5 recompile a multi-file project
It is important to clearly distinguish between compilation and connections. The compiler uses the source code file to generate some form of destination file (Object Files). During this process, the external symbol reference is not explained or replaced. Then we use the connector to connect these object files and some standard package plus your specified package, and finally connect to generate an executable. At this stage, the reference to the symbols in other files is interpreted in a target file, and the reference that cannot be interpreted is generally reported in the form of error information.
Basic steps should be, put your source file into a form of a target file, and finally connect all the target files to an executable file. How to do it is determined by your compiler. Here I only give the relevant commands of the GCC (GNU C compiler), which may also apply to your non-GCC compiler.
GCC is a multi-objective tool. It calls other components (pre-processing programs, compilers, combination programs, connector) when needed. Which components of specific components are called depends on the type of input file and the switch you pass to it.
In general, if you only give it C source file file, it will preprocess, compile, combine all files, and then connect the resulting target file into an executable file (generally generated file is named A.out). Of course you can do this, but this will destroy a lot of benefits we have decomposed into multiple files.
If you give it a -C switch, GCC only compiles its file into a target file, named the file name of the source file but the suffix is ".c" or ".cc" becomes ".o". If you give it a list of target files, GCC will connect them into executables, the default file name is A.out. You can change the default name, with the file name you specified by the switch-O. Therefore, after you change a source code file, you need to recompile it: 'gcc -c filename.c' then reconnect your item: 'gcc -o exec_filename * .o'. If you change a Header file, you need to recompile all #include's source files for this file, you can use 'gcc -c file1.c file2.c file3.c' and then connect it as above.
Of course, this is very cumbersome, fortunately, we have some tools to make this step simple. The second part of this article is to introduce one of the tools: GNU Make tool.
(Good guy, now I am beginning to see the true chapter. Do you learn something?)
2) GNU Make Tool
~~~~~~~~~~~~~~~~
2.1 Basic Makefile Structure
The main job of GNU Make is to read a text file, makefile. This file is mainly related to which files ('target' destination file) is from which other files ('DependenCIES' relying on files), what commands are used for this process. With this information, Make will check the file on the disk, if the timestamp of the destination file (the time when this file is generated or changed), Make performs the corresponding command to update Purpose file. (The purpose file is not necessarily the final executable, which can be any file.)
Makefile is typically called "makefile" or "makefile". Of course, you can specify other file names at the Make command line. If you don't specify, it will find "makefile" or "makefile", so use these two names is the simplest.
A Makefile mainly contains a range of rules as follows:
:
...
(Tab)
(Tab)
.
.
.
For example, consider the following Makefile:
=== makefile start ===
MyProg: foo.o bar.o
?? gcc foo.o bar.o -o myprog
Foo.O: foo.c foo.h bar.h
?? gcc -c foo.c -o foo.o
Bar.o: bar.c bar.h
?? gcc -c bar.c -o bar.o
=== Makefile end ===
This is a very basic makefile - make from the top, the first purpose, 'myprog', as its main goal (one it needs to guarantee the latest ultimate goal). The rules given instructions As long as the file 'myprog' is more old than any of the file 'foo.o' or 'bar.o', the next line will be executed.
However, before checking the timestamp of the file foo.o and bar.o, it will look down the rules that make foo.o or bar.o as the target file. It found about foo.o rules that rely on files foo.c, foo.h and bar.h. It can't find a rule that generates these relying on files from below, it starts to check the timestamp of these dependencies on the disk. If any of the timestamps in these files are more than foo.o, the command 'gcc -o foo.o foo.c' will be executed, so that the file foo.o is updated. Next, a similar inspection is made to the file bar., and the file is here is the file bar.c and bar.h.
Now, make back to 'myprog' rules. If any one of the two rules is executed, myProg needs to be rebuilt (because one of them. "New than 'myprog', the connection command will be executed.
I hope that you can see the benefits of using the Make tool to create a program - all cumbersome inspection steps in the previous chapter are made by Make: Check the timestamp. A simple change in your source file will cause that file being recompiled (because the .o file rely on .c file), the executable file is reconnected (because .o file is changed). In fact, the real benefits are when you change a Header file - you no longer need to remember that source file rely on it, because all the information is in makefile. Make will easily recompile all the source code files that have changed since this Header file, if necessary, re-connect.
Of course, you have to make sure the rules you write in makefile are correct, only listing the Header files of #include in the source file ...
2.2 Write Make Rules (Rules)
The method of the most obvious (and the simplest) rule is a one-to-see the source code file, which makes their target file as a purpose, while the C source file and the Header file of #include are relying on files. But you also have to list other Header files of these Header files #include as relying on files, as well as files included in the files included ... Then you will find more and more files, then Your hair begins to fall off, your temper begins to get bad, your face turns into a dish, you start to collide with the electric wire on the road, and finally you smash your computer monitor, stop programming. Is there any way to have some way?
Of course! To the compiler! When compiling each source file, it should be found to include what kinder files should be included. When using GCC, use the -m switch, which will output a rule for each of your C file, make the target file as a purpose, and this C file and all Header files whose #include should be used as a file. . Note that this rule will join all Header files, including the files surrounded by cornence brackets (`<',`>') and dual quotation marks (`"). In fact, we can quite certainly determine the system Header file (such as stdio.h, stdlib .h, etc.) Will not be changed by us, if you use -mm instead of transfer to GCC, those HEADER files surrounded by corner parentheses will not be included. (This saves some compile time)
The rules output by the GCC will not contain the command part; you can write your command or nothing, let Make use its implicit rules (refer to Section 2.4 below). 2.3 Makefile variable
The above mentioned that Makefiles mainly contains some rules. The other east wests they contain are variable definitions.
The variable in the makefile is like an environment variable (Environment Variable). In fact, environmental variables are interpreted as MAKE variables during Make. These variables are sensitive, generally using uppercase letters. They can be referenced almost anywhere, or they can be used to do a lot of things, such as:
i) Store a list of file names. In the above example, the rules that generate executables include some destination file names. The same files in the command line of this rule are delivered to the GCC as a command parameter. If you use a variable in this to store all the target file names, add a new target file to make a simple and less error-free.
II) Store executable file name. If your project is used in a non-GCC system, or if you want to use a different compiler, you must change the place to use the compiler into a new compiler name. But if you use a variable instead of the compiler name, then you only need to change a place, and the command name of all its places has changed.
Iii) Storage The compiler flag. Suppose you want to pass a set of identical options (for example, -wall -o -o) if you put this group option in a variable, then you can put this variable in all call compilers. . And when you want to change the option, you only need to change this variable in one place.
To set a variable, you only need to write the name of this variable on a line, follow the back to one = number, followed by the value of this variable you want to set. In the future, you have to reference this variable, write a $ symbol, followed by the variable name surrounded in parentheses. For example, in the following, we use the front Makefile to override the variables:
=== makefile start ===
Objs = foo.o bar.o
CC = GCC
Cflags = -wall -o-g
MyProg: $ (OBJS)
? $ (Cc) $ (objs) -o myprog
Foo.O: foo.c foo.h bar.h
? $ (Cc) $ (cflags) -c foo.c -o foo.o
Bar.o: bar.c bar.h
? $ (Cc) $ (cflags) -c bar.c -o bar.o
=== Makefile end ===
There are also some internal variables that are defined according to each rule content. Three more useful variables are $ @, $ === makefile start === Objs = foo.o bar.o CC = GCC Cflags = -wall -o-g MyProg: $ (OBJS) ? $ (Cc) $ ^ -o $ @ Foo.O: foo.c foo.h bar.h $ (Cc) $ (cflags) -c $ <-o $ @ Bar.o: bar.c bar.h? $ (cc) $ (cflags) -c $ <-o $ @ === Makefile end === You can use variables to do many other things, especially when you mix them with functions. Please refer to the GNU Make manual if you need further understanding. ('man make', 'man makefile') 2.4 Implied Rules (Implicit Rules) Note that in the above example, several commands of the .o file are the same. They are all generated from .o files and related files, which is a standard step. In fact, Make already knows how to do - it has some rules called implicit rules, which tells it when you don't give some commands, what should I do. If you delete the commands that generate foo.o and bar.o from their rules, Make will find its implicit rules and then find an appropriate command. Its command uses some variables, so you can set it according to your ideas: it uses the variable CC as a compiler (like our example), and transmits variable cflags (for C compiler, C compiler) CXXFLAGS, CPPFLAGS (C pre-regulator flag), target_arch (now don't consider this), then add the flag '-C', back with the variable $ <(first rely), then the flag '- o 'Call with variable $ @ (destination file name). A concrete command of a C compile will be: $ (CC) $ (CFLAGS) $ (Target_arch) -c $ <-o $ @ Of course, you can define these variables according to your own needs. This is why the code output with the -m or -mm switch with GCC can be used directly in a Makefile. 2.5 Imaginary purposes (Phony Targets) Suppose one of your projects will eventually generate two executables. Your main goal is to generate two executable files, but these two files are independent of each other - if a file needs to be rebuilt, does not affect the other. You can use the "Imaginary Purpose" to achieve this effect. A illusion is almost the same as a normal purpose, but this destination file does not exist. Therefore, Make always assume that it needs to be generated. When it is updated, it will execute the command line in its rule. If you start with our makefile: All: EXEC1 EXEC2 Where EXEC1 and EXEC2 are two executables we are as a purpose. Make put this 'all' as its main purpose, try to update 'all' every time you execute. But since this line is in this line rule, there is no actual file called 'all' (in fact ALL does not actually be generated on the disk), so this rule does not really change the status of 'all'. Since this file does not exist, Make will try to update the all rule, so check it to rely on Exec1, if EXEC2 needs to be updated, if necessary, reach our purpose. Illustrative purposes can also be used to describe a set of non-preset actions. For example, you want to delete all files generated by Make, you can set up such rules in Makefile: Veryclean: ?? rm * .o ?? rm myprog The premise is that there is no other rule dependes on this 'Veryclean' purpose, which will never be executed. However, if your clear use of the command 'make verycleclean', Make will use this purpose as its primary goal, perform those RM commands. What happens if there is a virtual file on your disk? At this time, this destination file must be the latest in this rule (all relying on files are already the latest), so they don't make the user clearly command Make to re-generate it. things happen. The solution is to mark all the ingredients (with .phony), telling make not to check if they exist on the disk, nor to find any implicit rules, directly assume that the specified purpose needs to be updated. Add the following line to the Makefile to include the rules of the above rules: .Phony: Veryclean Yes. Note that this is a special Make rule, make know .phony is a special purpose, of course, you can join any illusion you want to use in its relying on it, and Make knows that they are ingredients. 2.6 function (Functions) The function in the makefile is very similar to its variables - when you use, you use a $ symbol to keep the bracket, function name, and space with a comma-separated parameter, and finally the end of the bracket. For example, there is a function called 'Wildcard' in GNU Make, which has a parameter, which is to expand into a list of file names that are described by its parameters, and the file is spaced apart. You can use this command as shown below: Sources = $ (Wildcard * .c) This line produces a list of all files ending with '.c', and then stores into the variable Sources. Of course, you don't need to store the results into a variable. Another useful function is the PATSUBST (Patten Substitude, the abbreviation of matching replacement) functions. It requires three parameters - the first is a model that needs to match, the second means to replace it, the third is a word that needs to be processed by the space separated by the space. For example, dealing with the variables that have been defined above, Objs = $ (PATSUBST% .c,%. O, $ (Sources)) This line will process all the words in the Sources list (a column file name), if it is '.c', use '.o' to replace '.c'. Note that the% symbols here will match one or more characters, and it is called a 'handle' each time the string is called. In the second parameter, the% is interpreted as the handle that matches the first parameter. 2.7 A more effective Makefile With our current, we can build a quite effective Makefile. This makefile can complete most of our needs, you can use it directly in most projects without doing too much change. First we need a basic Makefile to build our program. We can let it search for the current directory, find the source file, and assume that they belong to our item, put it into a variable called Sources. Here, if all * .cc files are also included, it may be more insurance because the source code file may be a C code. Sources = $ (Wildcard * .c * .cc) With PATSUBST, we can generate the target file name by the source file name, and we need to compile these objectives. If our source file has both a .C file, there is also a .cc file, we need to use the embedded PATSUBST function call: Objs = $ (PATSUBST% .c,%. O, $ (PatSubst% .cc,%. O, $ (Sources))))) The innermost layer of PATSUBST calls will have a composite replacement of the .cc file, resulting in the external PATSUBST call processing, alternative to the .c file suffix. Now we can set up a rule to build an executable: MyProg: $ (OBJS) ?? gcc -o myprog $ (bjs) Further rules do not necessarily need, GCC already knows how to generate target files. Below we can set the rules that produce relying on information: Depends: $ (SOURCES) ?? GCC -M $ (Sourcees)> Depends Here, if a file called 'Depends' does not exist, or any source file is more new than an existing Depends file, then a Depends file will be generated. The Depends file will contain rules for the source code file generated by GCC (Note -M Switch). Now let's let Make make these rules as part of the Makefile file. The techniques used here are very like a #include system in C language - I ask Make to include this file inClude to makefile, as follows: Include Depends GNU Make See this, check whether the 'Depends' is updated, if not, it reacts the Depends file with the command we give it. It will then contain this set (new) rule into in and continue to handle the final target 'myprog'. When you see rules about MyProg, it checks if all target files are updated - using the rules in the Depends file, of course these rules are now updated. This system is actually a low efficiency, because whenever a source code file is changed, all source files must be preproced to generate a new 'Depends' file. And it is not 100% security, because when a Header file is changed, relying on information is not updated. But on the basic work, it is also quite useful. 2.8 A better Makefile This is a Makefile I have designed for my most items. It should be able to do not need to modify it in most items. I mainly use it on DJGPP, which is a DOS version of the GCC compiler. So you can see the executed command name, 'alleg' package, and RM -F variables reflect this. === makefile start === ############################################################### # # Generic makefile # # by George Foot # email: george.foot@merton.ox.ac.uk# # CopyRight (c) 1997 George Foot # All rights reserved. # 保 all copyright # # No warranty, no liability; # You Use this at your OWN RISK. # 不 保, is not responsible # You have to use this, your own risk # # You are free to modify and # distribute this without giving # Credit to the Original Author. # You can change and distribute this file casually # Without any honor of the original author. # (How is your meaning?) # ############################################################### ### Customising # 用户 用户 设 # # Adjust the folloading if Necessary; Executable is the Target # Executable's FileName, and libs is a list of libraries to link in # (E.G. Alleg, Stdcx, Iostr, ETC). You can override these on make's # Command Line of Course, if you prefer to do it.. # # If you need, adjust the following. Executable is the target's executable file name, Libs # Is a list of packages that need to be connected (such as Alleg, STDCX, ISTR, etc.). Of course you # You can overwrite them on the command line of Make, you are willing to have no problem. # Executable: = Mushroom.exe Libs: = alleg # Now alter any implicit rules' variables if you like, e.g .: # # Now change any variables in the implicit rules you want to change, for example Cflags: = -g -wall -o3 -m486 CxxFlags: = $ (cflags) # The next bit checks to see WHETHER RM IS in your Djgpp bin # Directory; if not it Uses del instead, but this can cause (Harmless) # `File Not Found 'Error Messages. If you are not using DOS AT ALL, # set the variable to thing thing to somethingly what will unquestionly ref # files. # # Let's check if there is any rm command in your DJGPP command directory, if not, we use # Del command instead, but it is possible to give us the 'File Not Found' this error message, this is not # What is the problem. If you are not using DOS, set it into a command that deletes files and unused. # (In fact, this step is redundant on the UNIX class, just convenient for DOS users. Unix # Users can delete these 5 lines of commands. ) IFNEQ ($ (Wildcard $ (Djdir) /bin/rm.exe),) RM-F: = RM -F Else RM-F: = DEL ENDIF # You shouldn't Need to change. # # P # From here, you should don't need to change anything. (I don't believe it, too NB!) Source: = $ (Wildcard * .c) $ (Wildcard * .cc) Objs: = $ (PATSUBST% .c,%. O, $ (PatSubst% .cc,%. O, $ (SOURCE))) DEPS: = $ (PatSubst% .O,%. D, $ (OBJS)) Missing_DEPS: = $ (Filter-Out $ (Wildcard $ (DEPS)), $ (DEPS)) Missing_Deps_sources: = $ (Wildcard $ (PatSubst% .d,%. C, $ (Missing_DEPS)) / $ (PATSUBST% .d,%. CC, $ (missing_deps)))))) CPPFLAGS = -MD .Phony: Everything DEPS OBJS CLEAN VERYCLEAN REBUILD Everything: $ (Executable) DEPS: $ (DEPS) Objs: $ (OBJS) Clean: ?? @ $ (rm-f) * .o ?? @ $ (rm-f) * .d Veryclean: Clean ?? @ $ (rm-f) $ (Executable) Rebuild: Veryclean Everything IFNEQ ($ (missing_deps),) $ (Missing_deps): ?? @ $ (rm-f) $ (patsubst% .d,%. O, $ @) ENDIF -inClude $ (DEPS) $ (Executable): $ (OBJS) ? GCC -O $ (Executable) $ (OBJS) $ (AddPRefix -l, $ (LIBS)) === Makefile end === There are several places worth explanating. First, when I define most of the variables, I use: = instead of = symbol. Its role is to immediately expand the functions and variables referred to in the definition. If you use =, the function and variable reference will remain in that child, that is, the value of changing a variable can cause the value of other variables to be changed. E.g: A = foo B = $ (a) # Now B is $ (a), and $ (a) is 'foo'. A = BAR # Now B is still $ (a), but its value has become 'bar'. B: = $ (a) # Now B's value is 'bar'. A = foo # B The value is still 'bar'. Make will ignore all the text behind the # symbol until the end of the line. Ifneg ... Else ... Endif System is a failure / effective tool that makes a certain part of the code in the makefile. IFEQ uses two parameters if they are the same, it is added until Else (or ENDIF, if there is no else), if the ELSE is added; if different, add a piece of ELSE to ENDIF to makefile (if there is ELSE). The use of IFNEQ is just the opposite. The 'filter-out' function uses two lists separated by spaces, which deletes the items existing in the second list in the first list. I use it to handle the DEPS list, delete all existing projects, but only those missing. I have said before, CPPFlags has some flags for passing to the pre-processor for implicit rules. The -md switch is similar to the -m switch, but the file name formed from the source file .c or .CC is the use of the suffix. D.D (this explains the steps I form a DEPS variable). The file mentioned in DEPS later added with '-include' to make the makefile, which hides all the error messages generated due to the fault. If any relying on the file does not exist, makefile will remove the corresponding .o file from the disk to make Make rebuild it. Because cppflags specifies -md, it is also re-generated. Finally, 'addprefix' functions put the first parameter value of each of the second parameter list. The purpose of this makefile is (these purposes can be transmitted to the Make command line to directly select): Everything: (Preset) Update the primary executable and generates or updates a '.d' file and one '.o' file for each source file file .o .o 'file. DEPS: just generates or updates a '.d' file for each source code program. Objs: Generate or update the '.d' file and target file for each source code program. Clean: Delete all an intermediary / dependent file (* .d and * .o). Veryclean: Make `Clean 'and delete executables. Rebuild: Do `Veryclean 'and` everything'; it is completely reconstructed. In addition to preset EVERYTHING, only Clean, Veryclean, and Rebuild are meaningful to users. I haven't found a directory for a source code file, this makefile will fail, unless the file is disabled. If this messy situation occurs, just enter `make Clean ', all target files and dependent files will be deleted, and the problem should be solved. Of course, it is best not to mess with them. If you find that this makefile file cannot complete its work in some case, please tell me, I will take it better. 3 summary ~~~~~~~~~~~~~~~ I hope this article is enough to explain how the multi-file project is working, and it means how security is reasonable and reasonable. At this point, you should easily use the GNU Make tool to manage small projects, if you fully understand the back of several parts, these are not difficult for you. GNU Make is a powerful tool, although it is mainly used to build a program, there are still many other uses. If you want to know more about this tool, its syntax, function, and many other features, you should see its reference file (INFO PAGES, other GNU tools, see their info pages.).