天天看點

【linux】Makefile簡要知識+一個通用Makefile

目錄

  • Makefile
    • Makefile規則與示例
      • 為什麼需要Makefile
      • Makefile樣式
      • 先介紹Makefile的兩個函數
      • 完善Makefile
      • 通用Makefile的使用
      • 通用的Makefile解釋
        • 零星知識點
          • make指令的使用
          • 即時變量與臨時變量
          • 變量的導出(export)
          • Makefile中的shell
          • Makefile中放置第一個指令
          • 假想目标
          • 小知識
          • 常用函數
        • *通用Makefile設計思想
  • 一個通用Makefile
    • 本程式的Makefile分為3類:
    • 源碼(注釋版)
      • Makefile.build
    • 使用說明
      • 1. 各級子目錄的Makefile
        • 注意
      • 2. 頂層目錄的Makefile
      • 3. 頂層目錄的Makefile.build **
      • 使用提示
        • Makefile附件
          • 一些符号
  • 參考

 meke指令一般用于編譯程式,而make指令都依賴于 Makefile 檔案。

 最簡單的Makefile如下:

hello:  hello.c
   gcc -o hello hello.c
clean:
   rm -f hello
           

 注:縮進使用 tab 鍵 , 因為Makefile 會為每個以 tab 鍵開頭的指令建立一個 shell 去執行。

 具體規則參考 《GUN Make 使用手冊》

以下為粗略筆記

 提高編譯效率。

 一個簡單的Makefile檔案包含一系列"規則":

目标...:依賴...
<tab>指令
           
  • 指令被執行的兩個簡單條件:
    • 目标檔案還沒有生成
    • 依賴檔案比目标檔案新

  1. $(foreach var,list,text)

     for each var in list,change it to text。

     對list中的每一個元素,取出指派給var,然後把var改為text所描述的形式。

    例子:

objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d)  // 最終 dep_files := .a.o.d .b.o.d
           
  1. $(wildcard pattern)

      把存在的 pattern 檔案列出來

src_files := $(wildcard *.c)
           

  1. 先來一個簡單粗暴、效率低的:
test: main.c hello.c hello.h
        gcc -o test main.c hello.c
           
  1. 再來一個Makefile,效率高、精煉,支援自動檢測頭檔案
objs := main.o hello.o

test : $(objs)
    gcc -o test $^

# 需要判斷是否存在依賴檔案
# .main.o.d   .hello.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))

# 把依賴檔案包含進來
ifneq ($(dep_files),)
include $(dep_files)
endif

%.o : %.c
    gcc -Wp,-MD,[email protected] -c -o $@ $<

clean:
    rm *.o test -f

distclean:
    rm $(dep_files) *.o test -f
           

了解自動化變量。

參考linux核心的Makefile來編寫一個通用的Makefile,特點:

  1. 支援多個目錄、多層目錄、多個檔案;
  2. 支援給所有檔案設定編譯選項;
  3. 支援給目錄設定編譯選項;
  4. 支援給某個檔案單獨設定編譯選項;
  5. 簡單、好用。

 執行make指令時,它會去目前目錄下查找名為Makefile的檔案,并根據它的隻是去執行操作,生成第一個目标。

 也可以用 -f 選項指定檔案,如:

make -f Makefile.build
           

指定檔案的名字随意定義。

 可以使用 -C 指定目錄,切換到其他目錄去,如:

make   -C   a/   abc.def
           

 可以指定目标,不再預設生成第一個目錄,如:

make   -C   a/   abc.def   other_target
           

 變量定義文法:

形式 說明
A = xxx 延時變量
B ?= xxx 延時變量,隻有第一次定義時指派才成功,若曾被定義過,則此指派無效。
C := xxx 立即變量
D += yyy

如果D在前面是延時變量,那麼現在它還是延時變量

如果D在前面是立即變量,那麼它現在還是立即變量

延時變量:

 使用時才确定該值。

如:

A = $@
tets:
    @echo $A
           

輸出 A 的值為 test。

即時變量:

 定義時立即确定該值。

A := $@
tets:
    @echo $A
           

輸出 A 的值為 空。

 export 是供給子目錄的 Makefile 使用的(即 sub-make),同一級的makefile是通路不到的。

 可以通過makefile中的内置變量MAKELEVEL可以檢視目前的makefile的level。

 Makefile中可以使用shell指令,如:

TOPDIR := $(shell_pwd)
           

 執行目标時,如果不指定目标,則預設執行第一個目标。

是以,第一個目标的位置很重要。有時候不太友善把第一個目标完整地放在檔案的前面,這時可以在檔案的前面直接放置目标,在後面再完善它的依賴和指令。比如:

First_target:      // 這句話放在前面
........               // 其他代碼,比如include其它檔案得到後的 xxx 變量
First_target : $(xxx) $(xxx)
    command
           

Makefile中可能會有這樣的目标:

clean:
    rm     -f     $(shell find -name "*.o")
    rm    -f      $(TARGET)
           

注:如果目前目錄下更好有個名為“clean”的檔案,那麼執行“make clean”時就不會執行makefile中目标clean的内容,是以要把clean設定為假想目标即可:

