· 7 min read Posted by Kevin Schildhorn
Gradle Cheat Sheet for Android and KMP Projects - Plugins
Gradle can be confusing and difficult to follow. When you create a new Android project they generate some Gradle files and send you off. Often, developers end up copying and pasting code from GitHub or Stack Overflow, not really understanding what’s going on.
This series will provide some definition of commonly seen code in Gradle builds, and act as a cheat sheet you can reference in the future. In addition to this post, I’ve also created a GitHub Gist that should cover a typical Gradle project setup, and contain a markdown cheatsheet. If you want to follow along with an existing project, I’d recommend either this multiplatform compose template or KaMP Kit, which is a great starting point for a KMM project.
In this article we’ll explore how gradle works, dive deeper into Gradle Plugins, and leave you with some handy Flashcards for reference.
What actually happens in a Gradle build
Before we start going over plugins let’s go over what happens in a build:
- The Gradle wrapper is downloaded based on the
gradle-wrapper.properties
- The build detects the
settings.gradle
file and evaluates to determine which projects participate. - Project instances are created for every project and their
build.gradle
scripts are all evaluated. - A task graph for requested tasks is created.
- Each of the selected tasks are scheduled and executed, in the order of their dependencies.
The main thing to note here is the order the build runs in. It first checks the settings.gradle
then goes through each submodule defined in the settings, and runs their build.gradle
files. Then as mentioned it executes tasks in order of dependencies. The actual order may be complex, but as a simplification in a KMP example we can say this:
Settings.gradle
runsbuild.gradle
of each submodulesettings.gradle
orbuild.gradle
resolves pluginsbuild.gradle
checks for blocks to run (such asandroid
andkotlin
) and runs them- Those blocks resolve
dependencies
The order I like to think of this is:
Settings.gradle
-> build.gradle
-> plugins
-> blocks
-> dependencies
So with that in mind, we’ll first go over plugins, then common kotlin
and android
blocks, then dependencies, and finally wrap back to some notes about the settings.gradle
. Gradle Source
Note: Gradle script files can come in two different extensions based on the chosen DSL: .gradle
for Groovy DSL and .gradle.kts
for Kotlin DSL. This blog post refers to these two extensions interchangeable, as it discusses the concepts of Gradle rather than just the syntax.
Note: In Gradle there are many ways to do the same thing. This post won’t necessarily go over best practices for location, but will give some suggestions on how to structure definitions in a multi-module project.
Note: This is not only a cheatsheet for you, but also for me. I am still learning, so if I have not covered something, or covered something incorrectly let me know.
Definitions
Before we start here are some terms I use during the post that might not be clear:
Level
: In this post when I say “level” I mean the module level. For example the root or top level would imply the root directoriesbuild.gradle
and module.DSL
: Domain Specific Language. Examples: Kotlin DSL, Plugins DSL, Gradle DSL
Plugins Overview
Plugins DSL
// yourModule/build.gradle.kts
plugins {
kotlin("multiplatform") // This is where you add your plugin
}
// settings.gradle.kts (root)
pluginManagement { // Where you configure the plugins used in this project.
repositories { // Listing where you want to pull the plugins from
google()
mavenCentral() // The Central Maven Repository (more about maven later on)
gradlePluginPortal() // Default Gradle plugin portal (https://plugins.gradle.org/)
}
plugins { // Listing the plugins you will use
kotlin("multiplatform")
.version("1.8.10") // Plugins must contain a version in their definition at some level
.apply(false) // You can choose to not apply plugins, in case you don't want to use the plugin at a certain level
}
}
Legacy Plugin Application
buildscript {
repositories { // Listing where you want to pull the plugins from
maven { url = uri("https://plugins.gradle.org/m2/") }
}
dependencies {
classpath("do.re.mi.exam:ple:1.8.20") // This a dependency for the plugins you want to add
}
}
apply(plugin = "org.jetbrains.kotlin.multiplatform") // Here is where you define a plugin
Gradle Docs
What are plugins
Plugins add functionality to your Gradle build. They add new tasks, domain objects, and features to the project. More info here
Example: Adding the kotlin("multiplatform")
allows you to add the kotlin block containing sourcesets and targets and adds tasks such as build iosArm64Binaries
How are plugins added?
Adding plugin to module
Let’s view this from a top down approach, starting at a modules build.gradle
. First, We add the plugin.
// build.gradle
plugins {
id("com.android.library")
kotlin("multiplatform")
.version("1.8.10") // Optional\*
.apply(false) // Optional
}
Plugins are added in the plugins
block, which can either be added to a build.gradle
or to a pluginManagement
block in the settings.gradle
(we’ll get to that). Submodules inherit plugins, so if you want to share plugins among submodules they can be defined to the root module.
There are two ways to include a plugin:
id("id")
: With id you are importing a plugin, by passing in the group ID and name of the pluginkotlin("id")
: Kotlin is a nice shorthand, that simply means we’re using the “org.jetbrains.kotlin” group ID. This is the same as callingid("org.jetbrains.kotlin.id")
Version: You must add a version to the plugin in your project. If you share the plugin with submodules you can define the version in the root module, otherwise you set the version in the module it’s used. Versions are inherited by submodules, but you can also override versions in submodules if needed.
Apply: You can choose to apply or not apply plugins using apply. This is good in cases where you want to define the plugin globally in the root project, but don’t want to actually use it in the root module. Submodules that declare this plugin will automatically have it applied.
KMP Note: Plugins defined in a module apply to all sourcesets. For example if you include the id("org.jetbrains.kotlin.id")
plugin, then it will try to apply that plugin to your iosMain
sourceset and cause an error.
Managing plugin
Second, we define where it’s coming from.
// settings.gradle
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
You need to define where the plugins come from, otherwise Gradle has no idea what you’re talking about. By default Gradle checks for plugins at the gradlePluginPortal. If the plugin you’re using is in another repo you can define it in the pluginMagement
block. The pluginManagement
block is where you set the repositories your plugins come from. Additionally you can set plugins here as well, using the same syntax as mentioned above. We’ll go over maven in a future post but for now know that mavenCentral
is a popular repository used for plugins and dependencies.
Legacy
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath(
"org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20"
)
}
}
apply(plugin = "org.jetbrains.kotlin.multiplatform")
Legacy is a little different. Instead of pluginManagement
we use buildScript
. Repositories
are the same, but note there’s a Repositories
block in this version. This is where you define classpaths, which are dependencies for plugins.
Personal recommendation
There’s a lot of different ways of defining plugins, but as of May, 2023, here is what I would recommend:
- Follow the android guidelines
- Define all plugin information
settings.gradle
usingpluginManagement
, with versions, but set apply to false. - Then include plugins in submodules, but without the version
Flashcards
Here are some quick references to code surrounding Gradle plugins
Blocks
plugins
: Where you define the plugins you want to use (More Info)repositories
: A block that tells Gradle where to look for items (More Info)pluginManagement
– Where you define the project’s plugins, containsplugins
andrepositories blocks
. (More Info)BuildScript
: (Legacy for plugin) Definesrepositories
and dependencies for adding plugins. Can also be used to add variables such as versions that are used across the project (More Info)
Plugins
id("id")
: Add a plugin with a group ID and a name (ex: “com.android.library”) (More Info)kotlin("id")
: Kotlin simply means we’re using the “org.jetbrains.kotlin” group IDversion("version")
: Setting the version of the plugin, required at some module level (More Info)apply(boolean)
: Whether to apply the plugin or not. Useful if you want to define the plugin in the root module, but only use it in other certain submodules (More Info)
Legacy
classpath
: A dependency for the plugins you want to add,classpath
is used in thebuildscript
blockapply("id")
: The legacy way to add a plugin
Repositories
gradlePluginPortal()
: The default Gradle plugin portal (https://plugins.gradle.org/). If no repository is defined for plugins then Gradle will use this as a default (More Info)mavenCentral()
: The Central Maven Repository (https://repo1.maven.org/maven2/) (More Info)