1. make tool
People usually use the make tool to automate compilation work. These tasks include: if only a few source files are modified, recompile only these source files; if a header file is modified, recompile all source files containing the header file. Taking advantage of this automatic compilation can greatly simplify development and avoid unnecessary recompilation.
The make tool completes and automatically maintains compilation work through a file called a makefile. The makefile needs to be written according to a certain syntax, which explains how to compile each source file and connect it to generate an executable file, and defines the dependencies between the source files. When one of the source files is modified, if other source files depend on this file, all source files that depend on this file must also be recompiled.
How does make work?
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 and use this file as the final target file;
3. If the main file does not exist or the file modification time of the subsequent .o file that main depends on is newer than the main file, then it will execute the command defined later to generate the main file;
4. If the .o file that main depends on also exists, then make will look for the dependency of the .o file in the current file. If found, it will generate the .o file according to that rule;
5. Of course, your C file and H file exist, so make will generate the .o file, and then use the .o file. The ultimate task of make is to execute the file main.
Note: If one or more files in DEPENDENCIES are updated, COMMAND will be executed. This is the core content of Makefile.
2. Makefile basic rules
TARGET ... : DEPENDENCIES ... COMMAND ...
- Target (TARGET) files generated by the program, such as executable files and target files; the target can also be an action to be performed, such as “clean”.
- Dependencies (DEPENDENCIES) are input files used to generate targets. A target usually depends on multiple files.
- Command (COMMAND) is an action executed by make. One can have multiple commands, each occupying one line. Note: The starting character of each command line must be the TAB character!
If one or more files in DEPENDENCIES are updated, COMMAND will be executed. This is the core content of the Makefile.
The simplest makefile example
main: the first target file, and is the executable target (that is, the final target file)
main:main.o add.o subtract.o gcc main.o add.o subtract.o -o main main.o:main.c add.h subtract.h gcc -c main.c -o main.o add.o:add.c add.h gcc -c add.c -o add.o subtract.o:subtract.c subtract.h gcc -c subtract.c -o subtract.o .PHONY:clean clean: rm -f main.o add.o subtract.o main
Common pseudo-targets .PHONY
In Makefiles, pseudo-targets are special targets that do not represent real file dependencies, but are used to execute a series of commands or other operations.
Here are some examples of common pseudo-targets:
1. clean: used to clean generated files or directories. Usually used to delete object files, executable files or other temporary files generated by compilation.
Example:
.PHONY: clean //Declare pseudo-targets before use to ensure they do not conflict with file names (standard usage) clean: rm -rf target_dir/*.o rm -f executable
Execution:
make clean
2. all: used to build all targets of the project or perform a series of operations. Typically used to compile an entire project or perform a set of tasks.
Example:
all: target1 target2 target3 target1: # Commands and rules used to build target1 target2: # Commands and rules used to build target2 target3: # Commands and rules for building target3
Execution:
make all
3. install: used to install software or copy files to a specified location. Usually used to install generated executable files, library files or other resources to the system directory or a specified location.
Example:
install: main cp main /usr/local/bin/
Where main is the main file.
Execution:
make install
4. test: used to run test suites or execute unit tests. Typically used to automate running tests and generate test reports.
Example:
test: ./run_tests.sh
Execution:
make test
5. help: used to display the help information or target list of the Makefile. Typically used to provide a brief description of the available targets and their purpose.
Example:
help: @echo "Available targets:" @echo " target1 - Build target1" @echo " target2 - Build target2" @echo " clean - Clean up generated files"
Execution:
make help
These are just some common examples of pseudo-goals, and you can add custom pseudo-goals based on your project’s needs and specific operations. In the Makefile, pseudo-targets are declared with .PHONY to ensure that they do not conflict with file names. For example:
.PHONY: clean all install test help
By using pseudo-targets, you can better organize and manage tasks and operations in your Makefile.
makefile automation variables
option name |
effect |
$@ |
The target file name of the rule |
$ |
The first dependent file name of the rule |
$^ |
List of all dependent files of the rule |
Example:
main:main.o add.o subtract.o gcc $^ subtract.o -o $@ main.o:main.c add.h subtract.h gcc -c $< -o $@ add.o:add.c add.h gcc -c $< -o $@ subtract.o:subtract.c subtract.h gcc -c $< -o $@ .PHONY:clean clean: rm -f main main.o add.o subtract.o
Custom variables in makefile
Example:
object=main.o add.o subtract.o is a custom variable, and $() is required when using the variable
object=main.o add.o subtract.o main:$(object) gcc $^ subtract.o -o $@ main.o:main.c add.h subtract.h gcc -c $< -o $@ add.o:add.c add.h gcc -c $< -o $@ subtract.o:subtract.c subtract.h gcc -c $< -o $@ .PHONY:clean clean: rm -f $(object) main
3. make automatic deduction
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 every [.o] file, because our make will automatically recognize it. and derive the command yourself.
As long as make sees a [.o] file, it will automatically add the [.c] file to the dependencies. If make finds a whatever.o, then whatever.c will be the dependency file of whatever.o . And gcc -c whatever.c will be derived as well.
Example:
Remove gcc command
object=main.o add.o subtract.o main:$(object) gcc $^ subtract.o -o $@ main.o:add.h subtract.h add.o:add.h subtract.o:subtract.h .PHONY:clean clean: rm -f $(object) main
Simplify further:
Remove .o intermediate files
object=main.o add.o subtract.o main:$(object) gcc $^ subtract.o -o $@ $(object): .PHONY:clean clean: rm -f $(object) main
Simplify further:
ELF=main is a custom variable, and $() is required when using the variable
ELF=main object=main.o add.o subtract.o $(ELF):$(object) gcc $^ subtract.o -o $@ $(object): .PHONY:clean clean: rm -f $(object) $(ELF)
Once a new source file is added, objects=main.o add.o subtract.o must be modified.
Q: Is there a permanent solution? Can it be adapted without modifying the makefile?
Answer: Use the relevant functions in the makefile.
4. Makefile common functions
1. wildcard function: files matching the pattern in the current directory
For example: src=$(wildcard *.c)
2. notdir function: remove path
For example: $(notdir $src)
3. patsubst function: pattern matching replacement
For example: $(patsubst%.c,%.o,$src) Equivalent to: $(src:.c=.o)
4. Shell function: execute shell command
For example: $(shell ls -d */)
The ls -d */ command is to obtain the folders in the current directory. The example is as follows:
[root@vm10-0-0-236 make_test]# ls -d */ add/subtract/
Simplify further:
Simplify common functions using makefiles
ELF=main src=$(wildcard *.c) object=$(src:.c=.o) $(ELF):$(object) echo $(src) #Print variable results echo $(object) #Print variable results gcc $^ subtract.o -o $@ $(object): .PHONY:clean clean: rm -f $(object) $(ELF)
Use shell commands
Use the shell command find to find the .c file
ELF=main src=$(shell find *.c) object=$(src:.c=.o) $(ELF):$(object) gcc $^ subtract.o -o $@ $(object): .PHONY:clean clean: rm -f $(object) $(ELF)
Use shell commands
Use the shell command ls to find the .c file
ELF=main src=$(shell ls *.c) object=$(src:.c=.o) $(ELF):$(object) gcc $^ subtract.o -o $@ $(object): .PHONY:clean clean: rm -f $(object) $(ELF)
5. Multi-level directory compilation method
If the source files are all in the same directory, there is no problem using the above makefile.
Distribution of source files in the same level directory:
[root@vm10-0-0-236 make_test]# tree . ├── add.c ├── add.h ├── main.c ├── makefile ├── subtract.c └── subtract.h
But if the source files are scattered in different directories, the above makefile is no longer applicable.
Distribution of source files in different directories:
[root@vm10-0-0-236 make_test]# tree . ├── add │ ├── add.c │ └── add.h ├── main.c ├── makefile └── subtract ├── subtract.c └── subtract.h
Multi-level directory makefile example:
ELF=main src=$(shell find -name '*.c') object=$(src:.c=.o) $(ELF):$(object) gcc $^ subtract.o -o $@ $(object): .PHONY:clean clean: rm -f $(object) $(ELF)
If a shared library is used, you can add -llist after the command
Example:
gcc $^ subtract.o -o $@ -llist
In fact, gcc $^ subtract.o -o $@ -llist can also be omitted, as follows:
ELF=main CC=gcc -g -llist src=$(shell find -name '*.c') object=$(src:.c=.o) $(ELF):$(object) $(object): .PHONY:clean clean: rm -f $(object) $(ELF)
where CC=gcc -g -llist
- -g: refers to adding debugging function;
- -llist: Add shared libraries;
Appendix
What is the difference between “:=” and “=” in makefile?
In Makefile, “:=” and “=” are two different assignment symbols, and they differ in the expansion and reference of variables.
(1) “:=” assignment symbol (simple assignment):
- When using “:=” for assignment, the value of the variable will be expanded immediately upon assignment, and subsequent references to the variable will use the expanded value.
- This assignment symbol will only be expanded once. Even if the subsequent assignment to the variable changes, the previously expanded value will not be affected.
(2) “=” assignment symbol (recursive assignment):
- When using “=” for assignment, the value of the variable will not be expanded immediately, but will be expanded when the variable is referenced.
- This assignment symbol will be recursively expanded, that is, it will be re-expanded every time a variable is referenced to reflect the latest assignment result.
Here’s an example illustrating the difference between the two:
# Use ":=" for assignment VAR1 := $(shell echo "Hello, world!") VAR2 := $(VAR1) # Use "=" for assignment VAR3 = $(shell echo "Hello, world!") VAR4 = $(VAR3) # Modify the value of the variable VAR1 := $(shell echo "Goodbye!") VAR3 = $(shell echo "Goodbye!") # Output the value of the variable all: @echo "VAR1: $(VAR1)" # Output "VAR1: Goodbye!" @echo "VAR2: $(VAR2)" # Output "VAR2: Hello, world!" @echo "VAR3: $(VAR3)" # Output "VAR3: Goodbye!" @echo "VAR4: $(VAR4)" # Output "VAR4: Goodbye!"
In the above example, VAR1 and VAR3 are assigned using “:=” and “=”, which expand to “Hello, world!” and “Goodbye!” respectively. VAR2 and VAR4 then reference the values of VAR1 and VAR3 respectively.
When the values of VAR1 and VAR3 are modified, the expansion value of VAR1 changes from “Hello, world!” to “Goodbye!”, while the expansion value of VAR3 is still “Goodbye!”. Since VAR2 uses the “:=” assignment symbol, its expansion value will not be affected by changes in the value of VAR1 and is still “Hello, world!”. VAR4 uses the “=” assignment symbol, and its expansion value will be updated to “Goodbye!” as the value of VAR3 changes.
If you need WORD or PDF related documents for this article, please leave a message in the comment area! ! !
If you need WORD or PDF related documents for this article, please leave a message in the comment area! ! !
If you need WORD or PDF related documents for this article, please leave a message in the comment area! ! !