.PHONY pattern rule: match pattern with leading slash - makefile

I am trying to simplify a Makefile. In that, I tried to have a pattern rule for removing files:
.PHONY:
rm-%:
rm $*
where % would contain the name of the file (usually the absolute path with a leading slash).
And then I just state that rm-/some/file is a dependency of make uninstall, and expect make to match this pattern rule.
However, it bite me. I did some tests and realized that the problem was that a leading slash did not match that pattern rule:
~/src/test$ cat Makefile
.PHONY:
a-/%:
$(info /$*)
.PHONY:
b-%:
$(info $*)
.PHONY:
foo: a-/as/d
#:
.PHONY:
bar: b-/as/d
#:
~/src/test$ make foo
/as/d
~/src/test$ make bar
make: *** No rule to make target 'b-/as/d', needed by 'bar'. Stop.
~/src/test$
Why do I need to specify that leading slash in the pattern rule (but not other slashes)?
The GNU make documentation says this:
the ‘%’ matches any nonempty substring, while other characters match
only themselves
https://www.gnu.org/software/make/manual/html_node/Pattern-Intro.html#Pattern-Intro
EDIT: Fix some typos about .PHONY:, use a suffix instead of a prefix as suggested in the answers/comments, and test further:
As I initially suspected, .PHONY: is doing something weird:
~/src/test/$ tree a b c d
a
└── a
b
└── b
c
└── c
d
└── d
0 directories, 4 files
~/src/test$ cat Makefile
%-rmdir:
rmdir $(#D)
%-rm:
rm $*
.PHONY: ./a/.-rmdir
./a/.-rmdir: ./a/a-rm
.PHONY: ./b/-rmdir
./b/-rmdir: ./b/b-rm
./c/.-rmdir: ./c/c-rm
./d/-rmdir: ./d/d-rm
.PHONY: foo
foo: ./a/.-rmdir ./b/-rmdir ./c/.-rmdir ./d/-rmdir
$(info foo)
~/src/test$ make foo
rm a/a
rm b/b
rm c/c
rmdir c
rm d/d
rmdir d
foo

You need to examine the section on How Patterns Match where you will find this text:
When the target pattern does not contain a slash (and it usually does
not), directory names in the file names are removed from the file name
before it is compared with the target prefix and suffix.
(and more detail about how this works).

As per documentation and #MadScientist answer, this is how patterns match. But workaround is very simple: replace your prefix rules with suffix rules and this directory mangling will not stand in the way.
Example:
$ cat Makefile
.PHONY: %-clean
%-clean:
$(info $*)
.PHONY: foo
foo: /as/d-clean
#:
.PHONY: bar
bar: ./as/d-clean
#:
Output:
$ make foo
/as/d
$ make bar
as/d
BTW. Please note that .PHONY: requires target name, it does not automagically apply to the target that follows.

Related

Why does makefile lazy evaluation find a file in a "parent" recipe but not the current one?

This question is a follow-up to What makefile lazy evaluation rule governs this behavior?. I'm still trying to grok some of the rules of gnu make's lazy evaluation.
I want to have a make variable for the content of a directory after that directory has been updated by a recipe.
This Makefile demonstrates that $(A_FILE) is evaluated to find the created file when it's in the "parent" of the recipe that actually creates the file:
A_FILE = $(wildcard subdir/*)
all: a
#echo $(A_FILE)
a:
#mkdir ./subdir
#touch subdir/b
$ rm -rf ./subdir/ && make
subdir/b
$
But the following Makefile has a seemingly trivial change: $(A_FILE) is referenced in the recipe where its containing directory is updated - but now the variable is empty:
A_FILE = $(wildcard subdir/*)
all: a
#echo $(A_FILE)
a:
#mkdir ./subdir
#touch subdir/b
#sleep 1
#echo $(A_FILE)
$ rm -rf ./subdir/ && make
$
I added the sleep to rule out timing issues of the directory being trawled too quickly after it had been updated.
What gives? Why does $(A_FILE) get evaluated against the updated subdir content if it's referenced in the higher-layer recipe but not in the lower-layer recipe where it's actually updated?
GNU make evaluates all lines in the recipe before it starts running any line in the recipe. So, when it is getting ready to run your recipe for the rule a it first expands all the lines, including the last line with $(A_FILE) in it. At that point no parts of the recipe have been run yet so the result is empty.
Then after all the expansion, the shell is invoked to run the lines in the recipe.

makefile : applying a single rule to a bunch of target

I'm starting to learn how to write makefiles and I can't find an existing topic related to my question, so apologies if it already exists.
As of now, it looks like this (and it works) :
INSTALL_DIR = $(realpath /home/$(USER)/bin/)
SRC = $(realpath ./)
script = $(SRC)/foo.sh
TAR = $(INSTALL_DIR)/foo
all : $(TAR)
$(TAR) : $(script)
ln -s $^ $#
What I would like to do is to use a minimum number of lines to generate symbolic links (that is, applying the existing rule for building TAR) to every bash script (that is $(SRC)*.sh) in the same fashion as I did for the first one ('foo.sh') here.
I could just make 'script' and 'TAR' like variable for every script manually but I'm sure there must be a better way. Little help ?
First we must find the scripts:
INSTALL_DIR = bin # there is no need for a full path here
SRC = $(realpath ./)
SCRIPTS := $(wildcard $(SRC)/*.sh) # /some/path/foo.sh /some/path/bar.sh /some/path/baz.sh
SCRIPTS := $(notdir $(SCRIPTS)) # foo.sh bar.sh baz.sh
Note my use of ":=" rather than "=". You can read about the difference here, but usually ":=" is better.
Now for the targets, the links we're trying to construct:
TARGETS := $(basename $(SCRIPTS)) # foo bar baz
TARGETS := $(addprefix $(INSTALL_DIR)/, $(TARGETS)) # bin/foo bin/bar bin/baz
The rules:
all: $(TARGETS)
$(INSTALL_DIR)/%: $(SRC)/%.sh
ln -s $< $#
That last rule is quite general. We could restrict it to the target list we defined:
$(TARGETS): $(INSTALL_DIR)/%: $(SRC)/%.sh
ln -s $< $#
Further refinements are possible, once this is working perfectly.
You're looking for a pattern rule. Something like:
SCRIPTS := foo bar xxx yyy
all: $(SCRIPTS)
% : %.sh
ln -s $< $#
Note that $(SCRIPTS) can also be populated by $(wildcard ...) if none of the scripts are generated by make (otherwise, $(SCRIPTS) would be missing any file that did not exist when make was first invoked).

How to set a Makefile target depend on pattern prerequisites?

I have a chain of pattern dependencies in a makefile, and in the end they should come together in one file, e.g.: *.x -> *.y -> onefile.z
So I made the files like this:
$ touch a.x b.x
and the rules:
%.y: %.x some-other-script
touch $#
onefile.z: %.y second-other-script
touch $#
This rule does not work:
$ make onefile.z
make: *** No rule to make target '%.y', needed by 'onefile.z'. Stop.
Using a wildcard:
%.y: %.x some-other-script
touch $#
z: $(wildcard *.y) second-other-script
touch $#
This does not work either: it sees there are no *.y files and proceeds making onefile.z skipping the first rule.
$ make onefile.z
touch onefile.z
$ ls
a.x b.x onefile.z Makefile
I probably could merge two rules into one, but in the real application, there are more steps, and some are making requests over HTTP and take much time, and in fact should not be repeated without a reason.
Is there a way to make such a dependency?
onefile.z: %.y second-other-script is a regular rule, not a pattern rule or a static pattern rule, so the % is interpreted literally. Even if it were a pattern rule, how is make supposed to infer what the stem is supposed to match?
$(wildcard *.y) tells make to find all the files that match *.y, but of course there are none yet so it returns an empty string.
The following should work, if I've understood your question correctly:
xfiles := $(wildcard *.x)
yfiles := $(xfiles:.x=.y)
%.y: %.x some-other-script
touch $#
onefile.z: $(yfiles) second-other-script
touch $#
See the Gnu Make documentation on
Wildcard functions $(wildcard ...)
Substitution functions $(foo:from=to)

Rule for all targets in make - even if the file exists

I want to create a Makefile that outputs foo no matter what target name is given to make.
So all of these should work:
$ make
foo
$ make a
foo
$ make foobar
foo
The following Makefile does almost what I want:
all %:
#echo foo
.PHONY: all
However it fails if there exists a file with the same name as the target:
$ touch abc
$ make abc
make: `abc' is up to date.
As .PHONY doesn't accept pattern rules, I don't know how I can get make to ignore every file.
How about:
all $(MAKECMDGOALS): ; #echo foo
.PHONY: all $(MAKECMDGOALS)

make wildcard subdirectory targets

I have a "lib" directory in my applications main directory, which contains an arbitrary number of subdirectories, each having its own Makefile.
I would like to have a single Makefile in the main directory, that calls each subdirectory's Makefile. I know this is possible if I manually list the subdirs, but I would like to have it done automatically.
I was thinking of something like the following, but it obviously does not work. Note that I also have clean, test, etc. targets, so % is probably not a good idea at all.
LIBS=lib/*
all: $(LIBS)
%:
(cd $#; $(MAKE))
Any help is appreciated!
The following will work with GNU make:
LIBS=$(wildcard lib/*)
all: $(LIBS)
.PHONY: force
$(LIBS): force
cd $# && pwd
If there might be something other than directories in lib, you could alternatively use:
LIBS=$(shell find lib -type d)
To address the multiple targets issue, you can build special targets for each directory, then strip off the prefix for the sub-build:
LIBS=$(wildcard lib/*)
clean_LIBS=$(addprefix clean_,$(LIBS))
all: $(LIBS)
clean: $(clean_LIBS)
.PHONY: force
$(LIBS): force
echo make -C $#
$(clean_LIBS): force
echo make -C $(patsubst clean_%,%,$#) clean
There is also a way of listing sub-directories with gmake commands only, without using any shell commands:
test:
#echo $(filter %/, $(wildcard lib/*/))
This will list all sub-directories with trailing '/'. To remove it you can use the substitute pattern:
subdirs = $(filter %/, $(wildcard lib/*/))
test:
#echo $(subdirs:%/=%)
Then to actually create rules executing makefiles in each sub-directory you can use a small trick - a phony target in a non-existent directory. I think in this case an example will tell more than any explanation:
FULL_DIRS =$(filter %/, $(wildcard lib/*/))
LIB_DIRS =$(FULL_DIRS:%/=%)
DIRS_CMD =$(foreach subdir, $(LIB_DIRS), make-rule/$(subdir))
make-rule/%:
cd $* && $(MAKE)
all: DIRS_CMD
Basically, target 'all' lists all sub-directories as prerequisites. For example, if LIB_DIRS contained lib/folder1 lib/folder2 then the expansion would look like this:
all: make-rule/lib/folder1 make-rule/lib/folder2
Then 'make', in order to execute rule 'all', tries to match each prerequisite with an existing target. In this case the target is 'make-rule/%:', which uses '$*' to extract the string after 'make-rule/' and uses it as argument in the recipe. For example, the first prerequisite would be matched and expanded like this:
make-rule/lib/folder1:
cd lib/folder1 && $(MAKE)
What if you want to call different targets than all in an unknown number of subdirectories?
The following Makefile uses macros so create a forwarding dummy-target for a number of subdirectories to apply the given target from the command line to each of them:
# all direct directories of this dir. uses "-printf" to get rid of the "./"
DIRS=$(shell find . -maxdepth 1 -mindepth 1 -type d -not -name ".*" -printf '%P\n')
# "all" target is there by default, same logic as via the macro
all: $(DIRS)
$(DIRS):
$(MAKE) -C $#
.PHONY: $(DIRS)
# if explcit targets where given: use them in the macro down below. each target will be delivered to each subdirectory contained in $(DIRS).
EXTRA_TARGETS=$(MAKECMDGOALS)
define RECURSIVE_MAKE_WITH_TARGET
# create new variable, with the name of the target as prefix. it holds all
# subdirectories with the target as suffix
$(1)_DIRS=$$(addprefix $(1)_,$$(DIRS))
# create new target with the variable holding all the subdirectories+suffix as
# prerequisite
$(1): $$($1_DIRS)
# use list to create target to fullfill prerequisite. the rule is to call
# recursive make into the subdir with the target
$$($(1)_DIRS):
$$(MAKE) -C $$(patsubst $(1)_%,%,$$#) $(1)
# and make all targets .PHONY
.PHONY: $$($(1)_DIRS)
endef
# evaluate the macro for all given list of targets
$(foreach t,$(EXTRA_TARGETS),$(eval $(call RECURSIVE_MAKE_WITH_TARGET,$(t))))
Hope this helps. Really helpfull when dealing with paralelism: make -j12 clean all in a tree with makefiles having these targets... As always: playing with make is dangerous, different meta-levels of programming are too close together ,-)

Resources