- 簡介:本文主要講解了在開發正常項目時,用于自動化部署生成目标檔案的Makefile。對其包含的主要文法進行了講解,最後給出了一個項目通用的Makefile模闆,以幫助大家了解。

1. Makefile 三要素
- 目标
- 依賴
- 指令
目标:依賴(檔案、其它目标)
<tab>指令1
<tab>指令2
<tab>指令3
target1:target2 target3
echo "target1"
target2:
echo "target2"
target3:
echo "target3"
輸出:
$ vim Makefile
$ make
echo "target2"
target2
echo "target3"
target3
echo "target1"
.PHONY:指定僞目标,即它指定的目标不是真正的目标,不管目前目錄下有沒有PHONY修飾的目标檔案,每次make時均會執行PHONY修飾的目标檔案下的指令行,一般用于修飾clean目标。
- 舉個例子:
$ cat -n Makefile1
1 clean:
2 rm -f foo
$ cat -n Makefile2
1 .PHONY: clean
2 clean:
3 rm
Makefile1和Makefile2的差别就是在Makefile2中定義了:
1 .PHONY: clean
- 直接Make看看
$ ls -l
total 8
-rw-r--r-- 1 huanli huanli 18 Jul 13 17:51 Makefile1
-rw-r--r-- 1 huanli huanli 32 Jul 13 17:51 Makefile2
$ make -f Makefile1 clean
rm -f foo
$ make -f Makefile2 clean
rm
- Makefile1和Makefile2的行為好像沒有差別,但建立一個檔案clean, 再make看看
$ touch clean
$ ls -l
total 8
-rw-r--r-- 1 huanli huanli 0 Jul 13 18:06 clean
-rw-r--r-- 1 huanli huanli 18 Jul 13 17:51 Makefile1
-rw-r--r-- 1 huanli huanli 32 Jul 13 17:51 Makefile2
$ make -f Makefile1 clean
make: 'clean' is up to date.
$ make -f Makefile2 clean
rm
- 差別來了,Makefile1拒絕了執行clean, 因為檔案clean存在。而Makefile2卻不理會檔案clean的存在,總是執行clean後面的規則。由此可見,.PHONY clean發揮了作用。
2. Makefile的變量、模式比對
2.1 系統變量
- CC:編譯器,如gcc
- AS:彙編器
- AR:打包程式
- MAKE:make
2.2 自定義變量
=
:延遲指派,當左值被引用(執行)的時候,才會根據右值的最新值對左值指派。
:=
:立即指派,立即根據右值的目前值,對左值進行指派。
?=
:空指派,隻有當左值此時為空時,才會根據右值目前值對左值進行指派。
+=
:追加指派,在左值的原值後面跟上(追加)右值的目前值。
make var=xxx
:在指令行中執行make指令的時候,由使用者對Makefile檔案中的var變量進行指派。
2.3 自動化變量
$<
:代表最左邊的依賴項
$@
:代表目标
$^
:代表所有的依賴項
2.4 模式比對
%
:比對任意個非空字元串(類似shell中的
*
),且在makefile語句中,
:
前後的兩個%代表的字元串是一樣的。是以可以使用如下語句表示将目前目錄下的所有c檔案編譯成.o的目标檔案:
%.o : %.c
$(CC) -c $< -o $@
但在make的規則中,.o檔案預設是由同名的.c檔案編譯來的,是以當需要某.o檔案時,make會自動編譯相應的.c檔案。上面的語句即使不寫,也是預設執行的!!!
3. Makefile的條件分支
- ifeq/ifneq
ARCH ?= x86
ifeq ($(ARCH), x86)
CC = gcc
else
CC = arm-linux-gnueabihf-gcc
endif
TARGET = my_target
OBJS = main.o func.o
$(TARGET) : $(OBJS)
$(CC) $^ -o $@
.PHONY:clean
clean:
rm $(TARGET) %.o
- ifneq
ifneq (var1, var2)
...
else
...
endif
4. Makefile中的常用函數
4.1 patsubst——模式替換
$(patsubst Pattern, Replacement, Text)
功能:搜尋Text中的單詞,将符合Pattern模式的單詞替換為Replacement。在Pattern模式中,可以使用通配符%代表任意個字元,但隻有第一個%具有通配左右,後面出現的将作為普通的%。如果需要中間某%具有通配作用,則可以将其之前的所有%用反斜杠進行轉義處理。
例如:
$(patsubst %.c, %.o, x.c.c bar.c)
執行後将會得到”x.c.o bar.o“。
當然,我們還有更簡單的寫法,同樣是達到上述目的,我們先定義一個變量·
sources = x.c.c bar.c
,然後執行
$(sources:.c=.o)
或者是
$(sources:%.c=%.o)
4.2 notdir
$(notdir 路徑名)
:傳回路徑名最後一個反斜杠之後的檔案名。
4.3 wildcard
$(wildcard Pattern)
:列出目前目錄下所有符合Pattern模式的檔案名。
注意:Pattern使用的是shell中的通配符,如?、*等
4.4 foreach
$(foreach Var,List, Text)
:它是一個循環語句,每一次取出字元串序列List中的一個單詞指派給Var,然後将Var代入到表達式Text中進行運算,得到的結果作為其中一個值。一個找出a、b、c、d4個目錄下的所有檔案指派給files變量的如下:
dirs := a b c d
files := $(foreach dir, $(dirs),$(wildcard $(dir)/*))
4.5 suffix
$(suffix 檔案名序列)
:傳回檔案名序列中各個檔案名點号之後的字尾字元串
4.6 重寫Makefile
環境:目前目錄下有module1和module2兩個檔案夾,裡面存放着若幹c檔案。
#根據ARCH變量的值,選擇合适的編譯器
ARCH ?= x86
ifeq($(ARCH),x86)
CC=gcc
else
CC=arm-linux-gnueabihf-gcc
endif
#目标檔案
TARGET=mp3
#生成檔案夾
BUILD_DIR=build
#源檔案檔案夾清單
SRC_DIR=module1 module2
#所有源檔案的路徑名清單
SOURCES=$(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))
#所有依賴檔案的路徑名清單
OBJS=$(patsubst %.c, $(BUILD_DIR)/%.o, $(nodir $(SOURCES)))
#編譯目标
$(BUILD_DIR)/$(TARGET) : $(OBJS)
$(CC) $^ -o $@
#建立生成檔案夾和中間檔案
$(BUILD_DIR)/%.o : %.c | create_build
$(CC) -c $< -o $@
#僞目标(清除生成檔案、建立生成檔案夾)
.PHONY:clean create_build
clean:
rm -r $(BUILD_DIR)
create_build:
mkdir -p $(BUILD_DIR)
4.7 頭檔案依賴
- 寫一個頭檔案,并将其添加到編譯器的頭檔案路徑中。
- 實時檢查頭檔案更新情況,一旦變化應重新編譯相關檔案。
- 執行個體
- 環境:main.c位于./module1檔案夾中,funcs.c位于./module2檔案夾中。其中main.c會調用到funcs.c中定義的函數func1()和func2()。
- 目标:編譯時,main.c能夠正确調用funcs檔案中的函數和全局變量。
- 解決方案:
- 目前目錄下建立./include檔案夾,并在裡面建立頭檔案funcs.h
#ifndf __FUNCS_H
#define __FUNCS_H
void func1(void);
void func1(void);
#define var "I am a varible"
#endif
- 更新funcs.c檔案,并在其頭部添加頭檔案包含語句
#include "funcs.h"
- 更新main.c檔案,并在其頭部添加頭檔案包含語句
#include "funcs.h"
- 修改Makefile檔案,添加頭檔案包含路徑
#根據ARCH變量的值,選擇合适的編譯器
ARCH ?= x86
ifeq($(ARCH),x86)
CC=gcc
else
CC=arm-linux-gnueabihf-gcc
endif
#目标檔案
TARGET=mp3
#生成檔案夾
BUILD_DIR=build
#源檔案檔案夾清單
SRC_DIR=module1 module2
#頭檔案路徑
INC_DIR=include
#編譯選項
CFLAGS=$(patsubst %, -I%, $(INC_DIR))
#頭檔案清單
INCLUDES=$(foreach dir, $(INC_DIR), $(wildcard $(dir)/*.h))
#所有源檔案的路徑名清單
SOURCES=$(foreach dir, $(SRC_DIR), $(wildcard $(dir)/*.c))
#所有依賴檔案的路徑名清單
OBJS=$(patsubst %.c, $(BUILD_DIR)/%.o, $(notdir $(SOURCES)))
#編譯目标
$(BUILD_DIR)/$(TARGET) : $(OBJS)
$(CC) $^ -o $@
#建立生成檔案夾和中間檔案
#此處在指令行中添加$(INCLUDES),可以確定頭檔案一有變化,就會重新編譯
$(BUILD_DIR)/%.o : %.c $(INCLUDES) | create_build
$(CC) -c $< -o $(CFLAGS) $@
#僞目标(清除生成檔案、建立生成檔案夾)
.PHONY:clean create_build
clean:
rm -r $(BUILD_DIR)
create_build:
mkdir -p $(BUILD_DIR)