聯盛德 HLK-W806 (十二): Makefile組織結構和編譯流程說明

目錄

Makefile基本概念

下面這些是在項目的Makefile中會用到的, 主要就說一下賦值和$方法

賦值=, :=, ?=, +=

Makefile中的賦值, 可以賦值一個列表, 例如
VAR = dir1 dir2 dir3

= 賦值

make會將整個Makefile展開後, 再決定變數的值, 也就是說變數的值會是整個 Makefile 中最後被指定的值

x = foo
y = $(x) bar
x = xyz

y的值會是 xyz bar ,而不是 foo bar

:= 賦值

表示變數的值等於 Makefile 執行到此處時的值, 而不是整個 Makefile 展開後的最終值

x := foo
y := $(x) bar
x := xyz

y的值將會是 foo bar ,而不是 xyz bar

:=賦值可以用來避免遞歸問題, 例如下面的賦值

CC = gcc
CC = ${CC}

all:
    @echo ${CC}

會報錯

$ make
Makefile:8: *** Recursive variable 'CC' references itself (eventually).  Stop.

如果改成:=賦值就沒問題了

CC := gcc
CC := ${CC}

all:
    @echo ${CC}

?= 賦值

  • 如果已經賦值, 就不變
  • 如果沒有被賦值過, 就賦予等號後面的值

+= 賦值

添加等號後面的值

Makefile 中$的用法

$在Makefile中是一種重要的符號

$(…) 或 ${…}的方法

$()${}是一樣的, 執行括弧內的內容, 類似於eval, 將整個$(…)替換為執行的結果字元串

直接執行

例如$(VAR = 4)表示執行shell命令VAR = 4, 因為無返回, 所以不會有任何操作

shell 執行shell命令

$(shell cc $(SDK_TOOLS)/wm_getver.c -Wall -O2 -o $(VER_TOOL)) 這個命令表示, 在shell下執行後面的命令

abspath 返回絕對路徑

PATH = $(abspath $(TOP_DIR)) 獲取絕對路徑

dir 返回目錄字元串

SUBDIRS ?= $(patsubst %/,%,$(dir $(wildcard */Makefile))), 獲取所有Makefile的目錄, 並去除結尾的斜桿

substr 字元串替換

TARGET ?= $(subst FROM, TO, TEXT), 這個命令會將字元串TEXT中的子串FROM變為TO後返回, 然後返回的值賦值給TARGET

patsubst 匹配替代

patsubst是patten substitude的縮寫,匹配替代的意思

OBJ = $(patsubst %.c, %.o, $(SRC)) , 在SRC中找到所有.c 結尾的文件,然後把所有的.c換成.o。

與使用OBJ = $(SRC:%.c=%.o)效果一樣.

wildcard 列出文件

