How to get pattern rules to match file names with spaces in Makefile? - makefile

In the GNU make docs, '%' is documented to match "any nonempty substring". However, it seems it actually only matches non-empty substrings that do not contain whitespace. For example, say you do this:
mkdir /tmp/foo
cd /tmp/foo
echo 'int main() { return 0; }' > "test.c"
echo 'int main() { return 0; }' > "test space.c"
Now, you should be able to build these using GNU Make's built-in pattern rules:
anthony#Zia:/tmp/foo$ make "test"
cc test.c -o test
anthony#Zia:/tmp/foo$ make "test space"
make: *** No rule to make target `test space'. Stop.
The same problem happens when you write a makefile.
anthony#Zia:/tmp/foo$ rm test
anthony#Zia:/tmp/foo$ echo 'all: test test\ space' > Makefile
anthony#Zia:/tmp/foo$ make
cc test.c -o test
make: *** No rule to make target `test space', needed by `all'. Stop.
Even if you explicitly add in a %: %.c rule, the result is the same. But if you add in an explicit rule to the Makefile, like this, it works:
test\ space: test\ space.c
$(CC) -o "$#" "$<" # first char is tab, of course.
Is there a trick to get spaces to work with implicit rules?
edit
I've sent a bug report: http://lists.gnu.org/archive/html/bug-make/2011-06/msg00002.html

I don't believe so. The notion of a list of whitespace-separated tokens being passed around as a string is pretty deeply ingrained in make. Those lists are parsed and reparsed. There's a reason why spaces in directory and file names is considered bad practice in the UNIX world.

This is a kludge, but as of today, people still get given paths with spaces in sometimes.
Anyway, making a link instead of directly accessing the directory in the % rule works OK.
# GNU makefile
DIR_WITH_SPACE=/c/Users/me/My\ Code
# *** DOESN'T WORK ***
%.h : $(DIR_WITH_SPACE)/%.h
cp -v "$<" "$#"
fix:
ln -s $(DIR_WITH_SPACES) dir_fixed
# Does work :)
%.h : dir_fixed/%.h
cp -v "$<" "$#"

Related

GNU Make: Canned recipe which is meant to generate prerequisites for rule causes error "No rule to make target"

I have this simple Makefile:
define some_canned_recipe
find 'foobar' -print
endef
run-something: $(call some_canned_recipe)
#$(info ** [Make] run-something)
#touch $#
I want the 'run-something' rule to be run if and only if one or more files or directories has changed under subdirectory 'foobar'. When I invoke 'make run-something' inside WSL2 however I get this error:
make: *** No rule to make target 'find', needed by 'run-something'. Stop.
Is there a way to achieve what I want (in terms of dynamically generating the prerequisites for the 'run-something' rule)?
PS: I'm aware that a silly solution would be:
define some_canned_recipe
$(shell find 'foobar' -print)
endef
even though this works its not really a good idea because $(shell ...) will run even when the rule 'run-something' is not being targeted.
You can do this using secondary expansion combined with implicit rules:
.SECONDEXPANSION:
run-something:
run-%: $$(shell find 'foobar' -print)
#$(info ** [Make] $#)
#touch $#
(note the $$ before the shell function)
I make no comments on whether I think this is the best way to do it :).

why make behaves different with quotes?

I know quotes are not supposed to be used within Makefile, but just out of curiosity, why make behaves differently with make foobar and make. See detailed code below.
Makefile:
TARGET = 'foobar'
$(TARGET): foobar.cpp
g++ -g $^ -o $#
clean:
rm foobar
output:
$ make
g++ -g foobar.cpp -o 'foobar' <-- correct
$ make clean
rm foobar
$ make foobar
g++ foobar.cpp -o foobar <-- incorrect but works. Why?
$ make clean
rm foobar
$ make baz <-- doesn't work, which is normal
make: *** No rule to make target 'baz'. Stop.
$
This only "works" because the shell is stripping the single quotes from your first example for you.
The quotes are literally in the value of the $(TARGET) make variable. make doesn't dequote the right-hand side of the TARGET = 'foobar' assignment.
You can see this by using $(info $(TARGET)) in your makefile.
So your target line:
$(TARGET): foobar.cpp
is creating a target with the name 'foobar' and not foobar like you expect.
This is why running make does the "right" thing and make foobar does something else.
make foobar is running the make built in rule for %: %.cpp.
The fact that your default 'foobar' target works to create foobar is because the shell sees the single quotes and strips them.
You'll notice that if you make make; make make will build your 'foobar' target twice but make foobar; make foobar will tell you there is nothing to be done the second time. That's because the first target creates a file different from what make is expecting.
If you were to quote $# in your recipe line you would see different behavior.
$(TARGET): foobar.cpp
g++ -g $^ -o '$#'
for example would have make run g++ -g foobar.cpp -o ''foobar'' and generate a foobar file while
$(TARGET): foobar.cpp
g++ -g $^ -o "$#"
would have make run g++ -g foobar.cpp -o "'foobar'" and generate a 'foobar' file (which would cause make; make to report nothing to be done for the second make run).
You want the quotes in the recipe line not in the variable here.
TARGET = foobar
$(TARGET): foobar.cpp
g++ -g $^ -o '$#'
clean:
rm foobar
That being said since you can't have spaces in make target names (not reliably at least) the need for those single quotes (or any quoting) is diminished since you only need it if the filename contains shell metacharacters.
It's because make uses a default rule when it does not find specific rules to build a target.
You can compile program from program.cpp even without or with an empty Makefile. Try
make -f /dev/null foobar
The default rules are specified by POSIX and your make implementation has probably some of its own.
Trying to build baz fails, because none of the default rules knows how to build a baz.c or baz.cpp or any of the other built-in source files that could be used to build baz.

