Setting up multiple Java VMs under Cygwin

It is useful to have different versions of the JVM installed, for a number of reasons:

  • Different optimisation features from different JVM implementations
  • Different language features from different JVM versions
  • Java classes compiled with “Tiger” won't run in “Mantis”…

It is also useful to be able to quickly switch between installed JREs/JDKs depending on the task at hand.

If I'm hacking in Linux, the JPackage project provides a much nicer solution to this problem, and the Linux distro' I'm using (SUSE 10.0) uses JPackage. It'd be nice if there was an update-alternatives for Cygwin, but since there isn't I've come up with this hack.


2007-03-09T08:06+1100 - Update: Neater JVersion code I've recently upgraded to Mustang and noticed that this broke my function, since it installs to a different base directory again… So here is a new version that hopefully covers off future default directory names that Sun comes up with for Dolphin and the Open Source releases.


# Path variables

if [ x"$X_PATH_NO_JAVA" = x ]; then
    export X_PATH_NO_JAVA=~/bin:$PATH     # save path without JAVA, for future switches
fi



if [ x"$JAVA_BASE" = x ]; then
    JAVA_BASE=d:\\java    # default if not set in Windows
fi



# Functions

function JVersion() {
    # Select a Java Development Kit to use.
    # This provides similar functionality in Cygwin, to the
    # updatealternatives command of Debian/SuSE linux.
    #
    # Note, different releases have different standard dirs,
    # so we just look for "k" which seems to be the
    # thing they still have in common.  Also the sed scripts
    # which pull out the version number installed, for
    # reporting, must be specific for each release...

    #Convert JAVA_BASE to Cygwin format
    export JAVA_BASE=`cygpath -up $JAVA_BASE`

    # Use function's argument to select, defaulting to Mantis
    case $1 in
        1.6 | 1.6.0 | 6.0 | 6 | mustang | Mustang | MUSTANG)
            export JAVA_VER=`ls $JAVA_BASE | grep k1.6.0`
            JVERNUM=`echo $JAVA_VER | sed s/^jdk//g`
        ;;
        1.5 | 1.5.0 | 5.0 | 5 | tiger | Tiger | TIGER)
            export JAVA_VER=`ls $JAVA_BASE | grep k1.5.0`
            JVERNUM=`echo $JAVA_VER | sed s/^j2sdk//g`
        ;;
        * | MANTIS)
            export JAVA_VER=`ls $JAVA_BASE | grep k1.4.2`
            JVERNUM=`echo $JAVA_VER | sed s/^j2sdk//g`
        ;;
    esac

    #Set the JAVA_HOME variable (used by some Java programs, so
    #must be in Windows format for those programs to understand)
    export JAVA_HOME=`cygpath -wp $JAVA_BASE/$JAVA_VER`

    #Add the correct JDK runtime to Cygwin's path
    export PATH=$JAVA_BASE/$JAVA_VER/bin:$X_PATH_NO_JAVA

    #Report what happened
    echo Java Version: $JVERNUM
    echo Java Base: $JAVA_BASE
    echo "Java Home: $JAVA_HOME (`cygpath -up $JAVA_HOME`)"
}



#Now run it to set up initial Java environment:

JVersion > /dev/null

This is all you need now, no complicated set-up variables and such rubbish. You'll notice that the setup steps are now just to set $X_PATH_NO_JAVA (save the $PATH without Java in it), and set up $JAVA_BASE if necessary. Then after the function is declared and I run it to set a default Java environment for Cygwin (Still Mantis for now, since my work is using it for a system I support).

Read on for old stuff and false-starts...

...

I have a shell function called JVersion which you use to select the version of the JVM you want to use, and a bunch of environment variables to make it all work. Here's how you use it:

$ JVersion tiger
Java Version: 1.5.0_04
Java Base: /cygdrive/d/java
Java Home: /cygdrive/d/java/j2sdk1.5.0_04
$ java -version
java version "1.5.0_04"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_04-b05)
Java HotSpot(TM) Client VM (build 1.5.0_04-b05, mixed mode, sharing)
$
$ JVersion mantis
Java Version: 1.4.2_05
Java Base: /cygdrive/d/java
Java Home: /cygdrive/d/java/j2sdk1.4.2_05
$ java -version
java version "1.4.2_05"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_05-b04)
Java HotSpot(TM) Client VM (build 1.4.2_05-b04, mixed mode)
$

When using Java from the command-line (DOS or Cygwin), you need to have the interpreter (for JRE) and development tools (for JDK) on the system search PATH. You also used to have to set the CLASSPATH for the JRE to find bootstrap and extension classes, but these are now found automatically via the System property "sun.boot.class.path" and the Java Extension Mechanism. However, it's still useful to have a JAVA_HOME variable for some third-party tools, such as IDEs.

I've written a bash function which uses some control variables to allow easy switching of JVMs on Cygwin. It's still a bit of a hack, but I find it useful when I'm on Windows to work from the bash prompt as well as whatever IDE I'm hacking in.