$(wildcard pattern) 是用於用於匹配列出文件名的方法

  • SRCS := $(wildcard *.c)這個表達式中, 所有以.c結尾的文件名都會被賦值給變數SRCS.
  • CCFILES += $(wildcard src/*.cpp), 將匹配後面通配符的文件都列出, 並追加賦值給CCFILES

$(變數名:% = %) 替代

BINS := $(SRCS:%.c=%) 這種方式屬於替代賦值, 在這個表達式中, 如果變數 SRCS 的值為 ‘foo.c bar.c’, 變數 BINS 將被賦值 ‘foo bar’.

notdir 去除目錄資訊

SRC = $(notdir wildcard), 去除所有的目錄資訊,SRC里的文件名列表將只有文件名

foreach 循環處理

格式為 $(foreach var, list, express), 把參數list中的單詞逐一取出放到參數var所指定的變數中, 然後再執行express所包含的表達式. 每一次express會返回一個字元串, 循環過程中express的所返回的每個字元串會以空格分隔, 最後當整個循環結束時, express所返回的每個字元串所組成的整個字元串(以空格分隔)就是foreach函數的返回值.

所以: var是一個變數, list可以是一個表達式或者一個列表變數, 而express一般是一個命令表達式, 用於處理var.

例子一
在遞歸編譯中常用的 $(foreach d, $(SUBDIRS), $(MAKE) -C $(d);), 將SUBDIRS中收集到的目錄, 值依次執行$(MAKE) -C $(d).

例子二

names := a b c d
files := $(foreach n,$(names),$(n).o)

將names中的值依次加上.o, 再賦值給files, $(files)的值變為a.o b.o c.o d.o

WM-SDK-W806 的 Makefile 分析

Makefile 文件結構

程式碼地址

下面列出了 WM-SDK-W806 中, 與make相關的文件, 可以看到這是一個遞歸make的結構. 主要的文件都已經在結構中標出

│  Makefile                  # 主Makefile文件, make 執行入口
├─app
│  │  Makefile
│  ├─inc
│  └─src
│          Makefile
├─bin
│  └─build
│      └─W806
│          ├─lib
│          └─obj
├─demo
│     Makefile
│
├─platform
│  ├─arch
│  │  │  Makefile
│  │  └─xt804
│  │      │  Makefile
│  │      ├─bsp
│  │      │      Makefile
│  │      └─libc
│  │              Makefile
│  ├─component
│  │  │  Makefile
│  │  ├─auto_dl
│  │  │      Makefile
│  │  └─FreeRTOS
│  │      │  Makefile
│  │      ├─include
│  │      └─portable
│  │          │  Makefile
│  │          ├─MemMang
│  │          │      Makefile
│  │          └─xt804
│  │                  Makefile
│  └─drivers
│          Makefile
└─tools
    └─W806
        │  .config            # 由menuconfig生成的,被conf.mk包含的配置資訊
        │  conf.mk            # 全局make配置文件, 會被每一個Makefile頭部包含
        │  inc.mk             # 全局的include配置, 在conf.mk底部包含, 所以實際上也會被每一個Makefile包含
        │  mconfig.sh         # make menuconfig時調起的shell腳本
        │  rules.mk           # 全局的make目標文件, 會被每一個Makefile底部包含
        │  wconfig            # menuconfig的欄位配置文件
        ├─projects
        └─utilities

Make 的執行順序

執行make時,

  1. 先執行主Makefile,
  2. 主Makefile中依次包含conf.mk, inc.mk, rule.mk
  3. 行進到rule.mk, 在其中中執行指定的目標, 如果未指定, 則執行默認的all目標.

Make的規則流程 – RULE.MK

這裡重點分析rule.mk文件, 因為這裡定義了所有的規則, 以及對應的目標處理關係. 當執行默認的all目標時, 其規則定義為

all: .subdirs $(OBJS) $(OLIBS) $(OIMAGES) $(OBINS)

目標 .subdir

這一步實現了對子目錄的遞歸訪問

  1. 在all目標中, 第一個前置目標為.subdirs
  2. .subdirs目標的執行是@set -e; $(foreach d, $(SUBDIRS), $(MAKE) -C $(d);),
    • set -e使得執行的命令返回值非0時, make立即退出
    • $(foreach ...)的功能前面寫了, 在這裡就是遍歷SUBDIRS中收集到的子目錄列表, 對每一項依次執行make -C 子目錄
    • SUBDIRS的定義SUBDIRS ?= $(patsubst %/,%,$(dir $(wildcard */Makefile))) , 注意, 這裡只是列出當前目錄的下一級目錄中包含的Makefile, 如果是跨一級目錄的Makefile是不會包含進來的
    • SUBDIRS除了收集的子目錄, 還有一處從主Makefile添加的platform目錄下的三個路徑
  3. 到這一步, make進行到了下一層, 對下一層同樣進行前面的處理
  4. 當進行到最底層時, 已經沒有可執行的.subdirs目標, make會開始執行後面的目標$(OBJS)

目標: $(OBJS)

這一步實現了從C, CPP, S文件到對象文件的編譯. OBJS的定義如下, 收集了當前目錄下的.c, .cpp, .S文件, 產生.o的對象文件列表, 因此這裡會處理一系列的.o文件前置目標

OBJS := $(CSRCS:%.c=$(OBJODIR)/$(subdir_path)/%.o) \
        $(CPPSRCS:%.cpp=$(OBJODIR)/$(subdir_path)/%.o) \
        $(ASRCS:%.S=$(OBJODIR)/$(subdir_path)/%.o)

對於.o文件的規則是如下的三條, 這是編譯時實際執行的命令

$(OBJODIR)/$(subdir_path)/%.o: %.c
	@mkdir -p $(OBJODIR)/$(subdir_path)
	$(CC) $(if $(findstring $<,$(DSRCS)),$(DFLAGS),$(CFLAGS)) $(COPTS_$(*F)) $(INCLUDES) $(CMACRO) -c "$<" -o "$@" -MMD -MD -MF "$(@:$(OBJODIR)/$(subdir_path)/%.o=$(OBJODIR)/$(subdir_path)/%.o.d)" -MT "$(@)"

$(OBJODIR)/$(subdir_path)/%.o: %.cpp
	@mkdir -p $(OBJODIR)/$(subdir_path)
	$(CXX) $(if $(findstring $<,$(DSRCS)),$(DFLAGS),$(CXXFLAGS)) $(COPTS_$(*F)) $(INCLUDES) $(CMACRO) -c "$<" -o "$@" -MMD -MD -MF "$(@:$(OBJODIR)/$(subdir_path)/%.o=$(OBJODIR)/$(subdir_path)/%.o.d)" -MT "$(@)"

$(OBJODIR)/$(subdir_path)/%.o: %.S
	@mkdir -p $(OBJODIR)/$(subdir_path)
	$(ASM) $(ASMFLAGS) $(INCLUDES) $(CMACRO) -c "$<" -o "$@"

