Deploying artefacts into your CloudBees maven repositories.

If you’re like me, and have a fair few hobby projects on the go at any one time, you’re may want to take advantage of some of the free cloud services that are available out there. I’m particulary fond of the Maven repositories that CloudBees offer, as it gives you a safe and easy way to deploy your artefacts, whether they’re 3rd party libraries, snapshots, or even releases of your own software.

Head over to CloudBees.com and register an account, its free.

I’ve recently been doing a fair bit of work 3rd party libraries that are not available on the maven repositories, you have to download the source and compile it yourself (I have another post about an example of that). Since I often develop on different machines, and I’m not actually changing the library, I wanted a single place where I could upload a pre compiled copy of it and just depend on it as I would any other library, such as JUnit.

The first task, is to get your project compiling and working locally, once that is done you can then add the following into your pom.xml

Repository (that references a configured repository on ~/.m2/settings.xml)

<repository>
            <id>cloudbees-private-snapshot-repository</id>
            <name>cloudbees-private-snapshot-repository</name>
            <url>dav:https://repository-jameselsey.forge.cloudbees.com/snapshot/</url>
        </repository>

Also, since maven 3 requires the webdav extension, you’ll need to add this into the build/extensions section of your pom.xml:

<extensions>
            <!-- Extension required to deploy a snapshot or a release to the CloudBees remote maven repository using Webdav -->
            <extension>
                <groupId>org.apache.maven.wagon</groupId>
                <artifactId>wagon-webdav</artifactId>
                <version>1.0-beta-2</version>
            </extension>
        </extensions>

After that, make sure that your ~/.m2/settings.xml file contains details of your CloudBees repositories, here is my copy, make sure you put your password in, and replace occurences of “jameselsey” with your own user ID.

<?xml version="1.0"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
-->
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
    <server>
        <id>cloudbees-private-snapshot-repository</id>
        <username>jameselsey</username>
        <password>YOUR_PASSWORD_HERE</password>
        <filePermissions>664</filePermissions>
        <directoryPermissions>775</directoryPermissions>
    </server>
    <server>
        <id>cloudbees-private-release-repository</id>
        <username>jameselsey</username>
        <password>YOUR_PASSWORD_HERE</password>
        <filePermissions>664</filePermissions>
        <directoryPermissions>775</directoryPermissions>
    </server>
    <server>
        <id>cloudbees-private-snapshot-plugin-repository</id>
        <username>jameselsey</username>
        <password>YOUR_PASSWORD_HERE</password>
        <filePermissions>664</filePermissions>
        <directoryPermissions>775</directoryPermissions>
    </server>
    <server>
        <id>cloudbees-private-release-plugin-repository</id>
        <username>jameselsey</username>
        <password>YOUR_PASSWORD_HERE</password>
        <filePermissions>664</filePermissions>
        <directoryPermissions>775</directoryPermissions>
    </server>
</servers>
<profiles>
    <profile>
        <id>cloudbees.private.release.repository</id>
        <activation>
            <property>
                <name>!cloudbees.private.release.repository.off</name>
            </property>
        </activation>
        <repositories>
            <repository>
                <id>cloudbees-private-release-repository</id>
                <url>https://repository-jameselsey.forge.cloudbees.com/release</url>
                <releases>
                    <enabled>true</enabled>
                </releases>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </repository>
        </repositories>
    </profile>
    <profile>
        <id>cloudbees.private.snapshot.repository</id>
        <activation>
            <property>
                <name>!cloudbees.private.snapshot.repository.off</name>
            </property>
        </activation>
        <repositories>
            <repository>
                <id>cloudbees-private-snapshot-repository</id>
                <url>https://repository-jameselsey.forge.cloudbees.com/snapshot</url>
                <releases>
                    <enabled>false</enabled>
                </releases>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </repository>
        </repositories>
    </profile>
    <profile>
        <id>cloudbees.private.release.plugin.repository</id>
        <activation>
            <property>
                <name>!cloudbees.private.release.plugin.repository.off</name>
            </property>
        </activation>
        <pluginRepositories>
            <pluginRepository>
                <id>cloudbees-private-release-plugin-repository</id>
                <url>https://repository-jameselsey.forge.cloudbees.com/release</url>
                <releases>
                    <enabled>true</enabled>
                </releases>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
            </pluginRepository>
        </pluginRepositories>
    </profile>
    <profile>
        <id>cloudbees.private.snapshot.plugin.repository</id>
        <activation>
            <property>
                <name>!cloudbees.private.snapshot.plugin.repository.off</name>
            </property>
        </activation>
        <pluginRepositories>
            <pluginRepository>
                <id>cloudbees-private-snapshot-plugin-repository</id>
                <url>https://repository-jameselsey.forge.cloudbees.com/snapshot</url>
                <releases>
                    <enabled>false</enabled>
                </releases>
                <snapshots>
                    <enabled>true</enabled>
                </snapshots>
            </pluginRepository>
        </pluginRepositories>
    </profile>