.PHONY    :   clean
           

PHONY 是 phoney,即僞造的意思。PHONY後面的target是僞造的target,不是真實存在的檔案。同時,注意make指令中後面的target預設是檔案。

  • $(foreach var, list, text)

    for each var in list,change it to text.

    直接上例子:

objs := a.o b.o
dep_files := $(foreach f, $(objs), .$(f).d)   // dep_files 的最終結果為“**.a.o.d .b.o.d**”
           
  • 列出 pattern 存在的檔案。
src_files = $(wildcard *.c) // src_files的值為目前目錄下所有 .c 檔案。
           
  • $(filter pattern..., text)

    把text中符合pattern格式的内容留下來。

obj-y := a.o b.o c/ d/
DIR := $(filter %/, $(obj-y))   //結果為:c/ d/
           
  • $(filter-out pattern..., text)

    把text中符合pattern格式的内容删除掉。

obj-y := a.o b.o c/ d/
DIR := $(filter-out %/, $(obj-y))   // 結果為:a.o b.o
           
  • $(patsubst pattern, replacement, text)

    尋找text中符合pattern格式的内容,用replacement代替他們。

subdir-y := a.o b.o c/ d/
subdir := $(patsubst %/, %, $(obj-y))   // 結果為:c d
           
  1. 在Makefile檔案中确定要編譯的檔案、目錄,比如:
obj-y += main.o
obj-y += a/
           

Makefile 檔案總是被 Makefile.build 包含的。

  1. 在 Makefile.build 中設定編譯規則,有 3 條編譯規則:
    1. 怎麼編譯子目錄?
      1. 進入子目錄編譯即可:
$(subdir-y):
    make -C $@ -f $(TOPDIR)/Makefile.build
           
2. 如何編譯目前目錄中的目标檔案?
           
%.o : %.c
    $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
           
3. 目前目錄下的 **.o** 和子目錄下的 **built-in.o** 要打包起來:
           
$(TARGET) : built-in.o
    $(CC) $(LDFLAGS) -o $(TARGET) built-in.o
           

思想總結:

  1. Makefile檔案确定要編譯的檔案和目錄
  2. Makefile.build檔案包含規則
  3. 每個需要編譯的子目錄都放個Makefile檔案,把需要編譯的檔案編譯成built-in.o
  4. 再把所有子目錄的built-in.o連結到頂層的built-in.o
  5. 最後把頂層built-in.o連結到APP

最好重新分析一下通用Makefile

  1. 頂層目錄的Makefile
  2. 頂層目錄的Makefile.build
  3. 各級子目錄的Makefile

CROSS_COMPILE = # 交叉編譯工具頭,如:arm-linux-gnueabihf-
AS      = $(CROSS_COMPILE)as # 把彙編檔案生成目标檔案
LD      = $(CROSS_COMPILE)ld # 連結器,為前面生成的目标代碼配置設定位址空間,将多個目标檔案連結成一個庫或者一個可執行檔案
CC      = $(CROSS_COMPILE)gcc # 編譯器,對 C 源檔案進行編譯處理,生成彙編檔案
CPP    = $(CC) -E
AR      = $(CROSS_COMPILE)ar # 打包器,用于庫操作,可以通過該工具從一個庫中删除或則增加目标代碼子產品
NM     = $(CROSS_COMPILE)nm # 檢視靜态庫檔案中的符号表

STRIP       = $(CROSS_COMPILE)strip # 以最終生成的可執行檔案或者庫檔案作為輸入,然後消除掉其中的源碼
OBJCOPY  = $(CROSS_COMPILE)objcopy # 複制一個目标檔案的内容到另一個檔案中,可用于不同源檔案之間的格式轉換
OBJDUMP = $(CROSS_COMPILE)objdump # 檢視靜态庫或則動态庫的簽名方法

# 共享到sub-Makefile
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

# -Wall : 允許發出 GCC 提供的所有有用的報警資訊
# -O2 : “-On”優化等級
# -g : 在可執行程式中包含标準調試資訊
# -I : 指定頭檔案路徑(可多個)
CFLAGS := -Wall -O2 -g 
CFLAGS += -I $(shell pwd)/include

# LDFLAGS是告訴連結器從哪裡尋找庫檔案,這在本Makefile是連結最後應用程式時的連結選項。
LDFLAGS := 

# 共享到sub-Makefile
export CFLAGS LDFLAGS

# 頂層路徑
TOPDIR := $(shell pwd)
export TOPDIR

# 最終目标
TARGET := test

# 本次整個編譯需要源 檔案 和 目錄
# 這裡的“obj-y”是自己定義的一個格式,和“STRIP”這些一樣,*但是 一般核心會搜集 ”obj-”的變量*
obj-y += main.o # 需要把目前目錄下的 main.c 編程序式裡
obj-y += sub.o # 需要把目前目錄下的 sub.c 編程序式裡
obj-y += subdir/ # 需要進入 subdir 這個子目錄去尋找檔案來編程序式裡,具體是哪些檔案,由 subdir 目錄下的 Makefile 決定。
#obj-y += $(patsubst %.c,%.o,$(shell ls *.c))

