27.5.13

Colons in make targets

I learned something interesting about GNU make recently. It's possible to write rules for targets which contain colons (:). This doesn't work very well for filenames, even though Linux/UNIX filesystems could support it in theory—from the evidence on stackoverflow, it seems to break make's handing of dependencies internally.
But there is one potential situation where the colon could be of use, in pattern rules. Consider the following makefile1:
SOME_VAR:=some_value
OTHER_VAR=other_value

all: ; @echo "Just a vanilla rule to show the 'cut & paste'-friendly rule syntax."

show\:%: ; @echo $(@:show:%=%)="$($(@:show:%=%))"
This creates a target pattern show:%, where % operates as a wildcard. Notice that we need to escape the colon in the target's definition, as an unescaped colon would be interpreted as part of the rule's target: deps syntax. However, when it comes to making substitution references, a colon can be used without needing to be escaped, despite being part of the syntax. (In fact, the substitution will actually fail if the colon is escaped in this case—this is probably due to this being a syntactical edge-case.)
The formulation $(@:show:%=%) in the rule's recipe takes the name of the target (e.g. show:something) and strips off the initial show:, leaving the rest of the target name as a result (e.g. something). We can then use this value as we'd use any data in make—in this case, we're using it to show the value of a makefile variable, which could be useful when debugging makefiles, as the examples show:
$ make show:SOME_VAR
SOME_VAR=some_value

$ make show:OTHER_VAR
OTHER_VAR=other_value
So we can see that this show: pattern rule handles both flavours of make variable (i.e. = and :=). It can even be used to inspect some of make's built-in special variables:
$ make show:MAKEFILE_LIST
MAKEFILE_LIST= makefile

$ make show:.FEATURES
.FEATURES=target-specific order-only second-expansion else-if archives jobserver check-symlink

$ make show:.VARIABLES
.VARIABLES=<D ?F DESKTOP_SESSION CWEAVE ?D @D XAUTHORITY GDMSESSION CURDIR SHELL RM CO _ [...]

$ make show:DESKTOP_SESSION
DESKTOP_SESSION=ubuntu
So now we have a fairly natural-looking syntax for building make targets which take a single variable 'parameter'. I can see other uses for this: a rule to write a version number or string into a header file, for example.
There's a minor refactoring which could be made: if the repetition of $(@:show:%=%) in the rule is unacceptably offensive, we can hoist out the substitution logic into its own variable (which needs to be a recursive (=) flavour), although we then have to use $(patsubst) to make the substitution work:
_showtarget=$(patsubst show:%,%,$@)
show\:%: ; @echo $(_showtarget)="$($(_showtarget))"
One final note—when I say make above, I specifically mean GNU make v3.81.
$ make -v
GNU Make 3.81
This trick might work in older versions of GNU make (and it could possibly break in future versions, but hopefully with enough publicity, it won't). I doubt it will work with any other variant of make. (But I believe the first rule of Makefiles, so other versions of make are irrelevant to me.)


1 Note that I'm using the inline rule syntax to work around the 'tabs' issue—using a semicolon after a rule's target, so the recipe does not need to be indented. This should allow you to copy the code out of the blog and have it work as intended when it's pasted into a makefile.

No comments :

Post a Comment