The Makefile is related to the compilation rules of the entire project. In a project, the source files are categorized and placed in several directories according to their type, function, and module. The Makefile defines a series of rules to specify which files need to be compiled first, which files need to be compiled later, which files need to be recompiled, and even perform more complex operations, because the Makefile is like a Shell script, where operating system commands can also be executed. The benefit of using a Makefile is “automated compilation”; once written, you only need to execute the make command, and the entire project will compile automatically, greatly improving the efficiency of software development. Make is a command tool that interprets the instructions in the Makefile.
Compilation Principles
Generally speaking, whether it is C, C++, or Pascal, the source files must first be compiled into intermediate code files. In Windows, this means .obj files, while in UNIX, they are .o files, known as Object Files. This action is called compilation. Then, a large number of Object Files are combined into an executable file, and this action is called linking.
During compilation, the compiler requires correct syntax and correct declarations of functions and variables. For the latter, you usually need to tell the compiler where the header files are located. As long as all syntax is correct, the compiler can produce intermediate target files. Generally, each source file should correspond to one intermediate target file (O file or OBJ file). During linking, the main task is to link functions and global variables, so we can use these intermediate target files (O files or OBJ files) to link our application. The linker does not care about which source file the function is in, only about the intermediate target files (Object Files); most of the time, due to too many source files, there are too many intermediate target files generated during compilation, and during linking, it is necessary to explicitly specify the names of the intermediate target files, which is inconvenient for compilation. Therefore, we need to package the intermediate target files. In Windows, this package is called a “library file” (Library File), which is a .lib file, while in UNIX, it is called an Archive File, which is a .a file. To summarize, source files first generate intermediate target files, which are then used to generate executable files. During compilation, the compiler only checks the program syntax and whether functions and variables are declared. If a function is not declared, the compiler will issue a warning, but it can still generate an Object File. During the linking process, the linker searches for the implementation of functions in all Object Files; if it cannot find them, it will report a linking error code (Linker Error). In VC, this type of error is generally: Link 2001 error, which means that the linker could not find the implementation of the function. You need to specify the Object File of the function.
Introduction to Makefile
The make command requires a Makefile to tell it how to compile and link the program.
Writing Rules:
1) If this project has not been compiled, then all our C files need to be compiled and linked. 2) If some C files in this project have been modified, we only compile the modified C files and link the target program. 3) If the header files in this project have been changed, we need to compile the C files that reference these header files and link the target program.
Makefile Rules:
target … : prerequisites … command…
The target is a target file, which can be an Object File or an executable file. It can also be a label; the label feature will be discussed in a later chapter on “pseudo targets”. Prerequisites are the files or targets needed to generate that target. The command is the command that make needs to execute (any Shell command).
This is a file dependency relationship, meaning that one or more target files depend on the files in prerequisites, and the generation rules are defined in the command. In simpler terms, if one or more files in prerequisites are newer than the target file, the command defined will be executed. This is the core content of the Makefile.
Example:
# Link:
edit : main.o kbd.o command.o display.o
cc -o edit main.o kbd.o command.o display.o
# Compile main.o
main.o : main.c defs.hcc -c main.c
# Compile kbd.o
kbd.o : kbd.c defs.h command.hcc -c kbd.c
# Compile command.o
command.o : command.c defs.h command.hcc -c command.c
# Compile display.o
display.o : display.c defs.h buffer.hcc -c display.c
# Clean: make clean
clean : rm edit main.o kbd.o command.o display.o
How Make Works
By default, when we just enter the make command, 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 in the file (in the above example, it will find the “edit” file and treat it as the final target file). 3. If the edit file does not exist, or if the modification time of the .o files that edit depends on is newer than the edit file, it will execute the commands defined afterward to generate the edit file. 4. If the .o files that edit depends on also exist, make will find the dependencies of the target for the .o files in the current file, and if found, it will generate the .o files according to that rule (this is somewhat like a stack process). 5. Of course, your C files and H files exist, so make will generate .o files and then use those .o files to fulfill the ultimate task of make, which is to execute the edit file.
Using Variables in Makefile
We declare a variable called objects:
objects = main.o kbd.o command.o display.o
So we can conveniently use this variable in our Makefile as “$(objects)”. Thus, our improved Makefile looks like this:
objects = main.o kbd.o command.o display.o
# Link:
edit : $(objects)cc -o edit $(objects)
# Compile object:
…
# Clean:
clean : rm edit $(objects)
So if new .o files are added, we only need to modify the objects variable.
Let Make Automatically Infer
GNU’s make is powerful; it can automatically infer files and the commands for the file dependencies, so we don’t need to write similar commands after every [.o] file because our make will automatically recognize and infer commands. As long as make sees a [.o] file, it will automatically add the [.c] file to the dependency. If make finds a whatever.o, then whatever.c will be the dependency file for whatever.o. Moreover, cc -c whatever.c will also be inferred, so our Makefile no longer needs to be written so complicatedly:
objects = main.o kbd.o command.o display.o
# Link:
edit : $(objects)cc -o edit $(objects)
# Compile:
main.o : defs.hkbd.o : defs.h command.hcommand.o : defs.h command.hdisplay.o : defs.h buffer.h
# Clean:
.PHONY : cleanclean : rm edit $(objects)
This method is known as the “implicit rules” of make. In the content above, “.PHONY” indicates that clean is a pseudo target file.
Alternative Styles of Makefile
objects = main.o kbd.o command.o display.o
# Link:
edit : $(objects)cc -o edit $(objects)
# Compile:$(objects) : defs.hkbd.o command.o files.o : command.h display.o
# Clean:
.PHONY : cleanclean :-rm edit $(objects)
Adding a small hyphen before the rm command means that if there are issues with certain files, ignore them and continue with the subsequent tasks.