【问题标题】:rule to call another Makefile from inside a Makefile从 Makefile 内部调用另一个 Makefile 的规则
【发布时间】:2019-01-09 23:12:21
【问题描述】:

我有一个Makefile,如果某些对象文件尚不存在,我会从该Makefile 运行另一个Makefile 的命令。

规则如下:

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/%.c $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

其中变量在同一个Makefile中定义如下:

COMMONDIR     := ../common
SOURCESCOMMON := $(wildcard $(COMMONDIR)/*.c)
OBJDIRCOMMON  := $(COMMONDIR)/obj
OBJECTSCOMMON := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.o, $(SOURCESCOMMON))
DEPENDSCOMMON := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.d, $(SOURCESCOMMON))

这条规则运行良好,但归根结底,该规则唯一需要的真正输入是另一个 Makefile,所以我尝试了:

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

但这不起作用,这是为什么呢?

为了完整起见,这里是完整的Makefile

CC = gcc

INC_PATH = -I../common/

SOURCEDIR := ./
SOURCES := $(wildcard $(SOURCEDIR)/*.c)
OBJDIR  :=./obj
OBJECTS := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.o, $(SOURCES))
DEPENDS := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.d, $(SOURCES))

COMMONDIR     := ../common
SOURCESCOMMON := $(wildcard $(COMMONDIR)/*.c)
OBJDIRCOMMON  := $(COMMONDIR)/obj
OBJECTSCOMMON := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.o, $(SOURCESCOMMON))
DEPENDSCOMMON := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.d, $(SOURCESCOMMON))

# ADD MORE WARNINGS!
WARNING := -Wall -Wextra

# OBJS_LOC is in current working directory,
EXECUTABLE := ../server
# .PHONY means these rules get executed even if
# files of those names exist.
.PHONY: all clean

# The first rule is the default, ie. "make",
# "make all" and "make parking" mean the same
all: $(EXECUTABLE)

clean:
    $(RM) $(OBJECTS) $(DEPENDS) $(EXECUTABLE)

# Linking the executable from the object files
# $^   # "src.c src.h" (all prerequisites)
$(EXECUTABLE): $(OBJECTS) $(OBJECTSCOMMON)
    $(CC) $(WARNING) $^ -o $@

-include $(DEPENDS) $(DEPENDSCOMMON)

$(OBJDIR):
    mkdir -p $(OBJDIR)

$(OBJDIR)/%.o: $(SOURCEDIR)/%.c Makefile | $(OBJDIR)
    $(CC) $(WARNING) -MMD -MP -c $(INC_PATH) $< -o $@

$(OBJDIRCOMMON):
    mkdir -p $(OBJDIRCOMMON)

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/%.c $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

编辑

我得到的错误是这样的:

Entering directory '/home/user/Documents/UnixSystem/network/common'
gcc -Wall -Wextra -MMD -MP -c utilities.c -o obj/utilities.o
gcc -Wall -Wextra -MMD -MP -c error.c -o obj/error.o
make[2]: Leaving directory '/home/user/Documents/UnixSystem/network/common'
gcc   ../common/obj/error.d.o   -o ../common/obj/error.d
gcc: error: ../common/obj/error.d.o: No such file or directory
gcc: fatal error: no input files
compilation terminated.

据我了解,另一个Makefile 的执行是成功的。但是在那之后它试图执行这个命令gcc ../common/obj/error.d.o -o ../common/obj/error.d这是错误的,但我不知道哪个规则以及它为什么会生成它。

【问题讨论】:

  • 为了完整起见,请解释一下您的意思 ...这不起作用
  • 查看编辑
  • 使用remake-x 来调试此类问题
  • 我建议你在你的项目中使用这个:github.com/igagis/prorab

标签: c makefile compilation


【解决方案1】:

为什么你做错了

配方A

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/%.c $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

和配方B

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

具有本质不同的含义,并且肯定不会产生相同的行为。

食谱A说:

  1. 任何目标$(OBJDIRCOMMON)/file.o 如果不存在,则必须更新 或早于$(COMMONDIR)/file.c$(COMMONDIR)/Makefile
  2. 如果目标$(OBJDIRCOMMON)/file.o 必须是最新的,那么$(OBJDIRCOMMON) 必须首先更新。
  3. 要使目标 $(OBJDIRCOMMON)/file.o 保持最新,请在 shell 中运行 $(MAKE) -C $(COMMONDIR) 的扩展。

食谱B说:

  1. 任何目标$(OBJDIRCOMMON)/file.o 如果不存在,则必须更新 或早于$(COMMONDIR)/Makefile
  2. 如果目标$(OBJDIRCOMMON)/file.o 必须是最新的,那么$(OBJDIRCOMMON) 必须首先更新。
  3. 要使目标 $(OBJDIRCOMMON)/file.o 保持最新,请在 shell 中执行 $(MAKE) -C $(COMMONDIR) 的扩展。

请注意,标准 A.1 与标准 B.1 不同。配方 A 将执行 如果$(OBJDIRCOMMON)/file.o 早于$(OBJDIRCOMMON)/file.c。食谱 B 不会。 配方 B 丢弃了目标文件对相应源文件的依赖, 并告诉 Make $(OBJDIRCOMMON)/file.o 只有在 早于$(COMMONDIR)/Makefile

归根结底,规则要求的唯一真正输入是另一个 Makefile

这里所说的“规则”实际上是命令行(扩展自)$(MAKE) -C $(COMMONDIR)。 该命令的输入是一回事;执行它的标准是另一个。

您的操作如何导致您看到的错误

这更棘手。让我们重现它。

这是一个围栏:

$ ls -R
.:
app  common

./app:
foo.c  main.c  Makefile

./common:
bar.c  Makefile

这里,./app/Makefile 正是你的 Makefile 和配方 A./common/Makefile, 您没有发布的只是:

obj/bar.o: bar.c
    gcc -MMD -MP -c -I. $< -o $@

因为这将用于说明。

我们建立在./app:

$ cd app
$ make
mkdir -p ./obj
gcc -Wall -Wextra -MMD -MP -c -I../common/ foo.c -o obj/foo.o
gcc -Wall -Wextra -MMD -MP -c -I../common/ main.c -o obj/main.o
mkdir -p ../common/obj
make -C ../common
make[1]: Entering directory '/home/imk/develop/so/make_prob/common'
gcc -MMD -MP -c -I. bar.c -o obj/bar.o
make[1]: Leaving directory '/home/imk/develop/so/make_prob/common'
gcc -Wall -Wextra obj/foo.o obj/main.o ../common/obj/bar.o -o ../server

这很好。

现在我像你一样更改./app/Makefile,使用配方B,然后重建。

$ make
gcc -Wall -Wextra -MMD -MP -c -I../common/ foo.c -o obj/foo.o
gcc -Wall -Wextra -MMD -MP -c -I../common/ main.c -o obj/main.o
gcc -Wall -Wextra obj/foo.o obj/main.o ../common/obj/bar.o -o ../server

还好……但请稍等!那个没有调用./common make 根本上,这是更改可能会影响的那个。更好clean

$ make clean
rm -f ./obj/foo.o ./obj/main.o ./obj/foo.d ./obj/main.d ../server

再试一次:

$ make
gcc -Wall -Wextra -MMD -MP -c -I../common/ foo.c -o obj/foo.o
gcc -Wall -Wextra -MMD -MP -c -I../common/ main.c -o obj/main.o
gcc -Wall -Wextra obj/foo.o obj/main.o ../common/obj/bar.o -o ../server

没有区别?啊,那是因为这个Makefile的clean没有全部删除 make 构建的文件:它忽略了 ../common/obj/bar.o。所以我会:

$ rm ../common/obj/*

再来一次:

$ make
make -C ../common
make[1]: Entering directory '/home/imk/develop/so/make_prob/common'
gcc -MMD -MP -c -I. bar.c -o obj/bar.o
make[1]: Leaving directory '/home/imk/develop/so/make_prob/common'
gcc   ../common/obj/bar.d.o   -o ../common/obj/bar.d
gcc: error: ../common/obj/bar.d.o: No such file or directory
gcc: fatal error: no input files
compilation terminated.

这是你的谜。

当我删除 ../common/obj 文件时,我不仅删除了其中的所有 object 文件 还有依赖文件../common/obj/bar.d。现在 Make 正在尝试通过运行来重新制作它:

gcc   ../common/obj/bar.d.o   -o ../common/obj/bar.d

怎么会?为了回答这个问题,我们首先将 ./app/Makefile 改回使用配方 A - 认为它完成了 - 然后做:

$ make --print-data-base > out.txt

out.txt 中转储 Make 从阅读所有内容中收集的所有信息 makefiles (Makefile 和所有它递归的 makefiles include-s, 在这种情况下,只是自动生成的 .d 文件)。

让我们看看数据库对../common/obj/bar.d 的评价。它说:

# Not a target:
../common/obj/bar.d:
# Implicit rule search has been done.
# Last modified 2019-01-11 16:01:33.199263608
# File has been updated.
# Successfully updated.

当然我们不希望../common/obj/bar.d 成为目标,而且它不是 目标,因为在阅读了所有的 makefile 并考虑了它的所有内置规则后, 以及它实际上可以找到的所有文件,Make 看不到 ../common/obj/bar.d 的任何方式 必须就这些文件中的任何一个进行更新。很好。

现在让我们再次回到 ./app/Makefile 中的配方 B - 考虑完成 - 再做一次:

$ make --print-data-base > out.txt

再次查看out.txt../common/obj/bar.d 相关的内容。这次我们发现:

../common/obj/bar.d: ../common/obj/bar.d.o
# Implicit rule search has been done.
#  Implicit/static pattern stem: '../common/obj/bar.d'
# Last modified 2019-01-11 16:01:33.199263608
# File has been updated.
# Successfully updated.
#  recipe to execute (built-in):
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

所以这次../common/obj/bar.d 一个目标!这取决于../common/obj/bar.d.o! 制作它的配方是:

    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

当然会扩展为:

gcc   ../common/obj/bar.d.o   -o ../common/obj/bar.d

感谢配方B,Make 是如何解决这个问题的?

首先它考虑了 makefile 中的任何规则或任何 内置规则提供了直接从任何现有文件生成 ../common/obj/bar.d 的方法, 并画了一个空白。

接下来它考虑了这些规则中的任何一条是否给了它一种方法 从intermediate file 生成../common/obj/bar.d中间文件是一个不存在但它自己可以制作的文件 从现有文件中,通过它已读取的任何规则或其内置规则。这 是时候看到方法了。

Make 的内置模式规则之一是:

%: %.o
#  recipe to execute (built-in):
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

您可以在out.txt 中找到它。你可以看到这是模式规则 它匹配:

../common/obj/bar.d: ../common/obj/bar.d.o

配方有一个配方可以链接一个程序,名为../common/obj/bar.d given 一个目标文件../common/obj/bar.d.o

没有目标文件../common/obj/bar.d.o,但它可以是一个中间文件吗?如果 Make 可以从 确实 存在的文件中找到生成 ../common/obj/bar.d.o 的规则, 那么它也可以用这个%: %.o规则生成../common/obj/bar.d

可以从现有文件中找到制作../common/obj/bar.d.o 的配方 因为我们只给了它一个! - 配方B

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

这告诉 Make 是否有任何目标匹配 $(OBJDIRCOMMON)/%.o(如 ../common/obj/bar.d.o) 不存在,但$(COMMONDIR)/Makefile 确实存在(确实存在),那么那个目标 通过运行更新:

$(MAKE) -C $(COMMONDIR)

让我们相信。它跑了$(MAKE) -C $(COMMONDIR)

make -C ../common
make[1]: Entering directory '/home/imk/develop/so/make_prob/common'
gcc -MMD -MP -c -I. bar.c -o obj/bar.o
make[1]: Leaving directory '/home/imk/develop/so/make_prob/common'

然后认为../common/obj/bar.d.o 是最新的。所以它转移到:

../common/obj/bar.d: ../common/obj/bar.d.o
    $(LINK.o) $^ $(LOADLIBES) $(LDLIBS) -o $@

然后跑了:

gcc   ../common/obj/bar.d.o   -o ../common/obj/bar.d

因为我们撒谎而失败:

make -C ../common

根本不会生成../common/obj/bar.d.o

gcc: error: ../common/obj/bar.d.o: No such file or directory

配方 A 不会出现这种情况,因为

$(OBJDIRCOMMON)/bar.d.o: $(OBJDIRCOMMON)/bar.d.c $(COMMONDIR)/Makefile | $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

是否提供了从现有文件制作$(OBJDIRCOMMON)/bar.d.o的方法, 因为$(OBJDIRCOMMON)/bar.d.c 不存在。所以../common/obj/bar.d 不是 一个目标。

坚持配方A,因为它是正确的,配方B是错误的。还回顾 并修复生成文件,以便make clean 始终删除所有可能已构建的非.PHONY 目标,仅此而已。最后,避免使用非.PHONY 目标编写配方,其中配方未提及目标。

【讨论】:

    【解决方案2】:

    应该有&&而不是管道

          $(OBJDIRCOMMON)/%.o: $(COMMONDIR)/Makefile | (OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR
    

    【讨论】:

    猜你喜欢
    • 2011-01-13
    • 1970-01-01
    • 1970-01-01
    • 2014-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多