The use of make and makefile ###Easy to understand

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! ! !