Makefile

Makefile 适合小型 C/C++ 项目,也适合学习构建规则。它的核心是:文件之间有依赖,依赖变了才重新执行命令。

核心规则

target: dependency
	command
  • target:要生成的目标,可以是 appmain.o,也可以是 clean 这种伪目标。
  • dependency:目标依赖的文件。
  • recipe:真正执行的命令,必须用 Tab 缩进。
  • 变量:把重复的编译器、参数、路径抽出来。
  • 伪目标:不是文件名,只是命令入口,例如 cleanrun
  • 增量构建:依赖文件时间更新后,才重新构建相关目标。

可直接用的小项目

目录结构:

hello-make/
├── Makefile
├── include/
│   └── hello.h
└── src/
    ├── hello.cpp
    └── main.cpp

include/hello.h

#pragma once
 
void say_hello();

src/hello.cpp

#include "hello.h"
#include <iostream>
 
void say_hello()
{
    std::cout << "Hello Makefile" << std::endl;
}

src/main.cpp

#include "hello.h"
 
int main()
{
    say_hello();
    return 0;
}

Makefile

CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -Iinclude
LDFLAGS :=
 
BUILD_DIR := build
TARGET := $(BUILD_DIR)/hello
SRCS := src/main.cpp src/hello.cpp
OBJS := $(SRCS:src/%.cpp=$(BUILD_DIR)/%.o)
DEPS := $(OBJS:.o=.d)
 
.PHONY: all run clean
 
all: $(TARGET)
 
$(TARGET): $(OBJS)
	$(CXX) $(OBJS) -o $@ $(LDFLAGS)
 
$(BUILD_DIR)/%.o: src/%.cpp
	@if not exist "$(BUILD_DIR)" mkdir "$(BUILD_DIR)"
	$(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@
 
run: $(TARGET)
	$(TARGET)
 
clean:
	@if exist "$(BUILD_DIR)" rmdir /s /q "$(BUILD_DIR)"
 
-include $(DEPS)

Linux/macOS 把创建和删除目录两行换成:

	@mkdir -p "$(BUILD_DIR)"
	@rm -rf "$(BUILD_DIR)"

核心命令自查表

命令作用
make构建第一个目标,通常是 all
make all构建完整项目
make run构建后运行
make clean删除构建产物
make -j44 线程并行构建
make -n只打印命令,不真正执行
make VERBOSE=1常见项目中用于显示详细命令,是否有效取决于 Makefile

常见错误

现象原因处理
missing separatorrecipe 没用 Tab把命令前缩进改成 Tab
改了头文件没重新编译没追踪头文件依赖使用 -MMD -MP-include $(DEPS)
clean 不执行当前目录有同名文件 clean.PHONY: clean
找不到头文件include path 没配-Iinclude
链接失败 undefined reference少了源文件或库检查 SRCSLDFLAGS

注意事项

  • Makefile 更适合规则明确的小项目;跨平台大型项目通常用 CMake 生成 Makefile。
  • recipe 的命令就是 shell 命令,不同系统写法可能不同。
  • Make 根据文件时间戳判断是否需要重建。