【问题标题】:Makefile: no rule to make targetMakefile:没有规则来制作目标
【发布时间】:2013-04-11 15:45:33
【问题描述】:

我一直在这个网站上寻找解决方案,并且现在也尝试了谷歌一段时间,但不知何故我无法让它工作。

我的源文件应该在 src 目录中,而目标文件应该在 obj 目录中。现在我尝试创建一个简单的 makefie,但我得到一个没有规则的错误,或者我无法使用目录。

CC = /usr/bin/gcc
CXXFLAGS =  -O2 -g -Wall -fmessage-length=0

SRC:=       nohupshd.cpp \
            task.cpp

OBJ:=       nohupshd.o \
            task.o

OBJDIR:=        obj
SRCDIR:=        src

DEP:=       src/task.h
LIBS:=

TARGET:=    nohupshd

all:    $(TARGET)

$(TARGET):  $(OBJ)
    $(CC) -o $(TARGET) $(OBJ) $(LIBS)

clean:
    rm -f $(OBJ) $(TARGET)

变体 1:

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
    $(CC) -S $(SRCDIR)/$< -o $(OBJDIR)/$@
    $(CC) -c $(SRCDIR)/$< -o $(OBJDIR)/$@

变体 1a:

%.o: %.cpp
    $(CC) -S $(SRCDIR)/$< -o $(OBJDIR)/$@
    $(CC) -c $(SRCDIR)/$< -o $(OBJDIR)/$@

当我使用这种模式时,我总是得到一个错误,即 nohupshd.o 没有规则来构建。

变体 2:

$(OBJ) : $(OBJDIR)/%.o: $(SRCDIR)/%.cpp
    $(CC) -S $(SRCDIR)/$< -o $(OBJDIR)/$@
    $(CC) -c $(SRCDIR)/$< -o $(OBJDIR)/$@

当我使用此变体时,我可以看到它尝试构建,但我收到错误消息,提示“文件”.o 不符合目标模式。

另一个问题是“$

更新:

与此同时,我的最新版本如下所示:

$(OBJDIR)/$(OBJ) : $(OBJDIR)/%.o : $(SRCDIR)/%.cpp
    $(CC) -S $< -o $(OBJDIR)/`basename $@ .o`.asm
    $(CC) -c $< -o $@

现在可以编译第一个对象文件 (nohupshd.o),但是当 make 尝试编译第二个文件时,它再次失败,提示:目标 'task.o' 与模式不匹配。

【问题讨论】:

  • 至少现在我理解了真正问题的一部分。目标文件位于 obj 目录中。当我将其作为依赖项 $(OBJDIR)/$(OBJ) 时,它会扩展为 obj/o1.o o2.o ... 而它应该是 obj/o1.o obj/o2.o ...,那么我真的必须在每个单独的文件上指定 obj 路径吗?

标签: c++ c build makefile


【解决方案1】:

你实际上有几个如果不正确的话。

首先你写 我的错误是,我假设模式 %.o 匹配任何以 .o 结尾但它不匹配的模式;这不是真的。模式确实 匹配任何以.o 结尾的字符串。但是,在目标端匹配的模式字符% 在先决条件端被相同的字符串替换。因此,如果您有一个目标 obj/task.o 并且它与模式 %.o 匹配,那么 stem (手册称之为)将是 obj/task,并且当先决条件是 %.c 时,这意味着该 make 将寻找先决条件obj/task.c。由于没有一个,并且 make 不知道如何构建一个,因此该规则被丢弃为不适用。在编写模式规则时,您必须将它们写成 ONLY 名称的相同部分匹配模式字符 (%)。必须明确指定所有不相同的部分,包括目录。

其次,$(OBJ) : $(SRC) 的规则确实不对。该行表示每个目标文件都依赖于所有源文件,因此每当任何单个源文件更改all 时,目标文件都将被重新编译。这真的不是你想要的(如果那是你想要的,你不需要 make:你可以写一个简单的 shell 脚本)。我不知道您所说的 是什么意思,因为规则是空的,它会调用模式规则;您不需要它来调用模式规则。目标依赖于$(OBJ),每个目标文件依赖于它的源文件(由于模式)。你根本不需要这条线。

