【问题标题】:Makefile does not create to the right folderMakefile 没有创建到正确的文件夹
【发布时间】:2014-08-20 20:51:51
【问题描述】:

我的项目目录结构如下:

drwxr-xr-x+ 1 account Domain Users     0 Aug 20 16:16 ./
drwxr-xr-x+ 1 account Domain Users     0 Aug 20 08:48 ../
drwxr-xr-x+ 1 account Domain Users     0 Aug 20 10:55 include/
-rw-r--r--  1 account Domain Users   597 Aug 20 16:16 Makefile
drwxr-xr-x+ 1 account Domain Users     0 Aug 20 10:55 obj/
drwxr-xr-x+ 1 account Domain Users     0 Aug 20 10:55 src/

在 src 文件夹中:main.cpp foo.cpp bar.cpp

在包含文件夹中:foo.h bar.h

当我运行以下 Makefile 时,会在项目目录中创建目标文件,而不是像我对 Makefile 所期望的那样:obj 目录:

1 ODIR = obj
2 SDIR = src
3 IDIR = include
4
5 _OBJS = main.o foo.o bar.o
6 OBJS = $(patsubst %,$(ODIR)/%,$(_OBJS))
7
8 CC = g++
9
10 CFLAGS = -w
11
12 PROG = program
13
14 #VPATH = src:include:obj:../src:../include:../obj
15
16 all: $(OBJS)
17     $(CC) $(CFLAGS) -I$(IDIR) $(OBJS) -o $(PROG)
18
19 $(ODIR)/main.o: $(SDIR)/main.cpp $(IDIR)/foo.h $(IDIR)/bar.h
20     $(CC) $(CFLAGS) -I$(IDIR) -c $(SDIR)/main.cpp
21
22 $(ODIR)/foo.o: $(SDIR)/foo.cpp $(IDIR)/foo.h
23     $(CC) $(CFLAGS) -I$(IDIR) -c $(SDIR)/foo.cpp
24
25 $(ODIR)/bar.o: $(SDIR)/bar.cpp $(IDIR)/bar.h
26     $(CC) $(CFLAGS) -I$(IDIR) -c $(SDIR)/bar.cpp
27
28 clean:
29     rm -f $(PROG) $(OBJS)

有人能告诉我为什么会这样吗?我的链接器看不到目标文件,因为它认为这些文件位于 obj 目录中。

【问题讨论】:

  • -o $@ 似乎从您的每个 .o 规则的标志中丢失。如$(CC) $(CFLAGS) -I$(IDIR) -c -o $@ $(SDIR)/main.cpp。有关其他方便的变量,请参阅 Automatic variables in Gnu make
  • 试试remake-x
  • 在您遵循@WhozCraig 的建议(应该作为答案)之后,您可以将三个对象规则($(ODIR)/main.o$(ODIR)/foo.o$(ODIR)/bar.o)合并为一个规则。跨度>
  • @WhozCraig 谢谢!这行得通!你能把它变成一个答案,以便我接受吗?并为其他新手详细说明(%@ 包含整个路径+文件,而省略它意味着只是文件)
  • @Beta 我如何将它们合并为一个规则?它们有不同的依赖关系,main 依赖于 [foo.h, bar.h],foo 依赖于 [foo.h],bar 依赖于 [bar.h]。 (如您所见,我对不使用 IDE 的自编译还是很陌生,像这样的东西让我很困惑)

标签: c++ makefile gnu-make


【解决方案1】:

详细说明 WhozCraig 的回答:

从这条规则开始:

$(ODIR)/main.o: $(SDIR)/main.cpp $(IDIR)/foo.h $(IDIR)/bar.h
    $(CC) $(CFLAGS) -I$(IDIR) -c $(SDIR)/main.cpp

命令 ($(CC) ...) 没有指定输出文件,因此他的编译器会将生成的目标文件 (main.o) 放在工作目录中,也就是您调用 Make 的目录,即项目目录。 请注意,Make 和编译器之间的交流并不多。 编译器知道它应该扫描哪些源文件,但没有人告诉它有关 obj/ 的任何信息,因此它推断目标文件名(main.o) 来自源文件的名称; Make 知道目标应该是obj/main.o,但它不知道编译器实际上在做什么。 (事实上​​,它并不知道它正在执行的命令调用了一个编译器。)

我们可以通过在命令中添加一个术语,指定目标文件名来解决这个问题:

