Only very recently did I discover a nifty feature of GNU make, which is user-defined functions and the call function.
It has proven useful when writing portable Makefiles that work on Cygwin and Unix-like systems when Java is involved. The issue with Java and Cygwin is the following. Suppose that you have installed Cygwin in C:/cygwin and you have installed Java SE for Windows. Then if you want to refer to a file /home/gerrit/test.xml you’ll have to translate the path to C:/cygwin/home/gerrit/test.xml. There’s a cygwin utility, cygpath, that works similar to realpath(1) in that it can translate a filename to its (Windows) full name, including the directory. For example, when I invoke cygpath -ma test.xml in my home dir it will give me C:/cygwin/home/gerrit/test.xml, the full path, as suitable for Java.
Trying to use relative paths throughout proved to difficult, since different tools assume different base directories for files. An XSLT 2 processor, for example, will try to resolve a pathless file name relative to the stylesheet’s directory, so you’d have to figure out what the relative path to your file from there will be. Another XSLT processor requirement (depending on you configuration) is that it wants to have URIs for files. In above example, it will expect file:///c:/cygwin/home/gerrit/test.xml as a file name argument, while you can just pass it /home/gerrit/test.xml on Linux.
The most elegant solutions (at least if you stick with make and don’t use XProc for automation, which has its own peculiarities) is to use a make function like this:
ifeq ($(shell uname -o),Cygwin) win_path = $(shell cygpath -ma $(1)) uri = file:///$(call win_path,$(1)) else uri = $(1) endif
Here’s an application example that uses the uri function twice:
%.idml.HUB %.idml.INDEXTERMS %.idml.TAGGED: %.idml Makefile xslt/*.xsl xslt/modes/*.xsl mkdir -p $<.tmp unzip -u -o -d $ $(SAXON) \ $(SAXONOPTS) \ -xsl:xslt/idml2xml.xsl \ -it:$(call lc,$(subst .,,$(suffix $@))) \ src-dir-uri=$(call uri,$ $@
It also features a function lc that converts its argument to lower case. It is defined as
lc = $(shell echo $(1) | tr '[:upper:]' '[:lower:]')
Makefile functions don’t survive export
When a sub-make is called, the function is exported as a plain variable rather than as a function, and the variable is expanded on the way. This issue is already described in a post on stackoverflow. Moving the function definition to another file that is imported by both Makefiles will improve the situation.
But today I ran into an issue where a Makefile invoked itself recursively and exported all of its variables. My user-defined function, even when reduced to a simple cat $(1), didn’t finish. This was also true when I outsourced the function definition to an included file, because the exported variables supersede the default definitions. So because $(1) was expanded to the empty string, the function that the sub-make called consisted of a mere cat, without argument placeholder, that was waiting for someone to feed it via STDIN.
The solution consisted of unexporting the function/variable:
# export all variables: export # except for the linecount function: unexport linecount linecount = $(shell wc -l $(1)) test1: $(MAKE) test2 test2: @echo $(call linecount,Makefile)
Invoking this yields:
$ make make test2 make: Entering directory `/home/gerrit' 11 Makefile make: Leaving directory `/home/gerrit'
Commenting out the ‘unexport’ line gives the following output
$ make make test2
and it doesn’t finish, because wc is waiting for input on STDIN.
To be continued…
I recently discovered (and needed, and used) secondary expansion, a true makeshift that I will deal with in another post. There you’ll get a glimpse why this kind of tinkering with good old make and its expansion mechanism was probably the last straw that have made others concoct cleaner alternatives to make, such as ant or XProc. But never forget that these features, such as call, eval, and seconary expansion, give you “all the configurability and flexibility of Sendmail”. Take dynamic evaluation. You have it in XProc processor vendor extensions, but not in ant (maybe with Jelly, I don’t know this stuff too well). Make is still our favorite process automation tool. Maybe XProc V.next, that will hopefully deal better with non-XML files, will be a runner-up.