Makefile中的依赖关系对于虚假目标无效

30

这是我Makefile的简化版本:

.PHONY: all 

all: src/server.coffee
  mkdir -p bin
  ./node_modules/.bin/coffee -c -o bin src/server.coffee

我希望运行make,并且只在src/server.coffee发生更改时重新编译。然而,每次运行make时都会重新编译:

$ make
mkdir -p bin
./node_modules/.bin/coffee -c -o bin src/server.coffee
$ make
mkdir -p bin
./node_modules/.bin/coffee -c -o bin src/server.coffee

如果我更改我的Makefile,不使用虚假目标,则它会按预期工作。新的Makefile:

bin/server.js: src/server.coffee
  mkdir -p bin
  ./node_modules/.bin/coffee -c -o bin src/server.coffee

结果:

$ make
mkdir -p bin
./node_modules/.bin/coffee -c -o bin src/server.coffee
$ make
make: `bin/server.js' is up to date.

为什么假目标不会尊重我的依赖关系?我问这个问题的原因是因为实际上,我不只是将一个文件编译成另一个文件,因此我不想跟踪所有输出文件的名称以用作目标。

5个回答

24
与其使用伪目标(正如@cmotley所指出的那样,它正在按照预期工作),当您想要避免额外工作时,您可以使用一个“空目标”:"empty target"
空目标是伪目标的一种变体;它用于保存您不时显式请求的操作的配方。与伪目标不同,此目标文件确实可以存在;但是文件的内容并不重要,通常为空。
空目标文件的目的是记录规则的配方上次执行的时间,以及最后修改时间。它这样做是因为配方中的命令之一是一个touch命令,以更新目标文件。
然而,在这种情况下,真的没有必要添加一个额外的空输出文件-您已经有了CoffeeScript编译的输出!这符合更典型的Makefile模式,正如您在问题中已经展示的那样。您可以采用这种方法进行重构:
.PHONY: all
all: bin/server.js

bin/server.js: src/server.coffee
  mkdir -p bin
  ./node_modules/.bin/coffee -c -o bin src/server.coffee

现在,您拥有了您想要的两个东西:一个漂亮的传统“all”目标,它是正确虚假的,并且不会执行额外的工作的构建规则。您还处于更好的位置,可以将其更加通用,以便轻松地添加更多文件:

.PHONY: all
all: bin/server.js bin/other1.js bin/other2.js

bin/%.js: src/%.coffee
  mkdir -p bin
  ./node_modules/.bin/coffee -c -o bin $<

10
只是为了明确,一个“空目标”必须是一个文件,并且仅用于其时间戳?是否还有其他类型的“.PHONY”,允许目标规则具有配方主体(包含要执行的命令),并且只有在依赖项需要更新时才运行?当前情况下,.PHONY根据是否具有配方主体来执行不同的操作似乎存在问题。 - jozxyqk

20
根据Make文档:
The prerequisites of the special target .PHONY are considered
to be phony targets. When it is time to consider such a target, 
make will run its recipe unconditionally, regardless of whether 
a file with that name exists or what its last-modification time is.

http://www.gnu.org/software/make/manual/html_node/Special-Targets.html

Make 无条件地运行 PHONY 目标的配方 - 先决条件并不重要。


1
好的,这很简单。你知道有什么好的解决方法来实现不必显式声明依赖于一组源文件的输出文件的目标吗? - Jonah Kagan
你可以声明一些变量来跟踪源文件和目标文件。例如,SRC = $(wildcard ./src/*.c) 和 OBJECTS = $(patsubst %.c, %.o, $(SRC))。然后,$(OBJECTS) 可以成为你的目标的先决条件。我认为这就是你想做的。 - user1172023

5

需要有一个目标文件来与server.coffee文件的修改时间进行比较。由于您没有一个具体的目标文件,make无法知道输出是否比依赖项更新,因此它将始终构建all


啊,谢谢。我没意识到 make 是通过计算来确定哪些文件需要重新编译的。非常有帮助。 - Jonah Kagan

4

正如其他人提到的,make会查看文件时间戳来确定依赖项是否已更改。

如果您想“模拟”带有依赖关系的虚假目标,则必须创建具有该名称的实际文件并在Unix系统上使用 touch 命令。

我需要一个解决方案,只有在更改了makefile(即更改了编译器标志,因此需要重新编译对象文件)时才清理目录。

以下是我使用的内容(在每次编译之前运行),文件名为 makefile_clean

makefile_clean: makefile
    @rm '*.o'
    @sudo touch makefile_clean
< p > touch 命令会将最后修改时间戳更新为当前时间。


2

感谢 @anonymous-penguin 的建议,但我认为将那些无用的文件(仅是标记)放在 /tmp 文件夹中是更好的选择。

以下是 makefile,只有当文件 Dockerfile 发生变化时,才会构建 docker 镜像。

# Configable variable
BUILD_FLAG_FILE = /tmp/ACMHomepage/Dockerfile_builded

# State variable
REBUILD = false

.PHONY: build
build: $(BUILD_FLAG_FILE)

$(BUILD_FLAG_FILE): Dockerfile
    $(eval REBUILD = true)
    @echo "Build image $(DB_NAME)..."
    @docker build -t $(DB_NAME) .
    @mkdir -p $(dir $(BUILD_FLAG_FILE))
    @touch $(BUILD_FLAG_FILE)

如您所见,它会在/tmp/ACMHomepage/Dockerfile_builded中创建一个空文件作为标记。如果构建了docker镜像,则变量REBUILD将为true,这非常有用!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接