Simple inference rule in makefile

I'm currently learning how to use makefiles. But I'm struggling with % pattern rules. I've boiled down my failing makefile to this very simple example:
I fill an empty directory with:
echo aaa > a.in && echo bbb > b.in
A first makefile like this works very well:
a.out : a.in
cat $< > $#
as
make && echo *.out && cat *.out
returns
cat a.in > a.out
a.out
aaa
but when I try to use a pattern rule modifying the makefile as follows:
%.out : %.in
cat $< > $#
make then returns me:
make: *** No targets. Stop.
It seems like a very simple problem but I can't get to what I am missing...
If you have a makefile with no targets lists (only patterns), and you just type make, then you haven't told make that it should build anything specific, so it won't do anything.
A pattern rule doesn't mean "go find all the files that match this pattern and build them". A pattern rule tells make "if you need to find a way to build a file that matches this target pattern, then here's how you do it".
If you type make a.out so make knows that you want to build a target a.out, then make will use your pattern rule to build it.
Alternatively, you can add the target to your makefile, something like this:
.PHONY: all
all: a.out
%.out : %.in
cat $< > $#

Makefile pattern rule either ignores phony rule or spontaneously deletes output file

I'm trying to write a makefile to produce several output files for each of several sources, using pattern rules.
I have the following Makefile (GNU Make 3.8.1):
all : foo.all bar.all
%.all : %.pdf %.svg
#echo Made $*
%.pdf :
touch $#
%.svg :
touch $#
.PHONY: foo.all bar.all
Since *.all do not represent real output files, I tried marking them as .PHONY. However, running make then doesn't work:
$ ls
Makefile
$ make
make: Nothing to be done for `all'.
According to make -d:
No implicit rule found for `all'.
Considering target file `foo.all'.
File `foo.all' does not exist.
Finished prerequisites of target file `foo.all'.
Must remake target `foo.all'.
Successfully remade target file `foo.all'.
Considering target file `bar.all'.
File `bar.all' does not exist.
Finished prerequisites of target file `bar.all'.
Must remake target `bar.all'.
Successfully remade target file `bar.all'.
Finished prerequisites of target file `all'.
Must remake target `all'.
Successfully remade target file `all'.
make: Nothing to be done for `all'.
which seems to be pretending to run the %.all rules, but skipping the bodies.
But with the .PHONY line commented out, Make runs the targets, but then spontaneously decides to delete the output files:
$ make
touch foo.pdf
touch foo.svg
Made foo
touch bar.pdf
touch bar.svg
Made bar
rm foo.pdf foo.svg bar.pdf bar.svg
According to make -d, it says:
Removing intermediate files...
Minimal example
A minimal example giving anomalous behavior:
%.all: %.out
#echo Made $*
%.out:
touch $#
I expect running make somefile.all to cause it to create the file somefile.out, but it gets deleted:
$ make somefile.all
touch somefile.out
Made somefile
rm somefile.out
Keeping make from deleting intermediary files
I recommend against using .PRECIOUS (see below as to why). Using .SECONDARY would preserve the .out files:
TARGETS=foo bar
all: $(TARGETS:=.all)
%.all: %.out
#echo Made $*
%.out:
touch $#
.SECONDARY: $(TARGETS:=.out)
$(TARGETS:=.all) just appends .all to all names in TARGETS. $(TARGETS:=.out) appends .out. We apparently cannot use %.out as a target of .SECONDARY. These just save having to relist all targets individually.
I prefer to not use .PRECIOUS for this because the documentation says
if make is killed or interrupted during the execution of their recipes, the target is not deleted.
This can leave corrupted files in the file system. Here's an example.
all: foo.all bar.all
%.all: %.out
#echo Made $*
%.out:
sh -e -c 'echo "{1, 2, 3" > $#; FAIL!; echo "}" >> $#'
.PRECIOUS: %.out
The FAIL! command simulates a tool that crashes in the middle of its work. Here's a shell session working with the Makefile above:
$ ls
Makefile
$ make
sh -e -c 'echo "{1, 2, 3" > foo.out; FAIL!; echo "}" >> foo.out'
sh: 1: FAIL!: not found
make: *** [foo.out] Error 127
$ cat foo.out
{1, 2, 3
Yikes... my foo.out file is incomplete. Let's try making again:
$ make
Made foo
sh -e -c 'echo "{1, 2, 3" > bar.out; FAIL!; echo "}" >> bar.out'
sh: 1: FAIL!: not found
make: *** [bar.out] Error 127
$ cat *.out
{1, 2, 3
{1, 2, 3
Make is none the wiser about files left around by earlier runs so when you run make again, it will take the corrupted files at face value. foo.out was not remade (despite the "Made foo" message) because it already exists and the Makefile went straight to trying to make bar.
.SECONDARY makes it so that:
The targets which .SECONDARY depends on are treated as intermediate files, except that they are never automatically deleted.
This means they are never automatically deleted just because they are intermediate files. The default make behavior of deleting targets that were being rebuilt if the tool rebuilding them crashed is not affected.
Using .PHONY with pattern rules
It seems though that .PHONY works only for targets that are explicit, not inferred. I've not found documentation confirming this. However, this works:
TARGETS:=foo bar
TARGETS_all:=$(TARGETS:=.all)
.PHONY: all
all: $(TARGETS_all)
.PHONY: $(TARGETS_all)
$(TARGETS_all): %.all: %.out
#echo Made $*
%.out:
touch $#
.SECONDARY: $(TARGETS:=.out)
In this rule $(TARGETS_all): %.all: %.out $(TARGETS_all): gives the list of targets to which the pattern can be applied. It makes foo.all and bar.all explicit targets. Without this, they would be inferred targets.
You can test that it works by creating file called foo.all in your directory and run make over and over. The foo.all file has no effect on make.
Your somefile.out files are considered intermediate by GNU make, which is why they are automatically deleted in your example. You can instruct GNU make to preserve these files by use the of .PRECIOUS special target, like this:
%.all: %.out
#echo Made $*
%.out:
touch $#
.PRECIOUS: %.out

Wildcard in implicit rule's prerequisites

BUILT_DIR = /tmp/obj
SRC = /source/dir
/tmp/obj/%/builtin.o : $(SRC)/%/*.c
gcc $^ -o $#
But you know :
In order for the pattern rule to apply, its target pattern must match the file name under consideration and all of its prerequisites (after pattern substitution) must name files that exist or can be made.
If i execute make /tmp/obj/hfa/builtin.o,make will complain :
make: *** No rule to make target/tmp/obj/hfa/builtin.o'. Stop.`
How can i modify the Makefile to satisfy my requirement?
You can use Secondary Expansion
BUILT_DIR = /tmp/obj
SRC = /source/dir
.SECONDEXPANSION:
/tmp/obj/%/builtin.o : $$(wildcard $(SRC)/%/*.c)
gcc $^ -o $#
The error you see indicates that there are no .c files which match the pattern $(SRC)/hfa/*.c since the % translates into hfa. So make cannot use the rule you've defined.
Make then starts to use the implicit rules for building and it would not match either.
Finally make gives up.
I just confirmed that the same Makefile and I get the same error only when there are no *.c files in the $(SRC)/hfa directory.
Otherwise, I see the gcc command getting executed.
And from your question, it is not quite clear what your requirement is.
The rule configuration that worked for me:
bar-%-foo:
#touch $#
.SECONDEXPANSION:
foo-%-bar: bar-$$*-foo
#echo "#: $#"
#echo "<: $<"
#touch $#
foo-bar: foo-biz-bar foo-baz-bar
.PHONY: foo-bar
And demo:
$:make foo-bar
#: foo-biz-bar
<: bar-biz-foo
#: foo-baz-bar
<: bar-baz-foo
rm bar-biz-foo bar-baz-foo
$:ls | grep foo
foo-baz-bar
foo-biz-bar

Resources