Lucent nmake Java Build Support

:JAVA: Assertion Operator

:JAVA: - Java source dependency operator

Description

The :JAVA: operator has the format:

:JAVA: files-or-directories

Java files to be built are specified by the right hand side arguments. There must be at least one right hand side argument. Each right hand side (RHS) argument specifies a pathname and may be one of the following types:

file
Pathname of a .java source file. Shell pathname expansion patterns such as *.java work as expected. This is typically, but not necessarily, a relative pathname.
directory
Pathname of a directory. This is equivalent to specifying the relative pathnames of all .java files which exist in the tree rooted at the specified directory. Shell pathname expansion is not supported for directories.

The :JAVA: operator uses dependencies among .java and .class files to insure that build steps run in the proper order, and that only necessary build steps get run. These dependencies are automatically derived from Java source files; furthermore, complex requirements of Java builds, such as dependency cycles and implicit targets, are correctly handled. However, to speed the development cycle this version of :JAVA: does not by default automatically update the dependencies themselves each time a build is run. If javadeps=1 or no $(LOCALJAVADEPS) file exists in the viewpath the dependencies are automatically updated from source, otherwise the dependencies in $(LOCALJAVADEPS)are used. The user is responsible for updating the dependencies whenever Java source is changed in a way that modifies the dependencies among .java and .class files.

In order to speed up dependency maintenance, this version of :JAVA: updates dependencies incrementally if possible. This means that only source files whose modification time is changed since the last run are rescanned for dependencies. Dependency information for other files is reused from the last run.

Since commonly used Java compilers have a high startup overhead, :JAVA: provides an optional feature (enabled by default) which reduces the number of Java compiler invocations by batching calls to the compiler. That is, instead of compiling Java files one by one as is normally the case in nmake based builds, :JAVA: arranges for Java compilations to be queued, and only runs the compiler when the queue length exceeds a threshold. This approach can dramatically reduce build times. The maxjavac variable provides user control over this feature.

Global and Local Java Builds

Due to cross-package dependencies, Java builds are best managed using a single top-level Makefile in the Java package root directory. This Makefile contains a global set of Java dependencies in all subpackages, ensuring that class files get rebuilt when necessary no matter which package they reside in. As a consequence, global and package local Java builds are organized differently from traditional C/C++ builds.

A global build is run from the package root directory and brings all subordinate packages (as specified on the RHS of :JAVA:) up to date. The top-level Makefile knows all dependencies among Java and class files in subordinate packages, detects out-of-date targets, and initiates all compilations directly from the top level directory. This is unlike traditional C/C++ builds, where the top-level Makefile merely recurses to lower level directories, and actual compilations are performed by local Makefiles located within each source directory. In the case of Java global builds, local Makefiles are not used, and not needed.

The use of the top-level Makefile provides correct dependency management, however, when developing inside a package, local Makefiles have some advantages: they allow a build to be conveniently started without leaving the local directory, and they allow specification of a subset of local targets for update. To provide these conveniences while retaining the benefits of global dependency management, :JAVA: provides support for the safe use of local inside-package Makefiles. These local Makefiles also use the :JAVA: assertion to specify a local set of Java source files to build. To actually perform a build, :JAVA: collects the build targets, changes to the package root directory (by default), and builds the specified targets in the context of the global package dependencies. This ensures that the minimal work will be performed to bring the specified local targets up to date, but that other targets, including possibly files in other packages, will be updated if required. An example of a local package build is shown below. If the build execution directory is different from the current directory, the build execution directory must have a Makefile.

A locally issued clobber will only remove generated files that are within the local package.

Variables and Atoms

Several user-settable nmake variables influence the behavior of the :JAVA: operator. The JAVAPACKAGEROOT variable has no default and must be defined:

JAVAPACKAGEROOT
Package root for Java source files. Typically specified in a global Makefile in the form $(VROOT)/<java-root-offset>.

All other variables are optional. The following variables specify the locations of files and commands used:

