開源學村

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 有些差異:

uboot 主 Makefile 分析


本文來自 CDUT LUG 社區作者蘇森的原創投稿。
轉載請註明原文出處,否則必究相關責任。
原文鏈接:https://blog.cdutlug.org/2017/11/makefile_introduction/
本文鏈接:https://linuxstory.org/makefile_introduction/

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
1
不太好
0
感覺很糟
0
Linux 愛好與學習者,雖然唱歌一般但業餘喜歡搗鼓點音樂相關的一些東西,惰性與勤奮並存的糾結體~

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:開源學村

    開源學村

    SoCal Linux博覽會回歸20周年

    現在已經是支持和推廣FOSS社區的第20個年頭了,SCaLE 20x - 南加州Linux博覽會將於2023年3月9日至12日在帕薩迪納會議中心舉行。 這個為期4天的年度活動彙集了充滿活力的開源用戶社 […]
    開源學村

    如何在Linux上殺死殭屍進程

    消滅殭屍進程! 也稱為「defunct」或「dead」進程 - 簡單來說,殭屍進程是指已經結束,但仍存在於系統進程表中的進程。理想情況下,一旦進程完成其工作/執行,它應該從進程表中清除。但由於某些原因 […]
    開源學村

    2022年,從學習Rust開始

    Rust作為一個新語言,已經連續五年(2016,2017,2018,2019,2020)在Stack Overflow開發者調查的「最受喜愛編程語言」。Rust是一個值得學習的編程語言,它對安全的專註,會幫助你許多。學習Rust 從這本小小的Rust Cheat Sheet出發,了解Rust語言的基本操作。
    開源學村

    使用 Linux 命令行解決Wordle 問題

    你可以使用 Linux 的 grep 命令和 fgrep 命令來解決Wordle 問題(進行篩選排除),grep 命令使用正則表達式進行搜索,fgrep 命令也像 grep 一樣搜索文本,但不使用正則表達式,grep 和 fgrep 命令在掃描單詞列表時提供了極大的靈活性。