1 Compilation and Interpretation
1 Compilation and Interpretation
CS61B P. N. Hilfinger
Fall 2008
[The discussion in this section applies to Java 1.5 tools from Sun Microsystems. Tools from other
manufacturers and earlier tools from Sun differ in various details.]
Programming languages do not exist in a vacuum; any actual programming done in any language
one does within a programming environment that comprises the various programs, libraries, editors,
debuggers, and other tools needed to actually convert program text into action. This document
discusses the tools that translate programs into executable form and then execute them.
javac C.java
This will produce .class files for C and for any other classes that had to be compiled because they
were mentioned (directly or indirectly) in class C. For homework problems, this is often all you need
to know, and you can stop reading. However, things rapidly get complicated when a program consists
of multiple classes, especially when they occur in multiple packages. In this document, we’ll try to
deal with the more straightforward of these complications.
1
2 P. N. Hilfinger
(the colon is used in place of comma (for some reason) to separate directory names). The interpreter
and compiler would then find the definition of a class such as ucb.io.StdIO in
/home/ff/cs61b/lib/java/classes/ucb/io/StdIO.class
javac A.java
javac p/A.java
Since this is tedious to write, it is best to rely on a makefile to do it for you, as described below in
§6.
5 Archive files
For the purposes of this course, it will be sufficient to have separate .class files in appropriate
directories, as I have been describing. However in real life, when one’s application consists of large
numbers of .class files scattered throughout a bunch of directories, it becomes awkward to ship
it elsewhere (say to someone attempting to run your Web applet remotely). Therefore, it is also
possible to bundle together a bunch of .class files into a single file called a Java archive (or jar file).
You can put the name of a jar file as one member of a class path (instead of a directory), and all its
member classes will be available just as if they were unpacked into the directory structure described
in previous sections.
The utility program ‘jar’, provided by Sun, can create or examine jar files. Typical usage: to
form a jar file stuff.jar out of all the classes in package myPackage, plus the files A.class and
B.class, use the command
This assumes that myPackage is a subdirectory containing just .class files in package myPackage.
To use this bundle of classes, you might set your class path like this:
Figure 1: Sample makefile for an editor program. Adapted from “GNU Make: A Program for Directing Recompi-
lation” by Richard Stallman and Roland McGrath, 1988.
program will complain if any dependency does not exist and there is no rule for creating it. To start
the process off, the user who executes the gmake utility specifies one or more targets to be updated.
The first target of the first rule in the file is the default.
In the example above, edit.jar is the default target. The first step in updating it is to update
all the files listed as dependencies (a bunch of .class files). The remaining rules tell how to update
each of these .class files. As you can see, they all look pretty much the same, and say that to
update X.class:
First update all the source files edit.java, command.java, etc. Next, if X.class is
missing or is older than any of these source files, then execute javac on all the source
files.
We chose to compile all the source files together like this because otherwise it is possible for the
compiler to get confused by old .class files that are still lying around.
Updating the source (.java) files is easy. There are no rules for any of them, so gmake simply
insists that they all exist in order to be considered up to date.
Now edit.class, for example, is up to date if it is younger (was created more recently) than all
the files edit.java, command.java, and so forth. If instead it is older, gmake assumes that that one
of those source files has been changed since the last compilation that produced edit.class and must
be “rebuilt.” Of course, if edit.class does not exist, then gmake also knows it has to be rebuilt.
If rebuilding is necessary, gmake executes the action “javac -g edit.java commands.java ...”,
producing new .class files. In our particular case, if any one of the .class files needs to be rebuilt,
6 P. N. Hilfinger
they are all rebuilt. If two of them need to be rebuilt (let’s say edit.class and files.class), then
gmake will execute the action for one of them and then, when it checks the other .class file, will
discover that it has already been updated. Thus, you needn’t worry that the javac command will
be executed more than once.
Once all the .class files are up-to-date, gmake will check to see if any of them are younger than
edit.jar (or if edit.jar does not exist). If any of the class files had to be rebuilt, then of course it
will be younger, and gmake will execute the indicated action: “jar cf edit.jar....”
To invoke gmake for this example, one issues the command
where the target-names are the targets that you wish to update and the makefile-name given in the
-f switch is the name of the makefile. By default, the target is that of the first rule in the file.
Furthermore, you may (and usually do) leave off -f makefile-name, in which case it defaults to either
Makefile, makefile, or (in the case of gmake only) GNUmakefile, whichever exists. It is typical to
arrange that each directory contains the source code for a single principal program. By adopting
the convention that the rule with that program as its target goes first, and that the makefile for the
directory is named Makefile, you can arrange that, by convention, issuing the command gmake with
no arguments in any directory will update the principal program of that directory.
It is possible to have more than one rule with the same target, as long as no more than one rule
for each target has an action. Thus, I can also write the latter part of the example above as follows:
edit.class:
javac -g edit.java commands.java display.java files.java
commands.class:
javac -g edit.java commands.java display.java files.java
display.class:
javac -g edit.java commands.java display.java files.java
files.class:
javac -g
The order in which these rules are written is irrelevant. Which order or grouping you choose is largely
a matter of taste, aside from which is the first (default) target.
Next, you can combine rules with the same dependencies and action. For example:
or just
Basic Compilation: javac and gmake 7
6.2 Variables
You can clarify the example from §6.1 considerably and eliminate redundancy by defining variables
to contain the names of the files.
# Makefile for simple editor
JFLAGS = -g
JAVA_SRCS = edit.java \
commands.java \
display.java \
files.java
edit.jar : $(CLASSES)
jar cf edit.jar $(CLASSES)
$(CLASSES): $(JAVA_SRCS)
javac $(JFLAGS) $(JAVA_SRCS)
The (continued) line beginning “JAVA SRCS =” defines the variable JAVA SRCS, which can later be
referenced as “$(JAVA SRCS)”. These later references cause the definition of JAVA SRCS to be sub-
stituted verbatim before the rule is processed. It is somewhat unfortunate that both gmake and the
shell use ‘$’ to prefix variable references; gmake defines ‘$$’ to be simply ‘$’, thus allowing you to
send ‘$’s to the shell in actions, where needed.
You will sometimes find that you need a value that is just like that of some variable, with a certain
systematic substitution. For example, given a variable listing the names of all source files, you might
want to get the names of all resulting .class files. You can rewrite the definition of CLASSES above
to get this.
CLASSES = $(JAVA_SRCS:.java=.class)
The substitution suffix ‘:.java=.class’ specifies the desired substitution. I now have variables for
both the names of all sources and the names of all class files without having to repeat a lot of file
names (and possibly make a mistake). (I have assumed here that each source file contains a single
class whose name is derived from the source file. You can’t use this trick if that isn’t so.)
Variables may also be set in the command line that invokes gmake. For example, the makefile
above contains what might look like an unnecessary definition of JFLAGS. However, defining it like
that allows one to write:
gmake JFLAGS="-g -deprecation" ...
which passes an extra flag to javac (this one happens to give a fuller explanation of certain warning
messages). Variable definitions in the command lines override those in the makefile, which allows the
makefile to supply defaults.
8 P. N. Hilfinger
clean:
rm -f *.class *~
Every time you issue the shell command “gmake clean,” this action will execute, removing all .class
files and Emacs old-version files.
The special .PHONY target tells gmake that clean is not a file, and is instead just the name of a
target that is always out of date. Therefore, when you make the “clean” target, gmake will always
execute the rm command, regardless of what files happen to be lying around. In effect, .PHONY tells
gmake to treat clean as a command.
Another possible use is to provide a standard way to run a set of tests on your program—what
are typically known as regression tests—to see that it is working and has not “regressed” as a result
of some change you’ve made. For example, to cause the command
make check
to feed a test file through our editor program and check that it produces the right result, use:
.PHONY: check
check: edit
rm -f test-file1
java edit < test-commands1
diff test-file1 expected-test-file1
where the test input file test-commands1 presumably contains editor commands that are supposed
to produce a file test-file1, and the file expected-test-file1 contains what is supposed to be
in test-file1 after executing those commands. The first action line of the rule clears away any old
copy of test-file1; the second runs the editor and feeds in test-commands1 through the standard
input, and the third compares the resulting file with its expected contents. If either the second or
third action fails, gmake will report that it encountered an error.
The result of these actions is that when gmake executes this final step for the edit program, the only
thing you’ll see printed is a line reading “Creating edit.jar ...” and, at the end of the step, a
line reading “Done”.
When gmake encounters an action that returns a non-zero exit code, the UNIX convention for
indicating an error, its standard response is to end processing and exit. The error codes of action
lines that begin with a ‘-’ sign (possibly preceded by a ‘@’) are ignored. Also, the -k switch to gmake
will cause it to abandon processing only of the current rule (and any that depend on its target) upon
encountering an error, allowing processing of “sibling” rules to proceed.
include $(MASTERDIR)/lib/java.Makefile.std
As you can probably guess, the include line is a special command that essentially gets replaced by
the contents of the named file.
Figure 2 illustrates what such a template file might look like. It uses one obscure new feature
that makes it possible to partially define an action, and allow others to add to it. The definition
of the phony target clean uses two colons rather than one. This is a signal that there may be
other “double-colon” rules for clean, complete with actions. They will all get used (in the order
encountered). For example, if you include this particular template in a place where you want to
define additional clean-up actions besides the ones defined in the template, you can write:
include $(MASTERDIR)/lib/java.Makefile.std
clean::
rm -rf test-output
which will cause gmake clean to remove the directory test-output as well as the class files and
Emacs-generated files removed in the template.
10 P. N. Hilfinger
# Targets defined:
# default:Default entry. Compiles classes from all source files.
# clean:: Remove back-up files and files that make can reconstruct.
# You can add additional clean-up actions by adding more
# ’clean::’ targets (note the double colon) to your makefile.
# check: Look in the subdirectory tests for all files whose name ends
# in ’.sh’. Each of these should be an executable shell script
# (a file of commands such as you could enter at the command
# prompt) that performs some test of the program. Run each
# and report all that fail (return a non-zero exit code).
#
JAVAC = javac
JFLAGS = -g
# Default entry
default: $(CLASSES)
$(CLASSES): $(JAVA_SRCS)
$(JAVAC) $(JFLAGS) $(JAVA_SRCS)
clean::
/bin/rm -f $(CLASSES) *~
check: $(CLASSES)
cd tests; for test in *.sh; do \
if ./$${test}; then \
echo "$${tests}: OK."; \
else \
echo "$${tests}: FAILED."; \
fi; \
done
Figure 2: An example of a file of standard makefile definitions that can be included from a specific makefile to
compile many simple collections of Java programs.