4.1 User -Defined Functions Storing command sequences in variables opens the door to a wide range of applications. For instance, here's a nice little macro to kill a process: [1] [1] "Why would you want to do this in a makefile ?" you ask. Well, on Windows, opening a file locks it against writing by other processes. While I was writing this book, the PDF file would often be locked by the Acrobat Reader and prevent my makefile from updating the PDF . So I added this command to several targets to terminate Acrobat Reader before attempting to update the locked file. AWK := awk KILL := kill # $(kill-acroread) define kill-acroread @ ps -W \ $(AWK) 'BEGIN { FIELDWIDTHS = "9 47 100" } \ /AcroRd32/ { \ print "Killing " $; \ system( "$(KILL) -f " $ ) \ }' endef (This macro was written explicitly to use the Cygwin tools, [2] so the program name we search for and the options to ps and kill are not standard Unix.) To kill a process we pipe the output of ps to awk . The awk script looks for the Acrobat Reader by its Windows program name and kills the process if it is running. We use the FIELDWIDTHS feature to treat the program name and all its arguments as a single field. This correctly prints the complete program name and arguments even when it contains embedded blanks. Field references in awk are written as $1 , $2 , etc. These would be treated as make variables if we did not quote them in some way. We can tell make to pass the $ n reference to awk instead of expanding it itself by escaping the dollar sign in $ n with an additional dollar sign, $$ n . make will see the double dollar sign, collapse it to a single dollar sign and pass it to the subshell. [2] The Cygwin tools are a port of many of the standard GNU and Linux programs to Windows. It includes the compiler suite, X11R6, ssh , and even inetd . The port relies on a compatibility library that implements Unix system calls in terms of Win32 API functions. It is an incredible feat of engineering and I highly recommend it. Download it from http://www.cygwin.com. Nice macro. And the define directive saves us from duplicating the code if we want to use it often. But it isn't perfect. What if we want to kill processes other than the Acrobat Reader? Do we have to define another macro and duplicate the script? No! Variables and macros can be passed arguments so that each expansion can be different. The parameters of the macro are referenced within the body of the macro definition with $1 , $2 , etc. To parameterize our kill-acroread function, we only need to add a search parameter: AWK := awk KILL := kill KILL_FLAGS := -f PS := ps PS_FLAGS := -W PS_FIELDS := "9 47 100" # $(call kill-program,awk-pattern) define kill-program @ $(PS) $(PS_FLAGS) \ $(AWK) 'BEGIN { FIELDWIDTHS = $(PS_FIELDS) } \ // { \ print "Killing " $; \ system( "$(KILL) $(KILL_FLAGS) " $ ) \ }' endef We've replaced the awk search pattern, /AcroRd32/ , with a parameter reference, $1 . Note the subtle distinction between the macro parameter, $1 , and the awk field reference, $$1 . It is very important to remember which program is the intended recipient for a variable reference. As long as we're improving the function, we have also renamed it appropriately and replaced the Cygwin-specific, hardcoded values with variables. Now we have a reasonably portable macro for terminating processes. So let's see it in action: FOP := org.apache.fop.apps.Fop FOP_FLAGS := -q FOP_OUTPUT := > /dev/null %.pdf: %.fo $(call kill-program,AcroRd32) $(JAVA) $(FOP) $(FOP_FLAGS) $< $@ $(FOP_OUTPUT) This pattern rule kills the Acrobat process, if one is running, and then converts an fo (Formatting Objects) file into a pdf file by invoking the Fop processor (http://xml.apache.org/fop). The syntax for expanding a variable or macro is: $(call macro-name[, param 1 . . . ]) call is a built-in make function that expands its first argument and replaces occurrences of $1 , $2 , etc., with the remaining arguments it is given. (In fact, it doesn't really "call" its macro argument at all in the sense of transfer of control, rather it performs a special kind of macro expansion.) The macro-name is the name of any macro or variable (remember that macros are just variables where embedded newlines are allowed). The macro or variable value doesn't even have to contain a $ n reference, but then there isn't much point in using call at all. Arguments to the macro following macro-name are separated by commas. Notice that the first argument to call is an unexpanded variable name (that is, it does not begin with a dollar sign). That is fairly unusual. Only one other built-in function, origin , accepts unexpanded variables. If you enclose the first argument to call in a dollar sign and parentheses, that argument is expanded as a variable and its value is passed to call . There is very little in the way of argument checking with call . Any number of arguments can be given to call . If a macro references a parameter $ n and there is no corresponding argument in the call instance, the variable collapses to nothing. If there are more arguments in the call instance than there are $ n references, the extra arguments are never expanded in the macro. If you invoke one macro from another, you should be aware of a somewhat strange behavior in make 3.80. The call function defines the arguments as normal make variables for the duration of the expansion. So if one macro invokes another, it is possible that the parent's arguments will be visible in the child macro's expansion: define parent echo "parent has two parameters: , " $(call child,) endef define child echo "child has one parameter: " echo "but child can also see parent's second parameter: !" endef scoping_issue: @$(call parent,one,two) When run, we see that the macro implementation has a scoping issue. $ make parent has two parameters: one, two child has one parameter: one but child can also see parent's second parameter: two! This has been resolved in 3.81 so that $2 in child collapses to nothing. We'll spend a lot more time with user-defined functions throughout the rest of the book, but we need more background before we can get into the really fun stuff! |