💾 Archived View for tobik.me › 2022 › 02 › 09 › make-patterns.gmi captured on 2022-03-01 at 15:13:53. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
Posted on 2022-02-09
Variable modifiers in bmake (make(1) on FreeBSD) are very powerful. Here are some examples of what you can do with them.
I wrote this for myself a while back and am now publishing this here. Some parts of this were very helpful when I switched USES=cargo to use the .crate file extension for crates on new ports and when I overhauled the Git source support in USES=cargo last year.
Commit: Mk/Uses/cargo.mk: Use canonical crate file extension going forward
Commit: Uses/cargo: Rework git source support based on patch-in-config sections
All of these examples can be tried out on the command line with
$ printf 'V=${:Ufoo}' | make -f - VV foo
:U allows you to return a default value in case a variable is undefined. The following will return 'foo' if FLAVOR was left undefined.
FLAVOR:Ufoo
The inverse is :D which returns a set value if a variable is defined. The following will return 'foo' if FLAVOR was defined:
FLAVOR:Dfoo $ make -f /dev/null FLAVOR=bla -VFLAVOR:Dfoo foo
Note that 'x:Uy' is *not* a shortcut for
${:?defined(x):${x}:y}
and neither is 'x:Dy' a shortcut for
${:?defined(x):y:${x}}
:D and :U will only ever be evaluated once but :? will be evaluated whenever it is encountered and thanks to the ${x} indirection inside, the value of 'x' will be looked up again later.
Zipping up 2 lists A and B can be done with
A:range:@i@${A:[$i]} ${B:[$i]}@
If A and B are not the same length the result might be of an odd-numbered length, so cannot be used with .for with multiple loop variables.
With 3 lists A, B, and C this becomes:
A:range:@i@${A:[$i]} ${B:[$i]} ${C:[$i]}@
etc.
$ printf 'A=1 2 3\nB=a b c\nC=x y z\nV=${A:range:@i@${A:[$i]} ${B:[$i]} ${C:[$i]}@}' | make -f - -VV 1 a x 2 b y 3 c z
Now that we know how to zip up word lists we can easily figure out how to enumerate a variable:
$ printf 'A=a b c\nV=${A:range:@i@$i ${A:[$i]}@}' | make -f - -VV 1 a 2 b 3 c
or
$ cat <<EOF | make -f - A= a b c .for index word in ${A:range:@i@$i ${A:[$i]}@} .info "${index}. ${word}" .endfor all: EOF make: "(stdin)" line 3: "1. a" make: "(stdin)" line 3: "2. b" make: "(stdin)" line 3: "3. c"
We still need to handle the case of empty lists because a suprising quirk of ':range' is that 'A:range' will return 1 if A is defined but empty:
$ printf 'A=\nV=${A:range:@i@$i ${A:[$i]}@}' | make -f - -VV 1
Luckily we can easily handle that:
$ printf 'A=\nV=${empty(A):?:${A:range:@i@$i ${A:[$i]}@}}' | make -f - -VV <empty>
We have the != assignment operator and the :sh and :! modifiers that capture stdout of a shell command. They behave differently. This is best shown with a couple of examples.
With != (expand V 3 times and command runs only once):
$ printf 'V!=echo ran>&2; echo foo\n.info hi' | make -f - -VV -VV -VV ran make: "(stdin)" line 2: hi foo foo foo
!= forces variable expansion at the time of definition, but :! and :sh are lazy. This is exemplified by how the 'hi' appears only after the command ran.
Laziness with ':!' but uncached (expand V 3 times and command runs 3 times):
$ printf 'V=${:!echo ran>&2; echo foo!}\n.info hi' | make -f - -VV -VV -VV make: "(stdin)" line 2: hi ran foo ran foo ran foo
This is not quite working as !=. The first 'ran' comes after the 'hi' due to the laziness of :!. The command is also rerun every time we access the value.
But there is a way around the rerun problem by combining :! with :_ and :?.
Lazy :! (expand V 3 times and command runs only once):
$ printf 'V=${defined(_CACHE):?${_CACHE}:${:!echo ran>&2; echo foo!:_=_CACHE}}\n.info hi' | make -f - -VV -VV -VV make: "(stdin)" line 2: hi ran foo foo foo