A Very Simple Android Artifactory Deployment

Ivan Melnikov
The Startup
Published in
9 min readOct 8, 2020

Hey! Although I used code screenshots from this service right here, I’ve also included a helpful gist here of the final code created throughout this article for that quick copy-paste goodness we’ve all grown to love.

Artifactory, and my life the last 2 days

Abstract

We’ve slowly been moving over from Jitpack to Artifactory at work — it’s a more robust, polished solution for corporate environments. As a result, we’ve been changing the way our build process works which has not been without its challenges. This article is intended to be a quick guide for getting you up and running with Artifactory. However, if you’re only here because your code doesn’t work, scroll down to the end — maybe that’s the issue?

Introduction

If you’re working in the software engineering field, then you’re probably building something. The final output of this something may be a binary file. For example, an Android application is packaged into an APK file. Java applications are packaged into JAR files. Similarly, Android libraries are packaged into AAR files. One can also use JAR files for the last one, however, the convention is to use AAR files since these can include layout files, as well as other resources for consuming Android applications to use.

At some point, you’d probably want to deploy these outputs (artifacts) somewhere, and have that “somewhere” store all of this for you for further consumption. In the JavaScript ecosystem, this is NPM. For Python packages, this is PyPI. Artifactory is similar— it’s basically your own version of these repository services, to which you may upload various artifacts. Do these have to be binary? Of course not — right alongside the APK, AAR, JAR files you can also have JSON, XML, Docker images etc.

Artifactory is considered to be the best (and, erhm, perhaps only) solution for corporate package hosting. It’s been around for a while, and the fact that it follows Apache Maven specifications (as well as others, but let’s forget about those for now) makes it easy to build your project, deploy it, and then consume somewhere else as a dependency.

Example dependency inclusion in Gradle

Let’s go over some very basic terms to get everybody on the same page. Right above you can see the dependencies block for a typical Gradle build file. The implementation directive indicates that we are going to need to download whatever (Maven) package is in quotation marks into our project. The syntax of that package is split into 3 parts: the group, the name, and version. All Maven packages follow this convention. Some other examples are androidx.test.espresso:espresso-core:3.3.0 or org.jetbrains.kotlin:kotlin-stdlib:1.4.10.

These terms translate very nicely into the Artifactory UI, as well as the configuration options you specify, so that the build script knows where to push your artifacts to. As a result, this also determines how consumers need to import your projects into their own (which string they will use after their own implementation call to download your project).

The name of your artifact becomes the artifactId, the group is the groupId, the version is still…the version. We need to introduce two more things, however: the context URL, and the repoKey. The context URL is the URL of your Artifactory deployment, and the repoKey is the overarching parent for all of your groups. For example, if you have libraryA, libraryB, libraryC, and all of them deal with some kind of network connectivity, you may publish all of those under the network repoKey. Visually, this is what this looks like in the Artifactory web UI:

Network repository key, with myGroupId being the group ID, and multiple libraries under it

Another way of thinking about this is if your whole organization is running on one Artifactory instance (most likely it is, the service costs lots of moolah), you firmware team may use one repository key, cloud team another, mobile apps team another.

This is all you really need to know/understand about how Artifactory works (and how it handles artifact lookup) to start integrating it into your application. Let’s do that now.

Project setup

The setup we’re going to be working with will be very simple — a regular Android Studio project with an app module. If you simply click through all of the default settings of a new AS project (no activity), you will be presented with the following project structure:

Sample project we’ll be using in this article

Let’s go into the root project build.gradle file. It’s very plain, nothing to write home about:

Many Gradle. Such Android. Very wow. Amaze.

Let’s start off by adding the necessary dependencies for Artifactory to work, as well as apply the necessary plugins:

Adding the build extractor dependency, as well as two plugins

The first dependency added, build-info-extractor-gradle, is the meat of the Artifactory setup. It allows us to then apply the com.jfrog.artifactory plugin to all of our subprojects. This, effectively, integrates Artifactory into our project. It doesn’t do anything (short of create some tasks in the Gradle task graph), but it’s there. Coincidentally, this is exactly how I am at work.

The Artifactory plugin works in conjunction with the maven-publish plugin, since Artifactory looks at the Maven publication configurations (something we will define later on) to figure out what it needs to upload.

The next step is where things get more interesting. We define the artifactory block in order to tell the plugin where to publish the artifacts to.

Adding the artifactory block for publishing

Here is where some of the terms we mentioned in the introduction come up. The context URL is the repository URL. Under the repository block you will specify the authentication information required for artifact upload. You will also see the repoKey parameter — as discussed, this is the top-level grouping value that Artifactory will use (Network in the introductory example). One thing to note here is that the password is really the API key. Generate one in the web UI.

