DRY - Gradle Configuration Values
Posted on 2024-10-28

As you work on your Gradle builds you might find a need to centralize your build configuration, for example you might want to have a default minSdk for your projects that apply com.android.library plugin. The legacy way of approaching this used to be adding build logic to the root build.gradle.kts along the lines of:

allprojects { project ->
    if (isAndroidLibrary()) configureAndroidLibrary(minSdk = 21)
}

This has several shortcomings, where one of the biggest ones is that it is not compatible with Gradle Isolated Projects as you are forcing a specific order of project evaluation.

Another way to do this is to have an included build that has a custom Gradle plugin with the value hard-coded in code:

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        configureAndroidLibrary(minSdk = 21)
    }
}

and in build.gradle.kts files use:

plugins {
    id("myPlugin")
}

This works fairly well. In androidx project we have used this mechanism for years allowing us to keep hundreds of our projects consistent. However, this way makes mixes declarative values with build logic making it harder for a newcomer to a project to know where to change the value.

A much better ergonomics would be to be able to set these values in settings.gradle.kts in a declarative way and get that picked up by all the relevant Project plugins. We have a way to do just that!

pluginManagement { includeBuild("build-logic") }
plugins {
    id("mySettingsPlugin")
}
myDefaults {
    minSdk.set(21)
}

This is implemented via a custom Gradle Settings plugin:

class MySettingsPlugin : Plugin<Settings> {
    override fun apply(settings: Settings) {
        val mySettings = settings.extensions.create("myDefaults", MySettingsExtension::class.java)
        settings.gradle.beforeProject { project ->
            project.extensions.getByType(ExtraPropertiesExtension::class.java).set("myDefaults", mySettings)
        }
    }
}
abstract class MySettingsExtension {
    abstract val minSdk: Property<Int>
}

Then this is read by your custom Gradle Project plugin:

class MyPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val myDefaults = project.extensions.getByType(ExtraPropertiesExtension::class.java).get("myDefaults")
                as? MySettingsExtension ?: throw GradleException("Settings extension type mismatch")
        configureAndroidLibrary(minSdk = myDefaults.minSdk)
    }
}

This lets you keep your settings.gradle.kts nice and tidy! You can take a look at an end-to-end example repository on GitHub.

Note, if you only need to set your minSdk default, Android Gradle Plugin team has in fact done all the work already by creating the com.android.settings settings plugin, so you can use that directly if you do not want to create your own.