天天看點

Makefile文法及通用模闆

  • 簡介:本文主要講解了在開發正常項目時,用于自動化部署生成目标檔案的Makefile。對其包含的主要文法進行了講解,最後給出了一個項目通用的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)      

繼續閱讀