Specifying glob patterns as Makefile targets - makefile

My make version on macOS High Sierra Version 10.13.6 looks like this:
$ 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.
This program built for i386-apple-darwin11.3.0
My current directory looks like this:
$ ls -1
Makefile
a.txt
b.txt
c.txt
My Makefile looks like this:
*.txt: FORCE
echo Target $# invoked
FORCE:
I am able to use this Makefile like this:
$ make a.txt
echo Target a.txt invoked
Target a.txt invoked
$ make b.txt
echo Target b.txt invoked
Target b.txt invoked
$ make c.txt
echo Target c.txt invoked
Target c.txt invoked
Where is this behavior of *.txt expanding to matching targets in the current directory documented? I tried searching the man pages and documents but could not find anything that specifies that we can use glob patterns as target. Can I rely on this behavior in my Makefiles?

See the GNU make manual right in the introduction to rule syntax:
The targets are file names, separated by spaces. Wildcard characters may be used (see Using Wildcard Characters in File Names)
and the entire section Using Wildcard Characters in File Names.
You cannot just look at man pages: the man pages only describe the command line interface and possible the barest summary of makefile syntax. They don't attempt to describe everything about how to write a makefile.
If you looked in the GNU make manual but didn't find this I'd be interested to understand what you searched for and where you expected to find the information so we can think about improving the docs.

Related

why does "make" delete target files only if implicit

Suppose I have a Makefile like this
B1.txt: A1.txt
python big_long_program.py A1.txt > $#
correct1.txt: B1.txt reference.txt
diff -q B1.txt reference.txt
touch $#
Then the output when I make correct1.txt is pretty well what I would expect:
python big_long_program.py A1.txt > B1.txt
diff -q B1.txt reference.txt
touch correct1.txt
Now if I have lots of files, B1.txt, B2.txt, B3.txt etc, so create an implicit rule:
B%.txt: A%.txt
python big_long_program.py A$*.txt > $#
correct%.txt: B%.txt reference.txt
diff -q B$*.txt reference.txt
touch $#
Instead this happens when I make correct1.txt:
python big_long_program.py A1.txt > B1.txt
diff -q B1.txt reference.txt
touch correct1.txt
rm B1.txt
i.e. there difference is that now the file B1.txt has been deleted, which in many cases is really bad.
So why are implicit rules different? Or am I doing something wrong?
You are not doing anything wrong. The behavior you observe and analyze is documented in 10.4 Chains of Implicit Rules. It states that intermediate files are indeed treated differently.
The second difference is that if make does create b in order to update
something else, it deletes b later on after it is no longer needed.
Therefore, an intermediate file which did not exist before make also
does not exist after make. make reports the deletion to you by
printing a rm -f command showing which file it is deleting.
The documentation does not explicitly explain why it behaves like this. Looking in the file ChangeLog.1, there is a reference to the remove_intermediates function as far back as 1988. At that time, disk space was expensive and at a premium.
If you do not want this behavior, mention the targets you want to keep somewhere in the makefile as an explicit prerequisite or target or use the .PRECIOUS or the .SECONDARY special built-in targets for that.
With thanks to MadScientist for the additional comments, see below.

Reading a file inside makefile

I want to read a file called metafile inside a makefile.
metafile looks something like this:
file1
file2
file3
I need to read this metafile inside my makefile line by line and check if the files mentioned inside exists or not and only print names of files which exists.
I've tried a few things without success. like:
FILE=`cat metafile`
for line in $(FILE); if [ -e $${line} ]; then echo $${line} fi; done;
You can put an arbitrary piece of shell script in a target. Keeping the file's contents in a Makefile variable does not make any sense to me, unless you also need the data in other targets for other reasons. (If so, you cannot use backticks, anyway.)
target:
#while read -r file; do \
test -e "$$file" && echo "$$file"; \
done <metafile
For what it's worth, the while loop is a safer and more idiomatic way to loop over a file's lines in a shell script than the for loop with backticks, even though you see that a lot.
The # prevents Make from echoing the shell script commands; take that out if for some reason you need to see them. In fact, I recommend against using this, especially while you are debugging -- use make -s to have make run silently once you are confident your recipe works correctly.
A more idiomatic way to do this in a Makefile is to have a target depend on these files, and use Make's own logic:
target: file1 file2 file3
#echo $(filter-out $?,$^)
This is GNU Make syntax; it might get more complex if you want to be portable to other Make flavors (to the point where maybe the shell script is preferable after all). It will echo everything on one line, but if you need separate lines, that should be a trivial fix.
I would simply build a small auxiliary Makefile snippet and include the dependencies:
target: target.d
target.d: metafile
sed 's/^/target: /' $< >$#
include target.d
This builds a small list of dependencies so you don't need to list them in the target: dependencies explicitly; so instead of file1 file2 file3 in the recipe above, the dependencies would live in the generated target.d which would contain
target: file1
target: file2
target: file3
You need to filter out the dependency on target.d (or leave it undeclared; I believe GNU Make should cope).

