【问题标题】:Creating a makefile for a C++ Project为 C++ 项目创建 makefile
【发布时间】:2021-09-16 00:57:25
【问题描述】:

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

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

这是我的代码:

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.

【问题讨论】:

    标签: c++ makefile


    【解决方案1】:

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

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

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

    一般来说,从许多不同的目录获取源文件并将构建输出全部放入 same 目录并不是 make 想要的工作方式(此外,这通常是一个坏主意;如果您不小心在两个不同的目录中创建了两个同名的不同源文件?)

    不过,make 确实提供了一个可以使这项工作发挥作用的功能:VPATH 将允许 make 在其他目录中搜索源文件。如果你在你的 makefile 中使用它:

    SrcFiles := $(shell find . -name "*.cpp")
    
    SrcFileDirs := $(sort $(dir $(SrcFiles))
    
    VPATH := $(SrcFileDirs)
    

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

    【讨论】:

    • 非常感谢您的回复!它完美地解决了我的问题。只是两个问题,为什么需要对源文件的目录进行排序?你建议我如何构建我的 makefile?
    • sort 函数不是绝对必需的,但它有一个副作用,它不仅可以对事物进行排序,还可以删除重复项。由于dir 的结果将是该目录中每个源文件的每个目录都列出一次,因此排序会删除那些重复项,效率更高。
    • 通常我建议目标文件与源文件保持相同的目录结构,只是在一个单独的顶级目录中。因此,如果您的来源是foo/bar.cppbiz/baz.cpp 等,那么您将使用Objects := $(patsubst %.cpp,$(ObjectsDir)/%.o,$(SrcFiles)),那么您可以使用最初的模式规则:$(ObjectsDir)/%.o : %.cpp,并且您不需要 VPATH。在您的配方中,您需要在调用编译器之前添加mkdir -p $(@D) 来创建目录。
    【解决方案2】:

    您的规则$(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”。

    【讨论】:

    • 感谢您及时而简洁的回复。使用$(ObjectsDir)/%.cpp.o: %.cpp 时遇到的一个问题是我收到以下错误make: *** No rule to make target 'Objects/Engine.cpp.o', needed by 'App'. Stop. 有没有办法解决这个问题?
    • 不幸的是,我构建的玩具示例并没有说明这个问题。我不确定究竟是什么导致了它们之间的差异。
    • 我可以看看你的玩具例子吗?
    • Makefile。在本地目录中添加了一些非常琐碎的 Foo.cpp Foo.hpp Bar.cpp 文件,其内容应该不那么重要。
    • 您是否尝试过将源文件移动到子目录中?当所有源文件都在基本目录中而不是在子目录中时,makefile 可以工作
    【解决方案3】:

    对于将来想知道如何设置 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)
    

    【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-03
    • 2021-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多