Divide and conquer your Android modules with Artifactory

In this article I’ll cover the easiest way to roll your own maven repository for hosting private libraries. This will allow you to make faster, more reproducible builds.

We’ll use a tool called Artifactory and install it on AWS’s Elastic Beanstalk service. To finish, we’ll take a library project and upload it there, and use it in our sample app project.

The Artifactory tutorial is based on Jeroen Mols’ excellent articles that detail Artifactory usage:

History / Motivation

We used to have a giant, monolithic app at M&S. It took ages to build and the code was everywhere. Then we split it up into separate gradle modules to do different tasks. We have modules for the UI, shared core utilities, and user authentication; Currently these are all still in our main git repository.

We also split out some features into external libraries. These are our A/B testing framework, and our new membership scheme called Sparks. We included these as git submodules into the main project.

So how do you build that project? First you’d check out the code. Then you’d type the git submodule init and git submodule update commands. Only then can we do the actual build.  My old Macbook Air could barely cope, with build times taking between 5 and 10 minutes from clean.

Alas, the codez!

I’ve created a sample repo with 2 branches showing the difference between using artifactory as a repository and a local module. You can find it here: https://github.com/zmarkan/ArtifactorySample

The implementation in both is super simple, just 2 classes:

  • a library called Echoer, which… echoes. It’s got a single method - String echo(String source) that just returns whatever you pass in as the source.
  • and an app module with our MainActivity. That uses our Echoer to set the text to it’s single TextView.
//Echoer
public class Echo {
    public String echo(String source){
        return source;
    }
}

//MainActivity
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Echo echoer = new Echo();

        ((TextView)findViewById(R.id.text)).setText(echoer.echo("Hello World!"));
    }
}

There are 2 branches, local_module and use_artifactory for the two use cases. In local_module we have our app directly accessing the Echoer library, which means we need to ship the code every single time we use the app, and compile it as well. In the use_artifactory branch on the other hand I present a better solution.

A better way

I recently came across a tool called Artifactory. It’s by the same people, who created jCenter. You know jCenter as the default maven repository in Android Studio projects.

In short Artifactory is the engine behind jCenter. Even better, it’s open source. It allows you to host your own private (or public) maven modules. This will allow us to host our own libraries, like the Echoer we built before. Let’s download it from here: https://www.jfrog.com/open-source/

You can see what it looks like by running the artifactory shell script in the bin/ directory. It’ll just start a server on your machine and you can play with it. Just don’t be afraid of the interface. It’s several levels of horrible but you don’t need to look at it. Much.

Once you’ve familiarised with it, you can read on.

To the cloud!

Having a local server isn’t cool. You know what’s cool? A million servers in the cloud.

… said every Oracle consultant ever.

This article assumes you don’t have your have DevOps people at your disposal, who could help set it up for you. (Also, if you’re already familiar with AWS then just skip this step) We’ll do the easiest thing in the world and use AWS to get Artifactory up and running on a remote machine anyone can have access to.

  1. Log in to your amazon console, and select Elastic Beanstalk. (It’s like a cheaper Heroku).
  2. Create a new beanstalk application, I named mine ArtifactorySample.
  3. Create a new environment, of type web server.
  4. Under Predefined configuration select Tomcat. Under environment type select Single instance.
  5. As a source, select upload your own, and pick the artifactory.war, in the webapps folder where you unzipped the Artifactory.
  6. Give the environment a name, and a URL. In my case I kept the predefined artifactorysample-env in both.
  7. You can whizz through next few steps and in the Review page hit the Launch button.
  8. Have a coffee / tea whilst it’s doing it’s magic.
  9. Congratulations, you can now do DevOps! (Actual DevOps experts might disagree)

Upload a library

Now that our Artifactory is is running, we can start uploading our libraries. We’ll just use gradle for that, considering it’s already running our build system.

First off, we’ll need the gradle plugin named build-info-extractor-gradle that will enable us to do that. This is in the project’s top-level build.gradle.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.0.0-alpha3'
        classpath "org.jfrog.buildinfo:build-info-extractor-gradle:3.1.1"
    }
}

