The Kobalt Diaries: Incremental Tasks

One of the recent additions to Kobalt is incremental tasks. This is the ability for each build task to be able to check whether it should run or not based on whether something has changed compared to the previous run. Here are a few quick outlines of how this feature works in Kobalt.

Overview

Kobalt’s incremental task architecture is based on checksums. You implement an incremental task by giving Kobalt a way to compute an input checksum and an output checksum. When the time comes to run your task, Kobalt will ask for your input checksum and it will compare it to that of the previous run. If they are different, your task is invoked. If they are identical, Kobalt then compares the two output checksums. Again, if they are different, your task is run, otherwise it’s skipped. Finally, Kobalt updates the output checksum on successul completion of your task.

This mechanism is extremely general and straightforward to implement for plug-in developers, who remain in full control of how exhaustive their checksum should be. You could decide to stick to the default MD5 checksums of the files and directories that are of interest to your task, or if you want to be faster, only check the timestamps of your file and return a checksum reflecting whether Kobalt should run you or not. And of course, checksums don’t even have to map to files at all: if your task needs to perform a costly download, it could first check a few HTTP headers and again, return a checksum indicating whether your task should run.

Having said that, build systems tend to run tasks that have files for inputs and outputs, so it seems logical to think about an incremental resolution that would be based not on checksums (which can be expensive to compute) but on file analyses. While a checksum can tell you “One of these N files has been modified”, it can’t tell you exactly which one, and such information can open the door to further incremental work (see below for more details).

One approach for file-based tasks could be for the build system to store the list of files along with some other data (timestamp or checksum) and then pass the relevant information to the task itself. The complication here is that file change resolution implies knowing the following three pieces of information:

  • Which files were modified.
  • Which files were added.
  • Which files were removed.

The downside is obviously that there is more bookkeeping required to preserve this information around between builds but the clear benefit is that if a task ends up being invoked, it can perform its own incremental work on just the files that need to be processed, whereas the checksum approach forces the task to perform its work on the entire set of inputs.

Implementation

Incremental tasks are not very different from regular tasks. An incremental task returns an IncrementalTaskInfo instance which is defined as follows:

class IncrementalTaskInfo(
	val inputChecksum: String?,
    val outputChecksum: () -> String?,
    val task: (Project) -> TaskResult)

The last parameter is the task itself and the first two are the input and output checksums of your task. Your task simply uses the @IncrementalTask annotation instead of the regular @Task and it needs to return an instance of that class:

@IncrementalTask(name = "compile", description = "Compile the source files")
fun taskCompile(project: Project) = IncrementalTaskInfo(/* ... */)

Most of Kobalt’s own tasks are now incremental (wherever that makes sense) including the Android plug-in. Here are a few timings showing incremental builds in action:

Kobalt

Task First run Second run
kobalt-wrapper:compile 627 ms 22 ms
kobalt-wrapper:assemble 9 ms 9 ms
kobalt-plugin-api:compile 10983 ms 54 ms
kobalt-plugin-api:assemble 1763 ms 154 ms
kobalt:compile 11758 ms 11 ms
kobalt:assemble 42333 ms 2130 ms
70 seconds 2 seconds

Android (u2020)

Task First run Second run
u2020:generateRInternalDebug 32350 ms 1652 ms
u2020:compileInternalDebug 3629 ms 24 ms
u2020:retrolambdaInternalDebug 668 ms 473 ms
u2020:generateDexInternalDebug 6130 ms 55 ms
u2020:signApkInternalDebug 449 ms 404 ms
u2020:assembleInternalDebug 0 ms 0 ms
43 seconds 2 seconds

Wrapping up

At the moment, Kobalt only supports checksum-based incremental tasks since that approach subsumes all the other approaches but I’m not ruling out adding input-specific incremental tasks in the future if there’s interest. In the meantime, checksums are working very well and pretty efficiently, even on large directories and/or large files.

If you are curious to try it yourself, please download Kobalt and report back!

A close look at Kotlin’s “let”

let is a pretty useful function from the Kotlin standard library defined as follows:

fun <T, R> T.let(f: (T) -> R): R = f(this)

You can refer to a previous article I wrote if you want to understand how this function works, but in this post, I’d like to take a look at the pros and cons of using let.

let is basically a scoping function that lets you declare a variable for a given scope:

File("a.txt").let {
    // the file is now in the variable "it"
}

There is another subtle use of let when applied to a nullable reference. The ?. operator
lets you make sure that the code in scope is only run if the expression is not null:

findUser(id)?.let {
    // only run if findUser() returned a non null value
}

