MakeFile分析
2026/5/20大约 9 分钟
MakeFile分析
以具体代码为例:
案例1
ifneq ($(MKENV_INCLUDED),1)
include mkenv.mk
endif
subdirs-y :=
include apps.mk
.PHONY: all clean distclean
all: $(subdirs-y)
@echo "Make Applications done."
ifneq ($(strip $(subdirs-y)),)
.PHONY: $(subdirs-y)
$(subdirs-y):
@echo "[BUILD] applications $@"
@$(MAKE) -C $@ all || exit $?;
endif
clean:
@if [ -n "$(subdirs-y)" ]; then \
for dir in $(subdirs-y); do \
echo "[CLEAN] applications $$dir"; \
$(MAKE) -C $$dir clean || exit $?; \
done; \
fi
distclean: clean
@if [ -n "$(subdirs-y)" ]; then \
for dir in $(subdirs-y); do \
echo "[DISTCLEAN] applications $$dir"; \
$(MAKE) -C $$dir distclean || exit $?; \
done; \
fi
.PHONY: gen_kconfig
gen_kconfig:
@echo "# Dynamic generate by Makefile, DO NOT Modify it.\n" > $(SDK_BUILD_DIR)/Kconfig.app; \
for dir in $(shell find $(SDK_APPS_SRC_DIR) -maxdepth 1 -mindepth 1 -type d); do \
echo "source \"$$dir/Kconfig\"" >> $(SDK_BUILD_DIR)/Kconfig.app; \
done; \ifneq ($(MKENV_INCLUDED),1)用于判断变量MKENV_INCLUDED是否为,如果为1就include mkenv.mk,防止多个文件编译重复包含mkenv.mk
定义变量subdirs-y,(-y是linux命名习惯,表示被包含编译的子目录),
subdirs-y :=
include apps.mkapps.mk的内容如下:
-include $(SDK_SRC_ROOT_DIR)/.config
subdirs-$(CONFIG_APP_ENABLE_HELLOWORLD) += helloworld
subdirs-$(CONFIG_APP_ENABLE_POKEMON_DETECT) += pokemon_detect案例2:
# Include base configuration
include ../mkenv.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/toolchain_riscv64_musl.mk
# Build configuration
DIR := $(shell basename `pwd`)
BUILD := $(SDK_APPS_BUILD_DIR)/$(DIR)
BIN := $(SDK_APPS_IMAGE_DIR)/$(DIR)
# Source files handling
SRC_DIRS := . screens
SRCS := $(shell find $(SRC_DIRS) -maxdepth 1 -type f \( -name "*.c" -o -name "*.cpp" -o -name "*.cc" \))
OBJS := $(addprefix $(BUILD)/, $(addsuffix .o, $(basename $(notdir $(SRCS)))))
DEPS := $(OBJS:.o=.d)
# Directory creation logic - Captured and sorted
OBJ_DIRS := $(sort $(dir $(OBJS) $(BIN)))
# Library configuration
include $(SDK_RTSMART_SRC_DIR)/libs/mk/liblvgl.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/libmpp.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/libopencv.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/libnncase.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/librtsmart_hal.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/lib3rdparty.mk
# Compiler flags
C_CXX_FLAGS := -fopenmp -march=rv64imafdcv -mabi=lp64d -mcmodel=medany -Os
C_CXX_FLAGS += -I. $(LIB_CFLAGS) -I$(SDK_SRC_ROOT_DIR)/include -include "generated/autoconf.h"
C_CXX_FLAGS += -DLV_LVGL_H_INCLUDE_SIMPLE
C_CXX_FLAGS += -Wall -Wextra -ffunction-sections -fdata-sections
CFLAGS := $(C_CXX_FLAGS) -std=gnu99
CXX_FLAGS := $(C_CXX_FLAGS)
# Linker flags
LDFLAGS := -T $(SDK_RTSMART_SRC_DIR)/libs/mk/link.lds --static -Wl,--gc-sections
LDFLAGS += $(LIB_LDFLAGS)
# Phony targets
.PHONY: all clean distclean
all: $(BIN)
@$(ECHO) [BUILD] applications $(DIR) done.
.PHONY: .FORCE
.FORCE:
# Rule to link the final ELF - Order-only dependency on directory
$(BIN): $(OBJS) .FORCE | $(OBJ_DIRS)
@echo "[LD] $@"
$(Q)$(CXX) $(CFLAGS) $(OBJS) $(LDFLAGS) -o $@
$(Q)$(STRIP) $@
# Rule for C objects - Order-only dependency on directory
vpath %.c $(SRC_DIRS)
$(BUILD)/%.o: %.c | $(OBJ_DIRS)
@echo "[CC] $@"
$(Q)$(CC) $(CFLAGS) -MD -MP -MF $(@:.o=.d) -c $< -o $@
# Rule for C++ objects - Order-only dependency on directory
vpath %.cc $(SRC_DIRS)
$(BUILD)/%.o: %.cc | $(OBJ_DIRS)
@echo "[CXX] $@"
$(Q)$(CXX) $(CXX_FLAGS) -MD -MP -MF $(@:.o=.d) -c $< -o $@
vpath %.cpp $(SRC_DIRS)
$(BUILD)/%.o: %.cpp | $(OBJ_DIRS)
@echo "[CXX] $@"
$(Q)$(CXX) $(CXX_FLAGS) -MD -MP -MF $(@:.o=.d) -c $< -o $@
# Actual directory creation rule
$(OBJ_DIRS):
$(Q)mkdir -p $@
clean:
@rm -rf $(BIN)
distclean: clean
@rm -rf $(BUILD)
-include $(DEPS)讲解:
# Include base configuration
include ../mkenv.mk
include $(SDK_RTSMART_SRC_DIR)/libs/mk/toolchain_riscv64_musl.mkinclude 用于导入文件,跟c语言用法一样,include ../mkenv.mk相当于将mkenv.mk的文件内容放在当前位置
mkenv.mk文件内容如下:
ifneq ($(MKENV_INCLUDED),1)
export SDK_SRC_ROOT_DIR := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))/../../)
endif
include $(SDK_SRC_ROOT_DIR)/tools/mkenv.mk
include $(SDK_SRC_ROOT_DIR)/.config
export SDK_APPS_IMAGE_DIR := $(SDK_BUILD_IMAGES_DIR)/sdcard/app/
export MKENV_INCLUDED_APPLICATIONS=1上段代码中:
ifneq ($(MKENV_INCLUDED),1)
...
endif用于防止文件被重复包含,export SDK_SRC_ROOT_DIR := $(realpath $(dir $(realpath $(lastword $(MAKEFILE_LIST))))/../../)这是 Makefile 中非常经典且高级的“获取项目绝对根路径”的写法。为了看懂它,我们需要从内向外像剥洋葱一样拆解:
$(MAKEFILE_LIST):这是 Make 的内置变量,记录了当前已经加载的所有 Makefile 的名字列表,现在有MakeFile ../mkenv.mk。lastword:取这个列表的最后一个单词。因为系统刚刚加载了当前的../mkenv.mk,所以它拿到的就是当前这个文件自身的名字(包括相对路径)。realpath(内层):把刚才拿到的名字转换成绝对路径(顺便会解析掉软链接)。比如变成了/home/fs/workspace/K230/rtos/src/applications/mkenv.mk。dir:抽取出这个文件所在的目录路径。结果变成了/home/fs/workspace/K230/rtos/src/applications/../../:在这个目录基础上往上退两层。变成了 `/home/fs/workspace/K230/rtos/../../realpath(外层):最后再进行一次绝对路径规范化,把..这种相对符号抹掉,最终得到干净的 SDK 根目录:/home/fs/workspace/K230/rtos。
export:把计算出来的SDK_SRC_ROOT_DIR声明为环境变量,这样后续所有被调用的子 Makefile 或 shell 脚本都能直接使用这个变量,再也不用猜相对路径了。
接下来include $(SDK_SRC_ROOT_DIR)/tools/mkenv.mk也就是包含/home/fs/workspace/K230/rtos/tools/mkenv.mk
export SDK_TOOLCHAIN_DIR?=$(shell echo $$HOME)/.kendryte/k230_toolchains
export SDK_TOOLS_DIR=$(SDK_SRC_ROOT_DIR)/tools
# Default target
.DEFAULT_GOAL := all
# Strip quotes and then whitespaces
qstrip = $(strip $(subst ",,$(1)))
define check_build_dir
$(shell \
if [ ! -d $(1) ]; then \
mkdir -p $(1); \
fi; \
)
endef
define del_mark
$(shell find $(SDK_SRC_ROOT_DIR) -type f -name ".parse_config" | xargs rm -rf {})
endef
# 1 src
# 2 dst
define sync_dir
@echo "Source: $(1)"
@echo "Destination: $(2)"
@rm -rf $(2)/*
@mkdir -p $(2) || exit 1
@rsync -aq --delete $(1)/ $(2)/
endef
define gen_kconfig
@if [ -f "$(1)/Kconfig" ]; then \
cp -f $(1)/Kconfig $(SDK_BUILD_DIR)/Kconfig.$(2); \
else \
echo "" > $(SDK_BUILD_DIR)/Kconfig.$(2); \
fi
endef
# Do not print "Entering directory ...",
# but we want to display it when entering to the output directory
# so that IDEs/editors are able to understand relative filenames.
MAKEFLAGS += --no-print-directory
# ifneq ($(MKENV_INCLUDED),1)
# JOBS := $(shell ps -o args= $(MAKEPPID) | grep -o -E -- '-j[0-9]+' | head -n 1 | cut -c3-)
# ifneq ($(JOBS),)
# ifneq ($(JOBS),1)
# $(error not support parallel build now)
# endif
# endif
# endif
# Turn on increased build verbosity by defining BUILD_VERBOSE in your main
# Makefile or in your environment. You can also use V=1 on the make command
# line.
ifeq ("$(origin V)", "command line")
BUILD_VERBOSE=$(V)
endif
ifndef BUILD_VERBOSE
BUILD_VERBOSE = 0
endif
ifeq ($(BUILD_VERBOSE),0)
Q = @
else
Q =
endif
RM = rm -rf
ECHO = echo
CP = cp
MKDIR = mkdir
SED = sed
CAT = cat
TOUCH = touch
PYTHON = python3
ZIP = zip
INSTALL = install
# Check if 'bear' command exists
BEAR_EXISTS := $(shell command -v bear >/dev/null 2>&1 && echo yes || echo no)
BEAR_COMMAND ?= bear
ifeq ($(BEAR_EXISTS),yes)
# Capture the Bear version
BEAR_VERSION := $(shell bear --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
# Convert BEAR_VERSION to a single integer and compare it to 30000
BEAR_VERSION_INT := $(shell echo $(BEAR_VERSION) | awk -F. '{print ($$1*10000 + $$2*100 + $$3)}')
# Check if the Bear version is 3.0.0 or greater
ifneq ($(shell [ $(BEAR_VERSION_INT) -ge 30000 ] && echo true),)
BEAR_COMMAND := bear --
endif
endif
export BEAR_COMMAND
PARALLEL ?= $(shell if command -v nproc > /dev/null 2>&1; then nproc; \
elif [ -f /proc/cpuinfo ]; then grep -c '^processor' /proc/cpuinfo; \
elif command -v sysctl > /dev/null 2>&1; then sysctl -n hw.ncpu; \
else echo 1; fi)
export NCPUS?=$(PARALLEL)
CONFIG_SDK_ENABLE_CANMV ?= n
def_config :=$(filter %_defconfig,$(MAKECMDGOALS))
ifneq ($(def_config),)
ifeq ($(shell if [ -f $(SDK_SRC_ROOT_DIR)/configs/$(def_config) ]; then echo 1; else echo 0; fi;), 0)
$(error "Please specify a valid CONFIG")
endif
CONFIG_BOARD_CONFIG_NAME := $(def_config)
else
-include $(SDK_SRC_ROOT_DIR)/.config
endif
CONFIG_BOARD := $(call qstrip,$(CONFIG_BOARD))
export CONFIG_SDK_ENABLE_CANMV
ifeq ($(strip $(filter $(MAKECMDGOALS),list_def list-def dl_toolchain)),)
ifeq ($(CONFIG_BOARD_CONFIG_NAME),)
$(error "Please run make xxx_defconfig first. Use 'make list-def' to see available configurations.")
endif
endif
MK_LIST_DEFCONFIG?=$(CONFIG_BOARD_CONFIG_NAME)
export MK_LIST_DEFCONFIG
export UBOOT_DEFCONFIG=$(patsubst %,%_defconfig,$(call qstrip,$(CONFIG_UBOOT_CONFIG_FILE)))
export RTSMART_DEFCONFIG=$(patsubst %,%_defconfig,$(call qstrip,$(CONFIG_RTSMART_CONFIG_FILE)))
export SDK_BOARDS_DIR=$(SDK_SRC_ROOT_DIR)/boards
export SDK_BOARD_DIR=$(SDK_BOARDS_DIR)/$(call qstrip,$(CONFIG_BOARD))
export SDK_OPENSBI_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/opensbi
export SDK_RTSMART_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/rtsmart
export SDK_UBOOT_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/uboot
export SDK_CANMV_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/canmv
export SDK_APPS_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/applications
export SDK_BUILD_DIR=$(SDK_SRC_ROOT_DIR)/output/$(call qstrip,$(CONFIG_BOARD_CONFIG_NAME))
export SDK_OPENSBI_BUILD_DIR=$(SDK_BUILD_DIR)/opensbi
export SDK_RTSMART_BUILD_DIR=$(SDK_BUILD_DIR)/rtsmart
export SDK_UBOOT_BUILD_DIR=$(SDK_BUILD_DIR)/uboot
export SDK_CANMV_BUILD_DIR=$(SDK_BUILD_DIR)/canmv
export SDK_APPS_BUILD_DIR=$(SDK_BUILD_DIR)/applications
export SDK_ARDUINO_SDK_BUILD_DIR=$(SDK_SRC_ROOT_DIR)/output/arduino-sdk
export SDK_BUILD_IMAGES_DIR=$(SDK_BUILD_DIR)/images
ifeq ($(strip $(filter $(MAKECMDGOALS),list_def list-def dl_toolchain)),)
$(call check_build_dir, $(SDK_BUILD_IMAGES_DIR))
$(call check_build_dir, $(SDK_OPENSBI_BUILD_DIR))
$(call check_build_dir, $(SDK_RTSMART_BUILD_DIR))
$(call check_build_dir, $(SDK_UBOOT_BUILD_DIR))
$(call check_build_dir, $(SDK_CANMV_BUILD_DIR))
$(call check_build_dir, $(SDK_APPS_BUILD_DIR))
$(call check_build_dir, $(SDK_ARDUINO_SDK_BUILD_DIR))
$(call check_build_dir, $(SDK_TOOLCHAIN_DIR))
endif
export MKENV_INCLUDED = 1
export MPP_FOR_CANMV_SDK = 1
MK_IMAGE_NAME?=$(call qstrip,$(CONFIG_BOARD_NAME))
export MK_IMAGE_NAMEexport SDK_TOOLCHAIN_DIR?=$(shell echo $$HOME)/.kendryte/k230_toolchains
export SDK_TOOLS_DIR=$(SDK_SRC_ROOT_DIR)/tools
# Default target
.DEFAULT_GOAL := all导出SDK_TOOLCHAIN_DIR为工具链路径为/home/fs/.kendryte/k230_toolchains,$用于声明变量,$$HOME为变量内容
导出SDK_TOOLS_DIR为/home/fs/workspace/K230/rtos/tools.DEFAULT_GOAL := all表示如果使用make不用任何参数,就执行make all
qstrip:qstrip = $(strip $(subst ",,$(1)))作用:去引号并去空格。因为底层配置文件.config里经常会有带引号的变量(比如CONFIG_BOARD="k230_evb"),在 Makefile 里当作路径拼装时引号会导致报错,这个函数专门用来把"k230_evb"扒皮变成k230_evb。check_build_dir:利用 shell 判断目录是否存在,不存在就mkdir -p创建它。del_mark:在全工程范围内搜索.parse_config缓存文件并删掉(通常用于清理旧配置)。sync_dir:调用强大的rsync命令将源目录同步到目标目录,确保增量拷贝。gen_kconfig:把各个模块自己的Kconfig汇总到总构建目录下,供图形化配置界面使用。
MAKEFLAGS += --no-print-directory
...
ifeq ($(BUILD_VERBOSE),0)
Q = @
else
Q =
endif
RM = rm -rf
ECHO = echo
CP = cp
MKDIR = mkdir
SED = sed
CAT = cat
TOUCH = touch
PYTHON = python3
ZIP = zip
INSTALL = install- 防刷屏:关闭了 Make 默认的“进入目录 xxx”、“离开目录 xxx”的疯狂刷屏提示。
V=1机制:这是 Linux 内核编译非常经典的设计。默认情况下Q = @,在 Makefile 命令前加@会隐藏原本的执行命令(比如隐藏超长的gcc -c ...),只打印简短的提示。但如果你排查编译报错时,在终端输入make V=1,Q就会变为空,把底层所有真实执行的编译命令全部暴露出来。- 文件接着统一定义了
RM,CP,MKDIR等通用操作命令的别名,方便跨平台兼容。**
# Check if 'bear' command exists
BEAR_EXISTS := $(shell command -v bear >/dev/null 2>&1 && echo yes || echo no)
BEAR_COMMAND ?= bear
ifeq ($(BEAR_EXISTS),yes)
# Capture the Bear version
BEAR_VERSION := $(shell bear --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
# Convert BEAR_VERSION to a single integer and compare it to 30000
BEAR_VERSION_INT := $(shell echo $(BEAR_VERSION) | awk -F. '{print ($$1*10000 + $$2*100 + $$3)}')
# Check if the Bear version is 3.0.0 or greater
ifneq ($(shell [ $(BEAR_VERSION_INT) -ge 30000 ] && echo true),)
BEAR_COMMAND := bear --
endif
endif
export BEAR_COMMAND
PARALLEL ?= $(shell if command -v nproc > /dev/null 2>&1; then nproc; \
elif [ -f /proc/cpuinfo ]; then grep -c '^processor' /proc/cpuinfo; \
elif command -v sysctl > /dev/null 2>&1; then sysctl -n hw.ncpu; \
else echo 1; fi)
export NCPUS?=$(PARALLEL)
CONFIG_SDK_ENABLE_CANMV ?= n- Bear 拦截器:
bear是一个极其强大的工具,它能在make编译时拦截所有编译参数,生成compile_commands.json。这个文件是 VSCode、CLion 等现代化 IDE 实现 C/C++ 代码精准跳转、智能补全 的基石。这段代码贴心地检测了你的环境里有没有装bear,以及版本是不是最新的 3.0+。 PARALLEL:自动嗅探你电脑的 CPU 核心数(用nproc或sysctl),并赋值给NCPUS,为后续的并发编译(如make -j8)做准备。
def_config :=$(filter %_defconfig,$(MAKECMDGOALS))
ifneq ($(def_config),)
ifeq ($(shell if [ -f $(SDK_SRC_ROOT_DIR)/configs/$(def_config) ]; then echo 1; else echo 0; fi;), 0)
$(error "Please specify a valid CONFIG")
endif
CONFIG_BOARD_CONFIG_NAME := $(def_config)
else
-include $(SDK_SRC_ROOT_DIR)/.config
endif
CONFIG_BOARD := $(call qstrip,$(CONFIG_BOARD))
export CONFIG_SDK_ENABLE_CANMV
ifeq ($(strip $(filter $(MAKECMDGOALS),list_def list-def dl_toolchain)),)
ifeq ($(CONFIG_BOARD_CONFIG_NAME),)
$(error "Please run make xxx_defconfig first. Use 'make list-def' to see available configurations.")
endif
endif
MK_LIST_DEFCONFIG?=$(CONFIG_BOARD_CONFIG_NAME)
export MK_LIST_DEFCONFIGMAKECMDGOALS:Make 内置变量,代表你敲在终端的命令目标。- 逻辑分支:
- 如果你在终端敲了
make k230_evb_defconfig,它会去检查configs/目录下到底有没有这个配置文件。有的话,就把名字存入CONFIG_BOARD_CONFIG_NAME。 - 如果你只是敲了
make,它就会安静地include $(SDK_SRC_ROOT_DIR)/.config把之前的配置读进来。
- 如果你在终端敲了
- 拦截器:如果没有配置(比如刚下好代码),并且你执行的也不是看列表(
list_def)或者下载工具链(dl_toolchain)的命令,系统会直接报错拦截($(error "Please run make xxx_defconfig first...")),逼你去选定一款板子。$(filter %_defconfig, ...) - 这是 Make 的字符串过滤函数。
- 语法:
$(filter <pattern>, <text>) - 作用:从
<text>中筛选出匹配<pattern>模式的单词。 %是通配符,表示匹配任意长度的任意字符。%_defconfig表示:匹配以_defconfig结尾的字符串。
再后面的代码是对def_config的处理,判断是否存在这个配置文件,然后执行相关逻辑,存在怎么处理,不存在怎么处理MK_LIST_DEFCONFIG?=$(CONFIG_BOARD_CONFIG_NAME)设置默认值
export UBOOT_DEFCONFIG=$(patsubst %,%_defconfig,$(call qstrip,$(CONFIG_UBOOT_CONFIG_FILE)))
export RTSMART_DEFCONFIG=$(patsubst %,%_defconfig,$(call qstrip,$(CONFIG_RTSMART_CONFIG_FILE)))
export SDK_BOARDS_DIR=$(SDK_SRC_ROOT_DIR)/boards
export SDK_BOARD_DIR=$(SDK_BOARDS_DIR)/$(call qstrip,$(CONFIG_BOARD))
export SDK_OPENSBI_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/opensbi
export SDK_RTSMART_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/rtsmart
export SDK_UBOOT_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/uboot
export SDK_CANMV_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/canmv
export SDK_APPS_SRC_DIR=$(SDK_SRC_ROOT_DIR)/src/applications
export SDK_BUILD_DIR=$(SDK_SRC_ROOT_DIR)/output/$(call qstrip,$(CONFIG_BOARD_CONFIG_NAME))
export SDK_OPENSBI_BUILD_DIR=$(SDK_BUILD_DIR)/opensbi
export SDK_RTSMART_BUILD_DIR=$(SDK_BUILD_DIR)/rtsmart
export SDK_UBOOT_BUILD_DIR=$(SDK_BUILD_DIR)/uboot
export SDK_CANMV_BUILD_DIR=$(SDK_BUILD_DIR)/canmv
export SDK_APPS_BUILD_DIR=$(SDK_BUILD_DIR)/applications
export SDK_ARDUINO_SDK_BUILD_DIR=$(SDK_SRC_ROOT_DIR)/output/arduino-sdk
export SDK_BUILD_IMAGES_DIR=$(SDK_BUILD_DIR)/images
ifeq ($(strip $(filter $(MAKECMDGOALS),list_def list-def dl_toolchain)),)
$(call check_build_dir, $(SDK_BUILD_IMAGES_DIR))
$(call check_build_dir, $(SDK_OPENSBI_BUILD_DIR))
$(call check_build_dir, $(SDK_RTSMART_BUILD_DIR))
$(call check_build_dir, $(SDK_UBOOT_BUILD_DIR))
$(call check_build_dir, $(SDK_CANMV_BUILD_DIR))
$(call check_build_dir, $(SDK_APPS_BUILD_DIR))
$(call check_build_dir, $(SDK_ARDUINO_SDK_BUILD_DIR))
$(call check_build_dir, $(SDK_TOOLCHAIN_DIR))
endif
export MKENV_INCLUDED = 1
export MPP_FOR_CANMV_SDK = 1
MK_IMAGE_NAME?=$(call qstrip,$(CONFIG_BOARD_NAME))
export MK_IMAGE_NAME- 源码区 (
src/):明确了 OpenSBI(底层固件)、U-Boot(引导程序)、RT-Smart(实时系统)、CanMV(AI视觉框架)以及 Applications(应用层)的源码位置。 - 输出区 (
output/):所有编译生成的.o文件和镜像,绝对不允许污染源码目录,必须统一全部输出到output/<你的单板名字>/目录下。这也是为了方便你执行make distclean时,直接把output文件夹一删就能彻底清理干净。