The control variables are

  • JAVA_BASE = the base directory where all the JVMs are installed. I put each JVM in it's own directory under D:\java This variable should be in the Windows environment, but it defaults to d:\java in my .bashrc if it was not set (i.e. I forgot to):
    if [ x"$JAVA_BASE" = x ]; then
         JAVA_BASE=d:\\java    # default if not set in Windows
    fi

    I'm sticking to DOS pathnames for this variable, as I intend to replicate this functionality with batch files one day, in case I'm mad enough to run Java from a Windows command shell. Anyway, I then convert this to a Cygwin path with the following sed(1) and awk(1) hack:

    JAVA_BASE=`echo $JAVA_BASE | sed 's////g'`   # fix DOS sloshes
    
    export JAVA_BASE=`echo $JAVA_BASE | awk '{print(tolower($1))}' \\
                                      | sed 's/d://cygdrive/d/g'` # fix drive
  • JAVA_MANTIS = the version (without the release part) corresponding to “Mantis
  • JAVA_TIGER = the version (without release) corresponding to “Tiger
  • JAVA_VER = the specific version and release of the currently selected JVM (e.g. 1.5.0_04)
    if [ x"$JAVA_MANTIS" = x ]; then
      export JAVA_MANTIS=`ls $JAVA_BASE | grep 1.4.2 \\
                                        | sed s/^j2sdk//g`
    fi
    
    if [ x"$JAVA_TIGER" = x ]; then
      export JAVA_TIGER=`ls $JAVA_BASE | grep 1.5.0 \\
                                       | sed s/^j2sdk//g`
    fi
    
    if [ x"$JAVA_VER" = x ]; then
      export JAVA_VER=$JAVA_MANTIS     # default if not set
    fi
  • JAVA_HOME = the install directory of the currently selected JVM (e.g. d:\java\j2sdk1.4.2_05)
  • X_PATH_NO_JAVA = the system search path, without a JVM
  • PATH = the system search path

The aim is to set JAVA_HOME and PATH so that the Java tools can be found. They are set using the previous variables:

export JAVA_HOME=$JAVA_BASE/j2sdk$JAVA_VER

if [ x"$X_PATH_NO_JAVA" = x ]; then
  export X_PATH_NO_JAVA=~/bin:$PATH  # save path without JAVA
fi

export PATH=$JAVA_HOME/bin:$X_PATH_NO_JAVA

So, the above code is run inside my .bashrc, which takes care of finding the installed JVMs and setting Mantis as the default to use. But what about switching? Well, here's the shell function:

function JVersion() {
    case $1 in
        1.5 | 1.5.0 | 5.0 | 5 | tiger | Tiger | TIGER)
            export JAVA_VER=$JAVA_TIGER
        ;;
        *)
        export JAVA_VER=$JAVA_MANTIS
        ;;
    esac

    JAVA_BASE=`echo $JAVA_BASE | sed 's////g'`   # fix DOS sloshes
    export JAVA_BASE=`echo $JAVA_BASE | awk '{print(tolower($1))}' \
                                      | sed 's/d://cygdrive/d/g'` # fix drive
    export JAVA_HOME=$JAVA_BASE/j2sdk$JAVA_VER
    export PATH=$JAVA_HOME/bin:$X_PATH_NO_JAVA

    echo Java Version: $JAVA_VER
    echo Java Base: $JAVA_BASE
    echo Java Home: $JAVA_HOME
}

You may have noticed that this hack is rather brittle. It works well, but only so long as you follow these steps:

  1. Install your Javas on your D: drive (or you have to fix all of the sed scripts!)
  2. Have a “base” directory, such as D:\java
  3. Have a Windows environment variable called JAVA_BASE equal to the “base” directory
  4. Install your Javas each in a sub-directory of your “base”, called j2sdkw.x.y_z

Additionally it only caters for a single release of each Java version, and only for Java versionsMantis” (1.4.2) and “Tiger” (1.5.0). If you want to install “Mustang” and switch to it, you'll need to add a JAVA_MUSTANG variable, equal to 1.6.0 and also add a case for 1.6.0 to both the JAVA_VER setup code and the JVersion case statement… ugly, but I haven't come up with a more elegant solution yet. Ultimately I'd like to make a proper replacement for update-alternatives, but since symlinks will only work for the Cygwin tools, and not the Java tools themselves, it seems unlikely I'll come up with a nicer solution.

A note about paths in Java on Cygwin:

This is noted in the Cygwin docs somewhere I think, but just a reminder: java.exe, javac.exe, etc. are Win32 programs, and are not linked to the Cygwin DLLs in any way. So always they will need DOS style paths (D:\somedir\etc). They won't understand Cygwin's /cygdrive/d/whatever. Also as I just alluded to, any Cygwin links will not be followed by the Java tools, for the same reason. In bash(1), you'll need to escape the sloshes if you're setting the classpath on the command line:

java -classpath D:\\\\java\\\\special-classes\\\\somefile.jar MyClass

Since java transposes the / and \ characters itself, it may be possible to do the following:

java -classpath D:/java/special-classes/somefile.jar MyClass

But I haven't tested it…


2006-06-17T16:39+1000 - Update: Cygwin's cygpath(1) command I should have seen this before spending so long playing with sed and sloshes…Cygwin's cygpath(1) command can be used to fix the Unix/DOS path stuff, much simpler than mucking about with sed and awk. So in the above, instead of code like this:

JAVA_BASE=`echo $JAVA_BASE | sed 's////g'`   # fix DOS sloshes

export JAVA_BASE=`echo $JAVA_BASE | awk '{print(tolower($1))}' \\
                                  | sed 's/d://cygdrive/d/g'` # fix drive
… that mess can be replaced replaced with this:
export JAVA_BASE=`cygpath -up $JAVA_BASE`

… (using back-quotes, or the $() notation if you prefer). Similarly, to run Java with a classpath specified in a Cygwin format (i.e. from a Cygwin script or prompt):

java -classpath `cygpath -wp /cygdrive/d/java/special-classes/somefile.jar` MyClass