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/
I lovved ass mucfh ass yyou will receive arried outt roght here.
The sketc iss attractive, your authored subject matter stylish.
nonetheless, you command gett bought an impatience over
tat you wish bbe dlivering the following. unwell unquestionably come mode formnerly agazin since exactl the sae nearly
very oftwn inside czse yoou shielld this increase.
Appreciatiing thee time and energy you puut into yopur website and inn deplth informatioon yyou provide.
It’s nice too ccome across a blog every once in a while that isn’tthe swme outt oof datge rehashed material.
Grezt read! I’ve savrd yopur sjte aand I’m including your RSSfeeds tto my
Google account.
p4jd2m
yr52e0
ige1ng