czwartek, 10 marca 2011

Mavenize existing android eclipse project

I have been working some time on android projects that try to use maven. We grew with the maven android plugin and maven/android eclipse plugin.
In this article I wanted to create guide how to mavenize existing android/eclipse project. During writing this post I discovered that few things have been different with new plugins (android or eclipse) and changed for better.

I used git to track what changes are being done by each steps described here to show the diff(s) and explain better what has happened.

  1. Create eclipse project for transition

    The project to show the mavenization process has been created with following parameters.

    Application name: MavenizeMe
    Package: net.retsat1.starlab.mavenize
    Activity: Me
    Sdk: 8

  2. Create android maven project from archetype

    mvn archetype:generate \
    -DarchetypeArtifactId=android-quickstart \ \
    -DarchetypeVersion=1.0.5 \
    -DgroupId=net.retsat1.starlab.mavenize \

    To change sdk to 8 you need to answer 'N' for the first question and correct the default 7.

  3. Copy pom.xml to the project with android

  4. Import the project as maven project.

    When the project gets imported you will notice changes in .project and .classpath.

    .project shows that the project has new nature of maven nature.
    One of the builders is removed and instead we have maven builder.

    - <name></name>
    + <name>org.maven.ide.eclipse.maven2Builder</name>
    + <nature>org.maven.ide.eclipse.maven2Nature</nature>

    I have been having lot of problems when apk builder was enabled. It did not go well with maven builder and I was getting some random errors during the build. I think the earlier versions of the plugin had not been removing apk builder and it clashed with maven very often.

    .classpath has a bunch of new entries
     -     <classpathentry kind="src" path="src"/>  
    - <classpathentry kind="src" path="gen"/>
    + <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
    + <classpathentry kind="con" path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
    <classpathentry kind="con" path=""/>
    - <classpathentry kind="output" path="bin"/>
    + <classpathentry kind="src" output="target/android-classes" path="gen">
    + <attributes>
    + <attribute name="optional" value="true"/>
    + </attributes>
    + </classpathentry>
    + <classpathentry kind="output" path="target/android-classes"/>

    Not sure why src has been removed. I reverted it back by rightclick on src and add to build path.

    There is a wide belief that eclipse files should not be committed to version control but should be generated for every developer on his own machine. In general I believe that is true but for android maven project this might be exception to the rule.
    In this case these files are already there :) What is more, mvn eclipse:eclipse generates something that does not work well as in case of regular java projects.

  5. Enabling ADT to run and debug.

    After cleaning the project I noticed that the project cannot be run with eclipse adt plugin. Could not find mavenize-me.apk!

    Maven does produce apk, but it has version number in the name./target/mavenize-me-1.0-SNAPSHOT.apk.

    At the same time ADT plugin expects the apk with the name as the project at the output folder.

    The solution is to either force maven to generate a project without a version information or copy generated apk to the location that adt expects. I used the second solution with the help of maven-ant-plugin.
    <property name="source"
    value="${}/${project.artifactId}-${project.version}.${project.packaging}" />
    <property name="destination"
    value="${}/classes/${}.apk" />
    <delete file="${destination}" />
    <copy file="${source}" tofile="${destination}" />

  6. Fixing the source location

    The apk generated by maven does not contain anything except the resource so the process crashes on emulator. It does not see the activity class Me. The default maven for java code is src/main/java but adt plugin prefers the src directory.

    We can either setup maven to work with
    or move the src folder to the location that maven prefers to work with. I chose the latter.

    mkdir -p src/main/java
    git mv src/net/ src/main/java/

    And set the eclipse classpath to new location. First you need to remove src from the path and add it src/main/java
    .classpath will show the diff as follows
     -    <classpathentry kind="src" path="src"/>  
    + <classpathentry kind="src" path="src/main/java"/>

  7. Remove the builders

    I did not understand why we need other builders than maven and java so I removed ResourceBuilder and Precompiler from the .project.

    - <name></name>
    - <arguments>
    - </arguments>
    - </buildCommand>
    - <buildCommand>
    - <name></name>
    - <arguments>
    - </arguments>
    - </buildCommand>
    - <buildCommand>

    It turned out when I added new id to the main.xml that it is not visible in net.retsat1.starlab.mavenize.R. The eclipse project does not compile and as a result run button does not work. Also the project reports errors. Maven on the other hand successfully compiles the project.

    The id is generated in
    target/generated-sources/r/net/retsat1/starlab/mavenize but not in gen folder by adt. No wonder, because I removed the builder responsible for that. But if it is generated in the r directory we do not need gen folder. It must be on the build path. Deleting gen will cause adt to complain about missing problem so I needed a nifty way to deceive eclipse. This way is to create a linked folder name gen pointing to the r folder. We use eclipse variable for that


     +        <link>  
    + <name>gen</name>
    + <type>2</type>
    + <locationURI>PROJECT_LOC/target/generated-sources/r</locationURI>
    + </link>

  8. The maven way

    At this moment I had source in the standard maven but still assets and res folders at root that does not look like a standard maven. I am not sure where would be the correct place in maven for them but chose src/main/android-assets and src/main/android-res. To make adt happy I did the same trick as with link to generated sources.

     +        <link>  
    + <name>assets</name>
    + <type>2</type>
    + <locationURI>PROJECT_LOC/src/main/android-assets</locationURI>
    + </link>
    + <link>
    + <name>res</name>
    + <type>2</type>
    + <locationURI>PROJECT_LOC/src/main/android-res</locationURI>
    + </link>

    The defaults in maven-android-plugin are the same as for adt plugin so is necessary to adjust those paths.

     -                    <assetsDirectory>${project.basedir}/assets</assetsDirectory>  
    - <resourceDirectory>${project.basedir}/res</resourceDirectory>
    + <assetsDirectory>${project.basedir}/src/main/android-assets</assetsDirectory>
    + <resourceDirectory>${project.basedir}/src/main/android-r</resourceDirectory>

  9. The maven project

    In those steps I achieved the project which is compiled solely by maven. The project is also using adt plugin for running and debugging so creates similar experience for the developers accustomed to adt.

    I am sure this is rather lengthy operation to convert to maven project and I had not discoverd many problems that bigger android maven projects may have on such trivial example. I have not considered a project with android tests.

    Another drawback of this setup is that Maven as a project builder is not very convenient. Is much slower than adt and also does not support incremental builds perfectly. In the background maven compiler is invoked constantly eventough there are no changes ins the source code. That might be little annoying.

    The pros for such setup is for sure the dependency management that maven may give you.
    On top of that also your continuous integration system will be more than happy to receive maven project and should generate the same artifact(s) as on developers workstations. I do not like when developers use different method of building the projects(on their workstation) than the official build does. Is often the case of works for me only or send my build to the customer scenario.

3 komentarze:

  1. Ten komentarz został usunięty przez autora.

  2. Any idea why

    is added to the .classpath?

    It is not when using ADT only.

  3. When using ADT, it is providing android.jar (from SDK) that has classes like java.lang.Object.
    When compiling with maven this is treated as regular java project (+android nature).
    JavaSE provides java.lang.Object and other classes. Android classes is provided by android.jar from maven repo (is not the same as the one from the SDK).
    Maven does the work of additional android features (resources,aidls,packaging to apk), but compiles the project as regular java.