</profiles>
</settings>

Now, if you run mvn clean install deploy:deploy, maven will create a clean build of your application, install it to your local repository, and then deploy it to your CloudBees snapshot repo.

[INFO] --- maven-deploy-plugin:2.7:deploy (default-cli) @ tess-two ---
WAGON_VERSION: 1.0-beta-2
Downloading: dav:https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/0.0.1-SNAPSHOT/maven-metadata.xml
Downloaded: dav:https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/0.0.1-SNAPSHOT/maven-metadata.xml (769 B at 0.7 KB/sec)
Uploading: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/0.0.1-SNAPSHOT/tess-two-0.0.1-20120720.120205-2.apklib
Uploaded: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/0.0.1-SNAPSHOT/tess-two-0.0.1-20120720.120205-2.apklib (5865 KB at 449.0 KB/sec)
Uploading: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/0.0.1-SNAPSHOT/tess-two-0.0.1-20120720.120205-2.pom
Uploaded: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/0.0.1-SNAPSHOT/tess-two-0.0.1-20120720.120205-2.pom (3 KB at 1.2 KB/sec)
Downloading: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/maven-metadata.xml
Downloaded: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/maven-metadata.xml (276 B at 0.6 KB/sec)
Uploading: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/0.0.1-SNAPSHOT/maven-metadata.xml
Uploaded: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/0.0.1-SNAPSHOT/maven-metadata.xml (769 B at 0.4 KB/sec)
Uploading: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/maven-metadata.xml
Uploaded: https://repository-jameselsey.forge.cloudbees.com/snapshot/tess-two/tess-two/maven-metadata.xml (276 B at 0.2 KB/sec)
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 49.874s
[INFO] Finished at: Fri Jul 20 13:02:24 BST 2012
[INFO] Final Memory: 16M/81M
[INFO] ------------------------------------------------------------------------

The same should work for releases, just alter the repository in the pom to point to the releases repo. This isn’t particularly CloudBees specific either, it should work on any remote maven repository, I just like CloudBees, and no, I don’t work for them :P

Any comments/suggestions, please shout!

Automating android application signing and zipaligning with maven

Code rage

This is something that has had me tearing my hair out for a few days now, I was pretty much border-line braveheart-ing my screen….

Code rage

I’ve recently been on a little drive to try to maven-ize my projects. All had been going well until I needed to sign and zipalign my APKs. This post will help you conquer that barrier with the use of some maven plugins.

When using ant, I was able to simply enter keystore details into build.properties and just call “ant release”. Unfortunately that approach doesn’t carry across to maven, and you have to provide some more configuration.

Firstly, before we go any further, I’m going to assume that you already have a maven android project setup, so you have a pom.xml, you’ve configured the maven-android-plugin and you can run “mvn clean install” to build your APK, and “mvn android:deploy” to deploy it to an emulator. If you haven’t got that far, I’d suggest you have a look at one of my previous posts to help get you up to speed.

So, when you want to build your application with maven, you’d run “mvn install”. That will, by default, use a debug key to sign your APK. When we want to build a releasable APK we still want to execute the same install goal, however we’ll want to use a proper key. Luckily, maven provides something called profiles.

