"%" in $(wildcard) not expanded? - makefile

I want to encode the the rule "to make <name>.done, you need all files of the pattern <name>.needed.*. I've attempted to write this with this Makefile:
%.done: $(wildcard %.needed.*)
cat $^ > $#
Yet when I run touch foo.needed.bar && make foo.done, all I get is
cat > foo.done
It appears the % inside $(wildcard) is being interpreted as a literal "%". How can I get it expanded to the right value ("foo" in this case)?

The % is just a placeholder for "any string" in pattern matching. It has no special meaning in the wildcard function and is interpreted literally.
You might attempt using $* instead (which would expand to the stem of the filename), but unfortunately it won't work either:
%.done: $(wildcard $*.needed.*)
The reason it doesn't work is that the automatic variables ($* is one of them) are not available for use in the dependency list.
The workaround is to request a secondary expansion for the target:
.SECONDEXPANSION:
%.done: $$(wildcard $$*.needed.*)
This will prompt GNU Make to go over the rule a second time after processing the Makefile as usual, expanding any escaped variables that weren't expanded the first time around. The second time around, the automatic variable have their appropriate values.

Related

Rebuilding when a variable change

Some make variables have an effect on the generation of the targets and thus one may want to rebuild if their value changes, for instance because they were specified explicitly on the command line. With GNU Make, it is relatively easy to do. For instance one can do this:
CHECK_CFLAGS:=.last-cflags.$(shell echo $(CFLAGS) | md5sum | awk '{ print $$1 }')
foo.o: foo.c $(CHECK_CFLAGS)
.last-cflags.%:
-rm .last-cflags.*
touch $#
(Obviously if you are using target specific value, things become more complex). Is there a way to achieve the same desired effect with standard (POSIX) Make? If not, with the BSD variant of Make?
If you can assume GNU make 4.0 or above, you can use the != assignment operator which was implemented in GNU make for portability with BSD make.
From the GNU make manual:
The shell assignment operator '!=' can be used to execute a shell
script and set a variable to its output. This operator first evaluates
the right-hand side, then passes that result to the shell for execution.
If the result of the execution ends in a newline, that one newline is
removed; all other newlines are replaced by spaces. The resulting
string is then placed into the named recursively-expanded variable.
Note there is one subtle difference between != and := $(shell ...): in the former the resulting variable is a recursive variable which means it will be re-expanded on use. So you need to be careful if your script might emit characters that are special to make (the $ basically).

Testing the eval function of GNU make

