正在关注 this tutorial ...
我有 2 个源文件和 1 个头文件。我想像教程中那样将它们放在不同的目录中。
所以我设置了这个项目:
.
├── include
│ └── hellomake.h
├── Makefile
└── src
├── hellofunc.c
└── hellomake.c
生成文件:
IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)
ODIR=obj
LDIR =../lib
_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))
_OBJ = hellomake.o hellofunc.o
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))
$(ODIR)/%.o: %.c $(DEPS)
$(CC) -c -o $@ $< $(CFLAGS)
hellomake: $(OBJ)
gcc -o $@ $^ $(CFLAGS)
.PHONY: clean
clean:
rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~
我生成的错误是:
gcc -o hellomake -I../include
gcc: fatal error: no input files
compilation terminated.
make: *** [hellomake] Error 4
发生了什么事?
最佳答案
您的教程提倡旧的和不良的做法,恕我直言,您应该避免。
在你的规则中:
$(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++ 标志,CXX
用于 C++ 编译器。)
-MMD -MP
标志用于自动生成 header 依赖项。稍后我们将使用它在仅 header 更改时触发编译。
好的,现在我们的变量已正确填充,是时候推出一些食谱了。
默认目标应该被称为 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 silences errors when 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)
关于c++ - 如何编写具有单独的源目录和头目录的 Makefile?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30573481/