Contents

Gradle - notes

Notes about Gradle

What should I know about Gradle? How to create a project with submodules? Or maybe: how to create simple project? Or how to use Gradle with Kotlin?

Notes unordered

This post is a set of very random notes, too chaotic to treat them as any valuable source of information. I write as I go through the Gradle documentation.

Information you can find here may be not only random, but also factually incorrect. I’m still building my mental model of how Gradle works.

So please don’t quote me on anything you read here 😄

Kinds of scripts

There are three kinds:

  • main build script which is represented by a class Project
  • there are initializing scripts which are used to prepare the environment before proper main script is executed; don’t have access to buildSrc
  • scripts with settings which are used to create and configure the hierarchy of projects which are parts of the build; they are represented by a class Settings

Gradle DSL is described in Gradle Build Language Reference

Lifecycle

A commandline call gradle ... starts a lifecycle of three phases, which are, in order:

  • initialization - Gradle finds out what projects need to be build and create an instance of a Project for each of them
  • configuration - when objects are configured in a project (or projects): in this phase build scripts for each of the project are executed (note: the code in task definitions is executed unless it is closed in doLast or doFirst closure)
  • exeuction - Gradle finds out what tasks (created and configured in configuration phase) needs to be executed and executes them

Notes about how to read Groovy code

How to write groovy build scripts: primer

Proerties, methods, blocks

Unqualified properties map to methods in Project class, and inside a block they may map to methods of delegated object) The signature of such methods denotes the parameters, and if the last one is of type closure or Action then this is usually visible in the syntax as a code block.

In general, properties may come from different sources:

  • Project object
  • extra properties: arbitrary key-value map
  • extentions properties which are usually added to the project by plugins and are avaibale as read only, named after the extension
  • convention propertyes also added by plugins; can be modifiable; represented by Convention
  • tasks declared in the scripts; are available through their name: for each task a property with the same name as a task is created; such properties are read-only

Dependencies used/required by the build script itself

Can be declaed inside buildscript block (where an instance of ScriptHandler is configured) - they will be added to the classpath of the script when it is executed, for example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import org.apache.commons.codec.binary.Base64

buildscript {
    repositories {
        mavenCentral()
    }

    dependencies {
        "classpath"(group = "commons-codec", name = "commons-codec", version = "1.2")
    }
}

tasks.register("encode") {
    doLast {
        val encodedString = Base64().encode("hello world\n".toByteArray())
        println(String(encodedString))
    }
}

In the above example dependencies {...} is a call to the method getDependencies() on an instance of Scripthandler (which returns DependencyHandler). Dependencies are grouped in configurations - in the example there is a configuration named “classpath” .

Configuration is a named set of dependencies.

In general, dependencies section looks like this:

1
2
3
 dependencies {
     configurationName dependencyNotation
 }

Java - plugins

Two basic types of projects created in Java are library or application. Each one has a separate plugin: java-library and application, respectively. Both are based in java plugin. The separation allows for interesting optimizations regarding the configirations (api and implementation in a definition of a dependency for a library) that influence the classpath.

application

In earlier versions of Gradle plugins were applied to a project by a call to apply, but now the recommended way is to use plugins block, for example:

1
2
3
4
5
6
7
8

plugins {
    application
}

application {
    mainClass.set("org.gradle.sample.Main")
}

Running application with commandline arguments:

1
gradle run --args="foo --bar"

java library

Java Library Plugin configures a project as a Java library. It has three standard configurations which can be used in build script:

  • implementation - here one can place dependencies needed to compile the production code of the library, but which are not part of an API exposed by the library
  • API - dependencies needed to compile the production code of the library which should also be visible from the outside of the library (e.g. by an application that is using the library)
  • testImplementation - dependencies needed to compile and run test sources

What I’ve understood is that introduction of the split od dependencies (between api and implementation) allows to achieve smaller classpath for a library or application that is using this library; it does not have to use all transitive dependencies of the library, only those defined as api.

Wait a sec. This reminds me of Java modules and inter-module requirements (requires oraz requires transitive). How are Java modules and Gradle configurations connected? Let’s check.

Modules usage

Here is the documentation about how to use modular java library.

That’s interesing - right now (October 2021) Java modules and Gradle configurations are mapped one-to-one but Gradle does not check the consistency bewteen requirements of Java modules and library configuration types.

Declaring main class’ module

Running a class in multimodule application (Java 9 or later) you need to also provide the name of the main module where your main class resides:

1
2
3
4
application {
    mainModule.set("org.gradle.sample.app") // name defined in module-info.java
    mainClass.set("org.gradle.sample.Main")
}

Excercises

Here are some exercises which I’ve prepared for myself to better know Gradle. They are, more or less, of increasing difficulty and follow normal requirements expansion process - or rather normal development process.

Initial build scripts should be modified in order to cover all of the following aspects as you go:

  • create a script for a simple java app with unit tests
  • run the application (using Gradle)
  • run the tests (…)
  • extract part of the code to external library - define it as main build’s subproject
  • use the library in the application - how to check if the dependencies from library’s implementation configuration are in fact not present on the classpath of the app?
  • change the app and library to be modular (as of Java 9 or later)
  • run modular application (…)
  • run tests
  • create a distribution (jlink? or default solution with zip/tar and running scripts?)
  • create GUI app which has its splashscreen with a date and SHA of the commit from which the build comes from
  • use a database - different for tests and different for application (don’t use Spring Boot)

Good luck!