The GNU make manual says that the eval function expands the arguments and then feeds the results of the expansion to make parser. The following is quoted from GNU make manual.
The argument to the eval function is expanded, then the results of that expansion are parsed as makefile syntax.
I don't quite understand how the make parser process the text fed by eval, so
I write following makefile to test.
define myprint
echo "this is a line"
endef
goal:
$(eval $(call myprint))
gcc -o goal test.c
I know that the correct invocation of myprint should be only use the call function: $(call myprint) and delete the 'Tab' character before echo. I write the makefile in this form just to test the eval function.
My expectation: first the eval function expands myprint, which is an echo command preceded by a 'Tab', and the 'Tab' is used to make the expanded text to be a legal recipe. Then the eval feeds the expanded text to maker parser, who will identify the text to be a recipe, and run it. As the command is legal, the makefile should run properly.
However, I meet such an error:
Makefile:6: *** recipe commences before first target. Stop.
Could somebody explain why make produce such an error?
the results of that expansion are parsed as makefile syntax
Your use of eval is different: you would like it to be parsed as shell syntax. You can write:
define myprint
echo "this is a line"
endef
goal:
$(myprint)
gcc -o goal test.c
or:
define myprint
echo "this is a $(1)"
endef
goal:
$(call myprint,line)
gcc -o goal test.c
Because after make expansion the recipes are valid shell syntax. But not what you wrote because the expansion of eval is still interpreted as make syntax, not shell. To illustrate a typical use of eval and call, consider this:
define myprint
echo "this is a $(1)"
endef
define mygoal
$(1):
$$(call myprint,line)
gcc -o $(1) $(2).c
endef
$(eval $(call mygoal,goal,test))
It is a bit more tricky than two first examples (without eval) but it illustrates the real purpose of eval: programmatically instantiate make constructs. Here is how it works, step by step:
During the first phase of its 2-phases algorithm, make expands the $(eval... function call, that is:
Expand the parameter of the $(eval... function (the $(call... function):
Expand the parameters of the $(call... function (goal and test). No effect in our case.
Assign the result to the temporary variables $(1) and $(2).
Expand the mygoal variable in this context, which replaces $(1), $(2) and $$(call... by goal, test and $(call..., respectively.
Instantiates (in memory) the result as a make construct, a complete rule in this case:
goal:
$(call myprint,line)
gcc -o goal test.c
The first phase continues but it has no effect on this instantiated rule because the recipes are expanded by make during the second phase.
During the second phase, when the time comes to build the goal target, make expands the recipe before executing it, that is:
Expand the $(call myprint... parameter (line, no effect).
Assign the result to temporary parameter $(1).
Expand variable myprint in this context, which produces:
echo "this is a line"
All this is thus the same as if we had written the rule:
goal:
echo "this is a line"
gcc -o goal test.c
Note the double $$ in the initial definition of mygoal:
It’s important to realize that the eval argument is expanded twice;
first by the eval function, then the results of that expansion are
expanded again when they are parsed as makefile syntax. This means you
may need to provide extra levels of escaping for “$” characters when
using eval.
$(eval …) needs a syntactically complete makefile fragment. It cannot be used to paste tokens into other makefile constructs. Perhaps the manual does not explain this clearly enough, but it's implemented by reading its argument as if it were an included makefile.
#RenaudPacalet, I write following makefile to test whether the expansion of call 'eats' one dollar.
define myprint
echo "In my print $$(ls)"
endef
goal:
$(call myprint)
$(info $(call myprint))
gcc -o goal test.c
Its output is:
echo "In my print $(ls)"
echo "In my print $(ls)"
In my print call.mk ... (files list)
As $(call myprint) outputs "In my print $(ls)" correctly, it must be expanded to echo "In my print $$(ls)" first, then it will be expanded to the correct shell command echo "In my print $(ls)". So I think the call function does not 'eats' one dollar.
Another evidence is the output of info function. The GNU make manual says:
$(info text…)
This function does nothing more than print its (expanded) argument(s) to standard output.
From the manual we can infer that make will expand the arguments of the info function. As the output of the info is echo "In my print $(ls)", so the arguments before expansion should be echo "In my print $$(ls)". So we can conclude that the call function doesn't 'eat' one dollar.

What is the difference between % and * in a makefile

The GNU make manual does not excel at explaining this part, I could not find the explanation or I could not infer the information elsewhere.
I realize % is a kind of wildcard, but what is the difference between % and * in the context of targets, dependencies and commands? Where can I use it and does it have the same meaning everywhere?
target: dependencies ...
commands
The wildcard character * is used to simply generate a list of matching files in the current directory. The pattern substitution character % is a placeholder for a file which may or may not exist at the moment.
To expand on the Wildcard pitfall example from the manual which you had already discovered,
objects = *.o
The proper way to phrase that if there no '.o' files is something like
objects := $(patsubst %.c,%.o,$(wildcard *.c))
make itself performs no wildcard expansion in this context, but of course, if you pass the literal value *.o to the shell, that's when expansion happens (if there are matches) and so this can be slightly hard to debug. make will perform wildcard expansion in the target of a rule, so you can say
foo: *.o
and have it work exactly like you intended (provided the required files are guaranteed to exist at the time this dependency is evaluated).
By contrast, you can have a rule with a pattern placeholder, which gets filled in with any matching name as make tries to find a recipe which can be used to generate a required dependency. There are built-in rules like
%.o: %.c
$(CC) $(CCFLAGS) $^ -o $#
(approximating the real thing here) which say "given a file matching %.c, the corresponding file %.o can be generated as follows." Here, the % is a placeholder which can be replaced by anything; so if it is applied against an existing file foo.c it says how foo.o can be generated.
You could rephrase it to say * matches every matching file while % matches any matching file.
Both % and * are ordinary characters in Make recipe lines; they are just passed to the shell.
% denotes a file "stem" in pattern substitutions, as in $(patsubst %.o,%.c,$(OBJS)). The pattern %.o is applied to each element in $(OBJS), and % captures the matching part. Then in the replacement pattern %.c, the captured part is substituted for the %, and a list of the substitutions emerges out of patsubst as the return value.
* is useful in the argument of the $(wildcard ...) operator, where it resembles the action of the shell * glob in matching some paths in the filesystem.
On the left hand side of a patsubst, where % denotes a match, it resembles * in that it matches some characters. However, % carries some restrictions, such as that it can only appear once! For instance whereas we can expand the wildcard */*.c, of course, we cannot have a double stem pattern substitution like $(patsubst %/%.o,%/foo/%.c,...). This restriction could be lifted in some future version of GNU Make, but it currently holds as far as I know.
Also there is a subtle difference between % and * in that % matches a nonempty sequence of characters. The wildcard pattern fo*o.c matches foo.c. The substitution pattern fo%o.c does not match foo.c, because then the stem % would be empty which is not allowed.

Using ifdef and ifndef directives

I'm trying to check whether a variable is defined using ifndef/ifdef, but I keep getting a not found error from the execution. I'm using GNU Make 3.81, and here is a snippet of what I have:
all: _images
$(call clean, .)
$(call compile, .)
#$(OPENER) *.pdf &
_images:
$(call clean, "images")
$(call compile, "images")
define clean
#rm -f ${1}/*.log ${1}/*.aux ${1}/*.pdf
endef
define compile
ifdef ${1}
dir = ${1}
else
dir = .
endif
ifdef ${2}
outdir = ${2}
else
outdir = ${1}
endif
#$(COMPILER) -output-directory ${outdir} ${dir}/*.tex
endef
And the exact error:
$ make
ifdef "images"
/bin/sh: 1: ifdef: not found
make: *** [_images] Error 127
Edit:
Considering Barmar comments, here goes the conclusions:
The contents of a define are shell command lines, not make directives;
to break lines inside commands within a define block, the linebreak must be escaped -- with \;
also, each block corresponding to one-liner commands is executed separately, each in a different shell execution, which means that, defining local variables won't work if the intention is to access the variable value in the next one-liner block.
Thanks tripleee for the nice work around.
You can combine the shell's facilities with Make's to get a fairly succinct definition.
define compile
#dir="${1}"; outdir="${2}"; outdir=$${outdir:-"$dir"}; \
$(COMPILER) -output-directory "$${outdir}" "$${dir:-.}/*.tex
The double-dollar is an escape which passes a single dollar sign to the shell. The construct ${variable:-value} returns the value of $variable unless it is unset or empty, in which case it returns value. Because ${1} and ${2} are replaced by static strings before the shell evaluates this expression, we have to take the roundabout route of assigning them to variables before examining them.
This also demonstrates how to combine two "one-liners" into a single shell invocation. The semicolon is a statement terminator (basically equivalent to a newline) and the sequence of a backslash and a newline causes the next line to be merged with the current line into a single "logical line".
This is complex enough that I would recommend you omit the leading # but I left it in just to show where it belongs. If you want silent operation, once you have it properly debugged, run with make -s.

Can GNU make handle filenames with spaces?

I have a directory containing several files, some of which have spaces in their names:
Test workspace/
Another directory/
file1.ext
file2.ext
demo 2012-03-23.odp
I use GNU's $(wildcard) command on this directory, and then iterate over the result using $(foreach), printing everything out. Here's the code:
FOO := $(wildcard *)
$(info FOO = $(FOO))
$(foreach PLACE,$(FOO),$(info PLACE = $(PLACE)))
Here's what I would expect to see printed out:
Test workspace
Another directory
file1.ext
file2.ext
demo 2012-03-23.odp
Here's what I would actually get:
Test
workspace
Another
directory
file1.ext
file2.ext
demo
2012-03-23.odp
The latter is obviously of no use to me. The documentation for $(wildcard) flat-out states that it returns a "space-separated list of names" but completely fails to acknowledge the huge problems this raises. Nor does the documentation for $(foreach).
Is it possible to work around this? If so, how? Renaming every file and directory to remove the spaces is not an option.
The bug #712 suggests that make does not handle names with spaces. Nowhere, never.
I found a blog post saying it's partially implemented by escaping the spaces with \ (\\ seems to be typo or formatting artefact), but:
It does not work in any functions except $(wildcard).
It does not work when expanding lists of names from variables, which includes the special variables $?, $^ and $+ as well as any user-defined variable. Which in turn means that while $(wildcard) will match correct files, you won't be able to interpret the result anyway.
So with explicit or very simple pattern rules you can get it to work, but beyond that you are out of luck. You'll have to look for some other build system that does support spaces. I am not sure whether jam/bjam does, scons, waf, ant, nant and msbuild all should work.
GNU Make does very poorly with space-separated filenames.
Spaces are used as delimiters in word list all over the place.
This blog post summarizes the situation well, but WARNING: it incorrectly uses \\ rather than \
target: some\ file some\ other\ file
some\ file some\ other\ file:
echo done
You can also use variables, so this would also work
VAR := some\ file some\ other\ file
target: $(VAR)
$(VAR):
echo done
Only the wildcard function recognizes the escaping, so you can't do anything fancy without lots of pain.
But don't forget that your shell uses spaces as delimiters too.
If I wanted to change the echo done to touch $#, I'd have to add slash to escape it for my shell.
VAR := some\ file
target: $(VAR)
$(VAR):
touch $(subst \,\\,$#)
or, more likely, use quotes
VAR := some\ file some\ other\ file
target: $(VAR)
$(VAR):
touch '$#'
In the end, if you want to avoid a lot of pain, both in GNU make, and in your shell, don't put spaces in your filenames. If you do, hopefully the limited capabilities of Make will be sufficient.
This method will also allow use of listed file names such as $? and user variables that are lists of files.
The best way to deal with spaces in Make is to substitute spaces for other characters.
s+ = $(subst \ ,+,$1)
+s = $(subst +,\ ,$1)
$(call s+,foo bar): $(call s+,bar baz) $(call s+,bar\ baz2)
# Will also shows list of dependencies with spaces.
#echo Making $(call +s,$#) from $(call +s,$?)
$(call s+,bar\ baz):
#echo Making $(call +s,$#)
$(call s+,bar\ baz2):
#echo Making $(call +s,$#)
Outputs
Making bar baz
Making bar baz2
Making foo bar from bar baz bar baz2
You can then safely manipulate lists of file names using all the GNU Make
functions. Just be sure to remove the +'s before using these names in a rule.
SRCS := a\ b.c c\ d.c e\ f.c
SRCS := $(call s+,$(SRCS))
# Can manipulate list with substituted spaces
OBJS := $(SRCS:.c=.o)
# Rule that has object files as dependencies.
exampleRule:$(call +s,$(OBJS))
# You can now use the list of OBJS (spaces are converted back).
#echo Object files: $(call +s,$(OBJS))
a\ b.o:
# a b.o rule commands go here...
#echo in rule: a b.o
c\ d.o:
e\ f.o:
Outputs
in rule: a b.o
Object files: a b.o c d.o e f.o
This info is all from the blog that everyone else was posting.
Most people seem to be recommending using no spaces in paths or using Windows 8.3 paths, but if you must use spaces, escaping spaces and substitution works.
If you are willing to rely on your shell a bit more, this gives a list which can hold names with spaces just fine:
$(shell find | sed 's: :\\ :g')
The original question said that "renaming is not an option", yet many commenters have pointed out that renaming is pretty much the only way Make can handle spaces. I suggest a middle way: Use Make to temporarily rename the files and then rename them back. This gives you all the power of Make with implicit rules and other goodness, but doesn't mess up your file naming scheme.
# Make cannot handle spaces in filenames, so temporarily rename them
nospaces:
rename -v 's/ /%20/g' *\ *
# After Make is done, rename files back to having spaces
yesspaces:
rename -v 's/%20/ /g' *%20*
You could call these targets by hand with make nospaces and make yesspaces, or you can have other targets depends on them. For example, you might want to have a "push" target which makes sure to put the spaces back in filenames before syncing files back with a server:
# Put spaces back in filenames before uploading
push: yesspaces
git push
[Sidenote: I tried the answer which suggested using +s and s+ but it made my Makefile harder to read and debug. I gave up on it when it gave me guff over implicit rules likes: %.wav : %.ogg ; oggdec "$<".]

Resources