Makefiles:他们如何确定构建目标?

Makefiles: How do they determine build targets?

我正在 运行在一个继承的 makefile 中发现有趣的行为,我正试图为我当前的项目扩展它。

LIBS := lib/precompiled1.a lib/precompiled2.a lib/mylib1.a lib/mylib2.a

mylib1-src := (source directory)
mylib1-obj := $(patsubst $(mylib1-src)/%.c, obj/LIB1/%.o, $(wildcard $(mylib1-src)/*.c))

mylib2-src := (source directory)
mylib2-obj := $(patsubst $(mylib2-src)/%.cpp, obj/LIB2/%.o, $(wildcard $(mylib2-src)/*.cpp))

default: all

lib/mylib1.a: $(mylib1-obj)
    ar -r $@ $^

lib/mylib2.a: $(mylib2-obj)
    ar -r $@ $^

all: exe/my_out

exe/my_out: $(my_out-obj)
    g++ $^ $(COMMON_FLAGS) $(LIBS) $(MY_FLAGS)

当我 运行 make 时,我 运行 进入了一个错误,它不能 link 反对 lib/mylib1.a,然后它就崩溃了。很酷,我只需要确保正在构建 .a 目标。像这样天真地摆弄 makefile:

lib/mylib1.a: $(mylib1-obj)
    ar -r $@ $^

lib/mylib2.a: $(mylib2-obj)
    ar -r $@ $^

default: all

现在编译 mylib1.a 就好了,但既不编译 mylib2.a 也不编译 'all'。

这就是我卡住的地方。如果我执行以下操作:

default: all

lib/mylib1.a: $(mylib1-obj)
    ar -r $@ $^

lib/mylib2.a: $(mylib2-obj)
    ar -r $@ $^

all: lib/mylib1.a exe/my_out

然后编译器抱怨 lib/mylib1.a 不是可执行文件。它不是,很好,但让我陷入了一个奇怪的境地。我可以单独编译 .a 文件。我可以 link 它们针对可执行文件。我不能一次完成所有这些事情,只能通过弄乱 makefile,这有点违背了重点。

我的问题是,为什么更改默认顺序会改变构建的内容?我怎样才能得到所有的 .a 文件来构建和 link 针对主要的可执行文件而不是每次都手动弄乱 makefile?总的来说,我对 makefile 还很陌生,我不确定为什么会出现这种情况。

谢谢!

默认目标是Makefile中的第一个目标,名为'default'的目标没有特殊意义。

对于最新版本的 Make,可以像这样明确指定默认目标:

DEFAULT_GOAL := 全部

应将库添加为可执行文件的先决条件,而不是添加到所有目标,例如:

exe/my_out: $(my_out-obj) lib/mylib1.a lib/mylib2.a
    g++ $^ $(COMMON_FLAGS) $(LIBS) $(MY_FLAGS)

lib/mylib1.a: $(mylib1-obj)
    ar -r $@ $^

lib/mylib2.a: $(mylib2-obj)
    ar -r $@ $^

正如安迪建议的那样,通过执行此操作(通常靠近 makefile 的顶部)强制默认规则为 all:

.DEFAULT_GOAL = all

然后全部定义为:

LIB_DEPS = lib/mylib1.a lib/mylib2.a
all: exe/my_out $(LIB_DEPS)

# Also to ensure that the libs are built first (for example with parallel builds):
exe/my_out: $(my_out-obj) $(LIB_DEPS)
    ... recipe here...

这样,如果您执行 make 或 make -j,您的构建顺序将得到保证。

注意 我没有看到 $(my_out-obj) 的规则 - 你错过了还是没有打印出来?

makefile 中目标的顺序无关紧要,但有一个例外:第一个是默认目标,即当您在没有目标的情况下调用 make 时工作的目标。可以在 GNU make 中使用 .DEFAULT_GOAL := ...

更改此行为

依赖顺序

串行构建 (make -j1) 中重要的是目标定义中依赖项的顺序,例如

T: A B C

T: A B
T: C
    rule

在串行构建中 make 将首先满足 "A",然后是 "B",然后是 "C"。如果 "C" 缺少在 "A" 正在构建时得到满足的依赖项,那么构建将成功。将 makefile 更改为

T: C A B

...串行构建将开始失败。

在并行构建 (make -jN) 中,依赖顺序无关紧要,因为 make 将尝试构建 "A"、"B" 和 "C" 并行,如果允许的话。

依赖关系不完整

如果未为目标 "T" 列出强制依赖项 "D",则一旦满足列出的依赖项,make 将允许构建目标 "T"。但它不会等待 "D" 完成。因此,构建成功取决于未定义的行为,

  1. "D" 在构建开始之前就已经存在并且未被当前构建触及,或者
  2. 目标 "D" 在目标 "T" 开始之前完成。

并行构建的最坏情况:如果 "T" 的规则在读取 "D" 和同时写入 "D" 时没有失败,那么你最终会构建成功,但构建工件无效。

不完全依赖的常用指标是

  • 从串行切换到并行时构建开始(随机)失败,
  • 移动到另一台构建机器时构建开始随机失败,或者
  • 从具有奇怪测试失败的成功构建构建工件。

解决方案

始终为所有目标指定完整的依赖项。在你的情况下至少是:

# Add internal dependencies included in $(LIBS)
exe/my_out: $(my_out-obj) $(filter lib/%.a, $(LIBS))
    g++ $^ $(COMMON_FLAGS) $(LIBS) $(MY_FLAGS)