After going back and forth about whether this idiom is superior to a simple null test, I am slowly leaning to abandoning it in favor of an if for the following reasons:

  • This idiom is only useful if you want to do an if that doesn’t have an else branch. I tend to view such constructs as suspicious since if without an else can be a source of bugs.

  • This idiom introduces a renaming. Either you use the default lambda syntax, in which case the renamed variable is implicitly called it, or you explicitly name the argument:

    val user = findUser(id)
    user?.let { foundUser ->
        // ...
    }
    

    This can occasionally be useful but sometimes, I just don’t feel like being forced to rename my variable.


  • Following the previous point, if doesn’t impose a renaming but Kotlin’s smart casting guarantees that you won’t have any surprise:

    val user = findUser(id)
    if (user != null) {
        // user is now a non null reference
    }
    

    Also, the fact that no new name was introduced and the variable keeps its name user the entire time improves readability in my opinion.

So for these reasons, I tend to default to a good old if these days. None of these arguments are deal breakers, it’s mostly a stylistic preference at this point. Let’s see if I’ll change my mind over the next few months.

The Kobalt diaries: profiles

When I started thinking about how profiles should work in Kobalt, I realized that the simplest approach I’d like to see in a build tool is defining a boolean variable and having if statements in my build file. So that’s exactly how Kobalt’s profiles are implemented.

You start by defining boolean values initialized to false in your build file:

  val experimental = false
  val premium = false

Then you use this variable wherever you need it in your build file:

  val p = javaProject {
      name = if (experimental) "project-exp" else "project"
      version = "1.3"
      ...

Finally, you invoke ./kobaltw with the --profiles parameter followed by the profiles you want to activate, separated by a comma:

  ./kobaltw -profiles experimental,premium assemble

Keep in mind that since your build file is a real Kotlin source file,
you can use these profile variables pretty much anywhere, e.g.:

dependencies {
    if (experimental)
        "com.squareup.okhttp:okhttp:2.4.0"
    else
        "com.squareup.okhttp:okhttp:2.5.0",

And that’s it.

The Kobalt diaries: it’s the little things

When I embarked on the ridiculously ambitious goal of writing a build tool, I had plans to tackle both big problems and small problems. My previous (and probably future) blog post cover the big problems such as performance, plug-in architecture and DSL syntax, but in this post, I’m going to cover a few little things that I was quite happy to finally be able to get from my build tool.

I’ve always found it a hassle to keep up with the latest versions of the dependencies of my build, especially since it’s its job to tell me. Therefore, Kobalt has a handy --checkVersions parameter that will check to see if it can find any new version of your dependencies:

$ ./kobaltw --checkVersions
New versions found:
        org.jetbrains.kotlin:kotlin-compiler-embeddable:1.0.0-beta-2423
        org.jetbrains.kotlin:kotlin-stdlib:1.0.0-beta-2423
        com.squareup.okhttp:okhttp:2.6.0
        com.squareup.retrofit:retrofit:2.0.0-beta2

Another convenient switch is --resolve, which looks up a dependency and gives you some information about it, such as which Maven repo it is found in and its own dependency tree. You can also use an id without a version (e.g. org.testng:testng:) to ask Kobalt to find the most recent version of that artifact:

$ ./kobaltw --resolve org.testng:testng:
╔══════════════════════════════════════════════════════════════════════════════════╗
║                                     org.testng:testng:                           ║
║           http://repo1.maven.org/maven2/org/testng/testng/6.9.9/testng-6.9.9.jar ║
╚══════════════════════════════════════════════════════════════════════════════════╝
╟ junit:junit:4.10
║      ╙ org.hamcrest:hamcrest-core:1.1
╟ com.beust:jcommander:1.48
╟ org.apache.ant:ant:1.7.0
║      ╙ org.apache.ant:ant-launcher:1.7.0
╟ org.yaml:snakeyaml:1.15
╙ org.beanshell:bsh:2.0b4

Finally, I’ve always been bugged by what I consider a glaring omission of the Gradle Android plug-in: not being able to run my applications. The plug-in generates tasks for the various variants of your application (assembleDevDebug, assembleDevRelease, installDevDebug, etc…) but strikingly, no "run" task. I’m happy to report that Kobalt’s Android plug-in supports exactly that. To see it in action, clone the Kobalt example and follow the instructions at the bottom of the README:

$ ./kobaltw runFreeDebug // build, install and launch that variant
$ ./kobaltw runFreeRelease // build, install and launch that variant

I’ve made a lot of improvements to the Android plug-in lately, but that will be the topic for another post.

The Kobalt diaries: annotation processing

I recently added apt support to Kobalt, which is a requirement for a build system these days, and something interesting happened.

First of all, the feature itself in Kobalt: pretty straightforward. The apt plug-in adds a new dependency directive similar to compile:

dependencies {
    apt("com.google.dagger:dagger:2.0.2")
}

The processing tool can be further configured (output directory, arguments, etc…) with a separate apt directive:

apt {
    outputDir = "generated/sources/apt"
}

In order to test this new feature, I decided to implement a simple annotation processor project and I went for a Version class generator. As I wrote this processor, I realized that it was actually something I could definitely use in my other projects.

Of course, you can always simply hard code the version number of your application in a source file but that version number is typically something that’s useful outside of your code: you might need it in your build file, or when you generate your artifacts, or maybe other projects need to refer to it. Therefore, it often makes sense to isolate that version number in a property file and have every entity that needs it read it from that property file.

This is how version-processor was born. It’s pretty simple really: all you need to do is annotate one of your classes with @Version and a GeneratedVersion.java file is created, which you can then refer to. That version number can either be hardcoded or specified in a properties file. Head over to the project’s main page for the details.

And of course, it’s built with Kobalt and if you are curious, here is the processor’s build file:

val processor = javaProject {
    name = "version-processor"
    group = "com.beust"
    artifactId = name
    version = "0.2"
    directory = "processor"

    assemble {
        mavenJars {}
    }

    jcenter {
        publish = true
    }
}

Happy version generating!

TensorFlow’s rough exterior

Like many others, I have paid very close attention to Google’s TensorFlow announcement and I’m planning to invest a decent amount of time to dive into it and understand it but watching Jeff Dean’s video about it, I couldn’t help but take notice of one of the code samples that he shows:

graph = tf.Graph()
with graph.AsDefault():
  examples = tf.constant(train_dataset)
  labels = tf.constants(train_labels)
  W = tf.Variables(tf.truncated_normal(rows*cols, num_labels]))
  b = tf.Variables(tf.zeros([num_labels]))

  logits = tf.mat_mul(examples, W) + b
  loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits, labels))

