当前位置: 首页 > 知识库问答 >
问题:

C Makefile,是否有可能将其因数化更多?

穆宾白
2023-03-14

我有一个学校的项目,我想写一个Makefile,我看到了一些使用多个源目录和多个可执行文件的Makefile的例子,但仍然无法将它正确地实现到我的Makefile中。

PS:我正在使用doctest进行单元测试(我无法更改它)。

以下是项目结构(我无法更改):

.
├── bin
├── build
├── extern
│   └── doctest.h
├── include
│   ├── file1.hpp
│   └── file2.hpp
├── src
│   ├── file1.cpp
│   └── file2.cpp
├── tests
│   ├── file1-test.cpp
│   └── file2-test.cpp
└──  Makefile

我有以下目录:

>

build:用于所有对象(. o)。

extern:对于doctest头(这是我存储任何其他库的地方)

include:用于所有标题(.hpp)。

src:对于所有类(.cpp)。

测试:对于所有单元测试(也是. cpp

你可以看到file1。cpp作为一个类,file1。hpp作为类标题和文件1测试。cpp作为类的单元测试。

这是我的Makefile:

BIN_DIR := bin/
BUILD_DIR := build/
EXTERN_DIR := extern/
INCLUDE_DIR := include/
SOURCE_DIR := src/
TESTS_DIR := tests/
DEP_DIR := .dep/

DEPENDS := $(patsubst %.o, $(BUILD_DIR)$(DEP_DIR)%.d, $(notdir $(wildcard $(BUILD_DIR)*.o)))

EXE := $(addprefix $(BIN_DIR), file1-test file2-test)

OBJS_1 := $(addprefix $(BUILD_DIR), file1.o)
OBJS_2 := $(addprefix $(BUILD_DIR), file1.o file2.o)

CXX := clang++
CXXFLAGS := -Wall -std=c++++11 -g -O3 -I$(INCLUDE_DIR) -I$(EXTERN_DIR)

vpath %.cpp $(SOURCE_DIR) $(TESTS_DIR)

.PHONY: all clean

all: $(EXE)

$(BUILD_DIR) $(BIN_DIR) $(BUILD_DIR)$(DEP_DIR):
    @mkdir -p $@

$(BUILD_DIR)%.o: %.cpp | $(BUILD_DIR) $(BUILD_DIR)$(DEP_DIR)
    @$(CXX) $(CXXFLAGS) -MMD -MP -MF $(BUILD_DIR)$(DEP_DIR)$(notdir $(basename $@).d) -c $< -o $@

$(BIN_DIR)%: $(BUILD_DIR)%.o | $(BIN_DIR)
    @$(CXX) -o $@ $^

$(BIN_DIR)file1-test: $(OBJS_1)
$(BIN_DIR)file2-test: $(OBJS_2)

.PRECIOUS: $(BUILD_DIR)%.o

-include $(DEPENDS)

clean:
    -rm -rf $(BIN_DIR) $(BUILD_DIR)

所以我的问题是:

>

  • 我的Makefile是否遵循良好的实践?

    它优化了吗?如果没有,我怎样才能做得更好?

    对于每个新的可执行文件,我必须添加一个OBJS_X变量和一个target$(BIN_DIR)fileX test:$(OBJS_X),我能去掉它吗?如果是的话,有人能给我写一些通用规则吗?这样我就不必每次需要一个新的可执行文件时都指定一个变量和一个目标。

    如果我只想编译一个可执行文件,我必须使用make bin/fileX test。是否可以只运行make fileX test而不是make bin/fileX test(但仍在bin目录中构建)?我试图实现这样一个规则:fileX test:$(BIN_DIR)fileX test,但它没有按我想要的那样工作,在编译的最后,它开始执行内置规则,我不知道为什么。有人能解释一下吗?

    最终答案:

    这是我认为一个很好的答案,如果它能帮助以后的人:

    BIN_DIR := bin/
    BUILD_DIR := build/
    EXTERN_DIR := extern/
    INCLUDE_DIR := include/
    SOURCE_DIR := src/
    TESTS_DIR := tests/
    DEP_DIR := $(BUILD_DIR).dep/
    
    CXX := g++
    CXXFLAGS := -Wall -std=c++11 -g -O3 -I$(INCLUDE_DIR) -I$(EXTERN_DIR)
    DEPFLAGS := -MMD -MP -MF $(DEP_DIR)
    
    vpath %.cpp $(SOURCE_DIR) $(TESTS_DIR)
    
    file1-test_OBJECTS := $(addprefix $(BUILD_DIR), file1.o)
    file2-test_OBJECTS := $(addprefix $(BUILD_DIR), file1.o file2.o)
    
    EXE := $(patsubst %_OBJECTS, %, $(filter %_OBJECTS, $(.VARIABLES)))
    
    .PHONY: all keep help check clean $(EXE)
    
    all: $(EXE:%=$(BIN_DIR)%)
    
    $(foreach E, $(EXE), $(eval $(BIN_DIR)$E: $($E_OBJECTS)))
    $(foreach E, $(EXE), $(eval $E: $(BIN_DIR)$E ;))
    
    $(BUILD_DIR) $(BIN_DIR) $(DEP_DIR):
        @mkdir -p $@
    
    $(BUILD_DIR)%.o: %.cpp | $(BUILD_DIR) $(DEP_DIR) $(BIN_DIR)
        @$(CXX) $(CXXFLAGS) $(DEPFLAGS)$(@F:.o=.d) -c $< -o $@
    
    $(BIN_DIR)%: $(BUILD_DIR)%.o
        @$(CXX) -o $@ $^
    
    -include $(wildcard $(DEP_DIR)*.d)
    
    keep: $(EXE:%=$(BUILD_DIR)%.o)
    
    clean:
        -@rm -rf $(BIN_DIR)* $(BUILD_DIR)* $(DEP_DIR)*
    

  • 共有2个答案

    蔺弘
    2023-03-14

    我把我的评论变成了一个答案,让其他人不同意这个观点:我认为CMake更适合你。看看这个,看看Make和CMake之间的区别,以及CMake的参数。

    与您的问题相关的优势:

    • 它将让你更容易遵循良好的实践
    • 它的伸缩性好得多
    • 您不必为添加到代码中的新可执行文件编写如此多的样板文件
    • 构建单个可执行文件是可能的,请将此作为提示
    萧辰沛
    2023-03-14
    匿名用户

    主要是你的makefile非常好。你可以做一些简化,但它们只是语法,而不是真正的性能等等。:

    DEP_DIR := .dep/
    

    您永远不会单独使用它,因此如果您将其定义更改为:

    DEP_DIR := $(BUILD_DIR).dep/
    

    你可以简化对它的引用。

    DEPENDS := $(patsubst %.o, $(BUILD_DIR)$(DEP_DIR)%.d, $(notdir $(wildcard $(BUILD_DIR)*.o)))
    
    -include $(DEPENDS)
    

    这似乎很复杂。为什么不摆脱依赖,只写:

    include $(wildcard $(DEP_DIR)*.d)
    

    这是:

    @$(CXX) $(CXXFLAGS) -MMD -MP -MF $(BUILD_DIR)$(DEP_DIR)$(notdir $(basename $@).d) -c $< -o $@
    

    也很复杂。你可以把它(如果你只是DEP_DIR)写成:

    @$(CXX) $(CXXFLAGS) -MMD -MP -MF $(DEP_DIR)$(@F:.o=.d) -c $< -o $@
    

    用于:

    .PRECIOUS: $(BUILD_DIR)%.o
    

    我肯定不会用这个。PRECIOUS应该很少使用,如果有的话。如果您试图避免将目标文件视为中间文件,那么最好直接将它们列为先决条件,例如:

    keep : $(EXE:$(BIN_DIR)%=$(BUILD_DIR)%.o)
    

    但是,除非您有特殊需要查看这些对象文件,否则让make删除它们并没有什么坏处。

    关于你关于捷径的问题:你之所以看到你的行为是因为你的目标定义:

    fileX-test: $(BIN_DIR)fileX-test
    

    没有附加配方,因此make将尝试使用隐式规则查找配方。它可以找到%:%的内置配方。c,因为您设置了vpath,所以它可以找到%。c匹配的文件,因此它使用它。为了避免这种情况,你可以只给出一个空的食谱;将上述内容替换为:

    fileX-test: $(BIN_DIR)fileX-test ;
    

    (注意添加了分号)。

    你的主要问题是如何简化:

    EXE := $(addprefix $(BIN_DIR), file1-test file2-test)
    
    OBJS_1 := $(addprefix $(BUILD_DIR), file1.o)
    OBJS_2 := $(addprefix $(BUILD_DIR), file1.o file2.o)
    
    all: $(EXE)
    
    $(BIN_DIR)file1-test: $(OBJS_1)
    $(BIN_DIR)file2-test: $(OBJS_2)
    

    您可以自动完成这项工作,但这样做需要了解GNU make的深层次部分。你可能会发现这组博客帖子很有趣:http://make.mad-scientist.net/category/metaprogramming/(从最底层/最老的开始,一路向上)。

    将上述内容替换为:

    # Write one of these for each program you need:
    
    file1-test_OBJECTS = file1.o
    file2-test_OBJECTS = file1.o file2.o
    
    # Now everything below here is boilerplate
    
    EXE = $(patsubst %_OBJECTS,%,$(filter %_OBJECTS,$(.VARIABLES)))
    
    all: $(EXE:%=$(BIN_DIR)%)
    
    $(foreach E,$(EXE),$(eval $(BIN_DIR)$E: $($E_OBJECTS)))
    $(foreach E,$(EXE),$(eval $E: $(BIN_DIR)$E ;))
    .PHONY: $(EXE)
    

     类似资料:
    • 我知道,我可能可以用SFINAE和函数重载单独做到这一点。但是,对于像这样的情况,使用SFINAE是一种矫枉过正的做法。 所以我想知道是否有干净的方法来混合模板专业化和sfinae。

    • 问题内容: 我们遇到了一个奇怪的问题,其中似乎有两个线程正在调用,然后在方法内部永远等待。从外部看,内部看起来像是一个僵局。 到目前为止,我们只看到这种情况发生一次。 谁能想到任何可能导致这些症状的东西? 编辑 :相关线程的线程转储在这里: 问题答案: 可能不是您想要的答案,但这可能是JVM错误。看到 http://bugs.sun.com/bugdatabase/view_bug.do?bug_

    • 问题内容: 任何Throwable都可以被捕获 输出: 因此,如果在初始化块期间做不好的事情,我希望能够捕获ExceptionInInitializerError。但是,以下操作无效: 输出: 并且如果我更改代码以另外捕获ArrayIndexOutOfBoundsException 被捕获的是ArrayIndexOutOfBoundsException: 谁能告诉我为什么呢? 问题答案: 顾名思义

    • 问题内容: 我在雄猫服务器(+ liferay)上收到此异常 我的课是这样的: 我在行上收到此异常, 当队列已满但大小为2 ^ 31时,可能会发生此错误,并且我确定没有那么多命令在等待。 一开始一切都稳定,但在我重新部署战争后,一切开始发生。此类不是战争的一部分,而是放在tomcat / lib中的jar中。 您是否知道为什么会发生这种情况以及如何解决? 问题答案: 从ThreadPoolExec

    • 问题内容: 拿这个对象: 如果我这样做: 然后y将返回。通过stringify传递函数有什么能做的吗?使用“ ye goode olde eval()”可以创建具有附加功能的对象,但是打包该对象又是什么呢? 问题答案: 您不能打包函数,因为任何序列化程序都看不到函数关闭的数据。甚至Mozilla 也无法正确打包闭包。 最好的选择是使用复活器和替换器。 https://yuilibrary.com/

    • 问题内容: 例如,如果我有两个对象: 和 我想将x和y值从foo转移到oof。有没有办法使用es6解构语法来做到这一点? 也许像这样: 问题答案: 虽然丑陋且有点重复,但您可以 它将读取对象的两个值,并将它们写入对象上它们各自的位置。 就个人而言,我还是想读 要么 虽然。