注意:此答案假定您使用的是GNU make。如果不是这种情况,可能有一些事情需要适应。我不会回答您关于跨平台可移植性的最后一个问题。首先是因为这是一个复杂的问题,其次是因为我没有 Windows 机器并且无法对这个操作系统进行任何测试。但是,如果您知道检测操作系统的便携式方法,请查看最后一个注释。同时,以下应该可以在 Windows 下运行。
在您的情况下使用 GNU make 的最直接和标准的方法可能是这样的:
MKDIR := md
RMDIR := rd /S /Q
CC := gcc
BIN := ./bin
OBJ := ./obj
INCLUDE := ./include
SRC := ./src
SRCS := $(wildcard $(SRC)/*.c)
OBJS := $(patsubst $(SRC)/%.c,$(OBJ)/%.o,$(SRCS))
EXE := $(BIN)/main.exe
CFLAGS := -I$(INCLUDE)
LDLIBS := -lm
.PHONY: all run clean
all: $(EXE)
$(EXE): $(OBJS) | $(BIN)
$(CC) $(LDFLAGS) $^ -o $@ $(LDLIBS)
$(OBJ)/%.o: $(SRC)/%.c | $(OBJ)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN) $(OBJ):
$(MKDIR) $@
run: $(EXE)
$<
clean:
$(RMDIR) $(OBJ) $(BIN)
解释:
-
wildcard make function 用于发现 C 源文件列表。
-
patsubst make function 用于将 C 源文件名转换为目标文件名。
-
.PHONY special target 告诉 make 它的所有先决条件都是假的:它们不是真正的文件,即使偶然已经存在同名的文件,make 也必须考虑它们。
-
$(OBJ)/%.o: $(SRC)/%.c | $(OBJ) 是 pattern rule,一个适用于所有类似对象构建规则的通用规则。这个告诉make如何通过编译对应的./src/xxx.cC源文件来生成每个./obj/xxx.o目标文件。
- 模式规则的
... | $(OBJ) 部分告诉make $(OBJ) 是order-only prerequisite。如果它不存在,Make 将构建它。否则它不会考虑它的最后修改时间来决定是否必须重建目标。目录几乎总是被列为仅订单的先决条件,因为它们的最后修改时间不相关。在这里,它用于告诉 make 在构建任何目标文件之前必须构建 $(OBJ) 目录。 $(EXE): $(OBJS) | $(BIN) 规则中的 $(BIN) 也是如此。
-
$@、$< 和 $^ 是 make automatic variables 中的 3 个。在规则的配方中,它们分别扩展为目标、规则的第一个常规先决条件和所有常规(非仅顺序)先决条件。
-
CC、CFLAGS 和 LDLIBS 是标准的 make implicit variables,分别用于定义 C 编译器、C 编译器选项和 -lxxx 链接器选项。
- 出于性能原因,
:= 变量赋值在这种特定情况下优于 = 赋值。有关详细说明,请参阅the GNU make documentation。
注意:因为你有一个include 目录,我猜你也有自定义头文件。它们应该被列为相关目标文件的先决条件,以便 make 知道如果它依赖的头文件发生更改,则必须重新构建目标文件。您可以通过在模式规则之后的任何位置添加没有配方的规则来做到这一点:
$(OBJ)/Distances.o: $(INCLUDE)/foo.h $(INCLUDE)/bar.h
如果项目的所有 C 源文件都包含头文件,只需添加:
$(OBJS): $(INCLUDE)/common.h
如果头文件有某种模式(例如,如果每个$(SRC)/xxx.c 都包含$(INCLUDE)/xxx.h),您还可以添加模式规则来声明这种依赖关系:
$(OBJ)/%.o: $(INCLUDE)/%.h
注意:如果您知道一种将 make 变量(例如 OS)设置为当前操作系统名称的方法,您可能可以使用 GNU make 条件修改前面的内容以使其可移植。例如,您可以将前两行替换为:
OS := $(shell <the-command-that-returns-the-current-OS-name>)
ifeq ($(OS),Windows)
MKDIR := md
RMDIR := rd /S /Q
else ifeq ($(OS),GNU/Linux)
MKDIR := mkdir -p
RMDIR := rm -rf
else ifeq ($(OS),pokemon)
MKDIR := bulbasaur
RMDIR := charmander
endif