Next up, in the build.gradle or our library we apply the plugins:

apply plugin: 'com.jfrog.artifactory'
apply plugin: 'maven-publish'

Now we can configure our plugins. The maven plugin knows how to package our library as an artifact, and handles versioning and naming. Artifactory plugin is the one that does the actual pushing. We need to tell it where to push, what to push, and how to authenticate with the server. The admin/password credentials are the default ones as we haven’t changed anything in our setup (but you totally should!)

publishing {
    publications {
        jar(MavenPublication) {
            groupId "com.zmarkan.echoer"
            version = "1.0.0"
            artifactId "echoer"
            artifact("$buildDir/libs/echoer.jar")
        }
    }
}

artifactory {
    contextUrl = 'http://artifactorysample-env.elasticbeanstalk.com'
    publish {
        repository {
            // The Artifactory repository key to publish to
            repoKey = 'libs-release-local'

            username = "admin"
            password = "password"
        }
        defaults {
            // Tell the Artifactory Plugin which artifacts should be published to Artifactory.
            publications('jar')
            publishArtifacts = true

            // Properties to be attached to the published artifacts.
//            properties = ['qa.level': 'basic', 'dev.team': 'core']
            // Publish generated POM files to Artifactory (true by default)
            publishPom = true
        }
    }
}

Also note, if you’re building Android specific libraries, you’ll need to point to that artifact, in AAR format.

Now let’s upload something, by running artifactoryPublish after assembling our jar:

./gradlew clean echoer:assemble artifactoryPublish

And if we look at Artifactory, we’ll see that it’s been uploaded successfully

artifactory example

Use the library

Now that we’ve done the heavy lifting, the next thing to do is how to use the library. By default, our project’s build.gradle goes to jCenter to look for dependencies. This is defined here:

allprojects {
    repositories {
        jcenter()
    }
}

We can now add under jcenter() the address to our private Artifactory installation:

maven { url "http://artifactorysample-env.elasticbeanstalk.com/libs-release-local/" }

Alternatively, we can also replace the jcenter() line with our artifactory’s ‘meta-directory’ - one that will combine local and jcenter() libs by itself, by omitting the ‘local’ part of the URL:

allprojects {
    repositories {
        maven { url "http://artifactorysample-env.elasticbeanstalk.com/libs-release/"   
        }    
    }
}

Now, in our app/build.gradle we can change the dependencies field to use the Echoer from our Artifactory repository:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.1.1'
    compile 'com.zmarkan.echoer:echoer:1.0.0'
}

Try running it now.

Making it private

There is one major problem with the above example. Although the it is not advertised anywhere, anyone can still access it, and all your libraries, if they know the URL.

Simplest way to prevent that is to protect against unauthorised read access using a password.

To do that, we need to login to Artifactory, and enter the Admin panel.

  • Under Security/General we untick the box saying ‘Allow anonymous access’
  • Under Security/Users we add a new user, and make sure they are in the ‘readers’ group.
  • I created the user named ‘consumer’ with the password ‘icanread’

Now we can provide username and password when configuring our maven repo:

maven {
            url "http://artifactorysample-env.elasticbeanstalk.com/libs-release-local/"
            credentials {
                username 'consumer'
                password 'icanread'
            }
        }
}

There are better ways to protect from unauthorised access, including encrypting passwords and such, you can read more about that in the excellent article by Jeroen Mols.

Versioning, etc…

Now that we’ve got our library uploaded, accessible, and running we probably want to update it. This can either be done to fix a bug, add a feature, or release a completely new version - sometimes with breaking changes to the API.

If that is unavoidable, I highly recommend reading and following Jake Wharton’s article - Java Interoperability Policy for Major Version Updates.

Fin

And that’s it! You should have a working knowledge of Artifactory and deploying libraries to private repositories.

Happy coding, may the Force be with you, and may your builds be passing!