Makefile 是一个工程文件的编译规则,它告诉 make 命令如何编译和链接程序。
目标: 依赖1 依赖2 ...
命令1
命令2
...
注意:命令行前必须是 Tab,不能是空格
hello: hello.c
gcc hello.c -o hello
不代表实际文件的目标,通常用于执行命令
.PHONY: clean
clean:
rm -f *.o
*
: 匹配任意字符串?
: 匹配单个字符[...]
: 匹配括号中的任意一个字符示例:
*.o: *.c
gcc -c $<
# 编译器设置
CC = gcc
CFLAGS = -Wall
# 目标文件
TARGET = myprogram
# 源文件和对象文件
SRCS = main.c helper.c
OBJS = main.o helper.o
# 默认目标
all: $(TARGET)
# 生成可执行文件
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $(TARGET)
# 生成目标文件
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理
clean:
rm -f $(OBJS) $(TARGET)
空格vs制表符
Makefile:4: *** missing separator. Stop.
循环依赖
Circular xxx <- xxx dependency dropped.
命令执行错误
-k
参数可以继续执行其他命令make -n
显示要执行的命令但不实际执行make -d
显示调试信息@
可以不显示该命令的执行过程# 定义变量
SOURCE_FILES = main.c utils.c
OBJECTS = $(SOURCE_FILES:.c=.o)
$@
: 当前目标$<
: 第一个依赖文件$^
: 所有依赖文件$*
: 匹配符 % 匹配的部分$(@D)
: 目标文件的目录部分$(@F)
: 目标文件的文件部分$(HOME)
, $(PATH)
export
可以将 Makefile 变量导出到环境中Make 预定义的变量:
CC # C 编译器,默认 cc
CXX # C++ 编译器,默认 g++
CFLAGS # C 编译选项
CXXFLAGS # C++ 编译选项
LDFLAGS # 链接器选项
AR # 静态库打包工具,默认 ar
ARFLAGS # ar 命令选项
RM # 删除命令,默认 rm -f
=
普通赋值
A = hello
B = $(A) world # B 的值会随 A 的变化而变化
:=
立即赋值
A := hello
B := $(A) world # B 的值在此时固定
?=
条件赋值
A ?= hello # 只有 A 未定义时才赋值
+=
追加赋值
A = hello
A += world # A 变成 "hello world"
# 替换字符串
$(subst from,to,text)
# 模式替换
$(patsubst pattern,replacement,text)
# 去除空格
$(strip string)
# 查找字符串
$(findstring find,text)
# 获取目录名
$(dir src/foo.c hacks) # 返回 src/ ./
# 获取文件名
$(notdir src/foo.c hacks) # 返回 foo.c hacks
# 添加后缀
$(suffix src/foo.c src/bar.h) # 返回 .c .h
# 替换后缀
$(basename src/foo.c src/bar.h) # 返回 src/foo src/bar
# 获取 shell 命令输出
FILES := $(shell ls *.c)
# 循环函数
$(foreach var,list,text)
# 条件判断
$(if condition,then-part,else-part)
# 调用其他函数
$(call variable,param1,param2)
ifeq ($(CC),gcc)
CFLAGS += -Wall
else
CFLAGS += -W
endif
ifdef DEBUG
CFLAGS += -g
endif
ifndef RELEASE
CFLAGS += -O0
endif
# 检查变量是否相等
result = $(if $(filter $(CC),gcc),YES,NO)
# 检查文件是否存在
exists = $(if $(wildcard $(file)),YES,NO)
make -j4 # 并行执行,最多4个作业
make V=1 # 显示详细编译信息
make DEBUG=1 # 启用调试模式
-f file
: 指定 makefile 文件名-C dir
: 切换到指定目录-n
: 显示要执行的命令但不执行-s
: 静默模式-k
: 出错后继续执行-j N
: 并行执行 N 个作业-B
: 强制重新构建所有目标--debug
: 输出调试信息# 完整的 Makefile 示例
CC := gcc
CFLAGS := -Wall -O2
TARGET := myapp
SRCS := $(wildcard *.c)
OBJS := $(SRCS:.c=.o)
# 调试模式
ifdef DEBUG
CFLAGS += -g -DDEBUG
endif
# 默认目标
all: $(TARGET)
# 链接
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $@
# 编译
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
# 清理
.PHONY: clean
clean:
$(RM) $(OBJS) $(TARGET)
# 目录结构
SRC_DIR := src
OBJ_DIR := obj
INC_DIR := include
BIN_DIR := bin
# 源文件和目标文件
SRCS := $(wildcard $(SRC_DIR)/*.c)
OBJS := $(SRCS:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
# 确保目录存在
$(shell mkdir -p $(OBJ_DIR) $(BIN_DIR))
# 编译规则
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) -I$(INC_DIR) -c $< -o $@
# 生成依赖文件
DEPS := $(OBJS:.o=.d)
# 包含依赖文件
-include $(DEPS)
# 生成依赖的规则
$(OBJ_DIR)/%.d: $(SRC_DIR)/%.c
@set -e; rm -f $@; \
$(CC) -MM -I$(INC_DIR) $< > $@.$$$$; \
sed 's,\($*\)\.o[ :]*,$(OBJ_DIR)/\1.o $@ : ,g' < $@.$$$$ > $@; \
rm -f $@.$$$$
.PHONY: all clean install uninstall dist help
# 默认目标
all: $(TARGET)
# 安装
install: $(TARGET)
install -d $(DESTDIR)/usr/local/bin
install -m 755 $(TARGET) $(DESTDIR)/usr/local/bin
# 卸载
uninstall:
rm -f $(DESTDIR)/usr/local/bin/$(TARGET)
# 打包发布
dist: clean
mkdir -p $(TARGET)-$(VERSION)
cp -R * $(TARGET)-$(VERSION)
tar czf $(TARGET)-$(VERSION).tar.gz $(TARGET)-$(VERSION)
rm -rf $(TARGET)-$(VERSION)
# 帮助信息
help:
@echo "make - 编译程序"
@echo "make clean - 清理编译文件"
@echo "make install - 安装程序"
@echo "make dist - 创建发布包"
# 配置选项
DEBUG ?= 0
RELEASE ?= 1
# 调试配置
ifeq ($(DEBUG), 1)
CFLAGS += -g -DDEBUG
LDFLAGS += -g
endif
# 发布配置
ifeq ($(RELEASE), 1)
CFLAGS += -O2 -DNDEBUG
LDFLAGS += -s
endif
# 操作系统检测
ifeq ($(OS),Windows_NT)
PLATFORM := Windows
EXE := .exe
RM := del /Q
else
PLATFORM := $(shell uname -s)
EXE :=
RM := rm -f
endif
# 平台相关设置
ifeq ($(PLATFORM),Windows)
CFLAGS += -D_WIN32
else ifeq ($(PLATFORM),Linux)
CFLAGS += -D__linux__
else ifeq ($(PLATFORM),Darwin)
CFLAGS += -D__APPLE__
endif
# 静态库
STATIC_LIB := libexample.a
$(STATIC_LIB): $(OBJS)
$(AR) rcs $@ $^
# 动态库
SHARED_LIB := libexample.so
$(SHARED_LIB): $(OBJS)
$(CC) -shared -o $@ $^ $(LDFLAGS)
# 库的安装
install-lib: $(STATIC_LIB) $(SHARED_LIB)
install -d $(DESTDIR)/usr/local/lib
install -m 644 $(STATIC_LIB) $(DESTDIR)/usr/local/lib
install -m 755 $(SHARED_LIB) $(DESTDIR)/usr/local/lib
ldconfig
# 获取 Git 版本信息
VERSION := $(shell git describe --tags --always --dirty)
COMMIT := $(shell git rev-parse --short HEAD)
BUILD_TIME := $(shell date +"%Y-%m-%d %H:%M:%S")
# 编译时加入版本信息
CFLAGS += -DVERSION=\"$(VERSION)\" \
-DCOMMIT=\"$(COMMIT)\" \
-DBUILD_TIME=\"$(BUILD_TIME)\"
# 并行编译
NPROCS := $(shell nproc)
MAKEFLAGS += -j$(NPROCS)
# 缓存支持
CCACHE := $(shell which ccache)
ifneq ($(CCACHE),)
CC := ccache $(CC)
endif
# 编译优化
CFLAGS += -pipe -march=native
.PHONY: test check coverage
# 单元测试
TEST_DIR := tests
TEST_SRCS := $(wildcard $(TEST_DIR)/*.c)
TEST_BINS := $(TEST_SRCS:$(TEST_DIR)/%.c=$(TEST_DIR)/%)
test: $(TEST_BINS)
@for test in $(TEST_BINS); do ./$$test; done
# 代码覆盖率
coverage:
$(MAKE) clean
$(MAKE) CFLAGS="$(CFLAGS) --coverage"
$(MAKE) test
gcov $(SRCS)