Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Makefile pattern rule either ignores phony rule or spontaneously deletes output file

I'm trying to write a makefile to produce several output files for each of several sources, using pattern rules.

I have the following Makefile (GNU Make 3.8.1):

all : foo.all bar.all

%.all : %.pdf %.svg
    @echo Made $*

%.pdf :
    touch $@

%.svg :
    touch $@

.PHONY: foo.all bar.all

Since *.all do not represent real output files, I tried marking them as .PHONY. However, running make then doesn't work:

$ ls
Makefile
$ make
make: Nothing to be done for `all'.

According to make -d:

 No implicit rule found for `all'.
  Considering target file `foo.all'.
   File `foo.all' does not exist.
   Finished prerequisites of target file `foo.all'.
  Must remake target `foo.all'.
  Successfully remade target file `foo.all'.
  Considering target file `bar.all'.
   File `bar.all' does not exist.
   Finished prerequisites of target file `bar.all'.
  Must remake target `bar.all'.
  Successfully remade target file `bar.all'.
 Finished prerequisites of target file `all'.
Must remake target `all'.
Successfully remade target file `all'.
make: Nothing to be done for `all'.

which seems to be pretending to run the %.all rules, but skipping the bodies.

But with the .PHONY line commented out, Make runs the targets, but then spontaneously decides to delete the output files:

$ make
touch foo.pdf
touch foo.svg
Made foo
touch bar.pdf
touch bar.svg
Made bar
rm foo.pdf foo.svg bar.pdf bar.svg

According to make -d, it says:

Removing intermediate files...

Minimal example

A minimal example giving anomalous behavior:

%.all: %.out
    @echo Made $*

%.out:
    touch $@

I expect running make somefile.all to cause it to create the file somefile.out, but it gets deleted:

$ make somefile.all
touch somefile.out
Made somefile
rm somefile.out
like image 855
Mechanical snail Avatar asked Nov 09 '13 21:11

Mechanical snail


1 Answers

Keeping make from deleting intermediary files

I recommend against using .PRECIOUS (see below as to why). Using .SECONDARY would preserve the .out files:

TARGETS=foo bar
all: $(TARGETS:=.all)

%.all: %.out
    @echo Made $*

%.out:
    touch $@

.SECONDARY: $(TARGETS:=.out)

$(TARGETS:=.all) just appends .all to all names in TARGETS. $(TARGETS:=.out) appends .out. We apparently cannot use %.out as a target of .SECONDARY. These just save having to relist all targets individually.

I prefer to not use .PRECIOUS for this because the documentation says

if make is killed or interrupted during the execution of their recipes, the target is not deleted.

This can leave corrupted files in the file system. Here's an example.

all: foo.all bar.all

%.all: %.out
    @echo Made $*

%.out:
    sh -e -c 'echo "{1, 2, 3" > $@; FAIL!; echo "}" >> $@'

.PRECIOUS: %.out

The FAIL! command simulates a tool that crashes in the middle of its work. Here's a shell session working with the Makefile above:

$ ls
Makefile
$ make
sh -e -c 'echo "{1, 2, 3" > foo.out; FAIL!; echo "}" >> foo.out'
sh: 1: FAIL!: not found
make: *** [foo.out] Error 127
$ cat foo.out
{1, 2, 3

Yikes... my foo.out file is incomplete. Let's try making again:

$ make
Made foo
sh -e -c 'echo "{1, 2, 3" > bar.out; FAIL!; echo "}" >> bar.out'
sh: 1: FAIL!: not found
make: *** [bar.out] Error 127
$ cat *.out
{1, 2, 3
{1, 2, 3

Make is none the wiser about files left around by earlier runs so when you run make again, it will take the corrupted files at face value. foo.out was not remade (despite the "Made foo" message) because it already exists and the Makefile went straight to trying to make bar.

.SECONDARY makes it so that:

The targets which .SECONDARY depends on are treated as intermediate files, except that they are never automatically deleted.

This means they are never automatically deleted just because they are intermediate files. The default make behavior of deleting targets that were being rebuilt if the tool rebuilding them crashed is not affected.

Using .PHONY with pattern rules

It seems though that .PHONY works only for targets that are explicit, not inferred. I've not found documentation confirming this. However, this works:

TARGETS:=foo bar
TARGETS_all:=$(TARGETS:=.all)

.PHONY: all
all: $(TARGETS_all)

.PHONY: $(TARGETS_all)
$(TARGETS_all): %.all: %.out
    @echo Made $*

%.out:
    touch $@

.SECONDARY: $(TARGETS:=.out)

In this rule $(TARGETS_all): %.all: %.out $(TARGETS_all): gives the list of targets to which the pattern can be applied. It makes foo.all and bar.all explicit targets. Without this, they would be inferred targets.

You can test that it works by creating file called foo.all in your directory and run make over and over. The foo.all file has no effect on make.

like image 116
Louis Avatar answered Oct 18 '22 15:10

Louis