Scott Logic Ltd

Using PhoneGap Build with Maven

Chris Price, June 29th, 2012

This post introduces a Maven plugin for easily building native PhoneGap apps for iOS, Android, WP7 (and more) from any WAR project, in any environment. It assumes that you are familiar with using Maven and have a WAR project you wish to build. In return, when you run your build you’ll get native binaries automatically built then installed/deployed to your repository. Best of all because it’s all part of your standard Maven build, your CI server can handle your one-click release builds signed with your distribution keys ready for app store distribution.

Introduction

PhoneGap Build is a hosted service which wraps HTML5 applications into native applications. The plugin connects to the service, uploads the exploded WAR directory, waits for the builds to complete and then downloads the native binaries installing/deploying them as attached artifacts.

Building your first native app

First of all you’re going to need to visit the PhoneGap Build website and sign up for an Adobe account (GitHub doesn’t work with the API unfortunately). Test out your shiny new credentials by running the following command from a terminal -

mvn com.github.chrisprice:phonegap-build-maven-plugin:list -Dphonegap-build.username=[USERNAME] -Dphonegap-build.password=[PASSWORD]

If everything works as planned then you should see all your applications and keys that are currently hosted on the service (empty that is, if you just signed up!). If there’s a problem then double check the command and also check that your credentials work on the website.

Including the plugin in your project

Now for something more exciting! You’ll need to make two additions to your project, firstly to configure maven to use the plugin during the build lifecycle, and secondly to customise how PhoneGap Build should wrap your application. Start by adding the following to the project>build>plugins section of your POM -

<plugin>
	<groupId>com.github.chrisprice</groupId>
	<artifactId>phonegap-build-maven-plugin</artifactId>
	<version>0.0.2</version>
	<executions>
		<execution>
			<id>phonegap-build</id>
			<!-- the goals are lifecycle bound by default -->
			<goals>
				<goal>clean</goal>
				<goal>build</goal>
			</goals>
			<configuration>
				<platforms>
					<platform>android</platform>
				</platforms>
			</configuration>
		</execution>
	</executions>
</plugin>

Now create a file called config.xml in src/main/phonegap-build which contains a few key details about your application. The config.xml file also gives you fine control over the configuration of the PhoneGap wrappers, documented in detail here however, for now just copy the following -

<?xml version="1.0" encoding="UTF-8" ?>
    <widget xmlns="http://www.w3.org/ns/widgets" xmlns:gap="http://phonegap.com/ns/1.0"
        id="[GROUP-ID].[ARTIFACT-ID]" version="[VERSION]">
    <name>PhoneGap Build Sample</name>
    <description>
        A sample for phonegap-build-maven-plugin. 
    </description>
    <author href="http://www.scottlogic.co.uk/blog/chris" email="cprice@scottlogic.co.uk">
        Chris Price
    </author>
</widget>

At the minimum you’ll need to change GROUP-ID, ARTIFACT-ID and VERSION to correspond to the maven project co-ordinates, feel free to change the author etc. but it isn’t required at this point.

Running the build

When you’ve made the changes to the config.xml, you should be able to run the following -

mvn clean install -Dphonegap-build.username=[USERNAME] -Dphonegap-build.password=[PASSWORD]

You should see your application being packaged locally and then uploaded to the service. After a short wait, the service needs some time to perform the actual build, the native Android apk file will be downloaded and installed in your local repository. Simples!

Multiple platforms

You now have an Android binary without having to mess around with the SDK or any PhoneGap project templates, impressive, but what about if we now get a requirement for a WP7 binary?

If we tweak the plugin configuration in your POM like so -

<platforms>
    <platform>android</platform>
    <platform>winphone</platform>
</platforms>

And re-run the command above -

mvn clean install -Dphonegap-build.username=[USERNAME] -Dphonegap-build.password=[PASSWORD]

You’ll see not only an Android version downloaded and installed but sat alongside it is now a WP7 xap. Building for webOS (webos), BlackBerry (blackberry) and Symbian (symbian) follows exactly the same pattern. iOS (ios) requires a little more configuration, you need to supply your developer certificate and provisioning profile, but once configured a single Maven build can produce native binaries for all the major mobile platforms and install/deploy them straight into your repository.

