首页 > 解决方案 > 带有生成的 C++ 源文件的 Makefile

问题描述

我正在使用一个工具来生成我需要包含在我的 C++ 项目编译中的 C++ 源文件。

为了非常简化事情,我的一个非生成源文件,main.cpp如下:

extern void bar();

int main(int, char**)
{
    bar();
    return 0;
}

Makefile的在下面。整体结构是我们项目中已经存在的结构,我只是添加了生成所需源文件bar.cpp和 makefile的部分bar.mk,它添加bar.cppSRCS变量中,因此它(我希望)被包含在编译中:

SRCS := $(CURDIR)/main.cpp

#################### Adding everything between here... #####
BAR_MK := bar.mk

ifeq (,$(findstring clean,$(MAKECMDGOALS)))
include $(BAR_MK)
endif
#################### ...and here ###########################

OBJ_DIR := $(CURDIR)/obj
OBJS := $(patsubst %.cpp,%.o,$(subst $(CURDIR),$(OBJ_DIR),$(SRCS)))

a.out: $(OBJS)
        @echo OBJS == $(OBJS)
        @echo SRCS == $(SRCS)
        $(CXX) $(OBJS)

#################### Adding everything between here... #####
GEN_DIR := $(CURDIR)/gen

# "Generate" my source file(s), add them to SRCS via an external makefile
$(BAR_MK): $(GEN_DIR)/bar.cpp
$(GEN_DIR)/bar.cpp:
        mkdir -p $(dir $@)
        echo "void bar () {}" > $@
        echo SRCS += $@ > $(BAR_MK)
#################### ...and here ###########################

$(OBJ_DIR)/%.o: %.cpp
        mkdir -p $(dir $@)
        $(CXX) -c -o $@ $<

clean:
        rm -rf $(OBJ_DIR)
        rm -f a.out
        rm -rf $(GEN_DIR)
        rm -f $(BAR_MK)

make问题是,由于“未定义对”的引用,我第一次运行时编译失败bar(),但make之后立即运行成功。这是因为在第一次运行时make,当它评估a.out:规则时,它只看到SRCS包含main.cpp,因此bar.cpp不会被编译。然后在第二次(和后续)运行中,SRCS同时包含main.cppand bar.cpp,因此bar.cpp在那时被编译。

这对我来说似乎很奇怪,因为根据“如何重新制作 Makefiles”的GNU Make 手册:

检查完所有 makefile 后,如果有任何实际更改,make 从一个干净的状态开始,并重新读取所有 makefile。

include bar.mk因此,在我第一次调用GNU Make 处理后make,发现它已过时并构建它,我希望它会从头开始,从头开始解析 Makefile,包括bar.mk并且反过来SRCS包含两者main.cppbar.cpp.

我的期望错了吗?或者我的(添加到)Makefile 有什么问题吗?我在 Linux 上使用 GNU Make 4.1。

标签: makefilegnu-make

解决方案


作为目标的规则bar.mk没有配方。所以 make 认为没有办法生成缺失的bar.mk. 但它不会立即失败,它会继续解析主 Makefile,生成bar.cpp......bar.mk但它无法提前知道这一点。结果是bar.mk生成但不包括在内......并且 make 不会失败。不确定这是否应该被视为错误。我填写了一份可能是错误的报告。

无论如何,如果您明确告诉 make 如何构建bar.mk它应该可以工作:

$(BAR_MK): $(GEN_DIR)/bar.cpp
        echo 'SRCS += $<' > $@

$(GEN_DIR)/bar.cpp:
        mkdir -p $(dir $@)
        echo "void bar () {}" > $@

编辑:添加更多评论/替代解决方案

请注意,该$(BAR_MK)规则仍然不完美:它声明了一个不是真实的先决条件。但在删除它之前,我们必须修复其他问题:您的模式规则与生成的源文件不匹配,因为您使用$(CURDIR)它会预先设置完整路径。让我们摆脱它:

SRCS := main.cpp
BAR_MK := bar.mk

ifeq (,$(findstring clean,$(MAKECMDGOALS)))
include $(BAR_MK)
endif

OBJ_DIR := obj
OBJS := $(patsubst %.cpp,$(OBJ_DIR)/%.o,$(SRCS))

a.out: $(OBJS)
    @echo OBJS == $(OBJS)
    @echo SRCS == $(SRCS)
    $(CXX) $(OBJS)

GEN_DIR := gen

$(BAR_MK):
    echo 'SRCS += $(GEN_DIR)/bar.cpp' > $@

$(GEN_DIR)/bar.cpp:
    mkdir -p $(dir $@)
    echo "void bar () {}" > $@

$(OBJ_DIR)/%.o: %.cpp
    mkdir -p $(dir $@)
    $(CXX) -c -o $@ $<

clean:
    rm -rf $(OBJ_DIR) a.out $(GEN_DIR) $(BAR_MK)

最后,如果您真的想生成两者bar.mkgen/bar.cpp使用相同的规则,则有一种可能性,仍然告诉 make:具有多个目标的模式规则

模式规则可能有多个目标。与普通规则不同,这不会像许多具有相同先决条件和配方的不同规则一样起作用。如果模式规则有多个目标,make 知道规则的配方负责生成所有目标。该配方仅执行一次以生成所有目标。在搜索与目标匹配的模式规则时,与需要规则的目标匹配的规则之外的规则的目标模式是偶然的:只需担心为当前有问题的文件提供配方和先决条件。但是,当运行此文件的配方时,其他目标将被标记为已自行更新。

在下面它将匹配模式规则的通配符abar

PATTERN_TARGET := $(subst bar,b%r,$(BAR_MK) $(GEN_DIR)/bar.cpp)
$(PATTERN_TARGET):
    mkdir -p $(GEN_DIR)
    echo "void bar () {}" > $(GEN_DIR)/bar.cpp
    echo 'SRCS += $(GEN_DIR)/bar.cpp' > bar.mk

但这可能太奇怪了,会完全破坏可维护性。更喜欢其他两个选项之一,它们更容易理解。


推荐阅读