Makefile没有创建到正确的文件夹

Makefile does not create to the right folder

本文关键字:文件夹 创建 Makefile      更新时间:2023-10-16

我有以下项目目录结构:

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

在include文件夹: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目录下。

详细说明一下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拼写两次),所以我们通过使用自动变量来减少它:

$(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 $@

我们再次看到冗余——最后三条规则几乎完全相同!我们可以把它们做成一个模式规则:

$(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 $@

我们可以更进一步,但这一天就够了。

缺少编译命令的目标说明符。当执行this时:

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

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

Gnu make提供了一个多个自动变量,用于在配方中进行替换,每个都提供一些配方的微粒,供您在形成命令时使用。请参阅此处了解完整的策略,但我相信您会想要这样的内容:

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

来自Gnu文档:

$@

规则目标的文件名。如果目标是则$@是归档文件的名称。在一个具有多个目标的模式规则(参见模式介绍)规则),$@是引起规则配方的目标的名称待运行

或者类似的东西。

另外,有很多方法可以为您的项目构建自动依赖。一种常见的方法(尽管有点难以遵循)在StackOverflow上给出了这个答案。这个问题本身是基于这篇讨论自动依赖生成技术的论文。

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

祝你好运。