How can I use makefiles with Java?

John Zukowski

There is no simple answer to this question, and there are even conflicting answers. Here's what several jGuru community members have to offer on the subject:

According to Sandip Chitale:

IMHO, one cannot truly use a make file with Java. The reasons are as follows:

  1. Standard 'make' is based on comparison of timestamps of the source and the target.
  2. In the case of Java, the source is a .java file and the target is the .class file. For various reasons there is not necessarily a one-to-one correspondence between .java and .class file. The reasons are:
    1. A single .java file may contain more than one class/interface in it. Each of those will get it's own .class file. For the public class, the name of the .java file has to match the name of the class. Therefore the .class file name for the top-level public class will match the .java file name. However for non-public classes, the name of the .class file may be completely different than name of .java file. For inner classes of the top-level public class, the name of the .class file will at least have the same prefix as the .java file (more than likely followed by '$' followed by the inner class name, anonymous or otherwise). For the inner classes of non-public top-level classes, the name of the .class file will have the prefix of the name of the outer class and not .java file.
    2. The 'package' statement also affects the place where the .class files are located. Thus it is impossible to predict the names and locations of .class files without parsing the .java file.

    For the above reasons it is not possible to write a generic 'make' rule like the following:
    cc -o @*.o @* or something like that

or even a one time make file so that it will always work.

Contrary to Sandip, according to Greg Brouelette:

To build make files for Java should be to buy the excellent O'Reilly book Managing Projects with make. You may also benefit from the O'Reilly book Learning the bash shell.

With tools in hand you're ready to build your development environment. For the sake of this example let's assume that our project is code named "Gumball".

I would have a Gumball directory with 3 sub-directories: com, lib, and minclude (for "make include"). Your source "com tree" starts under the com directory. This is where your version control manager places any code that you check out.

When you compile your code, the class files go into a "com tree" under Gumball/lib. As long as the Gumball/lib directory is in your classpath you'll have access to all of your compiled code.

Finally there is the minclude directory. I like to keep a minimal amount of work in my makefiles. I put all my rules and targets into a Rules.mk file that I keep in the minclude directory. That way, a change in my Rules.mk file will properly effect all files in my project.

OK, I've mentioned the "com tree" twice. What's a "com tree"? If you're using Java packages properly, then the suggested method of naming your packages is to reverse your domain name and make a directory structure out of it.

For example:

Assume that your domain name is jguru.com. If you had 3 directories of source code call gui, util, and app then you would have these package names:

com.jguru.gui com.jguru.util com.jguru.app

Since package names are related to the actual directory your code is in then your directory structure should look like this:


Since you have a directory tree that spawns from the top level of "com" it's called the "com tree". The com tree that's under the src directory will contain all your Java files. The com tree that's under your lib directory (your make files will create this com tree) will contain all your class files. If you ever need to completely clean out all the class files then you simply delete the com tree under the lib directory and re-make it (although I generally have a target called "clean" to do that).


My make file (which is actually named "makefile") in my Gumball directory looks like this:

TOP = . 
DIR	= Gumball 
SUBDIRS = com 
include $(TOP)/minclude/Rules.mk 

Notice that I'm including my rules file so my actual makefile doesn't have any rules in it. Also notice that TOP is equal to the dot meaning "this is the top directory".

In the Gumball/com directory my makefile looks like this:

TOP	= ..
DIR	= com
SUBDIRS	 = jguru

include $(TOP)/minclude/Rules.mk

DIR changed to show the current directory. SUBDIRS changed to show what directories are below it, and TOP changed to ".." to show that we're now one directory down from the top.

Let's keep going. The makefile in the Gumball/com/jguru looks like this:

TOP	= ../..
DIR	= com/somename
SUBDIRS	 = util gui app

include $(TOP)/minclude/Rules.mk

Do you see the pattern? I should mention that the order of your subdirectories is the order in which they are compiled. So if gui has a dependency on a class in the util directory then the util directory must be compiled first (as it is here in this example).

Lastly, let's look at one of the leaf directories in your tree (the util, gui, and app directories will all have similar makefile files).

TOP	= ../../..
DIR= com/jguru/util



include $(TOP)/minclude/Rules.mk

We have a new entry call JAVA_SRCS. This contains a list of Java source files in this directory. Notice that there is a backslash after each one except for the last one. This is a line continuation which allows us to compile multiple files.

Also notice that the SUBDIRS is equals to NULL. We use this to tell our Rules.mk file to stop recursing the directories.

As you can see, it's pretty simple so far. That's because all the work is in the Rules.mk file. Here's a copy of that beast:

# A line that starts with a # is a comment

# Point this to wherever your java home directory is
# In Unix/Linux it might be something like /user/java1.2.2


RM = rm -f

# New suffixes
.SUFFIXES: .java .class .h

# Temp file for list of files to compile
COMPILEME = .compileme$(USER)
COPYME = .copyme$(USER)


JFLAGS= -g -deprecation -d $(CLASS_DIR)	-classpath $(CP)

JAVAH	= $(JAVAH) -jni

PACKAGE		= $(subst /,.,$(DIR))

