What is the difference between % and * in a makefile - 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.

Related

How to handle spaces in filename while providing prerequisites using a variable for a make rule?

we have few html file title with spaces in a folder which is a prerequisite for make rule. while executing, the rule has failed stating there is no rule to make the target.
do we have anything to handle spaces in make file prerequisite?
prerequisite = $(wildcard D:/HtmlHelp/*.html)
the above variable results below files list
D:/HtmlHelp/Order Coordination.html D:/HtmlHelp/OverThres.html D:/HtmlHelp/Pmu.html
First file name has a space , which executing the rule with this prerequisite list ,make returns below error.
gmake[2]: *** No rule to make target `D:/HtmlHelp/Order', needed by `rule1'. Stop.
If you replace:
prerequisite = $(wildcard D:/HtmlHelp/*.html)
with simply:
prerequisite = D:/HtmlHelp/*.html
then GNU make will work, although you will need to replace in your rules $# with "$#", etc. The wildcard expansion will then happen in the rules, as https://www.gnu.org/software/make/manual/html_node/Wildcard-Function.html states:
Wildcard expansion happens automatically in rules. But wildcard expansion does not normally take place when a variable is set...
You can see this by inserting the statement:
$(info $(prerequisite))
into your makefile.
Another approach, from Using makefile wildcard command for file names with spaces, would be to use something like:
prerequisite = $(shell find 'D:/HtmlHelp/' -name '*.html' | sed 's/ /\\ /g')
to escape the spaces.
Finally, Can GNU make handle filenames with spaces? has a number of suggestions, with several referencing the blog post https://www.cmcrossroads.com/article/gnu-make-meets-file-names-spaces-them.

"%" in $(wildcard) not expanded?

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.

Does a '%' in a pattern match an empty string?

From the docs:
A vpath pattern is a string containing a % character. The string
must match the file name of a prerequisite that is being searched for,
the % character matching any sequence of zero or more characters (as
in pattern rules).
Now, although, it is true that % does match an empty string (string of zero length) in a vpath pattern (vpath % foo), this is not true for pattern-rules.
So, it is wrong for the documentation above, to equate between them, as:
...the '%' character matching any sequence of zero or more characters (as
in pattern rules.
As this is simply not true, as evident by the following Makefile:
all ::
al%l :
#echo '$#'
.
Executing, we get:
# It is evident that 'all' doesn't match 'al%l'
$ make -r
make: Nothing to be done for 'all'.
# But, 'all' does match 'al%'
$ make -r -f makefile -f <(echo 'al% : ; echo $#')
echo all
all
.
In fact, this is well documented:
For example, %.c as a pattern matches any file name that ends in
.c. s.%.c as a pattern matches any file name that starts with s.,
ends in .c and is at least five characters long. (There must be at
least one character to match the %.) The substring that the %
matches is called the "stem".
Agree?
Yes, it does.
The problem in your example is that you are mixing and matching single vs. double colon recipes. This is explicitly not allowed, you need to do one or the other for all matching rules.
Also, having different patterns does not qualify as being the same target and the most specific match will usually get run and the others ignored (even if a zero width match as in your example might be present).

Make: How to escape spaces in $(addprefix)?

Here's what I am trying to do:
EXTRA_INCLUDE_PATHS = ../dir1/path with spaces/ \
../dir2/other path with spaces/
CPPFLAGS += $(addprefix -I,$(EXTRA_INCLUDE_PATHS))
I want to get “-I../dir1/path with spaces/ […]”, but I get instead: “-I../dir/path -Iwith -Ispaces/ […]”.
How do I espace spaces in addprefix? I've tried doing this trick, but it produces same result:
space =
space +=
#produces “-Isome -Ipath”
CPPFLAGS += $(addprefix -I,some$(space)path)
Don't do it! As #MadScientist points out, you need to eschew all space-containing filenames in a makefile unless you want to be very sad. A space is a list separator (including in lists of targets), and there is just no way around that. Use symbolic links in your file system to avoid them! (mklink on windows, or use cygwin make which understands cygwin symbolic links).
That said, in the current case (where you are not defining a list of targets, but merely a list of include dirs), you could use a single character to represent a space, only translating it right at the end.
EXTRA_INCLUDE_PATHS = ../dir1/path|with|spaces/ ../dir2/other|path|with|spaces/
CPPFLAGS += $(subst |, ,$(patsubst %,-I'%',${EXTRA_INCLUDE_PATHS}))
Check the result:
$(error [${CPPFLAGS}])
gives Makefile:3: *** [-I'../dir1/path with spaces/' -I'../dir2/other path with spaces/']. Stop.. Here, $CPPFLAGS is in sh format—the spaces are quoted with ' so that the compiler sees each -I as a single parameter. make simply does not have this level of quoting.
If all your include dirs start with the same character sequence, you can exploit this for use with the substitute command:
CPPFLAGS += $(subst ..,-I ..,$(EXTRA_INCLUDE_PATHS))
Check the result with:
$(info ${CPPFLAGS})
(this is a slightly different (maybe more elegant) version of #bobbogo's answer)
If you escape spaces with a backslash (\ ), you can replace the escaped space by a pipe symbol (|), add the prefix and then undo the replace operation:
EXTRA_INCLUDE_PATHS = ../dir1/path\ with\ spaces/ ../dir2/other\ path\ with spaces/
CPPFLAGS += $(subst |,\ ,$(addprefix -I,$(subst \ ,|,${EXTRA_INCLUDE_PATHS})))

How to change default bash globbing?

I have a Makefile which has this line :
$(CROSS_COMPILE)$(CC) $(DLFLAGS) $(LIBPATH) -o $(OUTP)/$(SONAME) *.o $(LIBINCL)
This *.o expansion sometimes looks like 1.0 2.o 3.o 4.o. However, other times it can be 2.o 1.o 4.o 3.o (and other combinations). This causes the resulting shared object to have different checksums.
As a limited workaround, in some cases we change the line above to this :
$(CROSS_COMPILE)$(CC) $(DLFLAGS) $(LIBPATH) -o $(OUTP)/$(SONAME) $(sort $(wildcard *.o)) $(LIBINCL)
However, I can't do this fix for every source package. What I would like is for the shell (bash) to perform the glob substitution based on a sort of the filename, so that the '*.o' glob substitution above is consistent between machines and builds.
Any ideas ? Can I tell bash to (by default) change how it does globbing to accomplish what I want ?
I don't think it is a good idea to count on wildcard expansion to return filenames in a specific order -- but if you know that the filenames will be in a certain numeric interval, why not use brace expansion (https://www.gnu.org/software/bash/manual/bashref.html#Brace-Expansion)?
$ echo {1..4}.o
1.o 2.o 3.o 4.o

Resources