In short, maven profiles allow you to still perform the same standard goals, yet they behave slightly different, in the manner of binding extra steps to them, all will come clearer in a moment.

This has already been covered, in a useful post by the guys at Novoda, and various other blog posts scattered around the web. I’ve followed at least 10 tutorials and each time I was unable to get the signing process to work correctly, each time I encountered the following error :

INFO] jarsigner: attempt to rename C:\android-projects\jameselsey_andsam_branch_mavenbranch\target\LanguageSelection-1.0.0-SNAPSHOT.jar to C:\android-projects\jameselsey_ands
am_branch_mavenbranch\target\LanguageSelection-1.0.0-SNAPSHOT.jar.orig failed
[INFO] ------------------------------------------------------------------------
[ERROR] BUILD ERROR
[INFO] ------------------------------------------------------------------------
[INFO] Failed executing 'cmd.exe /X /C "C:\development\tools\Java\jdk1.6.0_20\jre\..\bin\jarsigner.exe -verbose -keystore my-release-key.keystore -storepass '*****' -keypass '
*****' C:\android-projects\jameselsey_andsam_branch_mavenbranch\target\LanguageSelection-1.0.0-SNAPSHOT.jar mykeystore"' - exitcode 1
[INFO] ------------------------------------------------------------------------

I’d tried everything, even copying out the command and pasting into a command window worked, but I could not get it to work from maven. Unfortunately it seems there may be an issue with the maven-jarsigner-plugin (as suggested in the comments on the Novoda post). However fear not, for there is an alternative, the maven-jar-plugin.

The maven-jar-plugin is a similar plugin to the jarsigner plugin, however the signing APIs are now deprecated and it points you to use the (apparently) broken maven-jarsigner-plugin. Using a deprecated API doesn’t particulary concern me in this instance, as its just signing artefacts.

Take a look at my profiles section, copy this into your pom.xml :

<profiles>
<profile><!-- release profile. uses keystore defined in keystore.* properties. signs and zipaligns the app to the target folder-->
            <id>release</id>
            <activation>
                <activeByDefault>false</activeByDefault>
            </activation>
            <build>
                <defaultGoal>install</defaultGoal>
                <finalName>${project.artifactId}-${project.version}</finalName>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-jar-plugin</artifactId>
                        <version>2.2</version>
                        <executions>
                            <execution>
                                <id>signing</id>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                                <phase>package</phase>
                                <inherited>true</inherited>

                                <configuration>
                                    <keystore>
                                        my-release-key.keystore
                                    </keystore>
                                    <storepass>mypassword</storepass>
                                    <keypass>mypassword</keypass>
                                    <alias>mykeystore</alias>
                                    <verbose>true</verbose>
                                    <verify>true</verify>
                                </configuration>
                            </execution>
                        </executions>
                    </plugin>

                    <plugin>
                        <groupId>com.jayway.maven.plugins.android.generation2</groupId>
                        <artifactId>maven-android-plugin</artifactId>
                        <inherited>true</inherited>
                        <configuration>
                            <sign>
                                <debug>false</debug>
                            </sign>
                        </configuration>
                    </plugin>

                    <plugin>
                        <groupId>com.jayway.maven.plugins.android.generation2</groupId>
                        <artifactId>maven-android-plugin</artifactId>
                        <configuration>
                            <zipalign>
                                <verbose>true</verbose>
                                <skip>false</skip>
                                <!-- defaults to true -->
                                <inputApk>${project.build.directory}/${project.artifactId}-${project.version}.apk</inputApk>
                                <outputApk>${project.build.directory}/${project.artifactId}-${project.version}-RELEASE.apk
                                </outputApk>
                            </zipalign>

                        </configuration>
                        <executions>
                            <execution>
                                <id>zipalign</id>
                                <phase>install</phase>
                                <goals>
                                    <goal>zipalign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
        </profile>
</profiles>

OK, so lets walk through what the above profile does. Firstly, the “id” of the profile is “release”, so when you want to apply this profile, you’d run “mvn install -Prelease”. The “activeByDefault” is set to false, which means you need to use the above arguement, if you flip that over, you won’t need the P flag.

