Java and Kotlin

Java and Kotlin are supported by default by Kobalt. You use the directive project{} to declare a new project and Kobalt will automatically detect how to compile it:

val p = project(wrapper) {
  name = "kobalt"
  group = "com.beust"
  artifactId = name
  version = "0.1"
}

The project{} directive creates an object of type Project.

Arguments

You can specify arguments to be passed to the Java and Kotlin compilers as follows:

val p = project {
    javaCompiler {
        args("-source", "1.7", "-target", "1.7")
    }
    kotlinCompiler {
        args("-no-stdlib")
    }
}

Project

A Project has two mandatory attributes: name and version. If you are planning to deploy your project to a Maven repository, you also have to specify its group (e.g. "com.beust") and artifactId (e.g. "kobalt").

Additionally, a Project lets you specify the following parameters:

sourceDirectories
The location of your source files
sourceDirectoriesTest
The location of your test source files
dependencies
The dependencies for your project
dependenciesTest
The dependencies for your tests

The source directories point to standard locations by default, so if you're using a standard layout for your project (src/main/java, src/main/kotlin, src/test/java, etc...), you do not need to specify any of these directories.

Mixed language projects

A Kobalt project can mix Kotlin and Java in it, simply specify all the source directories you need:

val p = project(wrapper) {
    name = "kobalt"
    // ...
    sourceDirectories {
        path("src/nonstandard/java", "src/nonstandard/kotlin")
    }
}

Kotlin and Java files can be in the same directories.

Tasks

Once you have at least one project configured, the plug-in lets you invoke the following tasks:

compile
Compile the project
compileTest
Compile the tests
test
Run the tests
clean
Clean the project

Templates

Both the Java and Kotlin plug-ins provide templates named respectively "java" and "kotlin".

$ ./kobaltw --listTemplates
Available templates
  Plug-in: Kobalt
    "java"              Generate a simple Java project
    "kotlin"            Generate a simple Kotlin project

They are both identical templates so we'll just look over the Kotlin one.

If you invoke ./kobaltw --init kotlin, the following will happen:

$ ./kobaltw --init kotlin
Template "kotlin" installed
Now you can run either `./kobaltw test` or `./kobaltw run`

$ ./kobaltw test
----- example:test
===============================================
Command line suite
Total tests run: 1, Failures: 0, Skips: 0
===============================================

All tests passed
BUILD SUCCESSFUL (5 seconds)

$ ./kobaltw run
----- example:run
Hello Kotlin world from Kobalt
BUILD SUCCESSFUL (0 seconds)

Variants

Variants let you configure your project to generate different artifacts compiled from different sources depending on the variant you selected.

Note

