Advanced makefiles
Pages: 1, 2
Shell functions and more make functions
Make is not an ideal tool for Java compilation. The Java compiler likes to resolve dependencies itself, which can cause make some confusion. When resolving dependencies, Java works from the source files and make works from the target. Java's structure invites the casual creation of new source files, and make is designed for new source files to be added manually.
In an ideal world, each new file would be added to the make script as part of a variable definition. This project is done in the real world, so make searches the source directories and constructs the list of targets automatically. Because of Java's idiosyncrasies and some aspects of our project, this requires four variables and a mix of shell and make functions.
In the previous version of this makefile, I generated the targets with an external shell script, exporting the list to a file as make variables, then importing the make variables into the makefile. Mitch improved my makefile so targets are generated in a much neater way.
# Search the directory containing the sources, and
# generate a corresponding name for the generated
# .class file. Make sure the classes in example/util/*
# are compiled first.
CLASSFILES_view:=
$(patsubst %.java,$(class_d)/%.class,
$(shell find example/util -name '*.java'))
# Find the non-util, non-views sources. Uses
# -path -prune instead of grep -v, from an example
# on the find(1) man page.
CLASSFILES_nonview:=
$(patsubst %.java,$(class_d)/%.class,
$(shell find example -path example/util -prune -o
-path example/views -prune -o -name '*.java' -a -print))
# Find the classes generated by xmlc from the
# .html files, except the ones in the mockup
# directory.
CLASSFILES:=
$(CLASSFILES_view) $(CLASSFILES_nonview)
VIEWFILES:=
$(patsubst %.html,$(class_d)/%.class,$(shell find
example -path example/views/mockups -prune -o -name
'*.html' -a -print))
This makefile includes shell functions as well as standard make functions. The syntax for a shell function is $(shell command). This returns the output of the shell function (stripping new lines).
The patsubst function has the syntax $(patsubst pattern,replacement,text). It uses the percent symbol (%) the same way pattern rules do--as a string which matches in both the pattern and the replacement text. It searches the text for whitespace-separated words that match the pattern and substitutes the replacement for them.
Makefile variables as commands, and automatic variables
# How to compile the .html and .java files.
htmlcompile=$(XMLC) -d $(class_d) -class $(subst /,.,
$(basename $<)) $<
javacompile=javac -sourcepath . -d $(class_d)
$(filter %.java,$?)
These are recursively expanded variables, calculated when the variable is actually needed. The variables need to be recursively expanded because they include automatic variables that rely on prerequisite lists and will be used as command strings in rules. (Automatic variables are explained in Introduction to make.)
$< and $? are the automatic variables. $< expands to the name of the first prerequisite. $? expands to a space-separated list of prerequisites that are newer than the target.
The $(filter %.java,$?) is necessary because when javacompile is used later in a rule, one prerequisite of the rule is the directory into which the Java files are compiled. The filter removes the directory, leaving only the .java files.
Note the use of make functions in the variable definition.
Pattern rules
$(VIEWFILES): $(class_example_view_d)/%.class:
example/views/%.html $(class_d)
$(htmlcompile)
$(CLASSFILES): $(class_example_d)/%.class:
example/%.java $(class_d)
$(javacompile)
The first line of these rules has three parts. These are a special type of GNU make rule called static pattern rules. There is a simple pattern rule described in Introduction to make. The syntax of a static pattern rule is:
targets: target-pattern: dependency-patterns commands
A .class file in the $(class_example_view_d) directory can be created from a corresponding .html file in the example/views/%.html directory, but only if the .class file is part of the $(VIEWFILES) list. Use static pattern rules if you have multiple ways of converting file type .A to .B and the way you choose depends on the files being converted.
The critical part of a pattern rule is the percent symbol. It refers to the same stem for both the target and the prerequisite file.
Simple rules
# A few things Make needs to know
.SUFFIXES : .html .java .class
.PHONY : clean all show_classpath
# The primary target: make all
all: $(VIEWFILES) $(CLASSFILES)
$(class_d):
mkdir $@
show_classpath:
@echo Here is the CLASSPATH passed to javac:
@echo $$CLASSPATH
# note the $$ expansion
clean:
-rm -rf $(class_d)
The all target relies on the pattern rules. The other rules are simple rules in the format:
target: prerequisites
command
Simple rules and phony rules are both described in the article Introduction to make.
Caveats and gotchas
- The techniques described in this article are for GNU make. make programs found on other Unix systems may not offer all of these features.
- make rules require a tab at the beginning of each command. A series of spaces doesn't work.
- make works backwards from the target to the prerequisites.
- make works better in languages in which the compiler does not try to resolve dependencies itself.
Final words
This makefile demonstrates many of make's advanced features. When writing your own makefiles, start with something simple and add new features one at a time.
make can be used anywhere one file needs to be generated from another. Try experimenting with using make for configurations and update scripts, as well as for software development.
Further reading
man makeinfo make- The University of Utah's document on makefile conventions
- The University of Hawaii's makefile tutorial
- Use the keyword "makefile" when running a search on the Web.
Jennifer Vesperman is the author of Essential CVS. She writes for the O'Reilly Network, the Linux Documentation Project, and occasionally Linux.Com.
Return to the Linux DevCenter.