Makefile 构建自动化:从基础语法到 C/C++ 编译实践

Makefile 构建自动化:从基础语法到 C/C++ 编译实践

Makefile 是组织编译的命令脚本文件,通过描述目标与依赖之间的关系树,自动化构建生成可执行文件。

make 根据 Makefile 中定义的规则关系树,按依赖顺序构建出可执行程序。

# 目标: 依赖
#     命令
a.out: demo.c
    gcc -o $@ $<

make 指令

Windows 环境

运行 make -v 可能出现:

'make' 不是内部命令或外部命令, 也不是可执行的程序, 或批处理文件.
# 系统在环境变量中找不到 make 命令的执行文件

解决方法:

  1. 找到 mingw 安装目录,进入 bin 目录,找到 mingw32-make.exe
  2. mingw32-make.exe 重命名为 make.exe

编译过程

编译流程

Makefile 核心语法

变量

使用 $(val) 获取变量 val 的值。

.PHONY: clean

CC=g++
BIN=exec
OBJS=AddTwoSum.o utils.o

$(BIN): $(OBJS)
	@echo "start compiling..."
	@echo $(CC)
	$(CC) -o $(BIN) $(OBJS)
	@echo "compile done"
AddTwoSum.o: AddTwoSum.cpp
	$(CC) -c -o $@ $<
utils.o: utils.cpp
	$(CC) -c -o $@ $<
clean:
	rm -f $(BIN) $(OBJS)

条件赋值

# 条件赋值 ?=  —— 仅在变量未定义时赋值
# 追加赋值 +=  —— 向变量追加值
CC = g++
CC ?= gcc          # CC 已定义,此句不生效
OBJS = AddTwoSum.o
OBJS += utils.o    # 追加
echo:
	@echo $(CC)
	@echo $(OBJS)

立即变量与延迟变量

# 使用 := 赋值 —— 立即变量(定义时马上展开)
# 使用 =  赋值 —— 延迟变量(使用时才展开,保留变量引用关系)
a = 1
b = 2
val_a := $(a)   # val_a = 1(立即求值)
val_b = $(b)    # val_b 的值随 b 变化(延迟求值)
a = 10
echo:
	@echo $(val_a)
	@echo $(val_b)

自动变量

# 常用自动变量
# $@ —— 目标
# $^ —— 所有依赖项
# $< —— 依赖项中的第一个
# $? —— 所有依赖项中被修改过的

.PHONY: clean

CC=g++
BIN=exec
OBJS=AddTwoSum.o utils.o

$(BIN): $(OBJS)
	@echo "start compiling..."
	@echo $(CC)
	$(CC) -o $@ $^
	@echo "compile done"
AddTwoSum.o: AddTwoSum.cpp
	$(CC) -c -o $@ $<
utils.o: utils.cpp
	$(CC) -c -o $@ $<
clean:
	rm -f $(BIN) $(OBJS)

变量替换(模式匹配)

.PHONY: echo

SRC = demo.c demo1.c
OBJ = $(SRC:.c=.o)
OBJ1 = $(SRC:%.c=%.o)

echo:
	@echo "SRC=$(SRC)"
	@echo "OBJ=$(OBJ)"
	@echo "OBJ1=$(OBJ1)"

# 输出:
# SRC=demo.c demo1.c
# OBJ=demo.o demo1.o
# OBJ1=demo.o demo1.o

环境变量

环境变量在运行时载入 Makefile。若存在同名变量,系统环境变量会被 Makefile 中定义的变量覆盖。

.PHONY: echo

CFLAGS = -g

echo:
	@echo "CFLAGS = $(CFLAGS)"
	@echo "SHELL = $(SHELL)"
	@echo "MAKE = $(MAKE)"
	@echo "HOSTNAME = $(HOSTNAME)"

# 输出示例:
# CFLAGS = -g
# SHELL = /usr/bin/sh
# MAKE = /usr/bin/make
# HOSTNAME = TX-PC0N4UHP

# 运行时可通过命令行传参覆盖:
# make HOSTNAME=CodeMax
# HOSTNAME = CodeMax

条件判断

# ifeq —— 判断相等
mode = debug

echo:
ifeq ($(mode),debug)
	@echo "debug mode"
else
	@echo "release mode"
endif

# ifneq —— 判断不等
echo:
ifeq ($(mode),)
	@echo "mode is empty"
else
	@echo "mode is not empty"
endif

# ifdef —— 判断变量是否已定义
echo:
ifdef mode
	@echo "mode is defined"
else
	@echo "mode is not defined"
endif

函数

函数分为三类:

  1. 自定义函数 —— 通过 call 调用
  2. 内嵌函数 —— Make 内置,直接调用
  3. 函数名与参数之间用空格分隔,参数之间用逗号分隔
# 内嵌函数示例 —— wildcard
SRC = $(wildcard *.cpp)

echo:
	@echo "SRC = $(SRC)"
# 自定义函数示例
define func
	@echo "param0 = $(0)"
	@echo "param1 = $(1)"
endef

echo:
	$(call func, hello world)

foreach 遍历

$(foreach VAR, LIST, TEXT)
  1. VAR —— 将 LIST 中空格分隔的每一项依次赋值给 VAR
  2. TEXT —— 使用 VAR 进行处理的脚本

if 函数

# $(if condition, then[, else])
install__path =
install_path = $(if $(install__path), $(install__path), /usr/local)

echo:
	@echo "$(install_path)"

call 函数

# $(call def_func, <param1>, <param2>...)

error / warning 函数

# $(error TEXT...)   —— 抛出错误并终止
# $(warning TEXT...) —— 输出警告并继续

完整示例

# 伪目标声明:防止当前目录有名为 clean 的文件导致规则跳过
.PHONY: clean

# 编译器
CC = g++

# 编译参数
# -g        加入调试信息
# -DDEBUG   编译 debug 版本
# -W -Wall  输出所有警告
# -fPIC     生成动态库时使用
CFLAGS = -g -DDEBUG -W -Wall -fPIC

# 头文件目录
HEADER  = -I./

ALGONAME = AddTwoSum
TOOLNAME = utils
OBJS= $(ALGONAME).o $(TOOLNAME).o

TARGET = algo

$(TARGET): $(OBJS)
	@echo "start compiling..."
	$(CC) $(CFLAGS) -o $@ $^
	@echo "compile done"
$(ALGONAME).o: $(ALGONAME).cpp
	$(CC) -c -o $@ $<
$(TOOLNAME).o: $(TOOLNAME).cpp
	$(CC) -c -o $@ $<
clean:
	rm -f $(TARGET) $(OBJS)

参考

avatar
hzzhu