Makefile学习路线

最近项目有需求,需要重构一个项目的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文件,而*是匹配静态的文件名。

  1. 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的思路

  1. 指定Target命名,Output路径,要参与编译的.a文件夹的名称(此时不包含路径,路径另外通过函数给出)
  2. 给每一个参与编译的.c文件一个归宿:有一个Output存放.o文件的相对路径。
  3. 接续上一条,C_OBJS,存放所有.c文件编译后的.o文件
  4. 接续上一条,首先需要将所有的.c文件的目录找出来,然后根据这个.c文件的目录进行patsubst的替换,将其替换为.o文件并存放到C_OBJS中。
  5. 进行Target编译:依赖格式的编译。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值