为 C++ 项目创建 makefile

Creating a makefile for a C++ Project

我目前正在尝试编写一个 Makefile,它在当前目录和所有子目录中搜索 C++ 文件,然后一个一个地编译它们(if 他们还没有自上次编译以来已经被编译或编辑) 到位于 ./Objects 位置的单个对象文件,然后最终使用对象文件并将它们全部链接到最终程序中。

我已经弄清楚如何使用“shell 查找”命令查找所有文件。但是,我一直在尝试弄清楚如何将源文件一次一个地编译成目标文件。

这是我的代码:

Appname := App
#The Directory
ObjectsDir := ./Objects

#Find all the source files
SrcFiles = $(shell find . -name "*.cpp")
#Get their names only
SrcFilesName = $(notdir $(SrcFiles))
#Add a .o suffix for the Object files name
ObjectsSuffix = $(addsuffix .o, $(SrcFilesName))
#Add the prefix "Objects/" as I wish to output all the objects to ./Objects
#A Source Files such as "./Test/Source/Test.cpp" becomes "./Objects/Test.cpp.o"
Objects = $(addprefix ./Objects/, $(ObjectsSuffix))

#Compiler related Variables
CXX = g++
CXXFLAGS = -Wall -std=c++20 -fsanitize=address  
CPPFLAGS = -DDEBUG -I ./Game/Headers -I ./Engine/Headers -I ./Engine/Headers/External
LDLIBS = $(shell sdl2-config --libs) -l dl

all : $(Appname)

$(Appname) : $(Objects)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(Objects) -o $(Appname) $(LDLIBS)

#The Problematic rule
#I would like this to run once per source file if they haven't been compiled or changed/edited, so that I don't end up recompiling the entire code base
$(ObjectsDir)/%.o: %.cpp
    mkdir -p Objects
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MMD -MP -c $^ -o $@ $(LDLIBS)

注意:当所有源文件都在基目录中时,makefile 当前有效。但是,当具有更复杂的布局时,会出现多个子目录错误,例如 make: *** No rule to make target 'Objects/Engine.cpp.o', needed by 'App'. Stop.

您的规则 $(ObjectsDir)/%.o: $(SrcFiles) 表示对于 每个单独的目标文件 ,它取决于 所有源文件 。这也意味着 $^ 是您所有的源文件,而不仅仅是一个源文件。

一个简单的修复,至少会导致编译符合您指定的行为:

$(ObjectsDir)/%.cpp.o: %.cpp
    mkdir -p Objects
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MMD -MP -c $^ -o $@ $(LDLIBS)

这意味着如果您更新 .cpp 文件,将重新编译相应的 .cpp.o 文件,而不会重新编译其他 cpp 文件。但是,如果您更新它所依赖的头文件,则不会处理此依赖项,并且您会得到漏报,其中有些东西需要重新编译但实际上没有。您正在生成带有 -MMD -MP 标志的依赖项跟踪文件,您可以通过将以下内容添加到您的 Makefile 中来使用它们:

Deps = $(Objects:%.o=%.d)
-include $(Deps)

这将更稳健地跟踪您的翻译单元具有的实际依赖性,而不仅仅是“Foo.cpp.o 依赖于 Foo.cpp”。

在模式规则中,词干(与 % 匹配的部分)在目标和先决条件中是相同的。因此,如果您的目标是 ./Objects/foo.o,则词干是 foo,前提条件是 foo.cpp

但是,您没有 foo.cpp,因此该规则不匹配。 Make 找不到任何其他匹配的规则,因此它表示它不知道如何构建该目标。

仅仅因为您可能有一些其他文件,例如 some/subdir/foo.cpp,并不意味着它不是在寻找那个文件,而是在寻找 foo.cpp

一般来说,从许多不同的目录中获取源文件并将构建输出全部放入相同的目录中并不是 make 想要的工作方式(此外,它通常是一个糟糕的想法;如果你不小心在两个不同的目录中创建了两个同名的不同源文件会怎样?)

然而,make 确实提供了一项功能,可以使这项工作正常进行:VPATH 将允许 make 在其他目录中搜索源文件。如果您在 makefile 中使用它:

SrcFiles := $(shell find . -name "*.cpp")

SrcFileDirs := $(sort $(dir $(SrcFiles))

VPATH := $(SrcFileDirs)

那么它应该可以工作(为了提高效率,您应该始终使用 :=$(shell ...))。

对于以后想知道如何设置 makefile 的任何人,这里是我的最终 makefile。

Appname := App
#Output Dir for Object and dependency files
ObjectsDir := ./Objects

#Find all the source files
SrcFiles := $(shell find . -name "*.cpp")
#Create a variable holding all the objects
Objects := $(patsubst %.cpp,$(ObjectsDir)/%.o,$(SrcFiles))

#Compiler Flags
CXX = g++
CXXFLAGS = -Wall -std=c++20 -fsanitize=address  
CPPFLAGS = -DDEBUG -I ./Game/Headers -I ./Engine/Headers -I ./Engine/Headers/External
LDLIBS = $(shell sdl2-config --libs) -l dl

.PHONY: all

all : $(Appname)

#Link the object files
$(Appname) : $(Objects)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(Objects) -o $(Appname) $(LDLIBS)

#Rule: For every object file require a dependency file and a C++ file
#1. Create the Folder
#2. Create Dependency File
#3. Create Object File
$(ObjectsDir)/%.o: %.cpp
    mkdir -p $(@D)
    $(CXX) $(CXXFLAGS) $(CPPFLAGS) -MMD -MP -c $< -o $@ $(LDLIBS)

clean:
    rm -rf $(ObjectsDir)

run:
    ./$(Appname)

#Include the dependency files
Deps = $(Objects:%.o=%.d)
-include $(Deps)