$(ODIR)/main.o: $(SDIR)/main.cpp $(IDIR)/foo.h $(IDIR)/bar.h
    $(CC) $(CFLAGS) -I$(IDIR) -c $(SDIR)/main.cpp -o $(ODIR)/main.o

现在编译器会将main.o 放在正确的位置——而Make 并没有发现任何区别,它仍然只是将命令传递给shell。

我们注意到这条规则中有很多冗余(例如$(ODIR)/main.o 拼写了两次),因此我们使用automatic variables 减少它:

$(ODIR)/main.o: $(SDIR)/main.cpp $(IDIR)/foo.h $(IDIR)/bar.h
    $(CC) $(CFLAGS) -I$(IDIR) -c $< -o $@

其中$&lt; 扩展为先决条件列表中的第一件事(即$(SDIR)/main.cpp),$@ 扩展为目标名称(即$(ODIR)/main.o)。出于稍后将变得清楚的原因,我们将标头依赖项拆分为针对同一目标的单独规则:

$(ODIR)/main.o: $(IDIR)/foo.h $(IDIR)/bar.h

$(ODIR)/main.o: $(SDIR)/main.cpp $(IDIR)/foo.h $(IDIR)/bar.h
    $(CC) $(CFLAGS) -I$(IDIR) -c $< -o $@

Yes, we can do that.

现在我们看看对象规则:

$(ODIR)/main.o: $(IDIR)/foo.h $(IDIR)/bar.h
$(ODIR)/foo.o: $(IDIR)/foo.h
$(ODIR)/bar.o: $(IDIR)/bar.h

$(ODIR)/main.o: $(SDIR)/main.cpp
    $(CC) $(CFLAGS) -I$(IDIR) -c $< -o $@

$(ODIR)/foo.o: $(SDIR)/foo.cpp
    $(CC) $(CFLAGS) -I$(IDIR) -c $< -o $@

$(ODIR)/bar.o: $(SDIR)/bar.cpp
    $(CC) $(CFLAGS) -I$(IDIR) -c $< -o $@

我们再次看到冗余——最后三个规则几乎相同!我们可以把它们做成一个pattern rule

$(ODIR)/main.o: $(IDIR)/foo.h $(IDIR)/bar.h
$(ODIR)/foo.o: $(IDIR)/foo.h
$(ODIR)/bar.o: $(IDIR)/bar.h

$(ODIR)/%.o: $(SDIR)/%.cpp
    $(CC) $(CFLAGS) -I$(IDIR) -c $< -o $@

我们可以走得更远,但一天就够了。

【讨论】:

  • +1 完全值得为额外的工作而投票。我还没有主动提出完整的依赖生成挑战,但这更近了一步。干得好。
  • @WhozCraig:谢谢。完整的依赖生成挑战是一件美丽的事情,但 OP 还没有准备好;第一次接触它甚至会给经验丰富的用户带来打击。
【解决方案2】:

缺少编译命令的目标说明符。例如:执行此操作时:

 $(CC) $(CFLAGS) -I$(IDIR) -c $(SDIR)/main.cpp

没有目标,所以默认是当前工作目录(在你的情况下,你的 Makefile 所在的根文件夹。

Gnu make 提供了大量 用于在配方中替换的自动变量,每个变量都提供配方的一些粒子供您在形成命令时使用。 See here 完整的开局,但我相信你会想要这样的东西:

 $(CC) $(CFLAGS) -I$(IDIR) -c -o $@ $(SDIR)/main.cpp

来自 Gnu 文档:

$@

规则目标的文件名。如果目标是一个 存档成员,则$@ 是存档文件的名称。在一个 具有多个目标的模式规则(请参阅模式简介 规则),$@ 是导致规则配方的目标的名称 运行。

或类似的东西。

另外,有多种方法可以为您的项目构建自动依赖项。 in this answer on StackOverflow 介绍了一种常见的方法(尽管有点难以理解)。问题本身基于this paper discussing automatic dependency generation techniques

它基本上归结为为每个源文件生成一个.d 文件,在.d 文件中是通过-MMD 命令开关选项生成的头文件列表。详细描述 i 超出了这个问题/答案的范围,但一定要看看并在这个主题上做一些谷歌搜索。在 gnu-make 和 cc 的依赖生成器之间,您可以构建一些非常简单的 Makefile,它们会自动为您完成大量工作。

祝你好运。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-09-03
    • 1970-01-01
    • 2015-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-03
    • 1970-01-01
    相关资源
    最近更新 更多