Java |
A Complete Makefile Setup for Java |
![]() |
The last few years integrated development environments (IDE) have become popular for software development. IDE's provides the developer with all the tools required for the development task such as file manager, version control system, modeling tools, editor, compiler, debugger, user interface builder, execution environment, profiler and performance analyzing tools. In particular the user interface builder part of IDE's has proved useful, and is the backbone of IDE's like VisualBasic, Delphi, VisualC++, VisualCafé to name a few.
In many cases however, it is far more efficient to do code development in a pure terminal oriented environment. This is done to avoid the vast overhead of the IDE itself, to achieve better control of the development process and to be able to choose development tools like editors and debuggers on an individual basis. For these reasons it is not uncommon to divide projects into UI modules and non-UI modules, and use IDE's only on the former.
Doing development in a pure environment requires a powerful make setup to be efficient. The makefile setup provided here is powerful yet simple. It was created to support large scale multi-platform development, but is equally well suited for the single source file project.
The makefiles in this document uses GnuMake syntax. GnuMake is the default make system on the Linux platform, it is available on all UNIX platforms, and a Microsoft Windows version can be downloaded from here. (Look for the file UnxUtils.zip. Download and unpack it, and make sure the wbin/ directory becomes part of your path.) The actual make command in GnuMake is sometimes called gnumake and sometime just make (as is the case on Linux). In this document the latter is used consistently.
There are no constraints on how a Java development project should be organized within a file system, but the setup that is suggested here is both intuitive, scalable and easy to maintain. The makefile setup below depends on this directory structure in order to work.
Some directory is chosen to be the development root directory (denoted JAVA_DEV_ROOT below), and underneath it are the following subdirectories:
$JAVA_DEV_ROOT/src /classes /jars /bin /docs /html /make
The src/ directory holds the Java source (.java) files. If you intend to develop software for the world market and your organization has a domain name, the domain name should be reflected in the directory structure beneath the source directory. For the Adobe (adobe.com) organization for instance this becomes:
$JAVA_DEV_ROOT/src/com/adobe
The reflected domain name as part of the directory structure ensures that Java classes are world wide unique. For in-house development projects or for a development for a particular customer, this part of the directory structure can be omitted.
Underneath this directory, packages can be organized in containers according to the projects at hand. A typical setup might be:
$JAVA_DEV_ROOT/src/com/adobe/illustrator/ui/window/ /illustrator/ui/dialogs/ /illustrator/ui/print/ /illustrator/ui/print/postscipt/ /illustrator/ui/print/pdf/ /framemaker/editor/.../... /framemaker/editor/.../... /...
Package names for source files within a given directory must always reflect the name indicated by the directory structure. For instance the source file
$JAVA_DEV_ROOT/src/no/geosoft/directory/Test.java
must be part of the package no.geosoft.directory, and thus the first statement of the file must be
package no.geosoft.directory;
Consequently, moving files and directories around within the source tree requires quite some updating on the source files themselves and spending some time planning the directory setup up front can save a lot of effort.
The classes/ directory contains target files produced by the source files, and the directory structure will be identical to the src tree.
The jars/ directory contains all .jar archives in use. This includes 3rd-party .jar files that should be copied here manually, and produced .jar files that will be put here automatically by make. It would be tempting to divide between your own .jar files and 3-rd party ones, but if you want to run your application through a .jar file you've made, all .jar files must be located in the same directory. Under UNIX both can be achieved by using symbolic links.
The bin/ directory can be used for convenience scripts for launching Java applications etc. This directory is not referred to by the make system and is optional. A developer will typically run an application by make run anyway. More on this below.
The docs/ directory will hold all output from the Javadoc documentation tool. The sub directory structure is automatically created by Javadoc and is similar to the one in the src/ and classes/ directory. The entry point for the HTML browser will be the file $JAVA_DEV_ROOT/docs/index.html.
The html/ directory should contain scratch .html files for testing Java applets. This directory is the applet equivalent of the bin directory for applications. The html directory is not referred to by the make system and is optional.
The make/ directory will hold the common (project independent) makefile shown below, optional scripts, make log files etc.
The following three environment variables has to be set prior to the use of the make system given below:
There are four different makefiles involved. These are:
The package makefile should be called Makefile and should be located within the package directory. An example package makefile is shown below:
PACKAGE = no.geosoft.directory SOURCE = \ Directory.java \ Entry.java \ Folder.java \ Item.java \ Test.java \ folder.gif \ item.gif \ RMI_SOURCE = MAIN = Test include $(JAVA_DEV_ROOT)/Makefile
The PACKAGE entry indicates the package name. According to the package name, the example file above is found at $JAVA_DEV_ROOT/src/no/geosoft/directory/Makefile.
The SOURCE entry lists all the source files. .java files will be passed to the Java compiler and all other files (like the .gif files above) will be copied unprocessed to the classes/ tree.
The RMI_SOURCE entry is optional and list all .java files that are to be passed to the rmi compiler. Note that these files must also be listed under the SOURCE entry.
The MAIN entry is optional and indicates which class contains the main() method. Leave open if none of them do.
Jar makefiles are only used if files from the class tree are to be archived into jar files. A jar file can contain the entire classes tree or a subset of it, and there can be more than one jar file, each archiving its own part of the classes tree. A jar makefile should be called Makefile.archive.jar where archive is the name of the actual archive. Jar makefiles should be located in the jars/ directory.
An example jar makefile is shown below:
JAR_CONTENT = \ no/geosoft/directory \ MANIFEST = Manifest.directory.jar include $(JAVA_DEV_ROOT)/Makefile
The JAR_CONTENT entry lists all package directories that are to be included in the archive. Only the root directory needs to be specified if all sub directories (i.e packages) are to be included.
For each archive, a manifest file must always be present. The manifest file for an archive should be located alongside the makefile in the jars/ directory and be indicated by the MANIFEST entry.
The project makefile lists all packages and jar archives that constitutes the project. The project makefile is mandatory since it is included by the package makefiles.
An example project makefile is shown below:
#*********************************************************************
#
# (C) 2000 Geotecnical Software Services - GeoSoft
#
#*********************************************************************
PACKAGES = \
no.geosoft.directory \
no.geosoft.user \
no.geosoft.access \
NODOC_PACKAGES = \
JARS = \
directory.jar \
JARS_3RDPARTY = \
mysql.jar \
jcarnac.jar \
MAIN_CLASS = Test
MAIN_PACKAGE = no.geosoft.directory
MAIN_JAR = directory.jar
RUN_PARAMETERS =
#*********************************************************************
#
# Javadoc
#
#*********************************************************************
WINDOWTITLE = 'Geotachnical Software Services - API Specification'
DOCTITLE = 'GeoSoft API'
HEADER = 'GeoSoft API'
BOTTOM = '<font size="-1">Copyright © 2000 - Geotechnical Software Services
<a href="http://geosoft.no">geosoft.no</a></font>'
include $(JAVA_DEV_ROOT)/make/Makefile
The PACKAGES entry lists all the packages that are governed by this makefile. This set is also the packages that will be documented by Javadoc.
The project might consist of directories with no .java files in them (for instance an images/ directory consisting of .gif files). This directory is still a "package" in the sense that it has to be processed by make, but Javadoc is not able to handle it and will in fact crash if it is encountered. Therefore, such "packages" needs to be listed under the NODOC_PACKAGES.
The JARS entry lists all the jar archives that the project is responsible for, while the JARS_3RDPARTY entry lists all 3rd party archives that are referred to.
The MAIN_CLASS, MAIN_PACKAGE and MAIN_JAR are being used for identifying the entry point when running a program. The RUN_PARAMETERS entry consist of options that are passed to the executable when it is started.
The Javadoc entries are optional and used for decoration of the produced Javadoc documentation.
The Main makefile is the heart and brain of the make setup, and should be located in the make/ directory and be called Makefile. It is not executed directly, but rather included by the project makefile. This makefile contains everything needed to build the project described in the other makefiles and it is rather complex. However, it it is written once and for all, and can to a large extent be left alone as is.
The Main Makefile is shown below:
#***************************************************************************
#
# (C) 2000 Geotechnical Software Services - GeoSoft
#
#***************************************************************************
#***************************************************************************
#
# Section 1: Directories.
#
#***************************************************************************
CLASS_DIR = $(JAVA_DEV_ROOT)/classes
DOC_DIR = $(JAVA_DEV_ROOT)/docs
MAKE_DIR = $(JAVA_DEV_ROOT)/make
JAR_DIR = $(JAVA_DEV_ROOT)/jars
ifdef JAVA_DEV_LOCAL
SOURCE_DIR = $(JAVA_DEV_LOCAL)/src
LOCAL_CLASS_DIR = $(JAVA_DEV_LOCAL)/classes
DESTINATION = $(JAVA_DEV_LOCAL)/classes
else
SOURCE_DIR = $(JAVA_DEV_ROOT)/src
DESTINATION = $(JAVA_DEV_ROOT)/classes
endif
JAVA_BIN = $(JAVA_HOME)/bin
#***************************************************************************
#
# Section 2. Tools and options.
#
#***************************************************************************
JAVA = java
JAVAC = javac
JAVAH = javah
RMIC = rmic
JAR = jar
DEBUG = jdb
DELETE = rm -f
COPY = cp
JINDENT = Jindent
PROFILER = -Xrunhprof
MAKEDIR = mkdir.exe -p
PRINT = @echo
JAVADOC = javadoc
CHMOD = chmod.exe
ifdef IS_UNIX
SEP = :
else
SEP = ;
endif
EMPTY =
SPACE = $(EMPTY) $(EMPTY)
LOCAL_JARTMP = $(patsubst %,$(JAR_DIR)/%,$(JARS))
LOCAL_JARLIST = $(subst $(SPACE),$(SEP),$(LOCAL_JARTMP))
OTHER_JARTMP = $(patsubst %,$(JAR_DIR)/%,$(JARS_3RDPARTY))
OTHER_JARLIST = $(subst $(SPACE),$(SEP),$(OTHER_JARTMP))
JRE = $(JAVA_HOME)/jre/lib/rt.jar
SOURCEPATH = $(SOURCE_DIR)
CLASSPATH = $(JRE)$(SEP)$(LOCAL_CLASS_DIR)$(SEP)$(CLASS_DIR)$(SEP)$(LOCAL_JARLIST)$(SEP)$(OTHER_JARLIST)
JAVAC_OPTIONS = -d $(DESTINATION) -classpath $(CLASSPATH) -sourcepath $(SOURCEPATH) -deprecation
JAVA_OPTIONS = -classpath $(CLASSPATH)
RMIC_OPTIONS = -d $(CLASS_DIR) -classpath $(CLASSPATH)
JAR_OPTIONS = -cvmf
JINDENT_OPTIONS = -p $(MAKE_DIR)/style.jin
PROFILER_OPTIONS = cpu=samples,depth=6
JAVADOC_OPTIONS = \
-d $(DOC_DIR) \
-sourcepath $(SOURCE_DIR) \
-classpath $(CLASSPATH) \
-author \
-package \
-use \
-splitIndex \
-version \
-windowtitle $(WINDOWTITLE) \
-doctitle $(DOCTITLE) \
-header $(HEADER) \
-bottom $(BOTTOM)
#***************************************************************************
#
# Section 3. Rules and dependencies.
#
# This section defines the exact rules for creating a target file from
# a (set of) source file(s). The rules can be quite complex and the
# makefile syntax is not extreamly readable. A quick crash course:
#
# target : depends
# rule
#
# target - the parameter given to make: What to build
# depends - file or other targets target depends on
# rule - how to create target
# $(VAR) - environment variable or variable defined above
# $@ - Current target
# $* - Current target without extension
# $< - Current dependency
#
#***************************************************************************
PACKAGE_LOC = $(subst .,/,$(PACKAGE))
PACKAGE_DIR = $(DESTINATION)/$(PACKAGE_LOC)
JAVA_FILES = $(filter %.java,$(SOURCE))
NONJAVA_FILES = $(patsubst %.java,,$(SOURCE))
CLASS_FILES = $(JAVA_FILES:%.java=$(PACKAGE_DIR)/%.class)
OTHER_FILES = $(NONJAVA_FILES:%=$(PACKAGE_DIR)/%)
JNI_CLASS_FILES = $(JNI_SOURCE:%.java=$(PACKAGE_DIR)/%.class)
JNI_HEADERS = $(JNI_SOURCE:%.java=%.h)
RMI_CLASS_FILES = $(RMI_SOURCE:%.java=$(PACKAGE_DIR)/%.class)
RMI_STUB_FILES = $(RMI_SOURCE:%.java=$(PACKAGE_DIR)/%_Stub.class)
RMI_SKEL_FILES = $(RMI_SOURCE:%.java=$(PACKAGE_DIR)/%_Skel.class)
ALL_CLASS_FILES = $(CLASS_FILES) $(RMI_STUB_FILES) $(RMI_SKEL_FILES)
JAR_CONTENT_CMD = $(patsubst %,-C $(CLASS_DIR) %,$(JAR_CONTENT))
# Make a list of all packages involved
ifdef PACKAGE
PACKAGE_LIST = $(subst .,/,$(PACKAGE))
MAIN_CLASS = $(MAIN)
MAIN_PACKAGE = $(PACKAGE)
else
PACKAGE_LIST = $(subst .,/,$(PACKAGES)) $(subst .,/,$(NODOC_PACKAGES))
endif
PLIST_CLEAN = $(patsubst %,$(SOURCE_DIR)/%/.clean,$(PACKAGE_LIST))
PLIST_BUILD = $(patsubst %,$(SOURCE_DIR)/%/.build,$(PACKAGE_LIST))
# Rule 0. Applied when make is called without targets. Invokes rule 10.
default : buildall
# Rule 1. Building a .class file from a .java file
$(PACKAGE_DIR)/%.class :: $(SOURCE_DIR)/$(PACKAGE_LOC)/%.java
$(JAVA_BIN)/$(JAVAC) $(JAVAC_OPTIONS) $<
# Rule 2. Building a .class file from a .java file. Invokes rule 1.
%.class : $(SOURCE_DIR)/$(PACKAGE_LOC)/%.java
$(MAKE) -k $(PACKAGE_DIR)/$@
# Rule 3. Building a JNI .h stub file from a .class file
$(SOURCE_DIR)/$(PACKAGE_LOC)/%.h : $(PACKAGE_DIR)/%.class
$(JAVA_BIN)/$(JAVAH) $(JAVAH_OPTIONS) $(PACKAGE).$*
# Rule 4. Building a JNI .h stub file from a class file. Invokes rule 3.
%.h : %.class
$(MAKE) -k $(SOURCE_DIR)/$(PACKAGE_LOC)/$@
# Rule 5. Building an RMI _Stub.class file from a .class file
$(PACKAGE_DIR)/%_Stub.class :: $(PACKAGE_DIR)/%.class
$(JAVA_BIN)/$(RMIC) $(RMIC_OPTIONS) $(PACKAGE).$*
# Rule 6. Building an RMI _Skel.class file from a .class file
$(PACKAGE_DIR)/%_Skel.class :: $(PACKAGE_DIR)/%.class
$(JAVA_BIN)/$(RMIC) $(RMIC_OPTIONS) $(PACKAGE).$*
# Rule 7. Building an RMI _Stub.class file from a .class file. Invokes rule 5.
%_Stub.class : %.class
$(MAKE) -k $(PACKAGE_DIR)/$@
# Rule 8. Building an RMI _Skel.class file from a .class file. Invokes rule 6.
%_Skel.class : %.class
$(MAKE) -k $(PACKAGE_DIR)/$@
# Rule 9. Default behaviour within a package: Simply copy the object from src
# to classes. Note that the location of this rule is important. It must be after
# the package specifics.
$(PACKAGE_DIR)/% :: $(SOURCE_DIR)/$(PACKAGE_LOC)/%
# $(MAKEDIR) $(PACKAGE_DIR)
$(COPY) $< $@
$(CHMOD) u+rw $@
# Rule 10. Build class files rmi stub and skeletons and process all other source
all : $(CLASS_FILES) $(RMI_STUB_FILES) $(RMI_SKEL_FILES) $(OTHER_FILES)
# Rule 11. Build JNI .h files. Invokes rule 4.
jni : $(JNI_CLASS_FILES) $(JNI_HEADERS)
# Rule 12. Build RMI stubs and skeleton files. Invokes rule 7. and rule 8.
rmi : $(RMI_CLASS_FILES) $(RMI_STUB_FILES) $(RMI_SKEL_FILES)
# Rule 13. Remove all produced files (except javadoc)
cleanall :
$(DELETE) $(PACKAGE_DIR)/*.class $(OTHER_FILES) $(JNI_HEADERS)
# Rule 14. Change ".clean" tag to "Makefile", thus call the package makefile which
# in turn recalls this makefile with target cleanall (rule 13).
%.clean :
$(MAKE) -k -f $(subst .clean,Makefile,$@) cleanall
# Rule 15: Call rule 14 for every package directory
clean : $(PLIST_CLEAN)
$(PRINT) Done clean.
# Rule 16. Change ".build" tag to "Makefile", thus call the package makefile which
# in turn recalls this makefile with target all (rule 10).
%.build :
$(MAKE) -k -f $(subst .build,Makefile,$@) all
# Rule 17. Call rule 16 for every package
buildall : $(PLIST_BUILD)
$(PRINT) Done build.
# Rule 18. Build a jar file. $* strips the last phony .JAR extension.
%.JAR :
$(DELETE) $(JAR_DIR)/$*
$(JAVA_BIN)/$(JAR) $(JAR_OPTIONS) $(JAR_DIR)/$(MANIFEST) \
$(JAR_DIR)/$* $(JAR_CONTENT_CMD)
# $(JAVA_BIN)/$(JAR) -i $(JAR_DIR)/$@
# Rule 19. Create given jar file by invoking its Makefile which triggers rule 18
%.jar :
$(MAKE) -k -f $(patsubst %,$(JAR_DIR)/Makefile.%,$@) $@.JAR
# Rule 20. Create all jar files by invoking rule 19
jar : $(JARS)
$(PRINT) Done jars.
# Rule 21. Build javadoc for all listed packages
javadoc :
$(PRINT) $(PACKAGES) > $(JAVA_DEV_ROOT)/packages.tmp
$(JAVA_BIN)/$(JAVADOC) $(JAVADOC_OPTIONS) @$(JAVA_DEV_ROOT)/packages.tmp
$(DELETE) $(JAVA_DEV_ROOT)/packages.tmp
$(PRINT) Done JavaDoc.
# Rule 22. Run application using classes tree
run :
$(JAVA_BIN)/$(JAVA) $(JAVA_OPTIONS) $(MAIN_PACKAGE).$(MAIN_CLASS) \
$(RUN_PARAMETERS)
# Rule 23. Run application using jar archive
runjar :
$(JAVA_BIN)/$(JAVA) -jar $(JAR_DIR)/$(MAIN_JAR) $(RUN_PARAMETERS)
# Rule 24. Run debugger
debug :
$(JAVA_BIN)/$(DEBUG) $(JAVA_OPTIONS) $(PACKAGE).$(MAIN)
# Rule 25. Run the auto indentation tool. Experimental setup.
indent :
$(JAVA_BIN)/$(JAVA) -classpath $(JAR_3RDPARY_DIR)/Jindent.jar \
$(JINDENT) $(JINDENT_OPTIONS) -f "*.java"
# Rule 26. Run profiler.
profile :
$(JAVA_BIN)/$(JAVA) $(PROFILER):$(PROFILER_OPTIONS) $(JAVA_OPTIONS) \
$(PACKAGE).$(MAIN)
# Rule 27. A combination of steps used for automatic building
complete : clean buildall jar javadoc
Using the make system is quite simple. You will run it from either the package level or from the project level.
The following commands can be applied from the package level (i.e when
standing in a given package directory):
make
- Process all source files in given package
make clean - Remove all produced files of given
package
make SomeFile.class - Produce a
specific file.
The following commands can be applied from the project level (i.e while
standing in the $JAVA_DEV_ROOT/ directory):
make - Process all source files in all
packages
make clean - Remove all produced
files in all packages
make jar - Create all
specified jar files
make SomeJar.jar -
Create a specific jar file
make javadoc -
Create documentation for entire project
make
run - Run application using the classes tree
make runjar - Run application through jar file
Make will make sure that only target files that are outdated will be processed. This is after all the basic idea behind make. Note however that to make a jar archive, the associated classes files must first be made explicitly. Building a jar archive will not trigger any make on the package level. This is true for the targets run, runjar and javadoc as well.
When creating Javadoc, an optional package description can be included for each package. To have this, create an overview.html HTML file in the package directory, and it will automatically be part of the final documentation.
A package makefile is typically nothing but a listing of the .java files of the directory together with the package name. This can easily be generated automatically and the following script do just that. The script should be run while standing in the package directory.
set file = Makefile set cwd = `pwd` set pattern = `echo $JAVA_DEV_ROOT/src/ | sed "s/\//\\\//g"` set package_loc = `echo $cwd | sed "s/\${pattern}//g"` set package = `echo $package_loc | sed "s/\//\./g"` rm -f $file touch $file echo "PACKAGE = $package" >> $file echo '' >> $file echo 'SOURCE = \' >> $file foreach sourcefile (`ls *.java *.gif *.jpg`) echo "\t ${sourcefile} \\" >> $file end echo '' >> $file echo 'RMI_SOURCE =' >> $file echo '' >> $file echo 'MAIN =' >> $file echo '' >> $file echo '' >> $file echo 'include $(JAVA_DEV_ROOT)/Makefile' >> $file echo '' >> $file
Note that the RMI and the MAIN tag cannot be set automatically and, if required, these should be added after the script has been run.
In a multi-developer environment the actual instance of the directory structure suggested above will be the official baseline of a project.
In addition to this, each developer will typically manage a delta baseline in his own local area containing updates to the official project with changes and additions not yet released to the official version.
The make setup above was created for handling such a setup.
First thing that needs to be done is to define the environment variable, JAVA_DEV_LOCAL pointing to the delta baseline directory.
The JAVA_DEV_LOCAL directory should have a src/ and a classes/ sub directory. The src/ directory should contain a copy of the part of the official src/ directory the developer is working on; Typically one or a few packages. For each of these packages the associated makefile must be present, but edited to contain only the source files present.
In addition to this, the project makefile should be present at JAVA_DEV_LOCAL but edited so that it contains only the packages present in the delta baseline.
With this in place, make can be run on the local baseline in the same way as on the official baseline as described above. Some of the targets does not make sense locally. These includes making jars, running jars (make runjar) or making Javadoc.
Managing a delta baseline like this is done simply by manipulating the classpath option of the java tools. By specifying the local classes directory before the official one, the local take precedence and are thus overriding the official code.
To get hold of the make files described in this document either cut-paste them directly from the browser or select from the entries below. Note that makefiles require rules to be indented by a tab character, and a cut-pasted version of the makefiles will have these as spaces instead.
This page is maintained by webmaster@geosoft.no |
![]() |