What is a Makefile

Makefiles define a set 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 which dependencies between files. Makefiles have their own writing format, keywords, and functions. C has its own formats, keywords, and functions. And in the Makefile you can use any command provided by the system shell to do the desired work. Once written, only one make command is required. The entire project is automatically compiled, greatly improving the efficiency of software development

The name of the makefile

By default, the make command looks for files named “GNUmakefile”, “makefile”, “makefile” in the current directory in order, and finds the explain file. In these three filenames, it is best not to use “GNUmakefile”, which is recognized by GNUmake. There are other makes that are only sensitive to all-lowercase “makefile” filenames, but basically, most of them support both “makefile” and “makefile” filenames by default.

Of course, you can write makefiles with other names, such as “make. Linux”, “make. Solaris”, “make. AIX”, etc. If you want to specify a specific Makefile, you can use the “-f” and “–file” arguments of Make, such as:

make -f Make.Linux
make --file Make.AIX
Copy the code

The rules of the Makefile

According to the rules

target ... : prerequisites ...
    command
Copy the code
  • Target: The target to generate, which can also be a label
  • Prerequisites: List of parameters that the target depends on
  • Command: Any shell instruction, typically used to generate targets. Prerequisites Must start with the [Tab] key or in a line with the Prerequisites separated by a semicolon

This is a file dependency, that is, target depends on the File in residence whose generation rules are defined in command. To put it bluntly, if more than one file in the Resident system is newer than the target file, the command defined by that system is executed. (Command must start with the Tab key otherwise the compiler won’t recognize command.) These are the rules for makefiles, and are at the heart of makefiles.

Here’s an example:

test : test1.o test2.o
	cc -o test test1.o test2.o
	
test1.o : test1.c test1.h
	cc -c test1.c
	
test2.o : test2.c test2.h
	cc -c test2.c
	
clean :
	rm test test1.o test2.o
Copy the code

In this case, test is the final target, and generating test depends on test1.o and test2.o, that is, the cc -o test test1.o test2.o instruction is executed to rebuild target if

  • The test was not found
  • Test exists, but test1.o is newer than test
  • Test exists, but the change time of test2.o is newer than test

After listing all the dependencies in the Makefile, the links are automatically compiled based on the dependencies when you execute the make command

But what does clean mean here? It has no other dependencies, so make does not automatically find dependencies on files and therefore does not automatically execute commands defined thereafter. To execute subsequent commands, specify the name of the lable after the make command, such as make clean. This approach is very useful for defining unnecessary or unrelated compile-related commands in a Makefile, such as package, backup, and so on.

Hidden rules

By default, every.o dependency file has a.c file of the same name. For example, if a target is test.o, then test.c is the default dependency file of test.o. This is an implicit rule for makefile that is automatically derived by make

How does make work?

In the default mode, we just type the make command. So:

  1. Make will look for a file named “Makefile” or “Makefile” in the current directory
  2. If found, it looks for the first target file in the file. In the above example, it finds the file “test” and makes it the final target file
  3. If the test file does not exist, or if the later.o file on which test depends is newer than the test file, it will execute the command defined later to generate the test file
  4. If the.o file on which test depends also does not exist, make looks for the dependencies in the current file that target the.o file and generates the.o file based on that rule if it does

That’s the dependency of make as a whole, and make will look for file dependencies layer by layer until it finally compiles the first object file

Variables are used in makefiles

Fundamentals of variables

To make makefiles easy to maintain, we can use variables in makefiles. A makefile variable is just a string. For example, we can define a objects variable and use it as $(objects). Variables are similar to C macro definitions in that the value of a variable is extended to where it is used during execution

objects = test1.o test2.o	
test : $(objects)
	cc -o test $(objects)
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
test : test1.o test2.o
	cc -o test test1.o test2.o
Copy the code

The first way is exactly the same as the second way

Variables of variables

When defining the value of a variable, we can use other variables to construct the value of the variable. There are two ways to define the value of a variable in a Makefile.

The value of the variable on the right can be defined anywhere in the file. That is to say, the variable on the right does not have to be a defined value. It can also use the value defined later. Such as:

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

all:
    echo $(foo) # We do "make all" to type the variable $(foo) with the value Huh?
Copy the code

The good thing about this feature is that we can push the real value of a variable back to define it. The bad thing is that it is recursive:

A = $(B)
B = $(A)
# This would trap make in an infinite number of variable expansions. Of course, our make is capable of detecting such definitions and reporting errors
Copy the code