第三,我不知道您为什么要尝试构建 .asm 文件,而不是直接从源代码编译到对象,但如果您真的想要它们,创建一个单独的构建它们的模式规则:创建一个模式规则$(OBJDIR)/%.o : $(OBJDIR)/%.asm 和一个规则$(OBJDIR)/%.asm : $(SRCDIR)/%.c。如果您希望 ASM 文件成为构建的产品,您应该将它们声明为 all 或类似的先决条件,否则它们将作为中间文件被删除。

第四,没有必要使用basename 之类的东西。有很多自动生成变量可以代替使用。例如,$* 扩展为词干,因此您可以写成 $(OBJDIR)/$*.asm。当然,如果您为 ASM 文件制定单独的模式规则,您可以直接使用 $@ 或 $&lt;。还可以使用各种make函数;请参阅手册。

第五,你定义了一个包含头文件的变量,DEP,但从不使用它。因为它没有被使用,所以如果您更改该文件,则不会重建任何内容。如果您知道所有源文件都包含每个标头,您可以使用$(OBJ) : $(DEP) 来定义它;但这确实意味着(如上面的第二点)对任何标头的任何更改都会导致所有对象重新编译。您最好自动生成先决条件;因为你使用的是 GCC,所以这很简单。

第六,您使用的是 C++ 文件 (xxx.cpp),但您使用的是 C 编译器。这将不起作用(链接行将失败:尽管编译器可以看到您正在编译 C++ 文件并做正确的事情,即使您调用 gcc,但当您将一堆对象链接在一起时,它不知道这些对象是否是C 对象或 C++ 对象(或 FORTRAN 或其他),因此您必须使用 C++ 前端进行链接,否则它不会引入正确的 C++ 库)。您应该使用 make 变量 CXX 来构建 C++ 代码,而不是 CC,并将其设置为 g++ 而不是 gcc。

第七,你不需要.SUFFIXES: .c .o 来使用模式规则。只有后缀规则才需要它们,而这里没有。您可以保留简单的.SUFFIXES:,但禁用内置模式匹配,这会稍微提高性能。

最后,您会注意到您实际上并不需要 $(SRC) 变量,因为 make 可以从模式规则中推断出它。但是,如果您想让您的 makefile 更轻松地更改,您可以从 SRC 变量构造 OBJ 变量的内容,例如 SRC = nohupshd.cpp task.cpp 然后 OBJ = $(patsubst %.c,$(OBJDIR)/%.o,$(SRC))。

所以,总而言之,这就是我建议您编写 makefile 的方式(尽管我不包括这里自动生成的依赖项):

.SUFFIXES:

CXX :=      g++
CXXFLAGS := -O2 -g -Wall -fmessage-length=0

OBJDIR :=   obj
SRCDIR :=   src

TARGET :=   nohupshd
SRC :=      nohupshd.cpp task.cpp
DEP :=      src/task.h
LIBS :=

# ----

OBJ :=      $(patsubst %.cpp,$(OBJDIR)/%.o,$(SRC))
ASM :=      $(patsubst %.cpp,$(OBJDIR)/%.asm,$(SRC))

.PHONY: all clean

all: $(TARGET) $(ASM)

$(TARGET): $(OBJ)
        $(CXX) -o $@ $^ $(LIBS)

