29

Here is a reduced version of my Makefile:

.PHONY: all 

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

I want to run make and only have it recompile when src/server.coffee has changed. However, it recompiles every time I run 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

If I change my Makefile to not use a phony target, it works as expected. New Makefile:

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

Result:

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

Why won't it respect my dependencies with a phony target? The reason I ask is because in reality, I won't just be compiling a single file into a single other file, so I don't want to have to keep track of the names of all the output files to use as targets.

Jonah Kagan
  • 447
  • 1
  • 4
  • 7

5 Answers5

23

Rather than a phony target (which as @cmotley points out, is working exactly as it should) what you might use when you want to avoid extra work is an "empty target":

The empty target is a variant of the phony target; it is used to hold recipes for an action that you request explicitly from time to time. Unlike a phony target, this target file can really exist; but the file's contents do not matter, and usually are empty.

The purpose of the empty target file is to record, with its last-modification time, when the rule's recipe was last executed. It does so because one of the commands in the recipe is a touch command to update the target file.

However, in this case there's really no need to add an extra empty output file — you already have the output of your CoffeeScript compilation! That fits the more typical Makefile pattern, as you already demonstrated in your question. What you could do is refactor to this approach:

.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

Now you have both things you wanted: a nice conventional "all" target that is correctly phony, and a build rule that won't do extra work. You are also in a better position to make this more generic so you can easily add more files:

.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 $<
natevw
  • 16,807
  • 8
  • 66
  • 90
  • 8
    Just to be clear, an **empty target** must be a file and is really just used for its timestamp? Is there any other kind of `.PHONY` which allows a target rule to have a recipe body (with commands that are executed) *and* only run when the dependencies have needed updating? It seems quite broken as it is to have `.PHONY` do different things depending on whether it has a recipe body or not. – jozxyqk Jun 23 '15 at 09:27
19

According to the Make documentation:

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 runs the recipe of PHONY targets unconditionally - prerequisites don't matter.

  • 1
    Okay, that's pretty straightforward. Do you know of any good workaround to achieve the goal of not having to explicitly declare the output files that depend on a set of source files? – Jonah Kagan Dec 13 '12 at 03:51
  • You could declare some variables to track source files and object files. For example, SRC = $(wildcard ./src/*.c) and OBJECTS = $(patsubst %.c, %.o, $(SRC)). Then, $(OBJECTS) could be a prerequisite for your target. I think that's what you're trying to do. –  Dec 13 '12 at 03:58
5

There needs to be some target file to compare against the modification time of server.coffee file. Since you don't have a concrete target make cannot know if the output is newer then the dependency or not, so it will always build all.

perreal
  • 94,503
  • 21
  • 155
  • 181
4

As the others mentioned, make looks at the timestamps of the files to figure out if the dependencies have changed.

If you want to "emulate" a phony target with dependencies, you will have to create a real file with that name and use the touch command (on Unix systems).

I needed a solution to only clean the directory if the makefiles were changed (i.e. the compiler flags were changed, so the object files needed to be recompiled).

Here's what I used (and runs before every compilation) with a file named makefile_clean:

makefile_clean: makefile
    @rm '*.o'
    @sudo touch makefile_clean

The touch command updates the last-modified timestamp to the current time.

Anonymous Penguin
  • 2,027
  • 4
  • 34
  • 49
2

Thanks @anonymous-penguin , but in my opinion, put those useless file, which is just a flag, in folder /tmp is a better idea.

Here is the makefile, which will going to build a docker image if and only if the file Dockerfile is changed.

# 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)

As you see, It will create a empty file as a flag in /tmp/ACMHomepage/Dockerfile_builded. If the docker image is built, the variable REBUILD will be true, which is useful!

Peterlits Zo
  • 476
  • 1
  • 4
  • 17