In this blog I want to walk through an example of how easy it can be to build and package up an existing Java project in such a way that the binaries will run on .NET as well as a regular JVM (i.e. "hybrid package").
I'm going to use JUnit as an example. I'll walk through, in detail, what it takes to build JUnit 4.8.2 using Ja.NET SE.
First, download and install the latest Ja.NET SE JDK. I usually install it into c:\janet.
Next, you also need to download the Ja.NET SE Additional Tools zip file. (i.e. Ja.NET-SE-Additional Development Tools.zip). This zip contains a "hybrid package" of Ant 1.7.1 that we will use when doing the build. Extract the apache-ant-1.7.1-bin-release.zip from the download and unzip that to a directory for use. I put it in c:\apache\apache-ant-1.7.1.
Once you have Ja.NET and Ant on your system, you'll need a command window and you will need to have your PATH include the bin directories for both Ja.NET and Ant. (e.g. SET PATH=C:\janet\bin;C:\apache\apache-ant-1.7.1\bin;%PATH%). Later on, when you do the builds by invoking Ant, we want to make sure Ant is running on Ja.NET, and not on some other JVM which you might have installed previously.
Next, you must download the JUnit 4.8.2 source. When you go to this page you will find a button in the upper right labeled "Download Source". When you click that, it will give you the link to get a zip of r4.8.2. You must first click on r4.8.2 and then click on the ZIP icon. Once done, you will get a buildable set of 4.8.2 source. Unzip the package to a directory. I used c:\projects\examples\junit-4.8.2.
Now this version of JUnit has a dependency on Hamcrest Core 1.1. So we need to first build this using Ja.NET SE. Luckily, the JUnit package you just downloaded contains a .jar with Hamcrest in it (i.e. lib\hamcrest-core-1.1.jar). And, fortunately the jar also contains the source files we need, so we will just use those. It is very simple to build as you will see.
Unzip the jar to a directory. I used c:\projects\examples\hamcrest-core-1.1. To build the source code, you simply have to compile and jar up the output. In the command window with the PATH set as described above, invoke javac specifying the options "-both -1.5" along with the dirctory that contains the hamcrest source. The -both option causes the compiler to generate two class files for each hamcrest class, one for a JVM (a .class file) and the other for .NET (.clazz file).
Then, you can use the jar utility to jar up the files and create a "hybrid package" that contains both the JVM class files as well as a merged .NET assembly. You will use the normal jar command options (i.e. -cf), as well as one additional -b option (i.e. both) which instructs the jar utility to merge the .clazz files in addition to doing its normal jar functions. Here are the commands that I used:
cd C:\projects\examples\hamcrest-core-1.1
javac -both -1.5 -d bin .\
jar -cbf hamcrest-core-1.1.jar -C bin .
Now you should have a "hybrid package" of Hamcrest in C:\projects\examples\hamcrest-core-1.1, and we are ready to build JUnit. First, change directory into the location that has the build.xml file for JUnit. For me, thats C:\projects\examples\junit-4.8.2. First we will clean the build directory to make sure we are starting from scratch and then we will build the JUnit jars from source. Here are the commands that I used:
cd C:\projects\examples\junit-4.8.2
ant clean
ant -Djanet.build.both=true -Dhamcrestlib=..\hamcrest-core-1.1\hamcrest-core-1.1.jar jars
Notice that you need to tell the build script where the hamcrest library that we just built is located by setting the property -Dhamcrestlib= ... . Also notice -Djanet.build.both=true setting. Setting this Ant property to true causes Ant to invoke every javac and jar task in the build.xml with the "-both" option set so that both JVM and .NET versions of the code get built. This is very similiar to what we did above when building Hamcrest.
A clean build will create a "hybrid package" of junit-4.8.2.jar leaving it in the directory .\junit4.8.2.
To try your build, you can use one of the simple examples from the Junit.org web site. Here is the class I used to test:
package org.janet.examples;
public class Subscription {
private int price ; // subscription total price in euro-cent
private int length ; // length of subscription in months
// constructor :
public Subscription(int p, int n) {
price = p;
length = n;
}
/**
* Calculate the monthly subscription price in euro,
* rounded up to the nearest cent.
*/
public double pricePerMonth() {
double r = (double) price / (double) length ;
return r ;
}
/**
* Call this to cancel/nulify this subscription.
*/
public void cancel() { length = 0 ; }
}
And here is the JUnit test class I used:
package org.janet.examples;
import org.junit.*;
import static org.junit.Assert.*;
public class SubscriptionTest {
@Test
public void test_returnEuro() {
System.out.println("Test if pricePerMonth returns Euro...");
Subscription S = new Subscription(200,2);
assertTrue(S.pricePerMonth() == 1.0);
}
@Test
public void test_roundUp() {
System.out.println("Test if pricePerMonth rounds up correctly...");
Subscription S = new Subscription(200,3);
assertTrue(S.pricePerMonth() == 0.67);
}
}
Save each of these to their proper location in some directory. Notice the package name in the source above. Next, compile the two Java files. Here is the command line I used:
javac -both -1.5 -cp C:\projects\examples\junit-4.8.2\junit4.8.2\junit-4.8.2.jar;. org\janet\examples\Subscription.java org\janet\examples\SubscriptionTest.java
Again, notice the -both option. This will allow us to run the JUnit test on a JVM as well as .NET.
To run the test on .NET using the "hybrid" JUnit package we just created, you can use the Ja.NET launcher as follows:
java -cp C:\projects\examples\junit-4.8.2\junit4.8.2\junit-4.8.2.jar;. org.junit.runner.JUnitCore org.janet.examples.SubscriptionTest
When you run it, you should see two JUnit test failures, as both tests as written fail.
To run the test on a JVM, again using the "hybrid" JUnit package, you can use the same Ja.NET launcher with one additional option:
java -vmdir:drlvm -cp C:\projects\examples\junit-4.8.2\junit4.8.2\junit-4.8.2.jar;. org.junit.runner.JUnitCore org.janet.examples.SubscriptionTest
Remember, Ja.NET SE includes the Harmony JVM packaged with it as well, so you can use the -vmdir:drlvm option to cause the launcher to run the JVM instead of the .NET CLR.
So, there you have it. As you can see, using the provided Ja.NET based tools it can be quite easy to build "hybrid" libraries. We didn't have to change any source code or build scripts, we just built the package as you would with any other Java JDK.
The next thing to explore is how you can use the "hybrid package" in a C# program. In my next blog, I'll "rewrite" the Subscription and SubscriptionTest in C# and we will run the tests again, using the "hybrid" version of JUnit 4.8.2.