The execution goal is to “sign”, which is the API on the plugin, documented here. We bind this execution on to the standard “package” goal. Then we provide the configuration elements which specify the details of the keystore.

In pure English, this binds the plugin phase onto the package goal of maven, so anytime we run using this profile it’ll execute the jar signing of our artefacts. The “verify” tag gives us extra piece of mind by verifying that the signing was successful afterwards.

I’ve also setup a zipalign profile that will take the APK, zipalign it, and rename it to “*-RELEASE.apk”, so I know that particular APK is the one which I’ll release to market.

So thats it, once more a success, and its just one small step on the ladder to android glory!

Phhew!! Done, finally I was able to shutdown the laptop and go to sleep at 3am….on a school night!!!

Good luck, feel free to comment!

Peace.

Android; Continuous integration, all made lovely with Maven

Lets face it, the eclipse ADT plugin is great for getting an android application up and running quickly, but if you want an easier way to get libraries, and a continuous intergration environment for robust automated testing and building, then maven is the way to go.

IntelliJ have recently released the Early Access Program to their version X of IDEA, this comes with Android support in the community edition, meaning we’re no longer bound to using eclipse for Android development!

The first thing you want to do, is to run through the “create new project” wizard in IntelliJ, its fairly straightforward, just run through and setup a simple android applicaiton. When you’ve got that, run it up on the emulator just to make sure any autogenerated code IntelliJ created works OK.

OK so now we’re ready to start gutting it out and providing the Maven framework. First thing you need to do, in the root directory of the android project, create a file called pom.xml with the following contents

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>MavenMess</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>apk</packaging>
    <name>Maven Android Plugin - samples</name>

    <dependencies>
        <dependency>
            <groupId>com.google.android</groupId>
            <artifactId>android</artifactId>
            <version>2.2.1</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <sourceDirectory>src</sourceDirectory>
        <plugins>
            <plugin>
                <groupId>com.jayway.maven.plugins.android.generation2</groupId>
                <artifactId>maven-android-plugin</artifactId>
                <version>2.6.0</version>
                <configuration>
                    <sdk>
                        <!--  platform or api level (api level 4 = platform 1.6)-->
                        <platform>8</platform>
                    </sdk>
                    <emulator>
                        <!--  the name of the avd device to use for starting the emulator-->
                        <avd>GoogleAPIs</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>
    </build>
</project>

The POM file is what maven uses to build/test your project. If you’re not familiar with maven at this stage I’d suggest a quick browse of their documentation to get to grips with the fundamentals.

The above POM does a couple of tasks, firstly it tells maven to build an APK file using the packaging statement. You need this otherwise maven wouldn’t know what to create, and might create a WAR or EAR file instead.

You declare the android SDK as a provided dependancy. Next comes the build section, this is where the magic happens. The source directory tells maven where it can find all your android source files to compile.

You’ll need to declare usage of the Jayway plugin, this is a neat little plugin that I picked up from Hugo Josefson from Jayway, at DroidCon UK 2010. There is some configuration that you can set here such as the API level and which emulator to deploy the application to. The second plugin is the maven Java compiler, we need this in order to actually compile the class files.

Right so thats your POM sorted, next you can go ahead and delete the following items

  • \bin
  • \libs
  • build.properties
  • build.xml

You won’t need those files/directories, those are used for the autogenerated ant build script that the android IntelliJ plugin creates, and we’re now using maven.

Right thats it for the maven configuration, next comes actually running the application. The maven goal of “mvn install” will compile all your classes, so run that to check the above is OK.

To deploy your application to a device, you can run the maven goal of “mvn android:deploy”. These goals are great, but what about a 1 button click for building and deploying? Easy…

Along the top of IntelliJ, click on the drop down menu for run targets and create a new run/debug configuration. Using the goal of “android:deploy” will deploy the application to the emulator, but you can also setup a goal of “mvn install” which happens before deployment, as shown in the screenshot below. This will build and deploy your application to the emulator with one click.

All you need to do then is to go into your emulator and start your application, simple!

Any questions/suggestions, please let me know!

Big thanks to the guys at Jayway for sharing this, and making it possible.