The final step is presented below — specify which subproject(s) Gradle should add the publishing, and artifactoryPublish blocks to:

The final piece of the configuration puzzle

Let’s go through this line by line to make sure we’re all on the same page. Now, because this is in the root project build file, we need to wrap our instructions in the project(“app”) closure. If you have other modules you want to use, copy the whole block, replace “app” with something else, and adjust the configurations accordingly. Better yet, put everything in the closure into the appropriate build file, but I digress.

We tell Gradle to add everything from the closure to the subproject. By specifying dependsOn, we’re able to tell Artifactory “Hey, before you publish, make sure you assemble everything first”. This is not necessarily required, but will come in handy during the final command invocation, I explain this later on.

Next, we create the MavenPublication (which comes from the maven-publish plugin), and we call it apk.

Inside the configuration, we need to specify several things: the artifactId, which is the name that Artifactory should use to index your artifact by; the groupId to which the artifact will belong to, the version, and finally the artifact itself. We’re publishing an APK, so I just hardcoded the release APK path (unsigned, so this will break if I were to use a keystore in this example).

Finally, we tell Artifactory which publication to publish — simply by specifying the name.

That’s it! We’re all done. If you want to publish your project to your Artifactory instance, run ./gradlew artifactoryPublish.

Now, if you didn’t specify the dependsOn directive, then you will need to either build or assemble your project first, and then publish by running ./gradlew assemble artifactoryPublish, for example. That directive makes things easier since the Artifactory plugin will perform the step for you before publishing the artifacts.

An easy way to check if your deployment is working is by looking for a similar line in your console output:

[pool-55-thread-1] Deploying artifact: https://your_artifactory_url/your_artifactory_repo_key/artifactory_groupId/artifactory_artifactId/v1.0.0/artifactory_artifactId-v1.0.0.apk

Publications and build types

By now you may be asking “Well that’s great and all, but what about publishing a library in the form of an AAR? What about build types?”. Well it’s actually rather straightforward! Assuming you have a module called library, which, coincidentally, is a library, you may simply create an AAR publication like so:

Creating an AAR publication for a library module

Key changes to note here would be project name at the top (since we’re going to want to publish the library module, and not app), as well as the change in the publication name — it’s now called aar, and so we need to pass that into Artifactory accordingly.

Building upon this example, what if we want to publish a debug, as well as a release build type? Well, the annoying thing is that you’ll have to publish to two separate repositories (Jitpack, for example, groups all build types, flavors under one artifactId). See edit 1. However, this isn’t that hard to configure:

Handling different build types

We need to iterate over android.buildTypes.all which will create two different publications (although you can call them whatever you’d like) debugAar, and releaseAar. As mentioned above, because we need to publish to two different repositories, the artifactIds have the variant name as a suffix. This will create two artifact identifiers on Artifactory: artifactory_artifactId-debug, and artifactory_artifactId-release.

Finally, you need to adjust the publications block at the end to tell Artifactory which one (or, well, both) to deploy and it’s off to the races. Happy deployments!

The conundrum

Let’s take a look at our Artifactory configuration from work:

Artifactory configuration at work

Does anybody see any issues? Well, apparently, loadArtifactoryProperty will not work in the repository block. I’m still not sure why, but best guess is that the nested levels of closure throw Gradle off when it tries to invoke the function. After 1–2 days of debugging, we realized the key was not being set until the call was changed to this.loadArtifactoryProperty(“artifactory_repoKey”). Everything else in the file (for example, the contextUrl parameter) worked just fine.

If you’re using the build-info-extractor-gradle dependency before version 4.17.2, the deployment will silently fail (only the build information will be uploaded in JSON format). However, with the newest version, you will get a NullPointerException with an empty message. You know, the best kind of helpful message. Check your parameters, something may be empty/null!

Anyways, that’s a fun tidbit for you. If you’re here, thanks for getting this far. Here’s a cookie. 🍪

Edit 1

Small edit to make regarding publishing multiple build types. Although publishing under separate artifact identifiers is alright, you may need to tweak Gradle in consuming applications to explicitly tell it what to pull for debug/release build types. Publishing under separate identifiers also breaks the Maven repository layout specification here. The structured is as follows:

/$groupId[n]/$artifactId/$version/$artifactId-$version-$classifier.$extension

Visually, this is represented in the image below:

How all the concepts come together in the Artifactory web UI

In order for our example to follow this behavior, we have to change two things: keep the artifactId constant across all publications, and manually specify the classifier (with the extension):

Publications following the Maven specification

--

--

Ivan Melnikov
The Startup

Android Software Engineer @ Google. CS and Nuclear Engineering by education, one is just…way less explosive.