Using Makefiles for Java development
Introduction
make is one of the original
Unix tools for Software Engineering, dating back to work at AT&T
Bell Labs in the 70's in the same group that invented C and Unix. In
general, the idea behind make
is to automate many of the steps commonly required in the construction
of
programs -- ie. to make your development task easier. Some of make's features make it particularly
well suited to C and C++ application development. However, make can be very useful for Java
development as well.
Many tutorials exist for a quick introduction to make:
A
GNU Make Tutorial
Make - a tutorial
An
Introduction to the UNIX Make Utility
Creating
Makefiles: A Mini Tutorial
If you have never used make
before, have a look at at least one of those tutorials before
proceeding, or at least check out the make
manual page (run man make in
a terminal window).
Telling make about Java
make already understands basic things about application
development in C: for example, that object files have a .o suffix, sourcecode files
have a .c suffix, and the
way to get the first from the second is to run the C compiler, cc, with appropriate
arguments. make does not
know the corresponding things about .java and .class files and the Java
compiler javac. But
it can easily be told about them.
Put this line in your Makefile:
.SUFFIXES: .java .class
That tells make to add .java and .class to the list of filename
suffixes that it knows about. But you still need to tell it what
to do with files with those suffixes. So put these lines in your
Makefile too:
.java.class:
javac $<
(Note that in this rule pattern, like every make rule, the second line -- the
command -- needs to start with a tab character, not spaces.) That
tells make that any file with a .class
suffix depends on a file with the same name but a .java suffix, and the way to
get the first from the second is to run javac with the depended-on file
(the .java file) as
argument. If you want to always pass certain flags to javac, you can put them in
here; it's just a command line that will be forked in a new shell. The $< is make's notation for the name of the
depended-on file, whatever it happens to be.
Now suppose your Java application has the source code files A.java, B.java and C.java and so you want them to
be (re)compiled to the corresponding .class file when you make
changes to any of them. For convenience, you can define the
corresponding class file names as a macro:
CLASSES = A.class B.class C.class
And then set up a rule with a dummy target that depends on the value of
that macro:
all: $(CLASSES)
With all that in a Makefile in your working directory, you just need to
run make all on the
command line, and if any of the
.java files have been modified more recently than their
corresponding .class
files, make will automatically
run javac on them for
you. Pretty simple, but pretty useful; it can save a lot of
typing.
Useful Makefile targets for coursework development
Besides compiling classes, you often need to run tests, turn in
assignments, generate documentation, and clean up your working
directory. Each of these actions can be easily scripted in a
Makefile, by associating each with a rule that contains the appropriate
commands.
Because some of these depend on source code files directly, it is
convenient to define another macro whose value is the list of source
file names:
SOURCES = A.java B.java C.java
Now you can define the rules you want. They might look something
like this:
turnin: $(SOURCES)
bundleP3
clean:
/bin/rm
$(CLASSES)
doc: $(SOURCES)
javadoc
-author $(SOURCES)
test: $(CLASSES)
java A
testfile1; diff outfile1 reffile1
java A
testfile2; diff outfile2 reffile2
java A
testfile3; diff outfile3 reffile3
With all that in your Makefile, you are ready to easily execute most
common, repetitive Java development tasks. And once you
understand the basics of make,
you can usually pretty easily write any other command-line task you
want to automate, such as checking files in and out of a CVS
repository, etc.
More complicated Java dependencies
As shown above, it is easy to get make
to understand that a .class
file depends on a .java
file with the same name. And the Java compiler javac itself understands some
dependencies: When it is compiling a class X that references another class
Y, and Y.java is newer than Y.class, or Y.class doesn't exist, it will
automatically compile Y
as well. However, javac
and the simple .class/.java dependency in make aren't smart enough to go the
other way and figure out that if you make a change to Y.java, you might have to
recompile X as well as Y.
One crude way to address this is to run make clean before make all. Since make clean (as defined above)
removes all .class files,
make all will then go
ahead and rebuild all of them, and all dependencies should then be
met. However, this will probably involve more compiling than is
really necessary.
A better approach is to explicitly express dependencies among .class files in your
Makefile. So for example if class A references classes B and C, and so depends on them, you
can write the line
A.class: B.class C.class
Now if you make a change to B.java
or C.java and run make
all, A.java will be
recompiled also.
Having to spell out all such dependencies is somewhat tedious, though
it may be necessary especially if you are using inner classes or
reflection. On the other hand, if you are doing a really complex
Java project, it may be better to use another framework entirely.
The Java-based build tool Ant, from the the Apache Ant project, is more
flexible than make.
Or, consider an integrated development environment (IDE) that understands
Java dependencies. Two popular and free ones are Eclipse and NetBeans.
Author: Paul Kube © 2006