JAVA_OBJS	= $(JAVA_SRCS:%.java=$(OUT_DIR)/%.class)
RMI_OBJS	= $(RMI_SRCS:%.java=$(OUT_DIR)/%.class)
STUB_OBJS	= $(RMI_OBJS:%.class=%_Stub.class)
SKEL_OBJS	= $(RMI_OBJS:%.class=%_Skel.class)

H_FILES		= $(JNI_SRCS:%.java=$(JNI_DIR)/%.h)

# Notice that each line that starts with an @ is ONE LONG LINE
# It may not show up or print out like that in the FAQ

# Walk down the SUBDIRS first
	@echo "subdirs is " $(SUBDIRS); 
	 if test "$(SUBDIRS)" != "NULL" ; then 
		for i in $(SUBDIRS) ; do 
		    (cd $$i ; echo "making" all "in $(CURRENTDIR)/$$i"; 
		    $(MAKE) CURRENTDIR=$(CURRENTDIR)/$$i all); 

# Then compile each file in each subdir
	@if test -r ${COMPILEME}; then CFF=`cat ${COMPILEME}`; fi; 
          $(RM) ${COMPILEME}; if test "$${CFF}" != ""; then  
          echo $(JC) $${CFF}; fi; if test "$${CFF}" != ""; then 
          $(JC) $${CFF}; fi

# "make clean" will delete all your class files to start fresh
	$(RM) $(OUT_DIR)/*.class *~ $(COMPILEME)
	$(RM) $(OUT_DIR)/*.gif *~ $(COMPILEME)
	$(RM) $(OUT_DIR)/*.jpg *~ $(COMPILEME)
	$(RM) $(OUT_DIR)/*.py *~ $(COMPILEME)

	@echo "2nd check: subdirs is " $(SUBDIRS); 
	 if test  "$(SUBDIRS)" != "NULL"; then 
		echo "Past the 2nd if then"; 
		for i in $(SUBDIRS) ;
		    (cd $$i ; echo "making" clean "in $(CURRENTDIR)/$$i"; 
		    $(MAKE) CURRENTDIR=$(CURRENTDIR)/$$i clean); 

	@if [ "$(H_FILES)" != "/" ] && [ "$(H_FILES)" != "" ]; then 
		echo $(RM) $(H_FILES); 
		$(RM) $(H_FILES); 
	@if [ "$(RMI_OBJS)" != "/" ] && [ "$(RMI_OBJS)" != "" ]; then 
		echo $(RM) $(RMI_OBJS); 
		$(RM) $(RMI_OBJS); 
	@if [ "$(STUB_OBJS)" != "/" ] && [ "$(STUB_OBJS)" != "" ]; then 
		echo $(RM) $(STUB_OBJS); 
		$(RM) $(STUB_OBJS); 
	@if [ "$(SKEL_OBJS)" != "/" ] && [ "$(SKEL_OBJS)" != "" ]; then 
		echo $(RM) $(SKEL_OBJS); 
		$(RM) $(SKEL_OBJS); 


### Rules

# .class.java rule, add file to list
$(OUT_DIR)/%.class : %.java
	@echo $< >> $(COMPILEME)

# Rule for compiling  a Stub/Skel files
$(OUT_DIR)/%_Skel.class :: $(OUT_DIR)/%_Stub.class

$(OUT_DIR)/%_Stub.class:: %.java
	$(RMIC) $(PACKAGE).$(notdir $(basename $(<F)))	

# Rule for compiling a .h file
$(JNI_DIR)/%.h : %.java
	$(JAVAH) -o $@ $(PACKAGE).$(notdir $(basename $< )) 

OK, I'm not going to explain every line, that's what the O'Reilly book is for. Essentially, this file tells the make utility where the javac program is (which means you can compile using different versions of Java just by changing the JAVA_HOME line). It then walks down all the subdirectories and compiles each Java file putting the resulting class file into the com tree under the lib directory.

However, because of the magic of the make utility it will only compile the Java files which have changed since the last time you did a make. If you want to force a recompile you can either run "make clean" in the directory you want to re-make, or use the Unix "touch" utility to update the date-time stamp on a particular java file.

This is only an example of the many ways you can use the make utility. With a little additional work you could add targets for "make jar" which could automatically build your jar file; you could add a "make doc" which would run javadoc on all your source code and so on.

The make utility is quite a useful tool.

According to Sandip Chitale, Finlay McWalter:

'javac' includes implicit compilation of direct dependencies. A more extensive support to compile indirect dependencies is also provided through the use of '-depend' (JDK1.1) or '-Xdepend' (JDk1.2) 'javac' flags.

Many members also provided references to available articles or tools:

According to Bogdan Ghidireac, Robert Castaneda, Didier Trosset:

Ant is a Java-based build tool from the Apache project: http://jakarta.apache.org/ant/index.html

According to Greg Brouelette:

Cygwin is a windows-based: http://sourceware.cygnus.com/cygwin/

According to Davanum Srinivas:

http://geosoft.no/javamake.html seems to discuss something similar to Greg's comments above.