List *.h and *.cpp using bash

I am using bash version 4.2.28 on Fedora 16. I have the extglob option set. I am trying to list all files matching *.h or *.cpp using ls *(h|cpp) but the command returns the following:
[agnel#damien cadcore]$ ls *(h|cpp)
ls: cannot access *(h|cpp): No such file or directory
I have verified that there are indeed several .h and .cpp files in my current directory. Am I doing something wrong or could this be a bug in bash or ls?
Update: Thank you for your answers. Using *.h *.cpp does what I need. However, I would still like to know why extglob didn't work like I expected.
The extended glob *(pattern-list) matches 0 or more occurrences of the following pattern list. It does not match an arbitrary string followed by something from the option list. You want:
$ ls *.#(h|cpp)
This matches something, followed by a period, followed by either "h" or "cpp"
I don't think you need complicated globbing in this case: simply try echo *.h *.cpp.
You should be able to do just ls *h *cpp

How to replace text in a makefile in a way that works with make AND dmake?

I've got this makefile:
ALL = ../lib/Mo.pm \
../lib/Mo/builder.pm \
../lib/Mo/default.pm \
../lib/Mo/has.pm \
all: $(ALL)
$(ALL): Mo.pm compress.pl Makefile
perl compress.pl $(#:../lib/%=%) > $#
What it's meant to do is something like this:
$ make -n
perl compress.pl Mo.pm > ../lib/Mo.pm
perl compress.pl Mo/builder.pm > ../lib/Mo/builder.pm
perl compress.pl Mo/default.pm > ../lib/Mo/default.pm
perl compress.pl Mo/has.pm > ../lib/Mo/has.pm
However with dmake on Windows this happens:
d:\mo-pm\src>dmake -n
perl compress.pl ..\lib\Mo.pm > ..\lib\Mo.pm
perl compress.pl ..\lib\Mo\builder.pm > ..\lib\Mo\builder.pm
perl compress.pl ..\lib\Mo\default.pm > ..\lib\Mo\default.pm
perl compress.pl ..\lib\Mo\has.pm > ..\lib\Mo\has.pm
I've been trying out various combinations of s/// and subst to make it work in dmake, and found out that it wants the path to have \s, which means a double substitution against both variants of the path (../lib/ and ..\lib) could work, but i can't figure out how to make it work for both make variants.
Any ideas or other ways to do this?
It's not only that the dir separator chars are different for both versions, moreover the dmake syntax seems to be deliberately designed to be incompatible with GNU make. The only part of the syntax that is actually compatible is pattern substitution, so this is the way to go:
all: $(ALL)
$(ALL) : Makefile compress.pl
../lib/%.pm : %.pm
perl compress.pl $< > $#
dmake actually substitutes the / for directory separator chars for you here. I've tested this Makefile with an echo instead, and it writes to the right directory.
Explanation: The pattern rules define rules for a particular file to be re-made when it matches a regular expression (the ../lib/%.pm part) and a prerequisite of a similar name is found (%.pm). The % in the prerequisite is replaced by the matching part of the % in the target. The extra rule with Makefile and compress.pl is needed because dmake doesn't like extra prerequisites in a pattern rule. As usual, $< and $# are make's special variables for source and target file.
So, the core difference is that your original rule said "the files named in this list can be made with the following rule), while the pattern rule says "any file looking like ../lib/%.pm can be made from a matching file in the current directory" and then gives a list of pm files to make.
Pattern rules are actually quite powerful, useful to know. Unfortunately, some makes don't know them, only the older suffix rules.
Further details of what's going on can be obtained by running
make -rdn

Makefile and rm -f file.{ext1,ext2,ext3} issue

Could you explain me, why Makefile rule:
clean:
rm -f foo.{bar1,bar2,bar3}
does not result in removing files: foo.bar1 foo.bar2 and foo.bar3?
I believe I saw pattern like that many times in various Makefiles, but I'm currently writing my own Makefile and can't make that rule work correctly (no files are removed).
I'm using:
gnu make 3.81
gnu bash 4.1.5
Bash evals that pattern as I suspect:
$ echo test.{a,b,c}
test.a test.b test.c
Thanks!
UPDATE
Thank to David's hint I found solution for the problem described above.
The gnu make uses the /bin/sh by default and that is why a.{1,2,3} isn't evaluated to a.1 a.2 a.3.
To make 'make' use bash instead of sh add following line to your Makefile:
SHELL=/bin/bash
from now a.{1,2,3} will be considered as a.1 a.2 a.3
Is there a file named clean in the directory? If so, make will consider that target up to date and won't run the corresponding command. To fix that, add this line to your makefile:
.PHONY: clean
If when you run make clean you get the output
make: `clean' is up to date.
then that's probably your problem.
It's because the shell isn't being invoked here, but rather rm is directly. Since the shell does the {} substitution, what rm 'sees' is the raw foo.{bar1,bar2,bar3} string. As there's no such file, nothing happens.
You should use one of GNUmake's string macros to have it perform the expansion for you.

Resources