To avoid this approach, we can use another method in make that defines variables in terms of variables. This method uses the “:=” operator:

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

# This method can only use the previously defined variables instead of the previous variables. The upper and lower methods are equivalent

y := foo bar
x := later
Copy the code

Append variable value

We can append values to variables using the “+=” operator, as in:

objects = main.o foo.o bar.o utils.o
objects += another.o
Copy the code

So, our $(objects) value becomes: “main.o foo.o bar.o utils.o another.

If the variable has not been defined before, the “+=” automatically becomes “=”, and if the variable has been defined before, the “+=” inherits from the assignment operator of the previous operation. If the previous “:=”, the “+=” will have “:=” as its assignment, as in :=

variable := value
variable += more
Copy the code

Is equivalent to:

variable := value
variable := $(variable) more
Copy the code

But if this is the case:

variable = value
variable += more
Copy the code

Since the previous assignment was “=”, “+=” will also be assigned to “=”, so there will not be a replacement variable definition, which is very bad, so make will automatically solve this problem for us, we don’t need to worry about this problem.

Advanced use of variables

Substitution of variable values

We can replace the common parts of a variable in the format”{var: a = b} “, its meaning is that the variables “var” in all the “a” string “end” of “a” to replace “b” string. Here “end” means “space” or “end”. Here’s an example:

foo := a.o b.o c.o
bar := $(foo:.o=.c)
# "$(foo)" all in all. "o" string "end" to replace "c", ultimately the value of the bar is a.c biggest Arthur c.
Copy the code

Another variable substitution technique is defined in “static mode” (see previous section), such as:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)
# This depends on having the same pattern in the replaced string, which must contain a "%" character. This example also makes the $(bar) variable "A.C.B.C.C".
Copy the code

Treat the value of a variable as a variable

x = y
y = z
a := $($(x))
# in this example, $(x) is y, so $(x) is y, so $(a) is z
Copy the code

Let make derive automatically

GNU Make is powerful enough to automatically derive the commands following files and file dependencies.

Whenever make sees a [.o] file, it automatically adds the [.c] file to the dependency. If make finds an imitate. o file, then imitate. c will be the imitate. o dependency. And cc-c whatever. C will also be derived, so our makefile will never have to be so complicated again

Rules for clearing target files

Every Makefile should have a rule for clearing object files (.o and execute files). This is not only easy to recompile, but it is also good for keeping files clean.

.PHONY : clean
clean :
    rm test $(objects)
Copy the code

The makefile annotation

There are only line comments in the Makefile, which, like UNIX Shell scripts, are commented with the “#” character, such as:

This is comment 1 in the makefile
This is comment 2 in the makefile
Copy the code

Reference other makefiles

Use the include keyword in a Makefile to include other makefiles. When the make command begins, it looks for other makefiles indicated by include and places their contents in the current location. Much like C’s #include, the included file is placed exactly where it was included. The syntax for include is:

include <filename>
# include can be preceded by blank characters, but it must not start with the [Tab] key
# filename can be the file mode of the current operating system Shell (path and wildcard can be saved)

-include <filename>
No matter what errors occur in the include process, do not report any errors and continue to execute. The above command will report an error if the target file for the include is not found
Copy the code

The pseudo target

In the first example, we mentioned a “clean” goal.

clean:
    rm *.o temp
Copy the code

Pseudo targets are not executed automatically, only explicitly called. If there is a file named “clean” in the current directory, then according to our rules, the command will not be executed because the target already exists. To solve this problem, PHONY is a special tag that explicitly identifies a target as a “PHONY target,” telling make that the target is a “PHONY target” regardless of whether the file exists.

.PHONY : clean
clean:
    rm *.o temp
Copy the code

PHONY, our command would have been executed regardless of the existence of a “clean” file

Command error

After each command is run, make checks the return code of each command. If the command returns successfully, make executes the next command. When all the commands in the rule return successfully, the rule is considered complete. If a command in a rule fails (the command exit code is non-zero), make terminates execution of the current rule, potentially terminating execution of all rules.

Sometimes, just because a command is wrong doesn’t mean it’s wrong. For example, mkdir command, we must create a directory, if the directory does not exist, then mkdir is successfully executed, all is well, if the directory does exist, then there is an error. The reason we use mkdir is that there must be such a directory, so we don’t want mkdir to fail and terminate the rule.

To do this, ignoring the error of the command, we can prefix the command line of the Makefile with a minus sign “-” (after the Tab key) to indicate that the command is considered successful regardless of the error. Such as:

clean:
    -rm -f *.o
Copy the code