Rebuilding when a variable change - makefile

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).

Related

GNU Make: shell cat file yields contents without newlines

Makefile:
.PHONY: all
SHELL:=/usr/bin/env bash
all:
$(eval x=$(shell cat file))
#echo "$x"
File:
foo
bar
Output:
foo bar
How do I get the contents of the file into the make variable without losing the newlines?
You can't do this with shell, as described in its documentation.
If you have a sufficiently new version of GNU make, you can use the file function however.
Make converts newlines from shell outputs to spaces (see here):
The shell function performs the same function that backquotes (‘`’)
perform in most shells: it does command expansion. This means that it
takes as an argument a shell command and evaluates to the output of
the command. The only processing make does on the result is to convert
each newline (or carriage-return / newline pair) to a single space. If
there is a trailing (carriage-return and) newline it will simply be
removed.
So, you cannot preserve spaces from the $(shell) command directly. That being said, make does allow multiline variables using define -- but beware, attempting to use such variables is problematic. Consider:
define x
foo
bar
endef
all:
#echo "$x"
Make expands the $x in place, and you end up with:
all:
#echo " foo
bar"
(where the newline is considered the end of the recipe line..).
Depending on what you want this for, you may be able to get around this is using a bash variable:
all:
#x=$$(cat file); \
echo $$x
Or potentially storing your output in a file, and referencing that when necessary.
all:
eval (cat file >> output.txt)
cat output.txt
(and yes, the last one is convoluted as written, but I'm not sure what you're trying to do, and this allows the output of your command to be persistent across recipe lines).
If the file contents are ensured not to contain any binary data, and if you're willing to to extra processing each time you access the variable, then you could:
foo:=$(shell cat file | tr '\n' '\1')
all:
#echo "$(shell echo "$(foo)" | tr '\1' '\n')"
Note that you cannot use nulls \0, and I suspect that probably means there's a buffer overflow bug in my copy of Make.

"%" 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.

UNIX - why doesn't $# include the command (thus always being >= 1)?

It's kind of hard to search for an answer on this since $# doesn't seem to go through properly on search engines. I was curious as to why argv typically includes the command name itself, while $# doesn't.
To make it clearer, if I have a script called testing.sh
#!/bin/bash
echo $#
./testing.sh returns 0 and not 1. Why?
bash is following the POSIX specification for $#:
Expands to the decimal number of positional parameters. The command name (parameter 0) shall not be counted in the number given by '#' because it is a special parameter, not a positional parameter.
The shell's interface to the arguments is simply different from C's. In bash terms, you might define
argv=( "$0" "$#")
argc=${#argv[#]}
since the shell (sensibly) separates the command name from the command's arguments.

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.

Makefile: couple syntax questions

package_version := $(version)x0d$(date)
what is the x0d part between version and date vars? is it just string?
What $(dotin_files:.in=) does below
code
dotin_files := $(shell find . -type f -name \*.in)
dotin_files := $(dotin_files:.in=)
what this means $(dotin_files:=.in)
code
$(dotin_files): $(dotin_files:=.in)
$(substitute) $#.in > $#
can target contain multiple files?
what is the meaning of declaring target variable as PHONY?
code
.PHONY: $(dotin_files)
In the regex replacement code below
code
substitute := perl -p -e 's/#([^#]+)#/defined $$ENV{$$1} ? $$ENV{$$1} : $$&/ge'
what are $$ENV{$$1} and $$&? I guess it's Perl scope...
thanks for your time
Variable Expansion
$() is variable expansion in make, this should just be string substitution - if your makefile is
version=1
date=1.1.10
package_version:=$(version)x0d$(date)
then the variable package_version will expand to 1x0d1.1.10.
Substitution
The syntax $(var:a=b) is a substitution reference and will expand to var with a suffix a substituted with b.
For example, in
foobar:= foo bar
faabar:=$(foobar:oo=aa)
$(faabar) will expand to the string faa bar.
Multiple Targets
Multiple targets in a make rule is equivalent to having n rules with a single target, eg
foo bar:foo.c bar.c
$(CC) -o $# $^
is equivalent to
foo:foo.c bar.c
$(CC) -o $# $^
bar:foo.c bar.c
$(CC) -o $# $^
remember that any variables here are expanded.
Phony Targets
The .PHONY target declares that a rule doesn't produce an actual file, so it will always be built. As always, variables are expanded first. In your case this will expand to something like
.PHONY: foo bar
Escaping
A dollar sign is an escape character in makefiles, the $$ in your perl example is a literal $, eg substitute will be the string
perl -p -e 's/#([^#]+)#/defined $ENV{$1} ? $ENV{$1} : $&/ge'
The dollar signs here are processed by perl, and probably give environment variables (I don't know perl).
x0d part between version and date vars, is it just string?
Yes.
What $(dotin_files:.in=) does below
Removes the .in from the filenames found with the shell find.
what this means $(dotin_files:=.in)
I think you meant $(dotin_files:.in=). As already answered, within the variable dotin_files it replaces any occurrence of ".in" with an empty string(the part between the "=" and ")".
can target contain multiple files?
Yes
what is the meaning of declaring target variable as PHONY?
make will ignore targets time-stamp and consider them as new
thus rebuilding them each time.
In the regex replacement code below what are $$ENV{$$1} and $$&?
To avoid expansion of $ENV, the $ is doubled, think of '%' in C format strings, thus the string
perl -p -e 's/#([^#]+)#/defined $$ENV{$$1} ? $$ENV{$$1} : $$&/ge'
when called as a shell command will become:
perl -p -e 's/#([^#]+)#/defined $ENV{$1} ? $ENV{$1} : $&/ge'
$ENV is the perl Environment hash, $1 I think it's a backreference in the s/// regexp group.
Michael, you've been asking a lot of basic Makefile questions, and the ones you're asking now are ones you should be able to answer for yourself by experiment.
can target contain multiple files?
Try it:
dotin_files := foo.in bar.in
$(dotin_files):
#echo $#
Now try make foo.in and make bar.in. What happens?
What $(dotin_files:.in=) does
It's a substitution reference. Try it yourself and see what happens, like this:
dotin_files := foo.in bar.in
dotin_files := $(dotin_files:.in=)
all:
#echo $(dotin_files)
What did it do?
substitute := perl -p -e 's/#([^#]+)#/defined $$ENV{$$1} ? $$ENV{$$1} : $$&/ge'
what are $$ENV{$$1} and $$&? I guess it's Perl scope...
Let's take a look:
all:
#echo $(substitute)
If you want to know more about Perl, or regexs, or find, or make, or whatever, feel free to ask here, but please take a little time to try to figure it out first.

Resources