A crash course in classpaths: Build
Tags: java, android, gradle
Published: 2021-02-04 19:23:02

---
title: A crash course in classpaths: Build
published: true
description: Wherein we learn about classpaths and class loading in Gradle builds
tags: java, android, gradle
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/5pyqmcjchyrllgdy6p8x.jpg
---

<small><em>The cover image represents Gradle's class loader hierarchy.</em></small>

Welcome to part 2 in my series on classpaths and class loading in Gradle projects. If you haven’t read [part 1](https://dev.to/autonomousapps/build-compile-run-a-crash-course-in-classpaths-f4g), I would recommend checking it out first, as it lays the foundation for what is to follow. In this part, we will be focusing on the classpaths available to the build process itself.

# What do we mean by “the build”?

As I alluded to in the first post, modern JVM development (nor least _Android_ development) rarely if ever involves direct interactions with SDK tools like `javac` or `kotlinc`. Instead we “run builds” with a “build tool.” Under the hood, these build tools (such as Gradle), invoke the more fundamental SDK tools. As an example, Gradle's `java` plugin registers a task, `compileJava`, of type `JavaCompile`; during its execution, that task will invoke the Java compiler, `javac`. 

Now we can give a simplified definition for "the build":

> **The build** is a high-level abstraction over the more fundamental SDK tool operations that compile and run your software applications.

How does this relate to classpaths and class loading? That's up next.

# The build classpath(s)

Build scripts in a Gradle build must be compiled, and then they must be run, in order to execute your build. This process is automated by Gradle, regardless of whether your script is written in Groovy (`.gradle`) or Kotlin (`.gradle.kts`). When you execute a build (whether that's clicking the green arrow in Android Studio or running `./gradlew app:assembleDebug` from the command line), Gradle will execute that instruction in two distinct steps: first it compiles your build scripts, and then it runs your build. If you're familiar with the [Gradle build lifecycle](https://docs.gradle.org/current/userguide/build_lifecycle.html), it may help to think of the first step as being in the configuration phase, while the second step is in the execution phase.

As with any other compilation in the JVM, compiling a script requires a compile classpath, which we call the build compile classpath. Let’s see what it looks like through a simple example. To do so, we will be using Gradle [build scans](https://gradle.com/) as a convenient visualization tool.

# A simple example to set the stage

Let's start by considering a very simple build script and inspecting the build classpath, which is on the **Build Dependencies** section in a build scan.

> In the examples below, I am deliberately using `gradle` and not `./gradlew`. I'm doing this to demonstrate that you can use the [global installation of Gradle](https://sdkman.io/sdks#gradle) on your system to build projects, and to simplify the example setup. Just create a `build.gradle` and run it!

```groovy
// root build.gradle
println "project name = $name"
```

We can then execute `gradle help --scan`, and we will see the following amidst the rest of the output:

```
> Configure project :
project name = classpaths-example
```

We'll also see a [build scan](https://scans.gradle.com/s/ezevdhyjpqkng#build-dependencies) for this simple build. These are the build dependencies: 

![Build dependencies for our trivial build script (there are none)](https://dev-to-uploads.s3.amazonaws.com/i/2teo881101hauwmuu371.png)

As you can see, there are none. While this may not seem surprising, given the simplicity of the script, it's not strictly accurate. First of all, we can infer from the fact that our `println` statement worked that the Groovy Development Kit (GDK) is on the classpath, and furthermore that so is the JDK. (`println` is Groovy shorthand for `System.out.println()`.) Finally, the `name` variable is a property on the `Project` instance that is backing our Gradle script, which means the Gradle API is also available by default. So, our build scan notwithstanding, Gradle provides quite a lot to us on the build classpath, by default.

Finally, there is one more dependency that is hidden. Our usage of the `--scan` option implicitly adds the Gradle Enterprise (GE) plugin to our build. It's unclear why that isn't visible in the scan. If you add an empty `settings.gradle` and run that build again, you'll get a [new scan](https://scans.gradle.com/s/bh22lflw2ggzw/build-dependencies?toggled=W1swXSxbMCwwXV0):

![Build dependencies for our trivial build script (now there's one)](https://dev-to-uploads.s3.amazonaws.com/i/73yo1yhidddf9o4ihnj9.png)

And now you can see that the GE plugin is a build dependency.

# Adding a plugin to your project

But what's a Gradle project without a plugin? Let's add a plugin and see how it changes the build classpath. <a name="source-1" />

```groovy
// root build.gradle
buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.21'
  }
}
```

_(Yes, mavenCentral)[<sup>1</sup>](#endnotes)_

If we configure this project with `gradle help --scan` (as we like to do), we can [see](https://scans.gradle.com/s/k6a7upjvqf2ve/build-dependencies?toggled=W1swXSxbMCwwXSxbMCwwLFswXV1d) what this has done to our build classpath:

![Build dependencies with Kotlin plugin added in the buildscript block](https://dev-to-uploads.s3.amazonaws.com/i/2il6dqb8whlvdnva19ot.png)

There's our Kotlin Gradle Plugin (with transitive dependencies) on the build classpath of the root build script. You'll note we haven't even applied the plugin at this point, let alone configured it in any way. Rather, we have explicitly added it to our build classpath using the venerable `buildscript` block.

While I'm certain most, if not all, of us have seen code like that, we have to admit it's a little verbose. Why should we have to explicitly specify our plugin dependencies, especially when there often isn't an obvious mapping from the dependency coordinates to the plugin ID? (Simply `kotlin`, as in `apply plugin: 'kotlin'`, in this case.) There's something suspiciously [cargo-cultish](https://en.wikipedia.org/wiki/Cargo_cult) about it.

# The `plugins` block

The alternative to explicitly adding to your build classpath is to use the `plugins` block. Let's convert the above example and see what, if anything, changes.

```groovy
// root build.gradle
plugins {
  id 'org.jetbrains.kotlin.jvm' version '1.4.21'
}
```

This block combines _adding_ Kotlin Gradle Plugin (KGP) with _applying_ it, so its behavior is a little different from the above.

> Sometimes it can be useful to split these operations and maintain an explicit `buildscript` block, but the scenarios in which that makes sense are rapidly disappearing. They're also outside of the scope of this post!

If we once again run `gradle help --scan`, we can [inspect](https://scans.gradle.com/s/d3vls3s4xhdl6/build-dependencies?toggled=W1swXSxbMCwwXSxbMCwwLFswXV0sWzAsMCxbMCwxXV1d) our build classpath:

![Build dependencies with Kotlin plugin applied with the plugins block](https://dev-to-uploads.s3.amazonaws.com/i/ozx11kxup0pvtq4f1jjy.png)

As we can see, it's very similar to the other example. There are three important differences:

1. We can see the additional level of nesting, with a top-level dependency of `org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.4.21`. This is known as a [plugin marker artifact](https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_markers), and is how Gradle manages the problem of mapping a plugin ID to its "physical" artifact (a jar and its transitive dependencies).
2. If you inspect the build dependencies in the two scans, you'll see that, in the first, they were all resolved from https://repo.maven.apache.org/maven2/ (aka `mavenCentral()`), whereas in the second they were all resolved from https://plugins.gradle.org/m2 (aka `gradlePluginPortal()`). There are ways to change the repository(ies) from which plugins are resolved, and we'll discuss them in just a bit.
3. As we already noted, `plugins` also applies the plugin. You can verify this by looking at the **Plugins** link on each build scan.

## Actually, you can use `plugins` without applying a plugin

I know I know, I kind of made a big deal about how using `plugins` automatically applies the plugin to your build. While that _is_ the default behavior, you can tell Gradle not to do it like so:

```groovy
// root build.gradle
plugins {
  id 'org.jetbrains.kotlin.jvm' version '1.4.21' apply false
}
```

There are two scenarios I know of that makes this a useful pattern. In the first, you just need the Kotlin plugin on your build classpath for some reason, maybe because another plugin expects it (for reasons we will touch on in the _next_ post). In the second, this is a way to force all subprojects in your build to use the same version of the given plugin. Consider this:

```groovy
// settings.gradle
include ':app'

// root build.gradle
plugins {
  id 'org.jetbrains.kotlin.jvm' version '1.4.21' apply false
}

// app/build.gradle
plugins {
  id 'org.jetbrains.kotlin.jvm'
}
```

You know what's next: `gradle help --scan` ([link](https://scans.gradle.com/s/wt363jzdsh7iu/build-dependencies?toggled=W1swXSxbMCwwXSxbMCwxXSxbMCwwLFsxXV0sWzAsMCxbMSwyXV1d)).

![Build dependencies with Kotlin added in the root script and applied in a subproject](https://dev-to-uploads.s3.amazonaws.com/i/j5og0pdn7fis5qp82b2a.png)

We can see from this that the build classpath is fundamentally unchanged (although we can now see the Gradle Enterprise plugin, since we added a `settings.gradle`). This confirms that KGP has _not_ been added to the build classpath of the `app` subproject: it is only on the classpath of the root build script. Nevertheless, subprojects can access it thanks to Gradle's well-defined (though poorly documented) class loader hierarchy that makes the root class loader the parent of all subproject class loaders. These subproject class loaders delegate to their parent in the normal way, as discussed in the first post of this series.

# Android Gradle Plugin

In contrast to KGP, most versions of AGP cannot be applied simply with `plugins`, because it only started publishing plugin marker artifacts with the [4.2.0](https://maven.google.com/web/index.html#com.android.application:com.android.application.gradle.plugin) series. Nevertheless, using the modern `plugins` approach with AGP 4.2.0-beta04 provides a good opportunity to show off an important feature of Gradle that I don't think many people know about.

```groovy
// settings.gradle
pluginManagement {
  repositories {
    google()             // AGP
    gradlePluginPortal() // KGP
  }
}
include ':app'

// app/build.gradle
plugins {
  id 'com.android.application' version '4.2.0-beta04'
  id 'org.jetbrains.kotlin.android' version '1.4.21'
}
```

There are two interesting features of this example: we no longer need a root `build.gradle`, and we have this new `pluginManagement` block in our settings script. The first follows from what we've learned so far, and the second is a bit out of scope, so I encourage you to read the [docs](https://docs.gradle.org/current/userguide/plugins.html#sec:plugin_management). It is quite powerful and can do more than just specify repositories. Settings scripts &mdash; not just for `include` statements anymore!

![Build dependencies with KGP and AGP](https://dev-to-uploads.s3.amazonaws.com/i/a5djb97offe6d6bg4k5q.png)

# Gradle build script compilation

With this foundation, we can return to the beginning with new eyes. What is the build? What is its relation to classpaths? In this post we have, fundamentally, been talking about the build _compile-time_. Our goal may be to compile our _projects_, but before we can do that we have to compile our _build scripts_. Everything we've learned in this post boils down to different ways to influence the build classpath so that we can compile our build scripts and then, ultimately, compile our projects. It really is classpaths all the way down.

{% twitter 1347684010214961154 %} 

# What's next?

Now that we have a pretty good handle on the "build compile-time", we can next ... well, learn one more trick for influencing the compile-time, and _then_ focus on the "build runtime." For the former, we'll learn about a useful and, frankly, cool monkey-patching technique, while for the latter we'll focus on considerations for plugin authors.

See you then!

# Special thanks

Thanks once again to [César Puerta](https://twitter.com/CesarDielo) for thoroughly reviewing this post and helping me keep my foot out of my mouth at several key points.

<a name="endnotes"/>
# Endnotes
<sup>1</sup> Pour one out for [jcenter](https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/). [<small>up</small>](#source-1)