1

The Makefile below has to create (multiple) output directories, and generate output in those directories, from input in the directory above. So, on entry, dirn exists, and dirn/file.foo exists. The build has to create dirn/out/file.bar.

This Makefile works when run as a single job (note that it creates the two required source directories and files in the $(shell)). It presumably works because makedirs is the first/leftmost prerequisite for all. However, it doesn't work for a multi-job make (ie. make -j4/whatever).

Any ideas on how to fix the dependencies to ensure that the output directories are made before they're required?

EDIT

I should have made it clear that I have tried various order-only prerequisite solutions, but I couldn't do this and guarantee that the target actually rebuilt (the point of order-only is generally to prevent rebuilding, not enforce dependency ordering). If you have an OO solution, please check it! Thanks.

# expected output:
# made directories
# copying dir1/out/../file.foo to dir1/out/file.bar
# copying dir2/out/../file.foo to dir2/out/file.bar
# created all output files
# done

    $(shell mkdir dir1 >& /dev/null; touch dir1/file.foo; \
            mkdir dir2 >& /dev/null; touch dir2/file.foo)

    OUTDIRS = dir1/out dir2/out
    OUTPUTS = dir1/out/file.bar dir2/out/file.bar 

    .DEFAULT_GOAL := all

    .PHONY: makedirs $(OUTDIRS)

    .SUFFIXES: .foo .bar

    %.bar : ../%.foo
        @echo "copying $< to $@"
        @cp $< $@

    all : makedirs outputs
        @echo "done"

    outputs : $(OUTPUTS)
        @echo "created all output files"

    makedirs : $(OUTDIRS)
        @mkdir -p $(OUTDIRS)
        @echo "made directories"

    clean :
        @rm -rf dir1 dir2
EML
  • 9,619
  • 6
  • 46
  • 78

1 Answers1

2

make $(OUTPUTS) have an order-only dependency on the directories themselves:

 $(OUTDIRS) : 
      mkdir -p $@

 $(OUTPUTS) : | $(OUTDIRS)

This will guarentee that the directories are created before $(OUTPUTS), but will not cause the outputs to rebuild if the directories are newer than the targets (which is important, as a directories' time stamp is set each time a file is added to it...).

Note: you can also add a mkdir -p in your output recipe's which will create the directory if it's not already there each time you run an output rule, but I prefer the above method.

Note2: in your existing makefile you could also just add a line: $(OUTPUTS): makedirs, which would force the makedirs rule to be run before any of the outputs are built, but again, I prefer the above solution :-)

---- EDIT: -----

Something is odd then -- what version of make are you using? I just ran the following: notice the sleep 1's when making the directories, which means if there was a concurrency issue, it would definitely have hit:

$(shell mkdir dir1 >& /dev/null; touch dir1/file.foo; \
        mkdir dir2 >& /dev/null; touch dir2/file.foo)


OUTDIRS = dir1/out dir2/out
OUTPUTS = dir1/out/file.bar dir2/out/file.bar

.DEFAULT_GOAL := all

$(OUTPUTS) : | $(OUTDIRS)

$(OUTDIRS) :
        @echo "making $@"
        sleep 1
        mkdir -p $@
        @echo "done making $@"


%.bar : ../%.foo
        @echo "copying $< to $@"
        @cp $< $@

all : outputs
        @echo "done $@"

outputs : $(OUTPUTS)
        @echo "created all output files"

clean :
        @rm -rf dir1 dir2

And the output was:

~/sandbox/tmp20> make -j -f Makefile2
making dir1/out
making dir2/out
sleep 1
sleep 1
mkdir -p dir1/out
mkdir -p dir2/out
done making dir1/out
done making dir2/out
copying dir1/out/../file.foo to dir1/out/file.bar
copying dir2/out/../file.foo to dir2/out/file.bar
created all output files
done all

Notice it concurrently builds dir1/out and dir2/out, and doesn't run the pattern rules until both are finished. I also verified that the solution I mentioned in note2 also works (at least on my machine...).

When doing pattern rules, you can specify dependencies outside of the pattern, so you can do:

foo.o: foo.h

%.o: %.c
    recipe here...

which will rebuild foo.o if foo.h is newer, or try to build foo.h if it doesn't exist before foo.o is built.

blackghost
  • 1,730
  • 11
  • 24
  • Thanks, but the problem with an order-only dependency is that it doesn't guarantee that the target will be rebuilt - only that the directories will be created. I can't get a usable OOD in my original Makefile, but I haven't tried on this reduced version - have you tried a specific change to the file above? – EML Jun 07 '17 at 07:44
  • On the note: I can't do this, because of the pattern rule. The stem matching in the pattern fails if the directory doesn't already exist (see the '..'). There doesn't seem to be any way around this (that I can find, anyway). – EML Jun 07 '17 at 07:47
  • On Note2: I don't think this will work, because `$(OUTPUTS)` has to be built by the pattern rule, not your new rule. I've tried various combinations of this, and getting `makedirs` into the pattern rule in the original Makefile, but none of them worked. – EML Jun 07 '17 at 07:49
  • Sorry - just got back to this, running 3.82. I get the same output as you with `-j`, but it doesn't work as a single job (no `-j`) - it only does the `dir2` copy, and omits `dir1`. Just trying to track it down... – EML Jun 12 '17 at 08:17
  • That is indeed weird (reproduced it here as well on 3.81). I ran with a `--debug=vi`, and it seems that make says it has to remake `dir1/out/file.bar`, and then says it's remade it without running any rules... I unfortunately have limited bandwidth at the moment, but if you do figure it out, please post, as I'm quite curious... – blackghost Jun 12 '17 at 14:18
  • Hmmm, looked it over, and I'm not sure why it is doing that. I posted this: https://stackoverflow.com/questions/44508805/makefile-considering-target-remade-without-running-any-rules to see if anyone else does (as it's not related to your original question) – blackghost Jun 12 '17 at 21:14
  • Confused as well. I have a solution with a recursive make, which I'll post when I get a minute later, but I'd prefer to find out what's going on here... – EML Jun 13 '17 at 07:04