在目標 \((OBJS)完成後, 開始執行目標\)(OLIBS)

目標:$(OLIBS)

這一步是將對象文件打包成庫, 來源定義為

OLIBS := $(GEN_LIBS:%=$(LIBODIR)/%)

其中GEN_LIBS定義在每一層的Makefile中, 是一個層層打包的過程, 為生成庫文件, 設計了兩套規則

  1. 在rule.mk中定義了名為 ShortcutRule 的規則宏用於展開
define ShortcutRule
$(1): .subdirs $(2)/$(1)
endef

通過以下語句展開為每個庫的目標規則

$(foreach lib,$(GEN_LIBS),$(eval $(call ShortcutRule,$(lib),$(LIBODIR))))
  1. 在rule.mk中定義了名為 MakeLibrary 的規則宏用於展開
define MakeLibrary
DEP_LIBS_$(1) = $$(foreach lib,$$(filter %$(LIB_EXT),$$(COMPONENTS_$(1))),$$(LIBODIR)/$$(notdir $$(lib)))
DEP_OBJS_$(1) = $$(foreach obj,$$(filter %.o,$$(COMPONENTS_$(1))),$$(OBJODIR)/$$(notdir $$(obj)))
$$(LIBODIR)/$(1)$(LIB_EXT): $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1)) $$(DEPENDS_$(1))
	@mkdir -p $$(LIBODIR)
	$$(if $$(filter %$(LIB_EXT),$$?),@mkdir -p $$(OBJODIR)/_$(1))
	$$(if $$(filter %$(LIB_EXT),$$?),@cd $$(OBJODIR)/_$(1); $$(foreach lib,$$(filter %$(LIB_EXT),$$?),$$(AR) $(ARFLAGS_2) $$(UP_EXTRACT_DIR)/$$(notdir $$(lib));))
	$$(AR) $(ARFLAGS) $$@ $$(filter %.o,$$?) $$(if $$(filter %$(LIB_EXT),$$?),$$(OBJODIR)/_$(1)/*.o)
	$$(if $$(filter %$(LIB_EXT),$$?),@$$(RM) -r $$(OBJODIR)/_$(1))
endef

通過以下語句展開為各個庫的目標規則, 這些規則將負責處理 ShortcutRule 的前置目標

$(foreach lib,$(GEN_LIBS),$(eval $(call MakeLibrary,$(basename $(lib)))))

這就是$(OLIBS)目標的機制

目標:$(OBINS)

這一步會打包為二進位image, 來源定義為

OBINS := $(GEN_BINS:%=$(BINODIR)/%)

其中GEN_BINS在主Makefile中定義, 就是W806.bin

GEN_BINS = $(TARGET).bin

首先也是通過 ShortcutRule 展開的規則集, 用於處理目標 $(OBINS)

$(foreach bin,$(GEN_BINS),$(eval $(call ShortcutRule,$(bin),$(BINODIR))))

然後通過下面的規則, 去處理 \((ODIR)/\)(TARGET)/bin/W806.bin目標

$(BINODIR)/%.bin: $(IMAGEODIR)/%.elf
	@mkdir -p $(FIRMWAREDIR)
	@mkdir -p $(FIRMWAREDIR)/$(TARGET)
	$(OBJCOPY) -O binary $(IMAGEODIR)/$(TARGET).elf $(FIRMWAREDIR)/$(TARGET)/$(TARGET).bin

再通過 ShortcutRule 展開的規則集, 處理目標 $(IMAGEODIR)/%.elf

$(foreach image,$(GEN_IMAGES),$(eval $(call ShortcutRule,$(image),$(IMAGEODIR))))

再通過 MakeImage 展開的規則集, 處理目標 $$(IMAGEODIR)/W806.elf

define MakeImage
DEP_LIBS_$(1) = $$(foreach lib,$$(filter %$(LIB_EXT),$$(COMPONENTS_$(1))),$$(LIBODIR)/$$(notdir $$(lib)))
DEP_OBJS_$(1) = $$(foreach obj,$$(filter %.o,$$(COMPONENTS_$(1))),$$(OBJODIR)/$$(notdir $$(obj)))
$$(IMAGEODIR)/$(1).elf: $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1)) $$(DEPENDS_$(1))
	@mkdir -p $$(IMAGEODIR)
	$(LINK) -Wl,--gc-sections -Wl,-zmax-page-size=1024 -Wl,--whole-archive $$(OBJS) $$(DEP_OBJS_$(1)) $$(DEP_LIBS_$(1)) $$(if $$(LINKFLAGS_$(1)),$$(LINKFLAGS_$(1))) -Wl,--no-whole-archive $(LINKFLAGS) $(MAP) -o $$@
endef

以上就是WM-SDK-W806 的 Makefile 分析. 這裡只說明了編譯的主體流程, 在配置項上也有不少內容, 如果有人感興趣, 可以另外開篇介紹.