CLASSPATH
Java class search path, as used by $(JAVAC). Within a Makefile, .SOURCE.class is preferred.
JAVABUILDDIR
Build execution directory for inside-package local builds. Default is $(JAVAPACKAGEROOT), meaning that builds are performed at package root level. This is the safest setting since it accounts for cross-package dependencies. To force build execution to be strictly local, set JAVABUILDDIR to `.'. Use with caution, since with this setting prerequisite files in other packages will not be updated.
JAVAC
Java compiler. Defaults to javac.
JAVACFLAGS
Java compiler flags.
JAVACLASSDEST
Destination directory for class files, typically $(VROOT)/<java-class-offset>. This sets the $(JAVAC) -d option. Default is $(JAVAPACKAGEROOT).
JAVAMAKEFILE
Makefile name in build execution directory, if that directory is different from the current directory. Defaults to [Mm]akefile.
JDEPS
Java source dependency scanner. This is an external tool used to extract dependency information from Java source files. Defaults to an external scanner which is known to produce dependency information in a format usable by nmake. Default is $(JDEPSDIR)/jdeps.
JDEPSDIR
Pathname of directory where javadeps package may be found. Defaults to first directory that exists of <nmake_install_root>/../jdeps and <nmake_install_root>/../../jdeps.
JDEPS.JAR
Location of jdeps jar file. This file is required in order to run $(JDEPS), and defaults appropriately to properly run the external scanner. Default is $(JDEPSDIR)/jdeps.jar.
LOCALJAVADEPS
Location of file containing Java dependencies. Defaults to localjavadeps. This file is generated automatically by $(JDEPS).
SYNCONFIG
Syntax configuration file for $(JDEPS). Default is $(JDEPSDIR)/synconfig.

The following variables provide fine control over the behavior of the :JAVA: operator:

maxjavac
Maximum number of Java files to be compiled in a single call to javac.
javadeps
If javadeps=1, force regeneration of Java file dependencies.

The following special atoms are used:

.SOURCE.class
javac -classpath command line option. Note that the meaning of this option changed between JDK1.1 and JDK1.2. One consequence is that the system library class.zip must be included in the .SOURCE.class atom for JDK1.1, but not for JDK1.2. See http://java.sun.com/products/jdk/1.2/docs/tooldocs/solaris/migration.html#clspath for details.
.SOURCE.java
Directories to search for Java files.

Example

DIRECTORY STRUCTURE

    <viewpath_root_dir>/                # $(VROOT)
        java/                           # $(JAVAPACKAGEROOT)
            jglob.mk                    # global Makefile
            Makefile                    # top level Makefile
            com/
                lucent/
                    stc/
                        pkg1/
                            Makefile    # local Makefile
                            A.java
                            B.java
                        pkg2/
                            C.java
                            D.java
                            E.java
        class/                          # $(JAVACLASSDEST)
            com/
                lucent/
                    stc/
                        pkg1/
                        pkg2/

GLOBAL BUILD EXAMPLE

Contents of <viewpath_root_dir>/java/jglob.mk. Note that JAVAPACKAGEROOT has no default and must be specified:

JAVAPACKAGEROOT=$(VROOT)/java
JAVACLASSDEST=$(VROOT)/class
.SOURCE.class: $(JAVACLASSDEST) /opt/exp/java/lib/classes.zip  

Contents of top level Makefile <viewpath_root_dir>/java/Makefile:

include $(VROOT)/java/jglob.mk
:JAVA: com/lucent/stc/pkg1 com/lucent/stc/pkg2  

Then a global build from directory <viewpath_root_dir>/java generates all dependencies and compiles all Java source files in packages 1 and 2:

$ nmake
+ /tools/nmake/javadeps-lu2.0.2/jdeps /tools/nmake/javadeps-lu2.0.2/jdeps.jar -C JAVACLASSES -n --vpath=/home/gms/wrk/nmake/java/phase2/userman/java --silent -s /tools/nmake/javadeps-lu2.0.2/synconfig -o localjavadeps -d ../class --classpath=../class:/opt/exp/java/lib/classes.zip com/lucent/stc/pkg1/A.java com/lucent/stc/pkg1/B.java com/lucent/stc/pkg2/C.java com/lucent/stc/pkg2/D.java com/lucent/stc/pkg2/E.java
+ /opt/exp/bin/javac -O -d ../class -classpath .:../class:/opt/exp/java/lib/classes.zip com/lucent/stc/pkg2/D.java com/lucent/stc/pkg1/B.java com/lucent/stc/pkg2/C.java com/lucent/stc/pkg2/E.java com/lucent/stc/pkg1/A.java

In this case, the following :JAVA: assertion has the same effect:

:JAVA: com

To force dependency regeneration in addition to updating the .class files, run

nmake javadeps=1

As described previously, the global build is totally managed by <viewpath_root_dir>/java/Makefile, no recursive makes are done.

LOCAL PACKAGE BUILD EXAMPLE

Continuing the above example, suppose we now want to work within a package. <viewpath_root_dir>/java/com/lucent/stc/pkg2/Makefile is an example of a package local Makefile:

include $(VROOT)/java/jglob.mk
:JAVA: C.java D.java E.java

The local package working directory is <viewpath_root_dir>/java/com/lucent/stc/pkg2. From this directory, a developer may edit the local Java files C.java, D.java and E.java, and then run nmake:

$ cd <viewpath_root_dir>/java/com/lucent/stc/pkg2
$ touch D.java
$ nmake
+ cd <viewpath_root_dir>/java
+ nmake javasdir=com/lucent/stc/pkg2 javarhs=C.java D.java E.java javaclassdest=../../../../../class javadeps=0
+ /opt/exp/bin/javac -O -d ../class -classpath .:../class:/opt/exp/java/lib/classes.zip com/lucent/stc/pkg2/D.java com/lucent/stc/pkg1/B.java com/lucent/stc/pkg2/C.java com/lucent/stc/pkg2/E.java

As described previously, the specified targets are passed up the top level Makefile, and the build is actually performed in $(JAVAPACKAGEROOT). Note that due to the complex dependencies of this example, a Java file from another package is brought up to date in addition to files in the local package.


:JAVA: - Java source dependency operator
Release lu3.5 @(#)jman.html 5.1.2.1 04/16/02