您的教程宣传了旧的和不良的做法,恕我直言,您应该避免它。
在这里你的规则:
$(ODIR)/%.o: %.c $(DEPS)
您告诉 make 在当前目录中查找源,而它们实际上位于 src 目录中,因此这种模式从未使用过,您也没有合适的模式。
确保你像这样组织你的项目目录:
root
├── include/
│ └── all .h files here
├── lib/
│ └── all third-party library files (.a/.so files) here
├── src/
│ └── all .c files here
└── Makefile
然后让我们使用良好的做法一步一步地完成这个过程。
首先,如果不需要,请不要定义任何内容。 Make 有很多预定义的变量和函数,您应该在尝试手动操作之前使用它们。其实他有这么多,你可以编译一个简单的文件,甚至目录里没有Makefile!
-
列出您的源代码并构建输出目录:
SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin # or . if you want it in the current directory
-
命名您的最终目标,即您的可执行文件:
EXE := $(BIN_DIR)/hellomake
-
列出您的源文件:
SRC := $(wildcard $(SRC_DIR)/*.c)
-
从源文件中,列出目标文件:
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
# You can also do it like that
OBJ := $(patsubst $(SRC_DIR)/%.c, $(OBJ_DIR)/%.o, $(SRC))
-
现在让我们处理标志
CPPFLAGS := -Iinclude -MMD -MP # -I is a preprocessor flag, not a compiler flag
CFLAGS := -Wall # some warnings about bad code
LDFLAGS := -Llib # -L is a linker flag
LDLIBS := -lm # Left empty if no libs are needed
(CPP 在这里代表 C PreP处理器,而不是 CPlusPlus!使用 CXXFLAGS 代表 C++ 标志和 @987654332 @ 用于 C++ 编译器。)
-MMD -MP 标志用于自动生成标头依赖项。稍后我们将使用它来在只有标头更改时触发编译。
好的,现在我们的变量已正确填充,是时候推出一些食谱了。
默认目标应该被称为all 并且它应该是你的 Makefile 中的第一个目标,这一点被广泛传播。它的先决条件应该是你在命令行上只写make时要构建的目标:
all: $(EXE)
一个问题是 Make 会认为我们实际上想要创建一个名为 all 的文件或文件夹,所以让我们告诉他这不是一个真正的目标:
.PHONY: all
现在列出构建可执行文件的先决条件,并填写其配方以告诉 make 如何处理这些:
$(EXE): $(OBJ)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
(CC 代表 C C编译器。)
请注意,您的$(BIN_DIR) 可能还不存在,因此对编译器的调用可能会失败。让我们告诉 make 你希望它首先检查它:
$(EXE): $(OBJ) | $(BIN_DIR)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(BIN_DIR):
mkdir -p $@
一些快速的附加说明:
-
$(CC) 是一个内置变量,已经包含在 C 中编译和链接时所需的内容
- 为避免链接器错误,强烈建议将
$(LDFLAGS)放在您的目标文件和$(LDLIBS)之后
-
$(CPPFLAGS)和$(CFLAGS)在这里没用,编译阶段已经结束,这里是链接阶段
下一步,由于您的源文件和目标文件不共享相同的前缀,您需要准确地告诉 make 要做什么,因为它的内置规则不涵盖您的具体情况:
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
同样的问题,您的$(OBJ_DIR) 可能还不存在,因此对编译器的调用可能会失败。让我们更新规则:
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
$(BIN_DIR) $(OBJ_DIR):
mkdir -p $@
好的,现在可执行文件应该可以很好地构建了。我们想要一个简单的规则来清理构建工件:
clean:
@$(RM) -rv $(BIN_DIR) $(OBJ_DIR) # The @ disables the echoing of the command
(再次重申,clean 不是需要创建的目标,因此请将其添加到 .PHONY 特殊目标中!)
最后一件事。还记得自动依赖生成吗? GCC 和 Clang 将创建与您的 .o 文件对应的 .d 文件,其中包含供我们使用的 Makefile 规则,因此我们将其包含在此处:
-include $(OBJ:.o=.d) # The dash is used to silence errors if the files don't exist yet
最终结果:
SRC_DIR := src
OBJ_DIR := obj
BIN_DIR := bin
EXE := $(BIN_DIR)/hellomake
SRC := $(wildcard $(SRC_DIR)/*.c)
OBJ := $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
CPPFLAGS := -Iinclude -MMD -MP
CFLAGS := -Wall
LDFLAGS := -Llib
LDLIBS := -lm
.PHONY: all clean
all: $(EXE)
$(EXE): $(OBJ) | $(BIN_DIR)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c | $(OBJ_DIR)
$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@
$(BIN_DIR) $(OBJ_DIR):
mkdir -p $@
clean:
@$(RM) -rv $(BIN_DIR) $(OBJ_DIR)
-include $(OBJ:.o=.d)