Makefiles for Java
There are quite a few built tools to help you compile Java code. Some of the most commonly used include Apache Ant, Apache Maven and Gradle. It is however perfectly feasible to use GNU make to build Java projects.
Makefile setup
Create a file called Makefile
in the root of the project directory, with the
following contents:
.SUFFIXES:
SOURCE = src
OUTPUT = build
CLASS_PATH = $(SOURCE)
sources = $(shell find $(SOURCE) -type f -name '*.java')
classes = $(sources:$(SOURCE)/%.java=$(OUTPUT)/%.class)
build_dirs = $(sort $(dir $(classes)))
all: $(classes)
$(build_dirs):
mkdir -p $@
$(OUTPUT)/%.class: $(SOURCE)/%.java | $(build_dirs)
javac -cp $(CLASS_PATH) -d $(OUTPUT) $<
run: $(classes)
cd $(OUTPUT); java com.example.Main
clean:
rm -vrf $(OUTPUT)/
The makefile above assumes the following:
- GNU make is installed
java
andjavac
are installed and available- The
find
command is available - Java source code is being kept under a directory called
src/
Using make
Before anything is built only the Java source code and makefile will exist:
$ tree
.
├── Makefile
└── src
└── com
└── example
├── Bar.java
├── Foo.java
└── Main.java
3 directories, 4 files
Running make will call javac
to compile the source code:
$ make
mkdir -p build/com/example/
javac -cp src -d build src/com/example/Foo.java
javac -cp src -d build src/com/example/Bar.java
javac -cp src -d build src/com/example/Main.java
The class files will be created under the build
directory:
$ tree
.
├── build
│ └── com
│ └── example
│ ├── Bar.class
│ ├── Foo.class
│ └── Main.class
├── Makefile
└── src
└── com
└── example
├── Bar.java
├── Foo.java
└── Main.java
6 directories, 7 files
Running make run
will run the Java program:
$ make run
cd build; java com.example.Main
Hello World!
And finally running make clean
will completely remove the build
directory:
$ make clean
rm -vrf build/
removed ‘build/com/example/Foo.class’
removed ‘build/com/example/Bar.class’
removed ‘build/com/example/Main.class’
removed directory: ‘build/com/example’
removed directory: ‘build/com’
removed directory: ‘build/’
Makefile explanation
Makefiles can be a little cryptic. Below is a quick explanation for each section of the makefile:
Setting .SUFFIXES
to nothing removes all known suffixes. This prevents make
trying to run default suffix rules:
.SUFFIXES:
The source directory, build directory and class path variables are set next:
SOURCE = src
OUTPUT = build
CLASS_PATH = $(SOURCE)
Next a list of Java source files is created. This is done using the shell
function to call find
:
sources = $(shell find $(SOURCE) -type f -name '*.java')
The sources
list is then used to create two additional lists; a list of class
file paths and a list of directories. This is done using
text and file name
functions. Note that sort
is used to remove any duplicate build directories.
classes = $(sources:$(SOURCE)/%.java=$(OUTPUT)/%.class)
build_dirs = $(sort $(dir $(classes)))
The first target not prefixed with a dot (.
) will be the default goal. In
this case a phony target is used to create all Java
classes:
all: $(classes)
The next target creates the build directories. The recipe uses an automatic
variable ($@
) to avoid hard coding directory
names.
$(build_dirs):
mkdir -p $@
The next target uses a pattern rule to build class
files using javac
. The pipe character is used to make sure the directories
listed in build_dirs
are order only prerequisites; without this Java classes
will be rebuilt unnecessary if build directories are modified.
$(OUTPUT)/%.class: $(SOURCE)/%.java | $(build_dirs)
javac -cp $(CLASS_PATH) -d $(OUTPUT) $<
The run
target switches into the build directory and runs the
com.example.Main
class. It's worth noting that setting the class path could
be used as an alternative to changing directory.
run: $(classes)
cd $(OUTPUT); java com.example.Main
Finally a clean
phony target is defined to make it easy to remove any
compiled classes:
clean:
rm -vrf $(OUTPUT)/