一、通用示例
CC = gcc
CFLAGS = -lpthread -L ./tlACSClib -lNetDEVSDK
GFLAGS = -g -Wall -O3
TARGET = tlACSC
SRCS = $(wildcard *.c)
OBJS = $(patsubst %c, %o, $(SRCS))
$(TARGET) : $(OBJS)
$(CC) $(GFLAGS) $^ -o $(TARGET) $(CFLAGS)
%.o : %.c
$(CC) $(GFLAGS) -c $< -o $@
.PHONY : clean
clean:
rm -rf $(OBJS) $(TARGET)
二、= 和 :=
1、“=”
make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:
x = foo
y = $(x) bar
x = xyz
在上例中,y的值将会是 xyz bar ,而不是 foo bar 。
2、“:=”
“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。
x := foo
y := $(x) bar
x := xyz
在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。
3、
other= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
4、include、-include、sinclude
include包含其他的Makefile文件或者.mk文件。从而可以使用包含文件中的变量。
-include是指定编译器,即使没有这个文件也照常编译,不报错。编译继续进行。
sinclude和-include的效果一样
5、命令前加@和-
如果make执行的命令前面加了@字符,则不显示命令本身而只显示它的结果;
通常make执行的命令如果出错(该命令的退出状态非0)就立刻终止,不再执行后续命令,但如果命令前面加了-号,即使这条命令出错,make也会继续执行后续命令
6、多级目录make -C
确实,当一个Makefile包含另一个Makefile时,可以实现递归构建,这在管理多目录项目时非常有用。这种方法允许你将大型项目分解成更小的、更易于管理的部分,每个部分都有自己的Makefile。然后,主Makefile可以调用这些子Makefile来构建整个项目。
递归构建通常通过以下方式实现:
在主Makefile中使用make -C命令:-C选项告诉make切换到指定的目录,并在那里执行Makefile。这允许你从项目的顶层Makefile中调用位于子目录中的Makefile。例如,如果你的项目结构如下:
project/
├── Makefile # 主Makefile
├── src/
│ └── Makefile # 子Makefile
└── include/
└── …
你可以在顶层Makefile中这样写:
all:
make -C src
这将切换到src目录并执行那里的Makefile。
在子Makefile中定义目标:
每个子Makefile应该定义它自己的构建目标,这些目标可以被顶层Makefile调用。这样,你可以在每个子目录中独立地管理构建过程。
传递变量:
有时,你可能需要在子Makefile中使用顶层Makefile中定义的变量。你可以使用make的命令行参数来传递这些变量。例如,在顶层Makefile中:
COMMON_FLAGS = -Wall -g
all:
make -C src COMMON_FLAGS=”$(COMMON_FLAGS)”
然后在子Makefile中,你可以这样使用这些变量:
gcc $(COMMON_FLAGS) -o my_program my_program.c
递归深度:
注意,递归构建可能会增加构建的复杂性,特别是当项目非常大且包含许多子目录时。确保你的Makefile结构清晰,并且每个子Makefile都明确其职责,以避免不必要的复杂性。
使用include指令:
虽然include指令本身不直接支持递归构建,但它可以用于将公共的Makefile片段(如变量定义、规则等)包含到多个Makefile中,从而减少重复代码并提高可维护性。
递归构建是多目录项目管理中的一种强大工具,但也需要谨慎使用,以确保构建过程的清晰性、可维护性和效率。
7、在Makefile的上下文中,由于每行通常都被视为一个独立的命令
在Makefile中使用if语句时,为了可读性和维护性,通常建议将if语句的各个部分(如条件判断、then子句、else子句和fi结束标记)写成多行。
Makefile中的if语句实际上是通过调用shell脚本来实现的,因此它们遵循shell脚本的语法规则。在shell脚本中,if语句可以跨越多行,而Makefile只是提供了一个执行这些脚本的环境。
为了将if语句写成多行,你可以使用反斜杠(\)来指示当前行的继续,或者在每个逻辑行的末尾使用换行符。然而,在Makefile的上下文中,由于每行通常都被视为一个独立的命令,因此你通常需要使用反斜杠来明确指示if语句的跨行继续。
以下是一个Makefile中使用if语句并写成多行的示例:
target:
@if [ "$(SOME_VARIABLE)" = "some_value" ]; then \
echo "SOME_VARIABLE is set to some_value"; \
else \
echo "SOME_VARIABLE is not set to some_value"; \
fi
在这个例子中,if语句被分成了多行,每行的末尾都使用了反斜杠来指示下一行是当前逻辑行的继续。这样做提高了代码的可读性,使得其他开发者更容易理解你的意图。
因此,尽管技术上可能将if语句的所有部分压缩到同一逻辑行(通过省略反斜杠并使用分号分隔命令),但这样做通常是不推荐的,因为它会牺牲代码的可读性和可维护性。最佳实践是将if语句写成多行,以清晰地表达条件逻辑和相应的操作。
Makefile的条件判断【ifeq、else、endif】在解析阶段就执行了,不是在命令执行阶段。可以使用shell判断代替make条件。
参考手册:
Makefile 命令大全
一、基本命令语法
1. 命令格式
target: dependencies
command1
command2
# 注释
2. 命令前缀
| 前缀 | 说明 | 示例 |
|---|---|---|
| (无) | 显示执行的命令 | echo "hello" |
@ |
不显示命令本身 | @echo "hello" |
- |
忽略错误继续执行 | -rm -f file |
+ |
即使使用 -n/-t/-q 也要执行 |
+make clean |
二、常用命令操作
1. 文件操作
# 创建目录
mkdir -p dir/subdir
# 复制文件
cp source dest
cp -r source_dir/ dest_dir/
# 移动/重命名
mv old new
# 删除文件
rm -f file # 强制删除
rm -rf directory/ # 递归删除目录
# 创建空文件
touch file
# 创建链接
ln -s target link_name
2. 文本处理
# 输出文本
echo "text"
echo -n "no newline"
# 写入文件
echo "content" > file
echo "more" >> file # 追加
# 读取文件
cat file
head -n 10 file
tail -n 5 file
# 文本替换
sed 's/old/new/g' file
sed -i 's/old/new/g' file # 直接修改文件
# 查找文本
grep "pattern" file
grep -r "pattern" dir/ # 递归查找
# 行数统计
wc -l file
3. 条件判断
# if 语句
if [ condition ]; then \
command1; \
else \
command2; \
fi
# 测试文件
if [ -f file ]; then # 文件存在
if [ -d dir ]; then # 目录存在
if [ -x file ]; then # 文件可执行
if [ -s file ]; then # 文件非空
if [ -z "$var" ]; then # 变量为空
if [ -n "$var" ]; then # 变量非空
# 字符串比较
if [ "$a" = "$b" ]; then # 相等
if [ "$a" != "$b" ]; then # 不相等
if [ "$a" \< "$b" ]; then # 小于(字典序)
# 数字比较
if [ $a -eq $b ]; then # 等于
if [ $a -ne $b ]; then # 不等于
if [ $a -lt $b ]; then # 小于
if [ $a -gt $b ]; then # 大于
if [ $a -le $b ]; then # 小于等于
if [ $a -ge $b ]; then # 大于等于
4. 循环控制
# for 循环
for i in 1 2 3 4 5; do \
echo $$i; \
done
# while 循环
counter=0; \
while [ $$counter -lt 5 ]; do \
echo $$counter; \
counter=$$(($$counter + 1)); \
done
# until 循环
counter=0; \
until [ $$counter -ge 5 ]; do \
echo $$counter; \
counter=$$(($$counter + 1)); \
done
5. 变量操作
# 定义变量
VAR = value
VAR := immediate_value
VAR ?= default_if_empty
VAR += additional_value
# 使用变量
echo $(VAR)
echo $${SHELL_VAR} # Make变量用$(),shell变量用$$
# 特殊变量
$@ # 目标名
$< # 第一个依赖
$^ # 所有依赖
$? # 比目标新的依赖
$* # 模式匹配的部分
三、函数使用
1. 字符串函数
# 字符串替换
$(subst from,to,text) # 文本替换
$(patsubst pattern,replacement,text) # 模式替换
$(strip string) # 去除空格
# 查找过滤
$(findstring find,in) # 查找子串
$(filter pattern...,text) # 保留匹配项
$(filter-out pattern...,text) # 排除匹配项
# 排序去重
$(sort list) # 排序并去重
$(word n,text) # 取第n个单词
$(wordlist s,e,text) # 取单词列表
$(words text) # 单词个数
$(firstword names...) # 第一个单词
$(lastword names...) # 最后一个单词
2. 文件名函数
# 目录/文件部分
$(dir names...) # 目录部分
$(notdir names...) # 非目录部分
$(suffix names...) # 后缀部分
$(basename names...) # 前缀部分
$(addsuffix suffix,names...) # 添加后缀
$(addprefix prefix,names...) # 添加前缀
# 路径操作
$(abspath names...) # 绝对路径
$(realpath names...) # 真实路径
3. 条件函数
# 条件判断
$(if condition,then-part[,else-part])
$(or condition1[,condition2...])
$(and condition1[,condition2...])
4. 文件操作函数
# 文件存在性
$(wildcard pattern) # 通配符匹配
$(shell command) # 执行shell命令
5. 控制函数
# 循环
$(foreach var,list,text) # 循环处理
# 调用用户函数
$(call variable,param,...) # 调用自定义函数
# 值函数
$(value variable) # 获取未展开的值
$(eval text) # 动态生成make代码
$(origin variable) # 变量来源
$(flavor variable) # 变量类型
四、高级命令技巧
1. 多行命令
target:
command1 && \
command2 && \
command3
# 或使用 .ONESHELL
.ONESHELL:
target:
command1
command2
command3
2. 错误处理
# 忽略错误
target:
-false
echo "继续执行"
# 检查命令返回值
target:
if ! command; then \
echo "命令失败"; \
exit 1; \
fi
# set -e 模式
target:
set -e; \
command1; \
command2
3. 调试命令
# 调试输出
$(info 信息: $(VAR))
$(warning 警告: $(VAR))
$(error 错误: $(VAR))
# 命令调试
target:
@echo "调试: VAR=$(VAR)"
$(info 调试: 目标=$@)
# 使用 --debug 选项
# make --debug=v 或 make --debug=i
4. 时间戳命令
target:
@echo "开始: $$(date +%Y-%m-%d_%H:%M:%S)"
# 执行命令
@echo "结束: $$(date +%Y-%m-%d_%H:%M:%S)"
# 计算执行时间
target:
@start=$$(date +%s); \
# 执行命令
@end=$$(date +%s); \
echo "耗时: $$((end - start)) 秒"
五、实用命令模板
1. 编译C/C++项目
CC = gcc
CFLAGS = -Wall -O2
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
TARGET = program
$(TARGET): $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
2. 安装脚本
PREFIX = /usr/local
BINDIR = $(PREFIX)/bin
DATADIR = $(PREFIX)/share/myapp
install:
install -d $(BINDIR) $(DATADIR)
install -m 755 myapp $(BINDIR)/
install -m 644 data/* $(DATADIR)/
uninstall:
rm -f $(BINDIR)/myapp
rm -rf $(DATADIR)
3. 备份脚本
BACKUP_DIR = backups
TIMESTAMP = $(shell date +%Y%m%d_%H%M%S)
backup:
mkdir -p $(BACKUP_DIR)
tar -czf $(BACKUP_DIR)/backup_$(TIMESTAMP).tar.gz src/ data/
find $(BACKUP_DIR) -name "*.tar.gz" -mtime +30 -delete
4. 测试脚本
test:
@echo "运行单元测试..."
@for test in tests/*_test; do \
echo "运行 $$test"; \
if ! ./$$test; then \
echo "测试失败: $$test"; \
exit 1; \
fi; \
done
@echo "所有测试通过"
5. 环境检查
check-env:
@echo "检查依赖..."
@for cmd in gcc make python3; do \
if ! command -v $$cmd >/dev/null 2>&1; then \
echo "错误: $$cmd 未安装"; \
exit 1; \
fi; \
done
@echo "所有依赖已安装"
六、特殊目标
1. 伪目标
.PHONY: all clean install uninstall help
2. 特殊内置目标
.DEFAULT: # 默认目标
.ONESHELL: # 所有命令在同一个shell执行
.SILENT: # 静默模式(不显示命令)
.IGNORE: # 忽略错误
.EXPORT_ALL_VARIABLES: # 导出所有变量
.NOTPARALLEL: # 禁止并行执行
3. 模式规则
%.o: %.c
$(CC) -c $< -o $@
%.txt: %.md
pandoc $< -o $@
七、命令行选项
1. 常用选项
make # 执行默认目标
make target # 执行指定目标
make -j4 # 并行执行(4个任务)
make -n # 只显示命令,不执行
make -s # 静默模式
make -k # 出错继续执行其他目标
make -i # 忽略所有错误
make -B # 强制重建所有目标
make -C dir # 进入目录执行
make -f file # 使用指定Makefile
2. 调试选项
make --debug[=FLAGS] # 调试输出
make -d # 详细调试信息
make -p # 打印数据库
make -q # 查询是否需要重建
make -r # 不使用内置规则
make -R # 不使用内置变量
八、环境变量
1. Make相关
MAKELEVEL # Make递归调用层级
MAKEFLAGS # Make标志
MAKECMDGOALS # 命令行指定的目标
2. 编译相关
CC # C编译器
CXX # C++编译器
CFLAGS # C编译选项
CXXFLAGS # C++编译选项
LDFLAGS # 链接选项
九、最佳实践示例
1. 完整项目模板
# 项目配置
PROJECT = myapp
VERSION = 1.0.0
PREFIX = /usr/local
# 源文件
SRC_DIR = src
INC_DIR = include
BUILD_DIR = build
DIST_DIR = dist
# 工具链
CC = gcc
CFLAGS = -Wall -O2 -I$(INC_DIR)
LDFLAGS = -lm
# 自动查找源文件
SRCS = $(wildcard $(SRC_DIR)/*.c)
OBJS = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRCS))
# 默认目标
all: $(BUILD_DIR)/$(PROJECT)
# 链接
$(BUILD_DIR)/$(PROJECT): $(OBJS)
@mkdir -p $(@D)
$(CC) -o $@ $^ $(LDFLAGS)
# 编译
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $< -o $@
# 清理
clean:
rm -rf $(BUILD_DIR) $(DIST_DIR)
# 安装
install: all
install -d $(PREFIX)/bin
install -m 755 $(BUILD_DIR)/$(PROJECT) $(PREFIX)/bin/
# 打包
dist: all
@mkdir -p $(DIST_DIR)
tar -czf $(DIST_DIR)/$(PROJECT)-$(VERSION).tar.gz \
$(BUILD_DIR)/$(PROJECT) \
README.md LICENSE
# 帮助
help:
@echo "可用目标:"
@echo " all - 编译项目(默认)"
@echo " clean - 清理构建文件"
@echo " install - 安装到系统"
@echo " dist - 打包发布"
@echo " help - 显示此帮助"
.PHONY: all clean install dist help
2. 多配置管理
# 配置选择
CONFIG ?= release
ifeq ($(CONFIG),debug)
CFLAGS += -g -DDEBUG -O0
else ifeq ($(CONFIG),release)
CFLAGS += -O3 -DNDEBUG
else ifeq ($(CONFIG),profile)
CFLAGS += -pg -O2
endif
# 根据配置输出到不同目录
BUILD_DIR = build/$(CONFIG)