What a mess…

I realize this is just one of the two front ends (Python, the other being in C++) but the syntactic conventions of the snippet above are all over the map.

I see capitalized functions (Graph()) when most of the functions are lowercased. Capital variables (W) and lowercase ones (b), both of which the result of the same function. Functions using underscores and others using capitalized camel case. There just doesn’t seem to be any rhyme nor reason to the conventions.

The only style that’s not represented in this short snippet is straight camel case.

This hurts my eyes. Hopefully, spending some time with this fascinating tool will demystify it somewhat. Or maybe it will motivate me to write a front end I feel more comfortable with, say in Kotlin.

The Kobalt diaries: Android

A lot of work has gone into Kobalt since I announced its alpha:

I’m plannning to post more detailed updates as things progress but today, I’d like to briefly show a major milestone: the first Android APK generated by Kobalt.

I picked the Code path intro app as a test application. I first built it with Gradle to get a feel for it and the apk was generated in about 27 seconds. Then I generated a Build.kt file with ./kobaltw --init, added a few Android related directives to reach the following (complete) build file:

import com.beust.kobalt.*
import com.beust.kobalt.plugin.android.*
import com.beust.kobalt.plugin.java.*

val p = javaProject {
    name = "intro_android_demo"
    group = "com.example"
    artifactId = name
    version = "0.1"

    dependencies {
        compile("com.android.support:support-v4:aar:23.1",
                file("app/libs/android-async-http-1.4.3".jar))
    }

    sourceDirectories {
        listOf(path("app/src/main/java"))
    }

    android {
        applicationId = name
        buildToolsVersion = "21.1.2"
    }
}

Then I launched the build with ./kobaltw assemble, and…

Less than five seconds to generate R.java, compile it, compile the code, run aapt, generate classes.dex and finally, generated the apk. If you are curious, you can check out the full log.

Admittedly, Kobalt doesn’t yet handle build types and flavors nor manifest merging, but the example app I’m building here doesn’t use those either so I don’t expect the build time to increase much. There is a lot more to be done before Kobalt’s Android plug-in is ready for more users, but this is a pretty encouraging result.

Exploring the Kotlin standard library (part 2)

I folded the two parts of this series into one blog post, which you can read here.

Exploring the Kotlin standard library

Standard.kt is part of the Kotlin library and it defines some essential functions. What’s really striking about this source file is that it’s less than fifty lines long and that each of the function it defines (less than ten) is a one liner. Yet, each of these functions is very powerful. Here is a quick overview of the most important ones.

let()

fun <T, R> T.let(f: (T) -> R): R = f(this)

let() is a scoping function: use it whenever you want to define a variable for a specific scope of your code but not beyond. It’s extremely useful to keep your code nicely self-contained so that you don’t have variables “leaking out”: being accessible past the point where they should be.

DbConnection.getConnection().let { connection ->
}
// connection is no longer visible here

let() can also be used as an alternative to testing against null:

val map : Map<String, Config> = ...
val config = map[key]
// config is a "Config?"
config?.let {
    // This whole block will not be executed if "config" is null.
    // Additionally, "it" has now been cast to a "Config" (no question mark)
}

apply()

fun <T> T.apply(f: T.() -> Unit): T { f(); return this }

