Reputation: 20909
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
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:
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>
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
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