Configuring the plugin to build for a platform will cause the build to fail if there is an error building for that platform so be sure to only include the ones you are interested in e.g. don’t attempt to build for iOS without configuring your developer certificate and provisioning profile.

Storing your credentials somewhere more secure

One of the things you’ll likely want to change if you start using the plugin in your project is how the service credentials are passed to the plugin. Passing them in on the command line is laborious and worse still very insecure. The best way to do this is to store them as a server in your settings.xml. This has the major advantage of allowing different developers to use their own credentials whilst still sharing the same project. It also allows the passwords to be encrypted if that floats your boat.

Signing your Android build for distribution

When it comes to distributing your application to the Play Store, the developer certificate that by default the service signs Android binaries with just isn’t going to cut it. You’ll need to follow a few steps to generate a self-signed (i.e. free!) private signing key and then configure the plugin to use it. This process is detailed on the plugin website.

Conclusion

This part covered configuring the plugin to build PhoneGap Android applications for development, and also signed distribution builds ready for the app store. In the next part I’ll cover building applications for iOS (which is slightly trickier because you’ll need to configure your developer certificate and provisioning profile).

Until then if you get stuck, please check-out the sample application or post an issue on the bug tracker, if you want to customise the Android PhoneGap wrapper be sure to revisit the config.xml settings and if you want to skip ahead, there’s also more detailed documentation on the plugin site, including instructions for building for iOS.



This entry was posted on Friday, June 29th, 2012 and is filed under Blog.

You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site .


