我目前正在学习如何在没有IDE的情况下进行编码,因此我正在学习如何编写makefile。这是我目前的测试项目:
\__ /CoDstructor/
|\__ Makefile
|\__ /bin/
| \__ CoDstructor.exe
|\__ /src/
| \__ /cod/
| |\__ main.cpp
| |\__ types.cpp
| \__ types.hpp
\__ /obj/
\__ /cod/
|\__ main.o
|\__ main.d
|\__ types.o
\__ types.d
我只有一个顶级makefile,它处理src /目录中的每个模块,并在obj /目录中创建对象和依赖项文件。 以下是文件:
的main.cpp
#include <iostream>
#include <cod/types.hpp>
int main() {
int s;
std::cin >> s;
return lol();
}
types.hpp
#ifndef TYPES_HPP_INCLUDED
#define TYPES_HPP_INCLUDED
int lol();
#endif // TYPES_HPP_INCLUDED
types.cpp
#include <iostream>
#include <cod/types.hpp>
int lol() {
std::cout << "lol";
return 0;
}
生成文件
APP_NAME = CoDstructor
DEBUG_TARGET = debug
RELEASE_TARGET = release
SRC_DIR = src
OBJ_DIR = obj
BIN_DIR = bin
INC_DIR = src
INCLUDE_DIRS +=
LIBRARY_DIRS +=
CXXFLAGS += -Wall
CXXFLAGS += -Werror
CXXFLAGS += -Wextra
CXXFLAGS += -pedantic
CXXFLAGS += -std=c++11
$(DEBUG_TARGET): CXXFLAGS += -g
$(RELEASE_TARGET): CXXFLAGS += -O3
LDFLAGS += -static
LDFLAGS += -static-libstdc++
LDFLAGS += -static-libgcc
$(DEBUG_TARGET): LDFLAGS += -g
$(RELEASE_TARGET): LDFLAGS +=
CPPMACROS =
$(DEBUG_TARGET): CPPMACROS += DEBUG
$(RELEASE_TARGET): CPPMACROS += NDEBUG
CXXFLAGS += $(foreach i,$(INC_DIR),$(addprefix -I,$(i)))
CXXFLAGS += $(foreach i,$(INCLUDE_DIRS),$(addprefix -I,$(i)))
CXXFLAGS += $(foreach i,$(CPPMACROS),$(addprefix -D,$(i)))
LIBS = $(foreach i,$(LIBRARY_DIRS),$(addprefix -L,$(i)))
SOURCES = $(subst ./,,$(shell find . -name *.cpp))
OBJS = $(subst $(SRC_DIR),$(OBJ_DIR),$(SOURCES:.cpp=.o))
DEPS = $(OBJS:.o=.d)
all: $(DEBUG_TARGET) clean
$(RELEASE_TARGET): $(BIN_DIR)/$(APP_NAME) clean
@echo Building release...
$(DEBUG_TARGET): $(BIN_DIR)/$(APP_NAME) clean
@echo Building debug...
$(OBJS): $(SOURCES)
@mkdir -p $(@D)
$(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
$(BIN_DIR)/$(APP_NAME): $(OBJS)
$(CXX) $(LDFLAGS) $^ -o $@ $(LIBS)
@echo $^
.PHONY: clean
clean:
@echo clean
# @-rmdir $(OBJ_DIR)
-include $(DEPS)
这是错误:
obj/cod/types.o: In function `main':
D:\PROJECTS\CoDstructor/src/cod/main.cpp:4: multiple definition of `main'
obj/cod/main.o:D:\PROJECTS\CoDstructor/src/cod/main.cpp:4: first defined here
obj/cod/main.o:main.cpp:(.text+0x2a): undefined reference to `lol()'
obj/cod/types.o:main.cpp:(.text+0x2a): undefined reference to `lol()'
collect2.exe: error: ld returned 1 exit status
我现在搜索了近两天如何解决这些错误。
对于第一个(主要的多个定义):
我已经检查了OBJS变量,它只包含和链接一次main.o.另外为什么在第一行说obj/cod/types.o
? types.hpp/types.cpp
中没有主要内容。
第二个错误(对lol()的未定义引用): 为什么引用未定义,而是没有给出编译器错误?
第三个错误(它每次重建所有内容,而不是仅更改一个或不是查找.d依赖项文件)
我正在运行最新的MinGW32版本(g ++ 4.9.1)和最新的MSYS(make)。
我在这里做错了什么?
答案 0 :(得分:3)
您的$(OBJS): $(SOURCES)
规则不是您认为的规则。因此,您要从同一个main.o
文件(命令行中为types.o
param)构建main.cpp
和$<
。因此,您有两个相同的文件是冲突的,而types.cpp
甚至没有构建。
正确的规则是$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
答案 1 :(得分:2)
从记忆中,没有检查过文档,问题是这样的:
$(OBJS): $(SOURCES)
@mkdir -p $(@D)
$(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
这意味着每个目标文件都依赖于所有来源。然后编译命令使用$<
,我认为这意味着第一个依赖项。因此,实际上,您从types.o
编译了main.o
和main.cpp
(我认为这是$(SOURCES)
中的第一个。)
一种解决方案是使用模式规则,例如:
%.o : %.c
@mkdir -p $(@D)
$(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
您的规则$(BIN_DIR)/$(APP_NAME): $(OBJS)
已经确认了对象文件,此模式规则将告诉您如何生成它们。
答案 2 :(得分:2)
您的关键问题在于:
$(OBJS): $(SOURCES)
@mkdir -p $(@D)
$(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
这条规则有两个问题。
您有多个目标文件。当你超越src
目录下只有一个子目录时,第一个动作就不会起作用。
第二个操作会将您的src/cod/main.cpp
编译两次,一次进入obj/cod/main.o
,然后进入obj/cod/types.o
。这是因为$<
是依赖项列表中的第一项,第一项是src/cod/main.cpp
。
第二个问题比第一个问题更容易解决。您需要一个模式规则:
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
$(CXX) -MMD -MP $(CXXFLAGS) -c $< -o $@
现在解决第一个问题。如果您有多个源目录,每个目录都是src
目录的子目录,该怎么办?您希望为每个子目录创建一个相应的obj
子目录。另请注意,您没有创建bin
目录。首先要做的是建立一个你需要制作的目录列表。
MKDIRS = $(sort $(foreach i,$(OBJS),$(dir $i)))
MKDIRS += bin
然后你需要一个规则来制作它们。让我们开始吧:
mkdirs:
mkdir -p $(MKDIRS)
然而这是有问题的。您将从mkdir收到错误消息,如果这些目录中的任何一个已存在,则构建将停止。我们只有在它们不存在的情况下才需要制作这些目录。 Make确实提供了将列表过滤到仅存在不存在的目录的工具,但我不愿意这样做。对我来说,最好使用shell做出一些决定:
mkdirs:
@sh -c \
'for d in $(MKDIRS); do \
if [ ! -d $$d ]; then echo mkdir -p $$d; mkdir -p $$d; fi \
done'
现在我们需要将该规则添加为依赖项。
$(RELEASE_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME)
@echo Release built
$(DEBUG_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME)
@echo Debug built
请注意,我已经为这些目标做了三件事。
我添加了mkdirs
目标。
我删除了clean
目标。你真的不想在这里做到这一点。它违背了单独汇编的目的。当您有数百个源文件并对其中一个进行更改时,您只需要重新编译该源文件,然后从已存在的数百个目标文件中重建可执行文件。在构建可执行文件之后,不要干净利落!如果您不得不这样做,可以从命令行执行make clean
。
我更改了消息。在满足依赖性之后将发布这些消息。他们将是你看到的最后一件事。要在操作开始之前获取消息,最简单的方法是构建一个打印所需消息的虚假目标。
最后几点说明:
请注意mkdirs
是假目标(all
也是如此)。最好将其添加到您的.PHONY
列表中,该列表最好放在前面。
最后,目标$(DEBUG_TARGET): mkdirs $(BIN_DIR)/$(APP_NAME)
是定时炸弹。 $(RELEASE_TARGET)
也是如此。有一天你会发现并行制作。在编译器尝试编译代码之前,不保证会生成目录。这些目录不存在,而kaboom,你的make只是失败了。使makefile对并行执行具有鲁棒性是一个不同的stackexchange问题。