最近项目有需求,需要重构一个项目的Makfefile文件, 原来的供应商提供的文件,太过复杂。故有以下学习过程,以下只做参考,如果有学习的需求, 我推荐B站Up主无限十三年跟着他的视频从头到尾敲一遍(当然不需要全敲),这只是一门编程语言,重要的是真正自己去写,和学其他语言一样,不会的去百度或者问AI,有需要的函数就去百度搜索有没有这个函数,慢慢就上手了。当前项目的Makefile我已经重构完成了,如果遇到什么问题可以留下评论。
- Makefile 是一个文本文件,定义了如何编译和链接程序。
- Make 是一个命令行工具,它会读取 Makefile 中的规则,并根据规则执行命令。
- Makefile 的核心思想是通过目标(Target)、依赖(Dependencies)和命令(Commands)来描述构建过程.
main.o: main.c
gcc -c main.c -o main.o
target: depencendices
command
遵循以上的基本格式,
# Defines the variables
CC = gcc # 编译命令
CFLAGS = -Wall -02 -Iinclude #编译标志位, 应该是可选的
SRC_DIR = src # 编译的.c文件目录
OBJ_DIR = obj # 编译后生成的.o文件
# Get all file of .c
SRCS = $(wildcard $(SRC_DIR)/*.c) #使用内置函数来返回符合规则的.c文件,规则定义为,所有SRC_DIR中的.c文件返回出来。
# Make the .c file to .o #这其实是一个路径, 包含所有.o文件的路径。
OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS)) # 所有它只是替换了 SRCS中的文件名路径,文件名没变。所以也是返回一个LIST
# Final obj
myprogram: $(OBJS)
$(CC) $(CFLAGS) -c $< -o $@ ##### 其实这里的$<($^指代all)指的是第一个依赖项,也就是OBJS, $@指的是myprogram这个Target。
clean
rm -f $(OBJ_DIR)/*.o myprogram
1. $< 表示当前规则下的第一个依赖,在上面,它应该代替的是main.c
$@ 表示当前规则中的目标,@ , 就是@ 到目标。
- *f --> force 强制删除 rm a.txt --> 会删除a.txt文件。
这两个组合在一起就是 rm -f *.o myprogram ,会强制清除所有的.o文件和Myprogram文件, 相当于通配符,表示all;
/% , %表示模式匹配符,用于模式规则,它表示匹配任意字符串,并将匹配的部分传递给命令。
%.o: %.c
gcc -c $< -o $@
这里的%表示,对于任意的.c文件,如CanIn_SWC.c,都可以生成对应的.o文件,—CanIn_SWC.o,%会匹配文件名,然后在命令中通过$< $@使用。
进一步解释,%是一个模式匹配规则, 这个模式匹配是动态的,当输入命令时会匹配对应的文件转换为.o文件,而*是匹配静态的文件名。
- makefile 内置函数 wildcard。用来匹配符合模式的文件名,并返回一个空格分隔的文件列表。语法, $(wildcard 模式)
如果,我们有一个目录为
project/
├── src/
│ ├── main.c
│ ├── utils.c
│ └── helper.c
└── Makefile
那么 SRCS返回值为, src/main.c src/utils.c src/helper.c,这样,就得到了所有.c文件的路径,找到了可以编译的对象,这应该是Makefile来进行编译的第一步吧。没有这个路径,就没有编译可言。一般来说,wildcard都是结合着patsubst使用,命令 $(patsubst……) 将每个.c文件路径替换为对应的.o文件路径。
举例而言,
OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS))
It generates as below,
src/main.c -> obj/main.o
src/utils.c -> obj/utils.o
src/helper.c -> obj/helper.o
要理解patsubst命令,首先需要有一个存储.c—>.o文件的文件夹,然后$(patsubst…)命令会将SRCS中的.c文件同步转换为.o文件,并移动到obj目录下。
编译和链接分开来?
先进行编译,即
HwDiag.o: HwDiag.c
gcc -c $<
SMDrv.o: SMDrv.c
gcc -c $<
#模式匹配 %表示所有的.o文件都是.c文件转换过来的;由下面这个代替上面两个!
%.o: %.c
gcc -c $< -o $@
然后链接到一个文件上执行 $@其实就是链接了。
PSA: HwDiag.o SMDrv.o
gcc $^ -o $@
#模式匹配 %表示所有的.o文件都是.c文件转换过来的;由下面这个代替上面两个!
%.o: %.c
gcc -c $< -o $@
伪目标(PHONY)
不生成文件的目标,只是一个标签。make clean来移除.o和PSA文件。
clean:
rm -f /*.o PSA
.PHONY: clean
CC = gcc
CFLAGS = Wall
SRC_DIR1 = src
#SRC_DIR2 = ./
OBJ_DIR= obj
SRCS= $(wildcard $(SRC_DIR1 )/*.c *.c) # return the current .c file
OBJS = $(patsubst $(SRC_DIR1)/%.c $(OBJ_DIR)/%.o $(SRCS)) # return the .o file split with blank.
$(OBJS): $(SRCS)
$CC$ $< -o $@
%.o: %.c
$CC$-c $< -o $@
.PHONY: clean
clean:
rm -f /*.o $(OBJS)
# ------------------------------- Correct ---------------------
target = app
src = $(wildcard *.c ./src/*.c)
obj = $(patsubst %.c, %.o, $(src))
include = ./include
$(target): $(obj)
gcc $^ -o $@
%.o: %.c
gcc -c $< -I $(include) -o $@
.PHONY: clean
clean:
rm -f $(obj) $(target)
# ----------------------GPT ---------------------------------
CC = gcc
CFLAGS = -Wall -I./include
SRC_DIR = src
OBJ_DIR = obj
INCLUDE_DIR = include
SRCS = $(wildcard *.c ./src/*.c)
OBJS = $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRCS))
TARGET = PSA
all: $(TARGET)
# Link the target to generate executable files
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
# 逐个生成目标文件
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJ_DIR)/*.o $(TARGET)
-----------------------------数独----------------------
# 目标可执行文件名
TARGET = sudoku
# 需要编译的源文件和目标文件
SRCS = $(wildcard *.cpp)
OBJS = $(SRCS:%.cpp=%.o) # 将.cpp替换为.o; 格式应该是 $(SRC: %.cpp=%.o)
# 编译器和编译选项
CC = g++
CFLAGS = -Wall -std=c++11 # 启用警告并使用 C++11 标准
# 默认目标:编译并链接
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $@
@echo "Build completed: $(TARGET)"
# 编译 .cpp 文件为 .o 文件
# %.o: %.cpp 是通用规则,表示将每个 .cpp 文件编译为对应的 .o 文件
%.o: %.cpp $(wildcard *.h) # 假设所有的 .h 文件都可能是当前 .cpp 文件的依赖
$(CC) $(CFLAGS) -c $< -o $@
@echo "Compiling $< to $@"
# 清理目标
.PHONY: clean
clean:
@echo "Cleaning up..."
@rm -f $(OBJS) $(TARGET)
@echo "Cleaned up: $(OBJS) $(TARGET)"
@$(info helloworld)
.PHONY: all
all: $(TARGET)
@echo "Build completed"
等号用法
# 简单的等于号赋值是延迟赋值,
INCLUDES = include
# := 是即时赋值
Includes := include1
# += 是追加赋值
# 将新的变量追加到现有的变量值中
# ?= 条件赋值
# 仅当该变量未赋值时才会赋值,否则不会重新赋值。
AOUTPUT ?= default_output # 只有在 AOUTPUT 未被赋值的情况下,才会给 AOUTPUT 赋值为 default_output
# != 会将后面的函数或者其他命令的返回值赋值给变量。 A != gcc --verison
即时展开有风险啊, 如果运行到Includes := include1 include1是没有的, 那么就会返回一个空字符串。
常用函数以及核心IF语句
$(dir)返回文件的目录
files := block.cpp src/command.cpp src/input.cpp src/main.cpp src/scene.cpp src/test.cpp
result = $(dir $(files))
dir函数返回你写的files的目录,没有后面的文件名。
而notdir只保留最后的文件名,不返回前面的路径。
result = $(basename $(files))
输出结果: block src/command src/input src/main src/scene src/test
可以看出这个函数可以用来保留单独的函数文件名。
result = $(suffix $(files)) # .cpp .cpp .cpp .cpp .cpp .cpp 取出它的后缀。
result = $(addsuffix .c, $(files)) # 第一个是需要添加的后缀名。
result = $(addprefix F01_Source/, $(result1))
result = $(join $(files), $(files2)) 多出的原样返回
一些常用的函数,
file = $(wildcard *.cpp)
result = $(if $(file), $(file), FILE Not Exist)
首先使用通配符匹配当前Makefile文件下的.cpp文件,接着,if语句会判断第一个位置是否为空,如果为空,会返回第三个语句,否则返回第二个!
f1 =
f2 =
f3 =
f4 = hello.cpp
f5 ?= main.cpp
file2 = $(or $(f1), $(f2), $(f4), $(f5))
它会返回条件中第一个不为空的部分,这种一般都是用在文件的操作中吧?这里会返回f4的内容。
f1 = d
f2 = a
f3 = dd
f4 = hello.cpp
f5 ?= main.cpp
file2 = $(and $(f1), $(f2), $(f4), $(f5))
如果一个条件为空,就会返回空值;如果全都不为空,则返回最后一个条件,这里返回的是最后一个。
file2 = $(intcmp 33,2,less,equal, greater)
第一个值大于第二个,返回最后一个选项的值(greater),否则返回第一个(less),如果相等,返回中间的值。
text = Something Write to files
$(file >m.txt, $(text))
r3 = $(file < m.txt)
$(info $(r3))
它的语法是一个>是覆盖,每次生成都会进行覆盖操作,而两个>>表示追加, 不覆盖,只是从后面追加新的内容。跑几次make写几次当前的text的内容。
$(foreach each, list, process)
files := block.cpp src/command.cpp src/input.cpp src/main.cpp src/scene.cpp src/test.cpp
result = $(foreach each, $(files), $(patsubst %.cpp, %.o, $(notdir $(each))))
对一列用空格分开的字符序列中每一项进行处理,并返回处理后的列表。这里做了操作, 将files进行处理,首先去掉其目录,只保留文件名称,其次,将cpp替换为.o。each是对files中以空格分开的字符串的遍历,最后是对每一个字符串的处理。
result = $(suffix $(files)) # .cpp .cpp .cpp .cpp .cpp .cpp
result = $(addsuffix .c, $(files)) # 第一个是需要添加的后缀名。
result = $(addprefix F01_Source/, $(result1))
vpath %.cpp src vpth <pattern><directories> %.cpp or % 去src里面搜索。
vpath %.h include # vpath变量指定特定的文件类型在文件夹下查找
VPATH = src include 是指定变量进行查找
VAPTH用于指定搜索文件的路径,如果某个cpp文件需要某个源文件,那么Make会按顺序在VPATH指定的路径中查找文件。
总结一下拿到一个项目的构建Makefile的思路
- 指定Target命名,Output路径,要参与编译的.a文件夹的名称(此时不包含路径,路径另外通过函数给出)
- 给每一个参与编译的.c文件一个归宿:有一个Output存放.o文件的相对路径。
- 接续上一条,C_OBJS,存放所有.c文件编译后的.o文件
- 接续上一条,首先需要将所有的.c文件的目录找出来,然后根据这个.c文件的目录进行patsubst的替换,将其替换为.o文件并存放到C_OBJS中。
- 进行Target编译:依赖格式的编译。



被折叠的 条评论
为什么被折叠?