# 第一個目标
all : start_recursive_build $(TARGET)
    @echo $(TARGET) has been built !
    
# 處理第一個依賴,**轉到 Makefile.build 執行**
start_recursive_build:
    make -C ./ -f $(TOPDIR)/Makefile.build
    
# 處理最終目标,把前期處理得出的 built-in.o 用上
$(TARGET) : built-in.o
    $(CC) -o $(TARGET) built-in.o $(LDFLAGS)
    
# 清理
clean:
    rm -f $(shell find -name "*.o")
    rm -f $(TARGET)
    
# 徹底清理
distclean:
    rm -f $(shell find -name "*.o")
    rm -f $(shell find -name "*.d")
    rm -f $(TARGET)
           

注意,include 指令,相對路徑為 執行本 Makefile.build 的路徑

# 僞目标
PHONY := __build
__build:

# 清空需要的變量
obj-y :=
subdir-y :=
EXTRA_CFLAGS :=

# 包含同級目錄Makefile
# 這裡要注意,相對路徑為 執行本 Makefile.build 的路徑
include Makefile

# 擷取目前 Makefile 需要編譯的子目錄的目錄名
# obj-y := a.o b.o c/ d/
# $(filter %/, $(obj-y))   : c/ d/
# __subdir-y  : c d
# subdir-y    : c d
__subdir-y	:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y	+= $(__subdir-y)

# 把子目錄的目标定為以下注釋
# built-in.o d/built-in.o
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)

# 擷取目前目錄需要編程序式的檔案名作為,并寫為目标
# a.o b.o
cur_objs := $(filter-out %/, $(obj-y))
# 使修改頭檔案 .h 後,重新make後可以重新編譯(重要)
dep_files := $(foreach f,$(cur_objs),.$(f).d)
# 列出存在的檔案
dep_files := $(wildcard $(dep_files))

ifneq ($(dep_files),)
  include $(dep_files)
endif


PHONY += $(subdir-y)

# 第一個目标
__build : $(subdir-y) built-in.o

# 優先編譯 子目錄的内容
$(subdir-y):
	make -C $@ -f $(TOPDIR)/Makefile.build

# 連結成 目标
built-in.o : $(cur_objs) $(subdir_objs)
	$(LD) -r -o $@ $^

dep_file = [email protected]

# 生成 cur_objs 目标
%.o : %.c
	$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<
	
.PHONY : $(PHONY)
           

  • 本程式的Makefile分為3類:
    1. 頂層目錄的 Makefile
    2. 頂層目錄的 Makefile.build
    3. 各級子目錄的 Makefile

  • 參考:
# 可以不添加下面兩個變量
EXTRA_CFLAGS  := 
CFLAGS_test.o := 

obj-y += test.o
obj-y += subdir/
           
  • obj-y += file.o 表示把目前目錄下的file.c編程序式裡,
  • obj-y += subdir/ 表示要進入subdir這個子目錄下去尋找檔案來編程序式裡,是哪些檔案由subdir目錄下的Makefile決定。
  • EXTRA_CFLAGS, 它給目前目錄下的所有檔案(不含其下的子目錄)設定額外的編譯選項, 可以不設定
  • CFLAGS_xxx.o, 它給目前目錄下的xxx.c設定它自己的編譯選項, 可以不設定
  1. "subdir/"中的斜杠"/"不可省略
  2. 頂層Makefile中的CFLAGS在編譯任意一個.c檔案時都會使用
  3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者組成xxx.c的編譯選項

主要作用:

  • 整個工程的參數初期定義
    • 架構
    • 工具鍊
    • 編譯工具
    • 編譯參數
    • 需要導出的變量
    • 等等
  • 定義最終目标
  • 跳轉到 Makefile.build

注意:該檔案雖然放在頂層,但是也是提供給各級子目錄使用的。

主要功能:

  • 把某個目錄及它的所有子目錄中、需要編程序式去的檔案都編譯出來,把各個subdir/built-in.o和目前目錄的目标 .o 合并打包為目前目錄的built-in.o 。

  • 執行"make"來編譯,執行 make clean 來清除,執行 make distclean 來徹底清除。
符号
$@ 表示規則中的目标檔案集
$% 當目标為函數庫的時候,則表示規則中的目标成員名。反之為空。如一個目标為"foo.a(bar.o)",那麼,"$%"就是"bar.o",以空格分隔開。
$< 依賴檔案集合中的第一個檔案,如果依賴檔案以"%"形式出現,則表示符合模式的一系列檔案集合
$? 所有比目标新的依賴集合,以空格分隔開。
$^ 所有依賴檔案集合,以空格分隔開。如果依賴有相同,則取其一。
$+ 和 "$^"類同,但是不會把相同的删除掉。
$* 這個變量表示目标模式中 "%"及其之前的部分,如果目标是 test/a.test.c,目标模式為 a.%.c, 那麼 "$* " 就是 test/a.test。

  • 以上筆記為學習和整理韋東山老師資料而來的
  • 李柱明部落格:https://www.cnblogs.com/lizhuming/
  • 本文連結:https://www.cnblogs.com/lizhuming/p/13956017.html