Mr. Llama
Mr. Llama

Reputation: 20909

GNU Make - Trailing "/"s being dropped?

I'm working on a makefile that creates multiples files in output directories. In order for those files to be created, the output directory needs to already exist or else the file creation fails.

Here's a minimal example that demonstrates the issue I'm running into:

.PHONY: default
default: dir/file.dat dir/other.dat

# In order to create these files, their parent directory must exist
dir/%.dat: | dir/
    touch "$@"

# To create a directory, use mkdir -p
%/:
    mkdir -p "$@"

When I run the makefile, I get an error that no rule exists to make dir. The debug run shows that make is dropping the trailing "/" from "dir/":

root@69654136a2ae:~# make -rdf dirs.mk
GNU Make 3.81
[snipped]

Considering target file `default'.
 File `default' does not exist.
  Considering target file `dir/file.dat'.
   File `dir/file.dat' does not exist.
    Considering target file `dir'.
     File `dir' does not exist.
     Looking for an implicit rule for `dir'.
     No implicit rule found for `dir'.
     Finished prerequisites of target file `dir'.
    Must remake target `dir'.
make: *** No rule to make target `dir', needed by `dir/file.dat'.  Stop.

Incidentally, asking for a directory target on the command line works just fine:

root@69654136a2ae:~# make -rdf dirs.mk /some/dir/
GNU Make 3.81
[snipped]

Updating goal targets....
Considering target file `/some/dir/'.
 File `/some/dir/' does not exist.
 Looking for an implicit rule for `/some/dir/'.
 Trying pattern rule with stem `/some/dir'.
 Found an implicit rule for `/some/dir/'.
 Finished prerequisites of target file `/some/dir/'.
Must remake target `/some/dir/'.
mkdir -p "/some/dir/"

How can I have make do what I want?
Ideally I'm looking for a generic solution. The above example only has one subdirectory, but the actual project will have many subdirectories, so I'd like to avoid copy-pasting a solution everywhere.

Upvotes: 3

Views: 625

Answers (2)

Renaud Pacalet
Renaud Pacalet

Reputation: 29280

Your %/ rule matches only target names with a trailing /. As, depending on the version of make you are using, trailing / can be dropped or not from names before they are considered as target names, this will not always work.

For one single directory, just don't use this trailing / and don't use a pattern rule:

dir/%.dat: | dir
    touch "$@"

dir:
    mkdir -p "$@"

For a more generic solution, leiyc's solution is fine. There are others:

  1. If you can get a list of all directories to create, you can create them all at once with:

    DIRS := dir ...
    $(shell mkdir -p $(DIRS) > /dev/null)
    
    <any-target>: <prerequisites>
        <recipe>
    
  2. If you can get a list of all directories to create but prefer order-only prerequisites, you can use the secondary expansion, and a rule to create the directories:

    DIRS := dir ...
    
    .SECONDEXPANSION:
    
    <any-target>: <prerequisites> | $$(@D)
        <recipe>
    
    $(DIRS):
        mkdir -p $@
    

And there are probably other possibilities...

Upvotes: 0

leiyc
leiyc

Reputation: 973

Yes, the / is dropped in dir/ target (issue can be reproduced under GNU Make 3.81).

One alternative solution is to use automatic variable $(@D) to get the directory part of the file name of the target, with the trailing slash removed. And make the directory before touch the file:

.PHONY: default
default: dir/file.dat dir/other.dat

# In order to create these files, make their parent directory first
dir/%.dat:
        mkdir -p $(@D)
        touch "$@"

Test under GNU Make 3.81 passed:

$ make --version
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
...

$ make
mkdir -p dir
touch "dir/file.dat"
mkdir -p dir
touch "dir/other.dat"

Upvotes: 1

Related Questions