It’s time to ditch the buildscript block

Stefan M.
4 min readApr 24, 2018

--

It was a fun journey with you. But now it is time to say goodbye 👋

The buildscript block

Each developer who uses Gradle as their underlying build tool knows about the buildscript block. Or at least knows how to use it. Before I dive deeper into why we should get rid of it I want to explain what it actually is and what it does.

buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
}
}

The build.gradle file above is automatically generated by Android Studio each time you create a new project (without Kotlin) and lives in the top level directory of your project.

But what does that block do? Basically it defines dependencies which are required for the build itself. Or in other words: It defines dependencies which are required to build your Application (whatever your “Application” is (Android App, Gradle Plugin, Swift Desktop Application…)).

In our Android example we have to define the Android Gradle Plugin dependency which helps us to build an APK:

// project/build.gradle
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.1'
}
}
// project/app/build.gradle.kts
apply {
plugin("com.android.application")
}

Since we know what it does, why should we ditch it? 🤔

Well, I didn’t say we don’t need the logic behind it anymore (apply dependencies which are required for the build). I rather say we have to replace the legacy buildscript block with something “new”.

Rise of the plugins block

The “new” plugins block is not really new. It was already introduced in Gradle 2.1. Back then it hasn’t got that much attention obviously.

However. The plugins block is way easier to read and to use:

// project/app/build.gradle.kts
plugins {
id("org.gradle.java")
kotlin("jvm") version "1.2.40"
}

You probably noticed that this is only one file instead of two?!

But there are some “limitations”. By default the plugin block can only resolve plugins which are either:

  • Core Plugins (provides by Gradle themself). These are e.g.: org.gradle.java, org.gradle.groovy, org.gradle.java-gradle-plugin and so on…
  • Published to the Gradle Plugin Portal.

Unfortunately not all Gradle Plugins are available at the Plugin Portal (looking at you, Android Gradle Plugin! 😡).

How can we apply such plugins, which aren’t either “core” or published to the Plugin Portal?

There is a pretty nice trick to do that. In my opinion it requires some “boilerplate code” in the settings.gradle.kts but it is worth to do it:

pluginManagement {
repositories {
gradlePluginPortal()
jcenter()
google()
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == "com.android.application") {
useModule("com.android.tools.build:gradle:${requested.version}")
}
}
}
}

First we have to define all the repositories where the plugin block should look for plugins (the order is important*). In the eachPlugin block we can take a look at each applied plugin (from id("name")) and behave based on that. In our example we say something like:
if id equals com.android.application then use “this” as module”.

*The order is not really “important”. But Gradle will look for the applied plugins in these repositories in the given sequence. You may want to add the repository with the most plugins available at first.

You may ask what the string in the useModule is? It is the classpath string which we have used in the legacy buildscript block.

With that information Gradle knows where to look at and how to search there for the plugins in the plugins block. Pretty cool, no? 😎

Finally we are able to apply the Android Gradle Plugin like this:

plugins {
id("com.android.application") version "3.1.1"
}

It’s time to change, now!

The version problem

Gradle is designed for modularity. Which means a project can contain different modules which may apply different (or the same) plugins. If you do so, you may end like this:

// project/app/build.gradle.kts
plugins {
id("com.android.application") version "3.1.1"
kotlin("android") version "1.2.40"
}
// project/library1/build.gradle.kts
plugins {
id("com.android.library") version "3.1.1"
kotlin("android") version "1.2.40"
}
// project/library2/build.gradle.kts
plugins {
id("com.android.library") version "3.1.1"
kotlin("android") version "1.2.40"
kotlin("kapt") version "1.2.40"
}

Imagine you want to update the applied plugins. It is really annoying to jump in each build.gradle.kts file and change the version there (beside of the fact that it may lead to errors. For example you forgot one Gradle file or used the wrong version). But there is help. Another time where the Gradle settings can help us.

Fun fact: As a Android Developer you might have never touched the settings.gradle or even don’t know that it exists, right? 😉

Beside of only adding useModule (for missing plugins in the Plugin Portal) we can additionally use useVersion. For all plugins! Even if they are published to the Plugin Portal (which is true for all Kotlin plugins):

pluginManagement { 
repositories { ... }
eachPlugin {
if (requested.id.id.startsWith("com.android")) {
useModule("com.android.tools.build:gradle:3.1.1")
}
if (requested.id.id.startsWith("org.jetbrains.kotlin")) {
useVersion("1.2.31")
}
}
}

With that little change we can apply our plugins without a version defined. Now Gradle knows the specific version for all plugins which start with “com.android” and “org.jetbrains.kotlin”:

plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
}

Summary

  • Remove the buildscript block
  • Copy & Paste the code from above into your settings.gradle
  • Use the plugins block and dance 🕺

--

--