Makefile 簡介
前言
這篇文章旨在介紹 Makefile 這一強力的工具,對 Makefile 的規則和變數等內容沒有做出完整的描述,只是簡單進行了一點介紹,希望更多的人能了解並使用 Makefile。
Makefile 是什麼
一個正式的軟體工程中,源文件按類型、功能、模塊等分別放在不同的目錄下,如果每次都在命令行這樣: gcc a.c b.c c.c -o test ,顯然是非常影響效率的,那麼這時候就需要 Makefile 來進行管理,在 Makefile 中指定哪些文件先編譯,那些文件後編譯,在什麼情況下編譯哪些文件等操作。
Makefile 就是一個用來幫助我們編譯的工具,和 Windows 下的 IDE 類似,只不過 Makefile 需要我們自己動手編寫,一個好的 Makefile 可以極大的提升工作的效率。
Makefile 規則
target ... : prerequisites ...
command
...
target 就是我們編譯文件要生成的目標, prerequisites 就是我們編譯文件需要的依賴, command 就是用依賴生成目標所需要執行的命令。
比如我們平時使用的 gcc a.c b.c -o test
這裡的 test 就是我們要生成的目標, a.c 就是我們生成目標需要的依賴,而 gcc a.c -o test 則是命令。將這行命令用 Makefile 的方式來寫就是:
test:a.c b.c
gcc a.c b.c -o test
Makefile 中的命令必須用 tab 開始,不能是空格。
Makefile 可以自動推導文件以及文件依賴關係後面的命令,在後面的示例中我們可以看到目標的依賴基本都是 .o 文件而不是 .c 文件,原因正是 Makefile 強大的自動推導功能。
通常 Makefile 中還會有一個名為 clean 的目標,用來清除編譯後產生的各種文件。一般情況下 Makefile 會根據依賴和目標的新舊來決定是否編譯,但是如果不小心修改了目標而造成目標比依賴新的情況的話,Makefile 會因為目標比依賴新而忽略這個目標下的命令,這個時候顯然會造成問題,一個解決的辦法就是使用 clean 這樣的目標來清除編譯後的文件,然後 make 重新編譯。
clean 這個目標有點特殊,他是不需要依賴的,因此也叫偽目標。一般使用方式如下:
clean:
rm *.o test -f
Makefile 使用
make 命令執行時,需要一個 Makefile 文件(文件名為 Makefile 、 makefile 、 *.mk ),以告訴 make 命令需要怎麼樣的去編譯和鏈接程序。執行時只用在命令行輸入 make , Makefile 就會自動執行第一個目標下的命令。而是否執行命令則取決於依賴,如果沒有目標文件或是目標後的依賴文件比目標文件新,Makefile 就會執行其下面的命令。
Makefile 中使用 # 注釋,只注釋 # 後的一行。
Makefile 中引用其他 Makefile,用 include 指令來引用。引用的效果就是原地展開。
Makefile 命令前面加 @ 來靜默執行,即執行命令時不列印命令本身。
Makefile 變數
Makefile 中的變數和 shell 腳本中非常相似,都是直接定義,不需要類型,引用時用 $(var) 。
偽目標( .PHONY ):偽目標形式上是一個目標,但是不需要依賴,偽目標一般只是為了執行目標下面的命令(比如 clean 就是偽目標)。
Makefile 中的幾種變數賦值運算符
- = 賦值,可以被賦值為變數的值,解析時取這個變數最後的值。
- := 也是賦值,被賦值為變數時解析為變數在這行語句時的值,即變數如果後面改變這裡的值也不改變。
- ?= 如果變數前面並沒有賦值過則執行這條賦值,如果前面已經賦值過了則本行被忽略。
- += 用來給一個已經賦值的變數接續賦值,意思就是把這次的值加到原來的值的後面。
關於 = 和 := ,比如 B=$(A)bcd ,那麼 B 的值取決於變數 A 最後一次被賦值的值,即使 A 在 B 之後再次被賦值,變數 B 仍然會隨著 A 的改變而改變。而 := 則只看之前 A 最後被賦值的值。
Makefile 的環境變數
- Makefile 中用 export 導出的就是環境變數。一般情況下要求環境變數名用大寫,普通變數名用小寫。
- 環境變數和普通變數不同,可以這樣理解:環境變數類似於整個工程中所有 Makefile 之間可以共享的全局變數,而普通變數只是當前本 Makefile 中使用的局部變數。所以要注意:定義了一個環境變數會影響到工程中別的 Makefile 文件,因此要小心。
Makefile 中的自動變數
自動變數是 Makefile 中提前預定義的特殊意義的符號,類似 C 語言中的宏 __LINE__ 等,提前被定義並被賦予了特殊含義。
- $@ 目標文件名,比如上文的 test 。
- $< 第一個依賴文件名,如果依賴目標是以模式(即「 % 「)定義的,那麼」 $< 「將是符合模式的一系列的文件集。注意,其是一個一個取出來的。
- $^ 依賴的文件集合,比如上文的 a.c b.c 。
此外還可以向 Makefile 傳參, $# 存放傳遞參數個數, $1 存放第一個參數的字元串, $2 存放第二個參數的字元串……
其他
通配符:比如在當前文件夾下有 1.c 2.c 12.c test.c 1.h 。
- % 若干個任意字元,和 * 很相似,但是 % 一般只用於規則描述中,又叫做規則通配符。
- * 若干個任意字元 *.c 匹配 1.c 2.c 12.c test.c 。
- ? 1個任意字元 ?.c 匹配 1.c 2.c 。
- [] 將 [] 中的字元依次去和外面的結合匹配 [12].c 匹配 1.c 2.c 。
Makefile 與 shell 腳本非常相似,shell 腳本中能使用的 Makefile 也能使用,比如 awk 等工具。
Makefile 示例
這個示例是我從九鼎創展的 x210 開發板常式中找的一個 Makefile 。
#
# Makefile for module.
#
CROSS ?= arm-none-eabi-
NAME := template-framebuffer-tingl-cube
#
# System environment variable.
#
ifneq (,$(findstring Linux, $(shell uname -s)))
HOSTOS := linux
endif
ifneq (,$(findstring windows, $(shell uname -s)))
HOSTOS := windows
endif
ifeq ($(strip $(HOSTOS)),)
$(error unable to determine host operation system from uname)
endif
#
# Load variables of flag.
#
ASFLAGS := -g -ggdb -Wall -O3
CFLAGS := -g -ggdb -Wall -O3
CXXFLAGS := -g -ggdb -Wall -O3
LDFLAGS := -T link.ld -nostdlib
ARFLAGS := -rcs
OCFLAGS := -v -O binary
ODFLAGS :=
MCFLAGS := -mcpu=cortex-a8 -mtune=cortex-a8 -march=armv7-a -mfpu=neon -ftree-vectorize -ffast-math -mfloat-abi=softfp
LIBDIRS :=
LIBS :=
INCDIRS :=
SRCDIRS :=
#
# Add necessary directory for INCDIRS and SRCDIRS.
#
INCDIRS += include \
include/hardware \
include/library
SRCDIRS += source \
source/startup \
source/hardware \
source/arm \
source/library \
source/library/ctype \
source/library/errno \
source/library/exit \
source/library/malloc \
source/library/stdlib \
source/library/string \
source/library/stdio \
source/library/math \
source/graphic \
source/graphic/maps/software
#
# Add external library
#
INCDIRS += source/tinygl \
source/tinygl/include
SRCDIRS += source/tinygl/
#
# You shouldn't need to change anything below this point.
#
AS := $(CROSS)gcc -x assembler-with-cpp
CC := $(CROSS)gcc
CXX := $(CROSS)g++
LD := $(CROSS)ld
AR := $(CROSS)ar
OC := $(CROSS)objcopy
OD := $(CROSS)objdump
MKDIR := mkdir -p
CP := cp -af
RM := rm -fr
CD := cd
FIND := find
ifeq ($(strip $(HOSTOS)), linux)
MKV210 := tools/linux/mkv210
endif
ifeq ($(strip $(HOSTOS)), windows)
MKV210 := tools/windows/mkv210
endif
#
# X variables
#
X_ASFLAGS := $(MCFLAGS) $(ASFLAGS)
X_CFLAGS := $(MCFLAGS) $(CFLAGS)
X_CXXFLAGS := $(MCFLAGS) $(CXXFLAGS)
X_LDFLAGS := $(LDFLAGS)
X_OCFLAGS := $(OCFLAGS)
X_LIBDIRS := $(LIBDIRS)
X_LIBS := $(LIBS) -lgcc
X_OUT := output
X_NAME := $(patsubst %, $(X_OUT)/%, $(NAME))
X_INCDIRS := $(patsubst %, -I %, $(INCDIRS))
X_SRCDIRS := $(patsubst %, %, $(SRCDIRS))
X_OBJDIRS := $(patsubst %, .obj/%, $(X_SRCDIRS))
X_SFILES := $(foreach dir, $(X_SRCDIRS), $(wildcard $(dir)/*.S))
X_CFILES := $(foreach dir, $(X_SRCDIRS), $(wildcard $(dir)/*.c))
X_CPPFILES := $(foreach dir, $(X_SRCDIRS), $(wildcard $(dir)/*.cpp))
X_SDEPS := $(patsubst %, .obj/%, $(X_SFILES:.S=.o.d))
X_CDEPS := $(patsubst %, .obj/%, $(X_CFILES:.c=.o.d))
X_CPPDEPS := $(patsubst %, .obj/%, $(X_CPPFILES:.cpp=.o.d))
X_DEPS := $(X_SDEPS) $(X_CDEPS) $(X_CPPDEPS)
X_SOBJS := $(patsubst %, .obj/%, $(X_SFILES:.S=.o))
X_COBJS := $(patsubst %, .obj/%, $(X_CFILES:.c=.o))
X_CPPOBJS := $(patsubst %, .obj/%, $(X_CPPFILES:.cpp=.o))
X_OBJS := $(X_SOBJS) $(X_COBJS) $(X_CPPOBJS)
VPATH := $(X_OBJDIRS)
.PHONY: all clean
all : $(X_NAME)
$(X_NAME) : $(X_OBJS)
@echo [LD] Linking $@.elf
@$(CC) $(X_LDFLAGS) $(X_LIBDIRS) -Wl,--cref,-Map=$@.map $^ -o $@.elf $(X_LIBS)
@echo [OC] Objcopying $@.bin
@$(OC) $(X_OCFLAGS) $@.elf $@.bin
@echo make header information for irom booting
@$(MKV210) $@.bin
$(X_SOBJS) : .obj/%.o : %.S
@echo [AS] $<
@$(AS) $(X_ASFLAGS) $(X_INCDIRS) -c $< -o $@
@$(AS) $(X_ASFLAGS) -MD -MP -MF $@.d $(X_INCDIRS) -c $< -o $@
$(X_COBJS) : .obj/%.o : %.c
@echo [CC] $<
@$(CC) $(X_CFLAGS) $(X_INCDIRS) -c $< -o $@
@$(CC) $(X_CFLAGS) -MD -MP -MF $@.d $(X_INCDIRS) -c $< -o $@
$(X_CPPOBJS) : .obj/%.o : %.cpp
@echo [CXX] $<
@$(CXX) $(X_CXXFLAGS) $(X_INCDIRS) -c $< -o $@
@$(CXX) $(X_CXXFLAGS) -MD -MP -MF $@.d $(X_INCDIRS) -c $< -o $@
clean:
@$(RM) $(X_DEPS) $(X_OBJS) $(X_OBJDIRS) $(X_OUT)
@echo Clean complete.
#
# Include the dependency files, should be place the last of makefile
#
sinclude $(shell $(MKDIR) $(X_OBJDIRS) $(X_OUT)) $(X_DEPS)
下面這個是 uboot1.3.4 中的 Makefile ,比較老了,與新版 uboot 有些差異:
本文來自 CDUT LUG 社區作者蘇森的原創投稿。
轉載請註明原文出處,否則必究相關責任。
原文鏈接:https://blog.cdutlug.org/2017/11/makefile_introduction/
本文鏈接:https://linuxstory.org/makefile_introduction/