Makefile
A Makefile is a text file that contains a series of rules to tell the build tool make how to build a software project. It describes the dependencies between each source file in the project and specifies how to compile the source files into target files, as well as how to link these target files to generate the final executable or library files.
Contents
-
Variable Definitions: A series of variables are defined to store information such as the compiler, compilation options, and target file names.
-
Rule Definitions: Each rule includes the target file, dependency files, and execution commands. The make tool uses these rules to determine which files need to be recompiled and which files are up to date and do not need recompilation.
-
Comments: Makefiles support comments for explaining and clarifying the contents within the Makefile.
-
Built-in Functions and Directives: Some built-in functions and directives can be used in Makefiles to manipulate variables, execute commands, and perform conditional checks. These functions and directives provide rich functionality for conveniently writing complex build rules.
Basic Syntax
target: dependencies
command
-
target: The name of the target file to be generated.
-
dependencies: A list of files that the target file depends on.
-
command: The command to generate the target file.
Variable Definition
CC = gcc
CFLAGS = -Wall
target: dependencies
$(CC) $(CFLAGS) -o target dependencies
Wildcard
sources = $(wildcard *.c)
objects = $(sources:.c=.o)
target: $(objects)
$(CC) $(CFLAGS) -o target $(objects)
Phony Target
.PHONY: clean
clean:
rm -f target *.o
-
.PHONY: Indicates a phony target, which is not a real file, telling make not to look for this filename.
Automatic Variables
objects = $(sources:.c=.o)
target: $(objects)
$(CC) $(CFLAGS) -o target $^
-
$^: A list of all dependency files.
Conditional Statements
DEBUG = 1
ifeq ($(DEBUG), 1)
CFLAGS += -g
else
CFLAGS += -O2
endif
-
ifeq: Executes if the condition is true.
Include and Export
include config.mk
-
include: Includes other Makefiles.
-
Used in Makefiles to pass configuration options to the called sub-Makefile or other commands, allowing the subprocess to use that variable.
Compilation Options Variables
CFLAGS = #-c # Compilation options variable
CFLAGS += #-g #-Wall -fno-stack-protector -rdynamic -g
# -g generates debug information that can be used by the debugger for source-level debugging. This is very useful for identifying and fixing errors during development.
# -Wall: Enables all common compiler warnings. This helps to discover potential code issues or non-standard writing.
# -fno-stack-protector: Disables stack protection. Stack protection is a security feature used to detect stack overflow attacks.
# -rdynamic: Generates a loadable dynamic link library (shared object), allowing symbols in the dynamic link library to be resolved at runtime. This is useful in some dynamically loaded module scenarios.
CFLAGS += #-I$(LIBMTN_CFG_PATH)/include
# -I: This is a compiler option used to specify the search path for header files. Here, the path to the header files follows -I. Spaces can be present between the -I option and the header file path.
CFLAGSL += #-L$(LIBMTN_PATH)
# -L: This is a compiler option used to specify the search path for library files. Here, the path to the library files follows -L.
LDLIBS += #-lpthread
# Used to specify the libraries needed during linking. Similar to CFLAGS and LDFLAGS, LDLIBS allows you to set options during the linking phase.
LDFLAGS += #-L$(ROOTDIR)/lib/$(openssl_dir) -lssl -lcrypto -lcrypt
# -L$(ROOTDIR)/lib/$(openssl_dir): This is a compiler option used to specify the search path for library files during linking. Here, the path follows -L.
# -lssl -lcrypto -lcrypt: This is a compiler option used to specify the libraries during linking. Here, -l is followed by the library names, and the compiler will search for and link these libraries in the specified search paths.
# Specifically, -lssl indicates linking the OpenSSL SSL library, -lcrypto indicates linking the OpenSSL Crypto library, and -lcrypt indicates linking the system's cryptographic library.
# LDFLAGS: This is a variable in a Makefile used to specify options during the linking phase. Similar to CFLAGS, LDFLAGS allows you to set options for linking.
Linking (Soft and Hard): ln -snf /tmp/serverkey.pem …/…/romfs/etc/serverkey.pem
Using for and if (demo): Polling subpaths and performing automatic compilation #Method 1
for target in $(SUBDIRS); do
echo "Building $(SUBDIRS)...";
$(MAKE) -C $$target;
done
#Method 2: Polling subpaths to check if Makefile exists
if [ -f $@/Makefile ]; then
$(MAKE) -C $@ ;
$(CC) main.c -o main.o;
else
echo "No Makefile in $@, skipping...";
echo "No Makefile in $@, skipping...";
fi
Makefile Organization Example
Use the tree command to view the code structure:
├── char_driver
│ ├── helloDev.c
│ ├── Makefile
│ └── test.c
├── char_ioctl
│ ├── char_ioctl_driver.c
│ ├── char_ioctl_user
│ ├── char_ioctl_user.c
│ └── Makefile
├── dns_src
│ ├── dns.c
│ ├── dns_excute.c
│ └── Makefile
├── GDB_debug
│ ├── backtrace.c
│ ├── gdb_server_proxy.c
│ ├── hello.c
│ ├── Makefile
│ ├── multiprocess_demo_2.c
│ ├── multiprocess_demo.c
│ ├── multithread_demo.c
│ └── Segmentation_fault.c
├── hook_fuction
│ ├── hook.c
│ └── Makefile
├── ifconfig_achieve
│ ├── ifconfig_1.c
│ ├── ifconfig_wlan_information.c
│ ├── ifconfig_wlan_ip.c
│ └── Makefile
├── ip_addr_exchange
│ ├── ip_addr_exchange.c
│ └── Makefile
├── main.c
├── Makefil
├── Makefile
├── mk_test
├── net_device_driver
│ ├── 1111111.c
│ ├── Makefile
│ ├── net_device.c
│ ├── net_device_dirver.c
│ ├── net_device_tun_driver.c
│ ├── net_device_tun_user_tx.c
│ ├── net_device_user1
│ ├── net_device_user2
│ ├── net_device_user2.c
│ ├── net_device_user.c
│ ├── net_device_user_tap
│ ├── net_device_user_tap.c
│ ├── net_device_user_tun.c
│ ├── openvpn-as_2.12.0-2e834031-Ubuntu20_amd64.deb
│ ├── tap_demo
│ ├── tap_demo1
│ ├── tap_demo1.c
│ ├── tap_demo.c
│ ├── tcpdump1.pcap
│ ├── tcpdump2.pcap
│ ├── tcpdump3.pcap
│ └── tcpdump4.pcap
├── net_device_driver2
│ ├── Makefile
│ ├── net_device_tun_driver.c
│ ├── net_device_tun_user
│ ├── net_device_tun_user.c
│ ├── tcpdump1.pcap
│ ├── tcpdump2.pcap
│ ├── tcpdump3.pcap
│ └── tcpdump.pcap
├── netlink_socket
│ ├── Makefile
│ ├── Module.symvers
│ ├── netlink_socket_dirver.c
│ └── netlink_socket_user.c
├── ping
│ ├── all
│ ├── Makefile
│ ├── ping.h
│ └── ping_system.c
├── ping_combin_bag.c
├── process_communication
│ ├── Makefile
│ ├── name_pipe_1.c
│ ├── name_pipe.c
│ ├── pipe.c
│ ├── queue_1.c
│ ├── queue.c
│ ├── semaphores_consumer.c
│ ├── semaphores_producer.c
│ ├── shared_memory_modify.c
│ ├── shared_memory_touch.c
│ ├── signals.c
│ └── signals_send.c
├── str_function
│ ├── Makefile
│ ├── str_function.c
│ └── str_function.h
├── tcp_test
│ ├── Makefile
│ ├── tcp_client.c
│ └── tcp_sever.c
└── udp_test
├── Makefile
├── udp_client.c
└── udp_server.c
Although there is a lot of code above, it is mainly divided into two parts:
-
Pure Application Layer Code
-
Driver Code and Combined Code with Application Layer
Driver and Application Layer Combined Code Makefile: char_driver
CC = gcc
CFLAGS = -o
ifneq ($(KERNELRELEASE),)
obj-m := helloDev.o
else
PWD := $(shell pwd)
#KDIR:= /lib/modules/4.4.0-31-generic/build
KDIR := /lib/modules/`uname -r`/build
all:
make -C $(KDIR) M=$(PWD)
$(CC) test.c $(CFLAGS) test.o
clean:
rm -rf *.o *.ko *.mod.c *.symvers *.c~ *~ *.a *.cmd* *.order
endif
Pure Application Layer Code: Root Directory Makefile Example
SUBDIRS := char_driver GDB_debug hook_fuction char_ioctl dns_src ifconfig_achieve ip_addr_exchange net_device_driver \
net_device_driver2 netlink_socket process_communication udp_test tcp_test ping str_function
MAKE = make
CC = gcc
C = -c
.PHONY: all $(SUBDIRS)
all: $(SUBDIRS)
# Here $@ refers to the target of the current rule, i.e., the name of the subdirectory.
#Method 1
$(SUBDIRS):
if [ -f $@/Makefile ]; then \
$(MAKE) -C $@ ; \
$(CC) main.c -o main.o; \
else \
echo "No Makefile in $@, skipping..."; \
echo "No Makefile in $@, skipping..."; \
fi
# File creation usage
mkdir -p ./mk_test || exit $?;
cp ./main.o ./mk_test/ || exit $?;
# Create a link when we need to use the same file in different directories, we don’t need to put a must-have identical file in every required directory,
# we just need to place it in a fixed directory and use the ln command to link it in other directories (link), without occupying disk space repeatedly.
# ln -snf /tmp/serverkey.pem ../../romfs/etc/serverkey.pem
#Method 2
# for target in $(SUBDIRS); do \
# echo "Building $(SUBDIRS)..."; \
# $(MAKE) -C $$target; \
# done
clean:
for dir in $(SUBDIRS); do \
if [ -f $$dir/Makefile ]; then \
$(MAKE) -C $$dir clean; \
else \
echo "No Makefile in $$dir, skipping..."; \
fi \
done
rm -rf ./mk_test/main.o
The above Makefile explanation: Method 1 and Method 2 use if and for statements respectively to achieve the same effect; you can uncomment whichever you want to compile.
Questions
$$target, can it be written as $$(target)?
In a Makefile, $$ is used to escape the $ symbol to prevent it from being interpreted as a Makefile variable or command-line variable.
Therefore, in the for loop, $$target is the correct way to reference the variable in the shell.
If written as $$(target), then Make will interpret $(target) as a Makefile variable rather than a shell variable.
So the correct way is $$target so that the shell can correctly interpret and execute the loop.