I recently forced myself to start using Maven rather than Ant for my current Java builds. This was more to see what all the fuss was about rather than hitting any sort of obstacles that Ant was creating. What I’ve found (as well as many others) is that it’s great for getting a simple project up and running quickly but creates challenges when you need to do anything out of the ordinary.
The first thing I found with regards to my Android development was that most of the JAR files I needed weren’t in any repository I could find. While not a serious problem, it did mean I had to either manually create the proper directory structures in my local repositories (I have a desktop and a laptop) or start using a self-maintained external one. I opted for the latter and chose Sonatype’s Nexus as my server. I went with Nexus for the simple reason that it used a lightweight native web server (Jetty) rather than a Tomcat instance as many others do. I’ve already got enough Tomcat instances running for my Atlassian products so the thought of adding a new one just for Java builds was unappealing.
For libgdx 0.8.1 dependencies (the current version as of this post), I decided to create four separate artifacts. The groupId was com.badlogic.gdx for all and the artifactId’s I used were gdx, gdx-backend-android, gdx-backend-jogl and gdx-natives. This was all done very easily in Nexus by clicking the “3rd party” repository, clicking the “Artifact Upload” tab then choosing “GAV Parameters” for “GAV Definition” and filling out the appropriate information for each artifact. The only artifact files I uploaded to the repository was the single JAR associated with each artifactId. Nexus was very cool and automagically created all the XML files needed in the repository for said artifacts.
Since one of my projects needs OpenFeint, I went through an identical process and used groupId com.openfeint and artifactId openfeint. I’m still using OpenFeint 1.0.1 so it was easy to simply upload its single JAR artifact. It seems like OpenFeint 1.6 has done something mysterious with the JAR that I have yet to understand.
So with the dependencies squared away, it came time to create the new structure for my project. Both of my libgdx projects are constructed in such a way that I can trivially build both an OS X executable and an Android APK. Ah, the beauty of libgdx's cross-platform-y goodness! My approach to accomplish this was to use three separate modules. I’ll use Mayan Madness as an example. It’s source directory tree looks like this:
MayanMadness
|- android
| |- AndroidManifest.xml
| |- assets
| |- libs
| |- pom.xml
| |- res
| |- src
|- common
| |- src
| |- test
| |- pom.xml
|- mac
| |- src
| |- test
| |- pom.xml
|- pom.xml
The root POM looks like this:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.whizzosoftware.mayanmadness</groupId>
<version>1.2</version>
<artifactId>app</artifactId>
<packaging>pom</packaging>
<modules>
<module>common</module>
<module>mac</module>
<module>android</module>
</modules>
</project>
This way, running a simple “mvn package” will create the packages for both Mac and Android and resolve their dependencies to the common module without having to pull it from the repository.
There’s actually very little code in the android and mac modules. They contain the class implementing a main method (or its Android equivalent) and any necessary platform-specific interface implementations. A quick example will illustrate…
Currently, OpenFeint is only available on Android. Therefore, I created an AchievementProcessor interface that lives in the common module. The concrete implementations of that interface live in the android and mac modules. For Mac, the implementation is a NoOp. For Android, the implementation uses OpenFeint’s APIs.
The mac module's POM dependencies look like this:
<dependencies>
<dependency>
<groupId>com.whizzosoftware.mayanmadness</groupId>
<artifactId>common</artifactId>
<version>1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.badlogic.gdx</groupId>
<artifactId>gdx-backend-jogl</artifactId>
<version>0.8.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.badlogic.gdx</groupId>
<artifactId>gdx-natives</artifactId>
<version>0.8.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
I used a nifty Maven plugin called osxappbundle-maven-plugin. This tackles the onerous job of creating a native-looking OS X bundle from Java JAR(s). It handles bundling up the JAR files, creating a native JVM launcher and creating the internal Info.plist file. It worked great although I found that the DMG it generated didn’t work properly. The ZIP file worked great.
For the Android module, things get more complicated. As you probably know, the Android SDK includes a tool that will automatically generate an Ant directory structure that you can use to build an APK. That’s great for Ant but what about Maven? Fortunately, there’s a Maven plugin available that doesn’t try to completely re-invent the wheel. It will basically use the same Ant Android project structure with the build.xml, build.properties and libs directory becoming unnecessary (for the most part - see below). That directory structure is what is used in the android module (as shown above).
The easy part was setting up the module dependencies. This was done with the following entry in the POM:
<dependencies>
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
<version>2.2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.whizzosoftware.mayanmadness</groupId>
<artifactId>common</artifactId>
<version>1.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.openfeint</groupId>
<artifactId>openfeint</artifactId>
<version>1.0.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.badlogic.gdx</groupId>
<artifactId>gdx</artifactId>
<version>0.8.1</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.badlogic.gdx</groupId>
<artifactId>gdx-backend-android</artifactId>
<version>0.8.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
I also needed to add the following plugins entry in the POM to get the Android SDK bits working:
<plugins>
<plugin>
<groupId>com.jayway.maven.plugins.android.generation2</groupId>
<artifactId>maven-android-plugin</artifactId>
<version>2.8.3</version>
<configuration>
<sdk>
<platform>7</platform>
</sdk>
<emulator>
<avd>NexusOne_2.1</avd>
</emulator>
<deleteConflictingFiles>true</deleteConflictingFiles>
<undeployBeforeDeploy>true</undeployBeforeDeploy>
</configuration>
<extensions>true</extensions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<!-- version 2.3 defaults to java 1.5, so no further configuration needed-->
<version>2.3</version>
</plugin>
</plugins>
Where things got a bit more complicated was with regards to the native components needed by libgdx on Android. I tried a few different things and finally compromised on a less-than-ideal solution that got my project working. Basically, the Maven Android plugin will copy anything in the project tree’s libs directory to the lib area of the APK. Therefore, I was able to put the armeabi and armeabi-v7a files into the project tree’s libs directory and everything worked. This has the unfortunate consequence of having to check-in native .so files into the project’s SVN tree thus preventing them from being versioned alongside the libgdx JARs that live in the repository. I’m sure there’s a better way to do this, and when I find it, I’ll alter my approach.
At any rate, this was how I got things up and running. I hope that this will help some people save a bit of time as I spent the better part of an evening working all of this out. If someone has determined a better way to do all of this, I'd love to hear it.