我有以下项目目录结构:
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目录中。
答案 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 $@
现在我们来看一下对象规则:
$(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,它可以自动地为你做大量的工作。
祝你好运。