工程目录
工程目录如图,build文件夹是编译出来的
.
├── app
│ ├── imx6ul.lds
│ ├── main.c
│ ├── makefile
│ └── start.S
├── bsp
│ ├── clk
│ │ ├── bsp_clk.c
│ │ └── bsp_clk.h
│ ├── delay
│ │ ├── bsp_delay.c
│ │ └── bsp_delay.h
│ └── led
│ ├── bsp_led.c
│ └── bsp_led.h
├── build
│ ├── bsp_clk.o
│ ├── bsp_delay.o
│ ├── bsp_led.o
│ ├── led_bsp.bin
│ ├── led_bsp.elf
│ ├── led_bsp.lst
│ ├── main.o
│ └── start.o
└── imx6ul├── cc.h├── fsl_common.h├── fsl_iomuxc.h├── imx6ul.h└── MCIMX6Y2.h
Makefile源码
# $@ 表示目标文件;
# $^ 表示所有的依赖文件;
# $< 表示第一个依赖文件;# = 延时赋值,该变量只有在调用的时候,才会被赋值;
# := 直接赋值,与延时赋值相反,使用直接赋值的话,变量的值定义时就已经确定了;
# ?= 若变量的值为空,则进行赋值,通常用于设置默认值;
# += 追加赋值,可以往变量后面增加新的内容。# 定义交叉编译器变量
CROSS_COMPILER ?= arm-linux-gnueabihf-# 定义编译选项
CFLAGS = -Wall -Wextra -std=c11 -O2# 定义目标文件
TARGET = led_bsp# 定义连接脚本文件路径
LD_SCRIPT = -T./imx6ul.lds# 定义目标文件夹
BUILD_DIR := ../buildSRC_DIRS := ./
INC_DIRS := ../imx6ul
LIB_DIRS := ../lib# 扩展源文件夹列表
SUBDIRS := clk led delay
SRC_DIRS += $(addprefix ../bsp/, $(SUBDIRS))
VPATH := $(SRC_DIRS)# 扩展头文件夹列表
INC_DIRS := $(addprefix -I, $(INC_DIRS))
INC_DIRS += $(addprefix -I, $(SRC_DIRS))# 扩展库文件夹列表
LIB_DIRS := $(addprefix -L, $(LIB_DIRS))# 定义需要链接的库
#LIBS = -lm -lpthread# 查找所有的源文件
SRCS_S := $(shell find $(SRC_DIRS) -name '*.S')
SRCS_C := $(shell find $(SRC_DIRS) -name '*.c')
# 去掉路径 只保存文件名
NOT_DIR_SRCS_S := $(notdir $(SRCS_S))
NOT_DIR_SRCS_C := $(notdir $(SRCS_C))
# 将.c .S替换成.o
OBJS_S := $(patsubst %.S, $(BUILD_DIR)/%.o, $(NOT_DIR_SRCS_S))
OBJS_C := $(patsubst %.c, $(BUILD_DIR)/%.o, $(NOT_DIR_SRCS_C))
OBJS := $(OBJS_S) $(OBJS_C)# 默认目标 链接所有.o
$(BUILD_DIR)/$(TARGET): $(OBJS)$(CROSS_COMPILER)ld $(LD_SCRIPT) $(LIB_DIRS) $(LIBS) -o $@.elf $^ # 编译成.o
$(OBJS_C) : $(BUILD_DIR)/%.o : %.c$(CROSS_COMPILER)gcc $(CFLAGS) $(INC_DIRS) -c -o $@ $<$(OBJS_S) : $(BUILD_DIR)/%.o: %.S$(CROSS_COMPILER)gcc $(CFLAGS) $(INC_DIRS) -c -o $@ $<# 生成bin文件
bin: $(BUILD_DIR)/$(TARGET)$(CROSS_COMPILER)objcopy -O binary $<.elf $<.bin# 生成反汇编文件
disassemble: $(BUILD_DIR)/$(TARGET)$(CROSS_COMPILER)objdump --source --all-headers --demangle --line-numbers --wide $<.elf > $<.lst# 生成空间信息
size: $(BUILD_DIR)/$(TARGET)$(CROSS_COMPILER)size --format=berkeley $<.elf # all目标生成目标文件、bin文件和反汇编文件
all: $(BUILD_DIR)/$(TARGET) bin disassemble size# 清理规则,用于清除生成的目标文件、可执行文件、二进制文件和反汇编文件
clean:rm -rf $(BUILD_DIR)# 确保编译目标文件夹存在
$(BUILD_DIR):mkdir -p $(BUILD_DIR)# 编译之前先创建目标文件夹
$(OBJS): | $(BUILD_DIR).PHONY: bin disassemble size all clean
基本编译规则
编译过程
在GCC中,每个编译阶段都有对应的指令来执行。以下是每个阶段对应的GCC指令:
-
预处理(Preprocessing):
gcc -E
这个指令告诉GCC只进行预处理,生成预处理后的代码,而不执行后续的编译、汇编和链接步骤。生成的预处理文件通常以.i
为扩展名 -
编译(Compiling):
gcc -S
这个指令告诉GCC只进行编译,将源代码转换为汇编代码,而不执行汇编和链接步骤。生成的汇编语言文件通常以.s
为扩展名 -
汇编(Assembling):
gcc -c
这个指令告诉GCC只进行汇编,将汇编语言代码转换为目标文件,而不执行链接步骤。生成的目标文件通常以.o
为扩展名 -
链接(Linking):
ld
这个指令执行完整的编译链接过程,将多个目标文件以及可能的库文件链接在一起,生成最终的可执行文件。可通过指定输入的目标文件和库文件来完成链接过程,并使用-o
选项来指定输出文件名
Makefile基本规则
规则定义了如何根据源文件生成目标文件,规则通常由三个部分组成:目标(target)、依赖关系(dependencies)和命令(commands)
以下是规则的一般格式:
codetarget: dependenciescommand
- 依赖全部存在时,才会执行command
- 程序会从默认规则开始执行,也是为了生成第一个目标(一般都是可执行文件)
- 当依赖不存在时,就会自动去别的规则中的目标中去寻找,以此类推
.PHONY
声明了伪目标,例如make all
,这样做的好处是,可以避免与同名的文件或目录发生冲突,同时确保这些目标能够被正确地执行
常用函数和语句
sheel
result := $(shell command)
会创建一个shell
进程执行command
,并将结果返回到result
需要注意执行效率,此函数可能会被多次调用到
wildcard(匹配文件名并返回列表)
$(wildcard pattern)
其中 pattern
是一个文件名模式,可以包含通配符(例如 *
和 ?
)
wildcard
函数会返回匹配 pattern
的文件列表,如果没有匹配的文件,则返回空字符串。
以下是一个示例:
SRCS := $(wildcard src/*.c)
在这个示例中,$(wildcard src/*.c)
将匹配 src
目录下所有以 .c
结尾的文件,并返回这些文件的列表给 SRCS
变量
foreach (遍历执行)
用于在每个元素上执行操作,它的语法如下:
$(foreach var, list, text)
var
是一个变量名,用于保存列表中的每个元素list
是一个空格分隔的元素列表text
是要执行的文本
foreach
函数会遍历列表中的每个元素,并将当前元素依次赋值给 var
,然后执行 text
中的命令。在每次迭代中,text
中可以使用 var
来引用当前元素
在这个示例中, 会遍历 FILES
列表中的每个文件名,并将其打印出来
FILES := file1.txt file2.txt file3.txt
$(foreach file, $(FILES), $(info File: $(file)))
addprefix(给列表加前缀)
用于在每个元素前添加一个前缀,它的语法如下:
makefileCopy code
$(addprefix prefix,names...)
prefix
是要添加的前缀names
是一个空格分隔的元素列表
addprefix
函数会将 prefix
添加到 names
列表中的每个元素前面,并返回修改后的列表以下是一个示例:
makefileCopy codeFILES := file1.txt file2.txt file3.txt
$(addprefix prefix_, $(FILES))
在这个示例中,$(addprefix prefix_, $(FILES))
会将 FILES
列表中的每个文件名前面都添加 prefix_
前缀,得到一个新的列表。结果将是 prefix_file1.txt prefix_file2.txt prefix_file3.txt
查找所有源文件
第一步先是通过addprefix
生成所有包含源文件的目录,将其存在SRC_DIRS
中,并借助info
将其打印出来
SUBDIRS := clk led delay
SRC_DIRS += $(addprefix ../bsp/, $(SUBDIRS))$(info SRC_DIRS : $(SRC_DIRS))#结果如下
SRC_DIRS : ./ ../bsp/clk ../bsp/led ../bsp/delay
使用 find
通过如下命令即可找出SRC_DIRS
中的所有.c
文件
SRCS_C := $(shell find $(SRC_DIRS) -name '*.c')$(info SRCS_C : $(SRCS_C))#结果如下
SRCS_C : ./main.c ../bsp/clk/bsp_clk.c ../bsp/led/bsp_led.c ../bsp/delay/bsp_delay.c
附上find
的常见用法:
-
在当前目录及其子目录中搜索所有文件:
find
-
在指定目录中搜索所有文件:
find /path/to/directory
-
在当前目录及其子目录中搜索所有
.txt
文件:find -name "*.txt"
-
在当前目录及其子目录中搜索所有大于 1MB 的文件:
find -size +1M
-
在当前目录及其子目录中搜索所有修改时间在一天之内的文件:
find -mtime -1
-
在当前目录及其子目录中搜索所有空文件:
find -type f -empty
find
命令非常强大,它支持多种匹配条件和动作,可以根据实际需求来进行灵活的文件搜索和操作
使用wildcard和foreach
通过如下命令即可找出SRC_DIRS
中的所有.c
文件
SRCS_C := $(foreach dir, $(SRC_DIRS), $(wildcard $(dir)/*.c))$(info SRCS_C : $(SRCS_C))#结果如下
SRCS_C : ./main.c ../bsp/clk/bsp_clk.c ../bsp/led/bsp_led.c ../bsp/delay/bsp_delay.c
notdir
用于从文件路径中提取文件名部分,去除路径。它的语法如下:
$(notdir names...)
其中 names
是一个空格分隔的文件路径列表
notdir
函数会返回 names
中每个文件路径的文件名部分,去除路径信息
以下是一个示例:
SRCS := src/main.c src/foo.c src/bar.c
FILE_NAMES := $(notdir $(SRCS))
在这个示例中,$(notdir $(SRCS))
将提取 SRCS
中每个文件路径的文件名部分,结果将是 main.c foo.c bar.c
patsubst
用于在字符串中执行模式替换操作。它的语法如下:
$(patsubst pattern, replacement, text)
其中:
pattern
是要匹配的模式replacement
是用于替换匹配模式的文本text
是要进行模式替换的原始文本
patsubst
函数会查找 text
中与 pattern
匹配的部分,并将它们替换为 replacement
以下是一个示例:
SRCS := file1.c file2.c file3.c
OBJS := $(patsubst %.c, %.o, $(SRCS))
在这个示例中,$(patsubst %.c,%.o,$(SRCS))
将把 SRCS
中的 .c
文件名替换为 .o
文件名,生成的 OBJS
变量会包含 file1.o file2.o file3.o
调用其他 Makefile
在 Makefile 中,可以通过 include
指令来引入其他的 Makefile。这样做的主要目的是为了将大型项目拆分成更小的模块,每个模块对应一个独立的 Makefile,方便管理和维护。同时,通过模块化的方式,可以提高代码的重用性和可维护性。
在 Makefile 中使用 include
指令的语法如下:
include other_makefile.mk
其中 other_makefile.mk
是要包含的其他 Makefile 的文件名。
通过引入其他的 Makefile,可以将项目拆分成多个模块,每个模块负责特定的功能或任务。这样做有以下几个优点:
- 模块化管理:将项目拆分成多个模块后,每个模块可以独立管理,减少了代码的耦合度,方便团队协作和代码维护
- 代码重用:通过将通用的功能和规则封装在单独的模块中,可以提高代码的重用性,避免重复编写相同的代码
- 可维护性:将项目拆分成多个模块后,每个模块的功能更加清晰明确,易于理解和维护。同时,可以针对每个模块进行单独的测试和调试
总之,通过引入其他的 Makefile,可以将大型项目拆分成更小的模块,提高了代码的组织性、可维护性和重用性,是一种良好的编程实践。