Painless Android releases

Android apps require not one, but two version numbers:

  • Version code: an integer that Android uses to check whether one version is more recent than another
  • Version name: a friendly version to display to the user, conventionally something like 1.2.3

This means that when you want to build a new release of your app, you have two things to manually update, and that is two things too many. You will make mistakes.

Luckily, it’s not too hard to automate this away in your Gradle build script.

Gradle inherited much of its design from Apache Maven. Maven defined a standard release feature that automatically handles typical pitfalls and mindless details of making a release: tagging in source control and incrementing your version number. For Gradle, there is a nice third-party implementation, the gradle-release plugin. So long as you don’t fight Maven-style version conventions, it can make cutting releases almost entirely automatic, modulo prompting you to confirm that it guessed correct version numbers.

If your project only has one version number, you just apply the release plugin and you’re done, but Android’s two-version-number system takes some customization.

I only discuss version numbers here, but the release plugin also does several other useful sanity checks.

First, move the versions out of your app/build.gradle into app/gradle.properties. They should look like so:

app/gradle.properties

version=1.0-SNAPSHOT
versionCode=1

app/build.gradle

android {
    // ...
    defaultConfig {
        versionCode project.versionCode.toInteger()
        versionName project.version
        // ...

“SNAPSHOT” is Maven’s convention for “between releases”. Version 1.0-SNAPSHOT means the code leading up to version 1.0. This convention is how the release plugin guesses what version number you are releasing: it just lops off the suffix.

When you run ./gradlew release, the release plugin updates the version thus:

  1. Edits gradle.properties, removing the “snapshot” part
    1.0-SNAPSHOT becomes 1.0
  2. Commits the change and tags this as version 1.0 in source control
  3. Builds the release
  4. Edits gradle.properties again, to next dev version
    1.0 becomes 1.1-SNAPSHOT
  5. Commits so you can immediately start working on version 1.1

Thus, out of this box, this handles the user-friendly version number, but not the “version code.”

Updating the version code

When Android installs an update to an app, it knows by version code whether the update is newer than what it currently has installed. 3 is newer than 2 and so on.

Thus, the obvious strategy for updating your version code is to add one on every release. If using the release plugin, you might do this as a manual step after it finishes a release. If you forget, you’ll accidentally build your next release with the same version code as you just used. If you have other branches, you need to remember to update them as well. Ouch.

There is a better way. Version codes need not be sequential, so instead of incrementing 1,2,3…, we can derive it from the date. A format like [2-digit year][month][day][0-9] works nicely. A release today gets version code 1704080, tomorrow, 1704090.

This format will cover you for 82 years at up to ten releases a day. If that’s not enough for you, use a four-digit year and a two-digit suffix, but watch out for integer overflow in 130 years or so.

The date-based strategy, however, means that you have to set your “version code” immediately before you release, instead of after. To do this, add a Gradle task right before updating version name.

app/build.gradle

task setVersionCode { doLast {
    // Add a task that updates version code
    def current = project.versionCode.toInteger()
    def releaseAs = new Date().format('YYMMdd0', TimeZone.getTimeZone('UTC'))
    if (releaseAs.toInteger() <= current) {
        // More than one release today
        releaseAs = current + 1
    }
    def releaser = project.plugins[net.researchgate.release.ReleasePlugin]
    def propsFile = releaser.findPropertiesFile()
    def props = new Properties()
    propsFile.withInputStream { props.load(it) }
    props.versionCode = releaseAs.toString()
    propsFile.withOutputStream { props.store(it, null) }
}}
// Execute our task before unSnapshotVersion, provided by the release plugin:
unSnapshotVersion.dependsOn setVersionCode

With this simple build script change (plus applying the release plugin), a single command updates both version numbers:

./gradlew release

The release plugin also runs the “build” task at the point of release, so this single command leaves you with both a release .apk and your working directory updated to the tip (snapshot) code ready to start work on the next release. There’s still a problem though: if you haven’t configured your build script to sign the build, you won’t be able to publish the release .apk.

Signing the build

To make Gradle sign a build, you need to add a “signingConfig”:

android {
    // ...
    signingConfigs {
        release {
            storeFile file('/home/myname/.javakeys/mykeys.jks')
            keyAlias 'myappsigningkey'
            // These two lines make gradle believe that the signingConfigs
            // section is complete. Without them, tasks like installRelease
            // will not be available! (see http://stackoverflow.com/a/19350401)
            storePassword "notYourRealPassword"
            keyPassword "notYourRealPassword"
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            // ...

This fails, so you put your real password in the “password” config place and get pwned. Your wife leaves you, and your dog dies. You didn’t that, right?

So where should you put your password? The top-voted answer on Stack Overflow says ~/.gradle/gradle.properties, presumably protected by 600 permissions. I don’t see the point. If you’re relying on file system permissions to keep the password secure, why have the password at all? You could just protect the keystore with file system permissions.

What you need is a prompt for the password.

Thanks to bug 1251, Gradle running in daemon mode (the default) doesn’t let you use System.console().readPassword("Password:"). You can disable daemon mode, but then you run afoul of (orphaned?) bug 2357 because Android Studio generates a default gradle.properties that includes jvmargs. Once you remove that configuration, you find that prompts don’t display when you build not in daemon mode (bug 869). That’s a pain because you can’t see the version number confirmation prompts.

As a result of this epic adventure, you’ll eventually find that the only reliable way to prompt for password is via Swing. No, I’m not joking. It’s not as gruesome as it sounds, thanks to Groovy’s Swing builder, so pop over to where Tim Roes documented how to do it.

Update: there’s a new version of this build script

Percieved Inhumanity

Butthurt (adjective): annoyed, bothered or bugged because of a perceived insult

In Change Your Organization: A Diary, James Shore writes an intriguing account of how he worked as a peon to change his organization from a position of little formal authority. The diary describes an experience that will seem familiar to many programmers and his commentary is often right and valuable. I wish he advised, however, on how not to be so butthurt.

The PM went back to his cube (on the other side of the building). A few minutes later, I got a call:

PM: Those two new issues are absolutely critical. They have to go in to the next drop.
Me: I only have time to do one before I leave. Which one’s most important?
PM: Both are critical. They won’t accept the build if both aren’t in. They have to be in.
Me: Well, I don’t see how that’s possible. I’ve estimated them at two hours each, and I don’t have that much time.
PM: Can you come in on Friday? (Friday is traditionally a holiday.)
Me: No, I’ll be out of town.
PM: Okay, well, they have to be in. Can’t you just do them tonight?
Me: I have to meet someone at the airport. The latest I can leave is 6:30, which doesn’t give me enough time to do both.
PM: It’s critical that these two things be in the drop. Can’t you just come in on Friday?
Me: No, I’ll be out of town.
PM: Who else is qualified to do work on these issues?
Me: Joe or John.
PM: John’s wife just had a baby, and Joe’s going to be at the beach on Friday. If you can’t do these tonight, I’ll have to call John or Joe.
Me: My estimate for these two tasks is four hours. I’ve estimated them in the best way I know how. I don’t see how I can get them done tonight.
PM: Can you come in for just a few hours Friday morning?
Me: I’ll be out of town.
PM: Where?
Me: (My home town, two hours away.)


I have some compassion for what our PM was going through. Remember, it was 2002. Jobs were hard to find. The PM was scared.

That still doesn’t excuse [pressuring us to work extra hours]. It didn’t do any good–in fact, I’m sure it hurt performance–and, in my opinion, it was inhumane and unethical.

Shore never quite goes far enough to invoke Godwin’s Law, but “inhumane?” As in “cruel and savage?”  Genocide is inhumane. A manager’s request for overtime hardly qualifies.

Murdered Emaciated ChildrenLumbergh from Office Space

Shore highlights the importance of respect for employees. Were he the manager, he says, he would “be absolutely professional and respectful … avoid placing blame, and focus on their well-being.” Though he does acknowledge this would not have saved the project, the analysis implies that respect is a necessary condition for success.

I think that projects will rarely succeed when executed by an organization lacking internal respect. That common sense seems logical, since resultant infighting and lack of cooperation would tend to decrease efficiency, but the intuition still begs for questioning.

About two minutes into one presentation, Gates had stood up, looked around the room, scowled at the newly arrived product manager, and said, “Where the fuck did we hire you from?” The manager left the meeting in tears and within a week had left the company.

Before arguing that Bill Gates cheated or bargained with Satan remember Linus called Subversion “the most pointless project ever started” (video transcript). Neither man, though successful, could reasonably be described as respectful in these instances. Personal experience, however, still does not allow me to believe that an inherently disrespectful culture can also be efficient and successful.

To resolve the contradiction, it is worth defining respect in an organizational setting. Shore advocates unconditional respect. “Don’t think of others as incompetent,” he says, “Look at problems and think, ‘There must be a reason things are the way they are.'” While sound, this denies the reality that incompetence often really did cause the problems. Second, it appeals only to the degenerate form of respect that implies only tolerance, as in “I disagree but respect your right to use Perl.”

“Civility” would be more accurate. Calling that respect risks a dangerous myopia that sees esteem as a goal unto itself. Explicit encouragement of unconditional respect focuses the group on an unproductive and impossible goal, distracting from productive work.

Simple distraction is not, however, the most harmful consequence of focus on respect for its own sake. True respect, respect that means admiration as well as tolerance cannot be unconditional. Respect must be earned. To advocate freely given respect undermines its meaning and its power as an intrinsic motivator.

Successful managers do respect their employees and foster respect among their employees. This works not because it is moral or ethical but because pursuit of respect motivates. An effective manager indicates a clear path to earning respect and makes that path align with the organization’s goals. Bill Gates knew how; Shore’s project manager did not.

Clocks in SOX

Most people never read Sarbanes Oxley, section 404, but plenty use it as an excuse for convoluted processes, mostly involving peculiar Chinese walls.

Something like “common sense,” at some companies, for example, says that those who deploy home-grown software must be different people from those who write it. The developers get annoyed because they have to explain to some moron in “change management” obvious things like that the Spring property configurator just needs the new name for the file where you put the password which is clearly different for the key store you need to get from the security people who must know which host you want it to deploy on. Programmers can spend endless days complaining about how those idiots could not figure this all out, because they definitely sent emails explaining that you need to configure acegi-context.xml.

In that respect, at least, the “segregation of duties” becomes helpful by encouraging developers to simplify the configuration their applications require.

It does not, however, encourage any extra rigor with regards to application quality. In fact, programmers become even more reluctant to fix their mistakes because they had such a difficult time getting it deployed last time.

Just to make a dysfunctional system that little bit funnier, some people have created a process involving Rational ClearQuest. In this process, the developer creates a “deployment ticket.” The ticket specifies a human being who should perform the deployment, a time window, instructions, and some other information no one reads. A potential deployer then receives the ticket and prioritizes it among the other incoming tickets. In the fashion of true technological progress, the two parties never need to communicate except through the ticket.

This is, of course, a recipe for inaccurate execution, if not total disaster. The deployer has no control over what time the scheduler requests the ticket be executed. The scheduler has no access to the deployer’s calendar or any idea of what other schedulers might simultaneously schedule that same deployer for. The schedulers know the deployment only takes a few minutes, but they build in a half day window for potential backlogs in the deployer’s queue. The deployer sees an entire half day for completion, so feels no particular urgency.

Meanwhile, the testers wait for deployment to complete, and you have transmogrified five man-minutes of work into four to six wasted hours for several people.