22 Responses to “Using PhoneGap Build with Maven”

  1. Adrian Herscu says:

    I tried your plugin and I was really amazed :) In few minutes I got my APK running on my Android tablet!
    Great work!

    I am now trying to integrate zxing barcode plugin and I am wondering how to do it using your great plugin. Any suggestion?

  2. Ah yes! This is really good for me too. I’ve been using Android for native app development but I’m new to the whole PhoneGap scene.

    Thanks!

  3. Nick F says:

    Great piece of work! One issue I’m having though is trying to use this plugin from both a build server and a developer workstation to build the same app. The phonegap-build server somehow tags requests coming from each environment as a separate app even though the credentials etc. are all identical. For a free acct, this results in the following error:

    build failed: Private app limit reached

    Have you come across this before?

  4. Nick F says:

    Found the issue – I needed to add

    <appId>xx</appId>

    Under the section of the plugin and enter the appId for the app I was trying to build (availale on the phonegap-build website and inside a text file along with the build artifacts from the initial successful build). I guess it defaults to using the creation part of the API if this value is missing.

  5. Ilyas says:

    Hi Chris

    I’m trying to configure your maven plugin and I’ve followed the documentation but I am getting this error

    [INFO] [phonegap-build:clean {execution: phonegap-build}]
    10-Jan-2013 16:31:20 org.apache.commons.httpclient.auth.AuthChallengeProcessor selectAuthScheme
    INFO: basic authentication scheme selected
    10-Jan-2013 16:31:20 org.apache.commons.httpclient.HttpMethodDirector processWWWAuthChallenge
    INFO: Failure authenticating with BASIC ‘Application’@build.phonegap.com:443
    [INFO] ————————————————————————
    [ERROR] FATAL ERROR
    [INFO] ————————————————————————
    [INFO] Client response status: 401
    [INFO] ————————————————————————
    [INFO] Trace
    com.sun.jersey.api.client.UniformInterfaceException: Client response status: 401
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:686)
    at com.sun.jersey.api.client.WebResource.post(WebResource.java:241)
    at com.github.chrisprice.phonegapbuild.api.managers.MeManagerImpl.requestToken(MeManagerImpl.java:55)
    at com.github.chrisprice.phonegapbuild.api.managers.MeManagerImpl.createRootWebResource(MeManagerImpl.java:49)
    at com.github.chrisprice.phonegapbuild.api.managers.MeManagerImpl.createRootWebResource(MeManagerImpl.java:25)
    at com.github.chrisprice.phonegapbuild.plugin.AbstractPhoneGapBuildMojo.getRootWebResource(AbstractPhoneGapBuildMojo.java:112)
    ……

    The authentication details I’ve entered in settings.xml are my Adobe ID details.

    • Chris Price says:

      A quick way of testing credentials is to run the following from within your project folder -
      mvn phonegap-build:list

      That will attempt to login to the service and list all your projects/keys. If that fails then double check that you can sign into the service via the website, I’ve had problems before where there’s been an updated license agreement that I’ve had to click through or somesuch. Let me know if that’s still failing.

      Cheers,
      Chris

      • Ilyas says:

        Thanks for the quick reply, it’s been a bad day today.

        I couldn’t even login via the website so had to reset my password. I’m now getting:

        [WARNING] iOsServer not specified, falling back to iOsCertificate/iOsCertificatePassword.
        [INFO] ————————————————————————
        [ERROR] BUILD FAILURE
        [INFO] ————————————————————————
        [INFO] ios certificate does not exist C:\development\personal-projects\my-app\target\phonegap-build\ios.p12.

        I’m buying a licence now so hopefully this will be cleared up. Thank you

  6. Ilyas says:

    Hi Chris

    I was thinking this morning, if I use the Phonegap build plugin which requires the index.html to be in src/main/webapp/, does this mean I won’t be able to use the various emulators which I think require the index.html to be in assets/www/index.html.

    Do you use your plugin exclusively or are you able to test locally on emulators as well as use phonegap build using the same project structure?

  7. Ilyas says:

    Hi, I’m still getting the following error even though I have put the .p12 and .mobileprovision files into Phonegap build. Do I need to put these files into my project?

    [INFO] Building zip: C:\development\personal-projects\my-app\target\phonegap-build\file.zip
    17-Jan-2013 12:19:09 org.apache.commons.httpclient.auth.AuthChallengeProcessor selectAuthScheme
    INFO: basic authentication scheme selected
    [WARNING] iOsServer not specified, falling back to iOsCertificate/iOsCertificatePassword.
    [INFO] ————————————————————————
    [ERROR] BUILD FAILURE
    [INFO] ————————————————————————
    [INFO] ios certificate does not exist C:\development\personal-projects\my-app\target\phonegap-build\ios.p12.

  8. Ilyas says:

    Yeah, I spotted the document after I posted the comment. I now get:

    [INFO] org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field “locked” (Class com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse), not marked as ignorable
    at [Source: com.sun.jersey.client.apache.ApacheHttpClientHandler$HttpClientResponseInputStream@5d283826; line: 1, column: 64] (through reference chain: com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse["locked"])
    [INFO] ————————————————————————
    [INFO] Trace
    com.sun.jersey.api.client.ClientHandlerException: org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field “locked” (Class com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse), not marked as ignorable
    at [Source: com.sun.jersey.client.apache.ApacheHttpClientHandler$HttpClientResponseInputStream@5d283826; line: 1, column: 64] (through reference chain: com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse["locked"])
    at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:564)
    at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:506)
    at com.sun.jersey.api.client.WebResource.handle(WebResource.java:684)
    at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
    at com.sun.jersey.api.client.WebResource$Builder.post(WebResource.java:568)
    at com.github.chrisprice.phonegapbuild.api.managers.KeysManagerImpl.postNewKey(KeysManagerImpl.java:44)
    at com.github.chrisprice.phonegapbuild.plugin.utils.IOsKeyManagerImpl.ensureIOsKey(IOsKeyManagerImpl.java:136)
    at com.github.chrisprice.phonegapbuild.plugin.BuildMojo.ensureIOsKey(BuildMojo.java:323)
    at com.github.chrisprice.phonegapbuild.plugin.BuildMojo.execute(BuildMojo.java:265)
    at org.apache.maven.plugin.DefaultPluginManager.executeMojo(DefaultPluginManager.java:490)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoals(DefaultLifecycleExecutor.java:694)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalWithLifecycle(DefaultLifecycleExecutor.java:556)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoal(DefaultLifecycleExecutor.java:535)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeGoalAndHandleFailures(DefaultLifecycleExecutor.java:387)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.executeTaskSegments(DefaultLifecycleExecutor.java:348)
    at org.apache.maven.lifecycle.DefaultLifecycleExecutor.execute(DefaultLifecycleExecutor.java:180)
    at org.apache.maven.DefaultMaven.doExecute(DefaultMaven.java:328)
    at org.apache.maven.DefaultMaven.execute(DefaultMaven.java:138)
    at org.apache.maven.cli.MavenCli.main(MavenCli.java:362)
    at org.apache.maven.cli.compat.CompatibleMain.main(CompatibleMain.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.codehaus.classworlds.Launcher.launchEnhanced(Launcher.java:315)
    at org.codehaus.classworlds.Launcher.launch(Launcher.java:255)
    at org.codehaus.classworlds.Launcher.mainWithExitCode(Launcher.java:430)
    at org.codehaus.classworlds.Launcher.main(Launcher.java:375)
    Caused by: org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field “locked” (Class com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse), not marked as ignorable
    at [Source: com.sun.jersey.client.apache.ApacheHttpClientHandler$HttpClientResponseInputStream@5d283826; line: 1, column: 64] (through reference chain: com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse["locked"])
    at org.codehaus.jackson.map.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:53)
    at org.codehaus.jackson.map.deser.StdDeserializationContext.unknownFieldException(StdDeserializationContext.java:267)
    at org.codehaus.jackson.map.deser.std.StdDeserializer.reportUnknownProperty(StdDeserializer.java:649)
    at org.codehaus.jackson.map.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:635)
    at org.codehaus.jackson.map.deser.BeanDeserializer.handleUnknownProperty(BeanDeserializer.java:1355)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:717)
    at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:580)
    at org.codehaus.jackson.map.ObjectMapper._readValue(ObjectMapper.java:2695)
    at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1308)
    at org.codehaus.jackson.jaxrs.JacksonJsonProvider.readFrom(JacksonJsonProvider.java:419)
    at com.sun.jersey.json.impl.provider.entity.JacksonProviderProxy.readFrom(JacksonProviderProxy.java:139)
    at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:554)
    … 27 more

  9. Ilyas says:

    After trying again, I get:

    [INFO] org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field “role” (Class com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse), not marked as ignorable
    at [Source: com.sun.jersey.client.apache.ApacheHttpClientHandler$HttpClientResponseInputStream@135b4b49; line: 1, column: 133] (through reference chain: com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse["role"])
    [INFO] ————————————————————————
    [INFO] Trace
    com.sun.jersey.api.client.ClientHandlerException: org.codehaus.jackson.map.exc.UnrecognizedPropertyException: Unrecognized field “role” (Class com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse), not marked as ignorable
    at [Source: com.sun.jersey.client.apache.ApacheHttpClientHandler$HttpClientResponseInputStream@135b4b49; line: 1, column: 133] (through reference chain: com.github.chrisprice.phonegapbuild.api.data.keys.IOsKeyResponse["role"])
    at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:564)
    at com.sun.jersey.api.client.ClientResponse.getEntity(ClientResponse.java:506)

  10. Chris Price says:

    Ah, the problem seems to be the new security model for certificate passwords has added a few more fields to the API response https://build.phonegap.com/blog/upcoming-security-changes see the “Creating a signing key” section.

    I’ve created an issue for this (https://github.com/chrisprice/phonegap-build/issues/9) and I’ll hopefully get a chance to fix it over the next couple of days.

    For now you may be able to work around it by uploading a key manually, specifying the key id in the pom and manually unlocking the key through the web interface before trying to build, but you might also run into the same problem when the plugin tries to validate the specified key exists…

  11. Matias says:

    Hi Chris, great plugin!

    I am running a mvn clean install and I get the following error message: Failed to execute goal com.github.chrisprice:phonegap-build-maven-plugin
    :0.0.4:build (phonegap-build) on project globant-rooms-client: Execution phonegap-build of goal com.github.chrisprice:phonegap-build-maven-plugin:0.0.4:build failed: Build error : No index.html found -> [Help 1]

    I have the index.html file in the src/main/webapp folder. Also, I’ve added this to the config:

    workingDirectory: ${project.build.directory}/phonegap-build
    warDirectory: same as working (otherwise it fails)

    Could you point me in any direction to solve this please? Thanks!!

Leave a Reply

© 2013 Scott Logic Ltd. All Rights Reserved.