clean:
        rm -f $(OBJDIR)/* $(TARGET)

$(OBJDIR)/%.o : $(SRCDIR)/%.asm
        $(CXX) $(CXXFLAGS) -c -x assembler-with-cpp $< -o $@

$(OBJDIR)/%.asm : $(SRCDIR)/%.cpp
        $(CXX) $(CPPFLAGS) -S $< -o $@

【讨论】:

  • 感谢您对模式的解释。这真的很有帮助。在您的示例中,有一个小错误,因为您在模式替换中编写了“.c”而不是“.cpp”,但这是一个小问题。我留下的 asm 原样,因为我不想为此制定单独的规则。我更喜欢生成 asm 输出,所以我可以检查编译器到底在做什么。 :) 大多数用户可以简单地删除它。如果我要创建一个单独的规则,我将不得不在单独的步骤中编译为 asm 到对象,这在这里没有意义。我现在用工作版本更新我上面的帖子。谢谢!
  • 当然可以随心所欲。您的解决方案的问题是您显着增加了构建时间,因为您将大部分编译器工作运行了两次。一旦程序集可用,几乎不需要将其转换为目标文件:大部分工作都在预处理、解析,尤其是优化以生成程序集。如果您想要汇编输出,最有效的方法是首先生成汇编,然后将 汇编 编译到目标文件中。
【解决方案2】:

不要在编译器行中重复目录名称。 $&lt; 和 $@ 已经有了目录名。

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp
    $(CC) -S $< -o $@
    $(CC) -c $< -o $@

【讨论】:

  • 是的,我在此期间发现了这一点,但这并不能解决其余问题。
  • 您必须更改 OBJ 以便目标文件具有 $(OBJDIR) 前缀。您不是要求构建 obj/task.o,而是要求构建 task.o。 Make 不会猜测您可能真的指的是其他文件。如果你想构建一个文件,那么目标必须是 that 文件名,完全正确。并且前提条件必须是那个文件名,完全正确。
  • 是的,经过大量的摆弄,我终于发现了makefile的样子。不幸的是,我找到了很多例子,但都过于简单或过于复杂。我很惊讶这样一个常见的事情在现实世界的工作示例中并不容易找到。
【解决方案3】:

所以最后我找到了如何编写这个makefile的答案,为了解释我的错误,请查看我标记为正确答案的帖子:

生成的 makefile 看起来像这样,为了完整起见,我将其发布在此处,包括头文件的依赖项(如果不需要,请删除 ASM 部分):

.SUFFIXES:
.SUFFIXES: .o .cpp
.SUFFIXES: .o .d

CC := g++
LNK:= ar
CXXFLAGS =  -O2 -g -Wall -fmessage-length=0

OBJDIR:=        obj
SRCDIR:=        src
HDIR:=      include

INCLUDE_PATHS:= -Iinclude -Iinclude/interfaces -Iinclude/support

CPP_FILES := propertyfile/propertyfile.cpp \
            propertyfile/propertyitem.cpp \
            propertyfile/propertyfactory.cpp

OBJ :=  $(patsubst %.cpp,$(OBJDIR)/%.o, $(CPP_FILES))
SRC :=  $(patsubst %.cpp,$(SRCDIR)/%.o, $(CPP_FILES))
ASM :=      $(patsubst %.cpp, $(OBJDIR)/$*.asm, $(CPP_FILES))

LIBS:=      

TARGET:=    libsupport.a

all:    $(TARGET)

$(TARGET):  $(OBJ)
    @echo "Linking..."
    @$(LNK) rvs $(TARGET) $(OBJ)
    @cp $(TARGET) ../lib
    @cp -r include ..

clean:
    rm -f $(OBJ) $(ASM) $(TARGET)

-include $(patsubst %.cpp,$(OBJDIR)/%.d, $(CPP_FILES))

$(OBJDIR)/%.o: $(SRCDIR)/%.cpp $(OBJDIR)/%.d 
    @mkdir -p `dirname $@`
    $(CC) $(CXXFLAGS) -S $< -o $(OBJDIR)/$*.asm $(INCLUDE_PATHS)
    $(CC) $(CXXFLAGS) -c $< -o $@ $(INCLUDE_PATHS)

$(OBJDIR)/%.d: $(SRCDIR)/%.cpp 
    $(CC) $(CXXFLAGS) -MM -MT $@ -MF $(OBJDIR)/$*.d -c $< $(INCLUDE_PATHS)  

我希望这可以帮助其他用户。我发现的所有示例要么非常简单,并且单独列出了多个文件,而不是规则的一部分,但并没有真正解释它是如何工作的,或者太复杂以至于我无法找到它对我的帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-15
    • 2010-10-24
    相关资源
    最近更新 更多