apply() defines an extension function on all types. When you invoke it, it calls the closure passed in parameter and then returns the receiver object that closure ran on. Sounds complicated? It’s actually very simple and extremely useful. Here is an example:

File(dir).apply { mkdirs() }

This snippet turns a String into a File object, calls mkdirs() on it and then returns the file. The equivalent Java code is a bit verbose:

File makeDir(String path) {
  File result = new File(path);
  result.mkdirs();
  return result;
}

apply() turns this kind of ubiquitous code into a one liner.

with()

fun <T, R> with(receiver: T, f: T.() -> R): R = receiver.f()

with() is convenient when you find yourself having to call multiple different methods on the same object. Instead of repeating the variable containing this object on each line, you can instead “factor it out” with a with call:

val w = Window()
with(w) {
  setWidth(100)
  setHeight(200)
  setBackground(RED)
}

run()

fun <T, R> T.run(f: T.() -> R): R = f()

run() is another interesting one liner from the standard library. Its definition is so simple that it looks almost useless but it’s actually a combination of with() and let(), which reinforces what I was saying earlier about the fact that because all these functions from the standard library are regular functions, they can be easily combined to create more powerful expressions.

Tying it all together

Of course, it’s actually possible (and encouraged) to combine these functions:

fun configurationFor(id: String) = map[id]?.let { config ->
  config.apply {
    buildType = "DEBUG"
    version = "1.2"
  }
}

This code looks up a Config object from an id and if one is found, sets a few additional properties on it and then returns it. But we can simplify this code even further. This time, I’m providing a fully self-contained snippet so you can copy and paste it directly into Try Kotlin in order to run it yourself:

class Config(var buildType: String, var version: String)

val map = hashMapOf<String, Config>()

fun configurationFor(id: String) = map[id]?.let { config ->
    config.apply {
        buildType = "DEBUG"
        version = "1.2"
    }
}

Don’t you feel that this combination of let() and apply() feels a bit boilerplatey? Let’s rewrite it a bit more idiomatically:

fun configurationFor(id: String) = map[id]?.apply {
    buildType = "DEBUG"
    version = "1.2"
}

Let’s unpack this rather dense snippet:

  • Looking up a value on a hash map can be done either with get() or with the bracket notation, which is preferred.
  • Since the key might not be present in the map, we use the safe dereference operator ?. which guarantees that we will only enter apply() if the result is non null.
  • Inside the apply() block, the this object is a Config, which lets us invoke functions on this object without any prefix. In this case, all we have is properties, but obviously, you could invoke regular functions just as well.
  • Once that code has run, the altered Config is returned.

use()

fun <T : Closeable, R> T.use(block: (T) -> R): R

Another interesting function of the standard library is use(), which gives us the equivalent of Java’s try-with-resources and of C#’s using statement.

This function applies to all objects of type Closeable and it automatically closes its receiver on exit. Note that as opposed to Java and C#, Kotlin’s use() is a regular library function and not directly baked in the language with a special syntax. This is made possible by Kotlin’s extension functions and closure syntax used coinjointly.

// Java 1.7 and above
Properties prop = new Properties();
try (FileInputStream fis = new FileInputStream("config.properties")) {
    prop.load(fis);
}
// fis automatically closed
// Kotlin
val prop = Properties()
FileInputStream("config.properties").use {
    prop.load(it)
}
// FileInputStream automatically closed

Because Kotlin’s version is just a regular function, it’s actually much more composable than Java’s. For example, did you want to return this prop object after loading it?

// Kotlin
fun readProperties() = Properties().apply {
    FileInputStream("config.properties").use { fis ->
        load(fis)
    }
}

The apply() call tells us that the type of this expression is that of the object apply() is invoked on, which is Properties. Inside this block, this is now of type Properties, which allows us to call load() on it directly. In between, we create a FileInputStream that we use to populate this property object. And once we call use() on it, that FileInputStream will be automatically closed before this function returns, saving us from the ugly try/catch/finally combo that Java requires.

You will find a lot of these constructs in the Kobalt build tool code, feel free to browse it.

Google Fi unboxing



I received my order of Google Fi, the package contained more than I expected.



The business card size item at the bottom is the SD card. The rest is:

  • A portable charger.
  • A case for your Nexus 6.
  • A headset.



The charger has two USB ports and a micro one. Apparently, you can charge it from any of these ports (very convenient) and then you can plug two phones at the same time (probably three if you can find a dual micro-USB cable).



Finally, the headset has something that’s hard to find in headsets in general: volume control. It also has an extra jack, so you can plug another heaset in it. The only downside of this headset is that the control block dangles on your cheek instead of being located much lower on the cable. I don’t understand why such headsets are still manufactured.

I haven’t tested the service yet, I’ll report back after I’ve had a chance to use it thoroughly.