聯盛德 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

How Patterns Match 模式匹配的工作機制

$(BUILD_DIR)/%.o: %.cxx這樣的 目標:前提 定義, 都是屬於模式匹配, 模式匹配僅在目標模式匹配了文件名, 並且此規則所有的前提都存在或可以構建的情況下才生效. A pattern rule can be used to build a given file only if there is a target pattern that matches the file name, and all prerequisites in that rule either exist or can be built.

如果有多個規則都滿足上面的條件, 則會有一些優先級的判斷. 例如文件存在的就比需要用其它規則滿足的優先級高. The rules you write take precedence over those that are built in. Note however, that a rule whose prerequisites actually exist or are mentioned always takes priority over a rule with prerequisites that must be made by chaining other implicit rules.

這時候還是有這樣的可能性, 多個規則都滿足這個條件, 這時候 make 會選擇最短的匹配(匹配最精確的那個). 如果多個規則都是最短, 則選第一個. It is possible that more than one pattern rule will meet these criteria. In that case, make will choose the rule with the shortest stem (that is, the pattern that matches most specifically). If more than one pattern rule has the shortest stem, make will choose the first one found in the makefile.

This algorithm results in more specific rules being preferred over more generic ones; for example:

%.o: %.c
  $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
%.o : %.f
  $(COMPILE.F) $(OUTPUT_OPTION) $<
lib/%.o: lib/%.c
  $(CC) -fPIC -c $(CFLAGS) $(CPPFLAGS) $< -o $@

如果給定上面這些規則要構建 bar.o, 並且文件 bar.c 和 bar.f 存在, make 就會選擇第一條規則, 如果 bar.c 不存在, 就會選擇選擇第二條規則. 如果要構建的是 lib/bar.o, 並且 lib/bar.c 和 lib/bar.f 都存在, 那么就會使用第三條規則, 因為這條規則匹配是最精確的. 但是如果 lib/bar.c 文件不存在, 那么就會選用第二條規則.

新增lib時的Makefile編寫

以在component中添加FatFs為例, 可以參看這個提交 feat: add fatfs lib 中的修改.

確定目錄結構

對於FatFs, 只有一個目錄FatFs, 不區分 inc 和 src, 放置的路徑是 platform/component/FatFs

將頭文件路徑加入 tools/W806/inc.mk

因為頭文件所在的目錄是platform/component/FatFs, 所以直接將這個目錄加到inc.mk
加入的內容為

INCLUDES += -I $(TOP_DIR)/platform/component/FatFs

如果這個lib包含多個頭文件目錄, 需要將這些目錄分別加入.

添加lib的Makefile

因為只有一級目錄, 所以添加一個Makefile就可以, 將這個lib命名為libfatfs, 於是Makefile的內容為

TOP_DIR = ../../..
sinclude $(TOP_DIR)/tools/W806/conf.mk

ifndef PDIR
GEN_LIBS = libfatfs$(LIB_EXT)
endif

#DEFINES +=

sinclude $(TOP_DIR)/tools/W806/rules.mk
INCLUDES := $(INCLUDES) -I $(PDIR)include
PDIR := ../$(PDIR)
sinclude $(PDIR)Makefile

以上內容可以直接復制使用, 只需要

  1. 修改 GEN_LIBS = libfatfs$(LIB_EXT) 這句
  2. 如果還有子目錄, 則還需要添加 COMPONENTS_libfatfs, 本例沒有所以不需要添加

將這個lib添加到上一級的Mikefile

上一級的路徑是/platform/component, 在Makefile的COMPONENTS_libwmcomponent中添加libfatfs, 變成

COMPONENTS_libwmcomponent = FreeRTOS/libwmrtos$(LIB_EXT) \
                            FatFs/libfatfs$(LIB_EXT) \
                            auto_dl/libautodl$(LIB_EXT) \
                            ascii_fonts/libasciifonts$(LIB_EXT)

這樣就可以了. 如果添加的是頂級lib, 沒有更高一級的lib了, 那么需要將這個lib加入到rules.mk中.

在 app 目錄下添加用戶自定義的lib, 和上面的方法也是一樣的

結束

以上就是WM-SDK-W806 的 Makefile 分析. 這里只說明了編譯的主體流程, 有問題請留言


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM