Makefile不会创建到正确的文件夹

时间:2014-08-20 20:51:52

标签: c++ makefile gnu-make

我有以下项目目录结构:

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目录中。

2 个答案:

答案 0 :(得分:2)

详细说明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 $@

$<扩展到先决条件列表中的第一个内容(即$(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 :(得分:1)

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

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

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

Gnu make提供了一个 plethora 自动变量用于配方中的替换,每个变量都提供了一些用于形成命令的配方粒子。完全开局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命令切换选项生成的头文件列表。详细描述我超出了这个问题/答案的范围,但绝对要看一看并对该主题进行一些谷歌搜索。在gnu-make和cc的依赖生成器之间,你可以构建一些非常简单的Makefile,它可以自动地为你做大量的工作。

祝你好运。