Kobalt's variant system is very similar to Android's build types, so you should already be familiar with these concepts if you have built Android applications. The difference is that Kobalt supports variants in its core, so that all projects (not just Android's) can take advantage of it.

A variant is made of at least one of the following two components:

Product flavors usually contain different source files and different logic (e.g. a "free version" and a "pro version". Build types lead to different archives (e.g. "debug" and "release", with the "release" version being obfuscated). This effect is achieved by defining identical source files in different directories and then letting Kobalt build the correct one. Each product flavor and build type has a name which translates directory into a source directory. For example:

productFlavor("free") {
}

buildType("release") {
}

With these variants defined, you can now add source files in the "src/free/java" and "src/release/java" directories (Kotlin is also supported):

src/free/java/Product.java
src/release/java/Product.java

If you define at least one variant, new tasks get added to your build:

$ ./kobaltw --tasks

===== java =====
compileFreeRelease
compileFreeDebug

===== packaging =====
assembleFreeRelease
assembleFreeDebug

For example, if you define two flavors, "pro" and "free", and two build types, "debug" and "release", four tasks will be added that combine these: "proDebug", "proRelease", "freeDebug" and "freeRelease". If you assemble any of these, an artifact named after that combination will be created, e.g. "kobalt-0.273-free-debug.jar".

Variants can have they own dependencies{} section, which will be used only if this specific variant is being compiled or assembled:

    productFlavor("debug") {
        dependencies {
            compile("joda-time:joda-time:2.9.3")
        }
    }

BuildConfig

If you have at least one variant defined in your build file, a special file called BuildConfig.java (or BuildConfig.kt) will be automatically generated.

Note

You need to define packageName in your project in order for this file to be generated or Kobalt will fail.

This class contains at least two fields defining the current variant:

class BuildConfig {
    companion object {
        val PRODUCT_FLAVOR : String = "pro"
        val BUILD_TYPE : String = "debug"
    }
}

You can add your own custom fields to this file by calling the buildConfig directive inside your flavor:

productFlavor("free") {
    buildConfig {
        field("aStringField", "String", "\"The free field\"")
        field("anIntField", "Int", "42")
    }
}

The generated file will then contain:

class BuildConfig {
    companion object {
        val PRODUCT_FLAVOR : String = "free"
        val BUILD_TYPE : String = "debug"
        val aStringField : String = "The free field"
        val anIntField : Int = 42
    }
}

Take a look at the variants example project to see an actual example using variants and BuildConfig.

Tests

Kobalt will automatically attempt to run test classes found in the test directory with the correct runner (e.g. TestNG, JUnit, Spek, etc...). Additionally, you can configure how these tests are run with the test{} directive, which accepts the following parameters:

args
The arguments passed to the runner
includes
Which classes to include. Note that the parameters to this function are path globs (see below)
excludes
Which classes to exclude. Note that the parameters to this function are path globs (see below)

Here are a few examples of test{} directives:

test {
    args("-log", "1", "src/test/resources/testng.xml")
}

Specifying includes and excludes:

test {
    includes("**/D*.class")
    excludes("**/BrokenTest.class", "**/NotYetImplementedTest.class")
}

Note that includes and excludes are path globs: they must match the entire path of the class and they can use ** and * to match any directory or file name.

Application

The "application" plug-in lets you run your application directly from kobaltw. You configure it as follows:

application {
    mainClass = "com.beust.kobalt.wrapper.Main"
    jvmArgs("-Djava.library.path=libs", "-Ddebug=true")
    args("doc", "assemble")
}

And you launch you app with "run":

./kobaltw run

The application directive supports the following parameters:

taskName
The name of the task that will launch the main class (default: "run")
args
Arguments to pass to the program.
jvmArgs
Arguments to pass to the JVM.
mainClass
The class in your code that contains the main function.

Note that you can have multiple application{} directives, each launching a different main class with a different task name:

application {
    mainClass = "com.example.Main1"
    taskName = "runMain1"
}

application {
    mainClass = "com.example.Main2"
    taskName = "runMain2"
}

apt

The apt plug-in adds support for annotation processing. It's made of two parts.

The apt dependency directive

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

Instead of using compile, you use apt in your dependencies and you point to the jar file that contains the annotation processor. This will instruct any compiler involved in the build to run this annotation processor first.

The apt configuration directive

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

This directive lets you configure the output directory and a few other settings that drive the annotation processor. This directive is optional.

For a full example defining and then using an annotation processor, see the version-processor project.

Packaging

The Packaging plug-in lets you generate (directive assemble) and install (directive install) various archives for your project: jar, war and zip files.

assemble

The assemble directive controls which artifacts get generated for your project.

assemble {
    jar {
    }
}

If you don't specify a name for your archive, a default one will be used that contains your project name, version and the corresponding suffix, e.g. kobalt-1.1.jar or sec-0.2.war.

zip

All these archives are zip files, so the zip archive is at the top of the hierarchy and jar and war inherit all its attributes, which include name, include and exclude.

include and exclude

All archives let you include and exclude files.

include has two different forms:

assemble {
    zip {
        include("kobaltw", "README")
        include(from("doc/"),
            to("html/"),
            glob("**html"))
    }
}

The first form, with just one parameter, simply copies the file from your directory into the archive, preserving its path. The second form has three parameters which allow you to move the file to a different path into your archive. Note the use of the from, to and glob directives, which are necessary to disambiguate the call.

jar

A jar is like a zip with two additional available parameters:

fatJar
If true, all the dependencies and their dependencies will be included in the jar file
manifest
Specify attributes to add to the manifest

Here is how you generate an executable jar file:

assemble {
  jar {
      fatJar = true
      manifest {
          attributes("Main-Class", "com.beust.kobalt.KobaltPackage")
      }
  }
}

war

The war directive generates a war file suitable to be deployed into a servlet container.

mavenJars

The mavenJars directive generates several jar files (binary, source, javadoc) which are required by Maven repositories. It's basically a shortcut that saves you the trouble from having to assemble these jar files manually in your build file. It allows you to specify Manifest attributes, just like the jar directive.

install

The install section lets you specify how the artifacts get installed. There are two ways to use install.

Bulk install

The simplest way to install is either not specify anything in the install directive or an optional target string, which specifies the target directory ("libs" by default):

    install {
        target = "libs"
    }

With this approach, everything that was generated in kobaltBuild/libs gets copied to this target directory.

Fine grained install

You can also be more selective in what you install with either the copy directive to copy individual files or with include, which lets you specify more sophisticated ways of moving files to a destination:

    install {
        copy(from("README"), to("deploy"))
        include(from("kobaltBuild/libs"), to("deploy"), glob("**/*"))
    }

Multiple install targets

You can specify multiple install directives by specifying different task names for each of them:

    install {
        taskName = "installSingle"
        target = "libs"
    }

    install {
        taskName = "installMulti"
        copy(from("README"), to("deploy"))
        include(from("kobaltBuild/libs"), to("deploy"), glob("**/*"))
    }

You might find the directive collect() useful when installing: this function is invoked on a list of dependencies and returns a list of jar files that represent the transitive closure of the dependencies:

    install {
        collect(compileDependencies).forEach {
            copy(it.file.absolutePath, to("deploy"))
        }
    }

Publishing

The Publishing plug-in lets you upload files to Bintray and Github. These files can be either generic ones (e.g. a zip file, a README, etc...) or a Maven-compatible form of your project.

Bintray / JCenter

You upload files to Bintray with the uploadBintray task. Before you can upload, you need to create a file local.properties in the root directory of your project with the following keys:

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

# Optional. Include this if you want to upload to your org's repo instead of your own.
# bintray.organization=...

The values for the user and apikey keys can be found in your bintray profile, as described here. Add this file to your .gitignore file and make sure you never upload it to your source control.

Before you can upload, you also need to create the package in bintray, as explained here. Once this is done, you are ready to do your first upload.

The simplest way to define what files will be uploaded is to use mavenJars{}, which will upload all the artifacts necessary for your project to be usable by Maven compatible build tools:

  assemble {
      mavenJars {}
  }

  bintray {}

This directive is described here and is a shortcut to upload all the files that are required in a Maven repo (jar file, javadocs, source files and POM file). You can take a look at this build file for a full example of how to package and publish a project to Bintray / JCenter.

You can also specify the files to upload individually with the file directive. The following build file uploads a zip file to a bintray directory defined by the version number of your project:

bintray {
  file("${kobalt.buildDirectory}/libs/${kobalt.name}-${kobalt.version}.zip",
       "${kobalt.name}/${kobalt.version}/${kobalt.name}-${kobalt.version}.zip")
}
$ ./kobaltw uploadBintray
...
========== kobalt-line-count:uploadBintray
  kobalt-line-count: Found 2 artifacts to upload
  All artifacts successfully uploaded

The bintray directive accepts the following parameters:

publish (false)
If true, the uploaded file will be published in your personal space (e.g. https://dl.bintray.com/cbeust/maven). Once the file is uploaded there, it can be automatically synchronized to JCenter by linking your project to JCenter on the Bintray web site. By default, files are not published.
sign (false)
If true, sign the files with GnuPG (gpg). This is only required if you plan to later synchronize these files from JCenter to Maven Central. Keep this to falseif you are only interested in uploading to JCenter.
file
The first parameter is the file you want to upload and the second one is the path where it will be uploaded to.
name
The package name on Bintray, if different than the project name.
description
The description of the current version.
issueTrackerUrl
The URL to an issue-tracking system where fellow developers can follow and help you solve bugs and defects.
vcsTag
The VCS tag for the current version.

If there is a need to specify some of the OpenPGP key information when signing, it can be stored in the local.properties file as follows:

gpg.keyId=24875D73
gpg.password=secret
gpg.secretKeyRingFile=/Users/me/.gnupg/secring.gpg

Configuring your POM file

When you upload your package to Bintray or JCenter in Maven form, Kobalt will create a default POM file for you. You can configure it with the pom{} directive in your project. Here is an example:

import org.apache.maven.model.*

val kobalt = project {
    pom = Model().apply {
        name = project.name
        description = "A build system in Kotlin"
        url = "http://beust.com/kobalt"
        licenses = listOf(License().apply {
            name = "Apache 2.0"
            url = "http://www.apache .org/licenses/LICENSE-2.0"
        })
        scm = Scm().apply {
            url = "http://github.com/cbeust/kobalt"
            connection = "https://github.com/cbeust/kobalt.git"
            developerConnection = "[email protected]:cbeust/kobalt.git"
        }
        developers = listOf(Developer().apply {
            name = "Cedric Beust"
            email = "[email protected]"
        })
    }

Note that the classes used in this snippet (Model, License, etc...) are the actual Apache Maven class, so you have access to the full POM model.