Kobalt

A build system.

Download »

Downloading and installing Kobalt

Download the zip file (bottom left of the screen) then unzip it in a location we'll call KOBALT_HOME:

cd $KOBALT_HOME
unzip kobalt-xxx.zip

Change to your project directory and call the kobaltw command with --init:

cd ~/java/project
$KOBALT_HOME/kobaltw --init

This command will do two things:

  1. Create a default Build.kt file in your current directory based on what was found there.
  2. Install the Kobalt Wrapper in your current directory (script `kobaltw`) and in the kobalt/ directory. From now on, you can just use ./kobaltw to build and you can ignore $KOBALT_HOME.

You can now attempt to build your project with Kobalt:

./kobaltw assemble
If your project follows a regular build structure (e.g. Maven's hierarchy), this should compile your file and create a .jar file. If not, you will have to make a few edits to your Build.kt. As of this writing, Kobalt supports Java and Kotlin projects.

Structure of a build file

General concepts

The build file is typically called Built.kt and it is a valid Kotlin file. Typically, it contains imports, the declaration of one or more projects and the declaration of additional configurations (e.g. packaging, publishing, etc...). Since it's a Kotlin file, it can also contain any class or function you need:

import com.beust.kobalt.*
import com.beust.kobalt.plugin.kotlin.kotlinProject

val kobalt = kotlinProject {
    name = "kobalt"
    group = "com.beust"
    artifactId = name
    version = "0.62"
    directory = homeDir("kotlin/kobalt")
}
Here are a few noteworthy details about this small build file:

Directives

Now that we have declared a project, we can use it to configure additional steps of our build, such as the packaging:

import com.beust.kobalt.plugin.packaging.packaging

// ...

val packKobalt = packaging(kobalt) {
    jar {
    }
}

This is the simplest jar declaration you can have. You can trigger the creation of this jar file by invoking the task "assemble". Note that we passed the kobalt variable to the packaging function, so we make it clear which project we are currently configuring for packaging. The jar directive accepts various settings, so let's be a bit more specific. And let's add a zip file too:

val packKobalt = packaging(kobalt) {
    jar {
        fatJar = true
        manifest {
            attributes("Main-Class", "com.beust.kobalt.KobaltPackage")
        }
    }
    zip {
        include("kobaltw")
        include(from("${kobalt.buildDirectory}/libs"),
                to("kobalt/wrapper"),
                "${kobalt.name}-${kobalt.version}.jar",
                "${kobalt.name}-wrapper.jar")
    }
}

Our jar file is now declared to be a "fat jar" (which means it will include all its dependencies) and we specified a Main-Class to the jar Manifest, which means we will be able to invoke it with java -jar kobalt-0.61.jar. If you don't like this name, you can override it with a name = "myName.jar" statement.

The zip directive follows a similar structure, although here we are specifying which file we want to include. For more details on the packaging plug-in, please see its documentation.

Dependencies

You can declare compile and test dependencies as follows:

dependencies {
    compile("com.beust:jcommander:1.48",
            "com.beust:klaxon:0.14")
}

dependenciesTest {
    compile("org.testng:testng:6.9.5")
}

Maven repos

Kobalt already knows the location of the most popular Maven repos (Maven Central, JCenter, JBoss) but you can add repos with the repos() directive:

val repos = repos("https://dl.bintray.com/cbeust/maven/")

Using plug-ins

Kobalt comes with a few preconfigured plug-ins but you will want to include external ones as well, which can be downloaded either from a Maven repository (Sonatype, JCenter, ...) or from a local file.

First of all, let's take a quick look at the tasks available in the default distribution (your actual output might differ somewhat):

$ ./kobaltw --tasks
  ===== java =====
    compile          Compile the project
    compileTest      Compile the tests
    test             Run the tests
    clean            Clean the project

  ===== publish =====
    generatePom      Generate the .pom file
    uploadJcenter    Upload the artifacts to JCenter

  ===== packaging =====
    assemble         Package the artifacts

Let's modify our build to include a plug-in. We do this by adding a call to the plugins directive on top of the build file:

val repos = repos("https://dl.bintray.com/cbeust/maven/")
val p = plugins("com.beust:kobalt-example-plugin:0.42")

Now, run the --tasks command again:

$ ./kobaltw --tasks
  ===== java =====
    compile         Compile the project

  ===== publish =====
    generatePom     Generate the .pom file
    uploadJcenter   Upload the artifacts to JCenter

  ===== kobalt-example-plugin =====
    coverage        Run coverage

  ===== packaging =====
    assemble        Package the artifacts
Notice the new "coverage" task, provided by the plug-in kobalt-example-plugin that we just included. With the simple action of declaring the plug-in, it is now fully loaded and available right away. Of course, such plug-ins can allow or require additional configuration with their own directives. Please read the plug-in developer documentation for more details.

You can specify more than one project in a build file, simply by declaring them:
val p1 = javaProject { ... }
val p2 = kotlinProject { ... }
If some of your projects need to be built in a certain order, you can specify this when you create your project. For example:
val p2 = kotlinProject(p1) { ... }

This declares that the Kotlin project p2 depends on p1, which means that the project p1 will be built first.

You can also run tasks for a specific project only as follows:

./kobaltw p2:assemble
This will run the assemble task only for the p2, instead of running it for all projects.

Publishing

Kobalt supports JCenter natively so you can upload your project and make it available on JCenter very easily.

First of all, make sure you specified the group, artifactId and version of your project, as required by Maven:

val kobalt = kotlinProject {
    group = "com.beust"
    artifactId = "kobalt"
    version = "0.72"

Next, create a file local.properties in the root directory of your project with the following keys:

bintray.user=...
bintray.apikey=...

The values for the user and apikey keys can be found in your bintray profile, as described here. Note that you should not check this local.properties file into your source control (so add it to .gitignore). Next, make sure that your build creates a jar file (using the packaging directive, as explained above).

Now, all you need to do is to upload your package:

./gradlew uploadJcenter

Writing a plug-in

A good starting point to write a plug-in is the kobalt-example-plugin project, which shows a minimalistic plug-in.

Building

You only need to do two things to build a Kobalt plug-in:

1. Add Kobalt as a dependency:

dependencies {
    compile("com.beust:kobalt:0.61")
}

2. Declare the main class of your plug-in in the Jar file's manifest:

val p = packaging(examplePlugin) {
    jar {
        manifest {
            attributes("Kobalt-Plugin-Class", "com.beust.kobalt.example.ExamplePlugin")
        }
    }
}

Implementing

A plug-in typically has three components:

BasePlugin

The main class of your plugin extends BasePlugin and implements its apply() method and name variable:

public class ExamplePlugin : BasePlugin() {
    override val name = "kobalt-example-plugin"

    override fun apply(project: Project) {
        println("Applying plugin ${name} with project ${project}")
    }
}

Plugin tasks

Next, you can declare tasks with the @Task annotation:

@Task(name = "coverage", description = "Run coverage",
    runAfter = arrayOf("compile"))
public fun coverage(project: Project): TaskResult {
    println("Running the coverage on project ${project}")
    return TaskResult()
}

Directives

Finally, you need to define functions that can be used from the build file (directives). You are encouraged to use the Kotlin DSL approach to expose these functions so that the build file syntax can remain consistent. Typically, these functions will update data that your tasks can then use to do their job.

These can be either straight functions or extension functions. For example, here is the kotlinProject directive:

@Directive
public fun kotlinProject(init: KotlinProject.() -> Unit): KotlinProject {
    val result = KotlinProject()
    result.init()
    return result
}

This function returns a KotlinProject and the user can then override variables or invoke methods from this class in their build file:

val kobalt = kotlinProject {
    name = "kobalt"
    group = "com.beust"
...

Using an extension function to define a directive allows you to add new functions to Kobalt classes. For example, currently, a project can have "dependencies" and "dependenciesTest". For a coverage plug-in, we would want to add a "dependenciesCoverage" section, which can be easily done by defining an extension function on Project:

@Directive
public fun Project.dependenciesCoverage(ini: Dependencies.() -> Unit) : Dependencies {
    val result = Dependencies()
    result.init()
    return result
}

And we can now use:

val p = kotlinProject {
    dependenciesCoverage("com.example:foo:0.1")
}