Post

Gradle peace in multi module projects

Complications


Hello fellow Gradle user, you’ve probably copy pasted the configuration in every module you’ve had, no worries, today you’ll forget copy pasting most of the code in the gradle configuration.

Gradle is slow we get that, but building multi modules helps that, since gradle rebuilds only the modules affected of changes.

One day you woke up and chose violence, copy pasting the same config over and over again in every build.gradle file you had for every new module you created.

You decided to create a new module (hope this post doesn’t hurt your ego)

then another module

and you had to duplicate this in every gradle file, you hated your life because Gradle doesn’t simplify this out of the box, but don’t worry, we all hate Gradle because it’s complicated for no reason and a build system shouldn’t be, but hey, maybe Bazel will get more attention in the future.

Solution


One day I woke up mad (it was a Tuesday, doing a code review to myself, hope i’m not the only one doing this to himself) and decided to live up to my motto, “a good programmer doesn’t write the same code twice”, in this case I didn’t want to write the same Gradle config twice, so I chose to fight.

Then in my project build.gradle I chose peace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
subprojects {

    switch (it.name) {
        case "app":
            apply plugin: 'com.android.application'
            apply plugin: 'kotlin-android'
            apply plugin: 'kotlin-kapt'
            apply plugin: 'kotlin-parcelize'
            dependencies {
                implementation fileTree(dir: 'libs', include: ['*.jar'])
            }
            applyAndroid(it, true)
            break

        //setup gradle for Kotlin libs
        case ["enums", "regex"]:
            apply plugin: 'java-library'
            apply plugin: 'kotlin'
            applyKotlinModule(it)
            break

        default:
            //setup gradle for libraries
            apply plugin: 'com.android.library'
            apply plugin: 'kotlin-android'
            apply plugin: 'com.github.dcendents.android-maven'
            apply plugin: 'kotlin-parcelize'
            applyAndroid(it, false)

            dependencies {
                implementation fileTree(dir: 'libs', include: ['*.jar'])
                // Dependencies for local unit tests
                testImplementation "junit:junit:$junitVersion"
                testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
                testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"
                testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"
                testImplementation "org.robolectric:robolectric:$robolectricVersion"
                testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
                testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"

                // AndroidX Test - Instrumented testing
                androidTestImplementation "androidx.test.ext:junit:$androidXTestExtKotlinRunnerVersion"
                androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion"
            }

            break
    }

}
1
2
3
4
5
6
7
8
9
def applyKotlinModule(project) {
    project.java {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    project.dependencies {
        implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def applyAndroid(project, buildConfigCase) {
    project.android {
        compileSdkVersion compileVersion

        defaultConfig {
            minSdkVersion minVersion
            targetSdkVersion compileVersion
            versionCode verCode
            versionName verName
            testInstrumentationRunner testRunner
        }

        compileOptions {
            sourceCompatibility = 1.8
            targetCompatibility = 1.8
        }

        kotlinOptions {
            jvmTarget = "1.8"
        }

        testOptions.unitTests {
            includeAndroidResources = true
        }

        buildFeatures {
            aidl = false
            renderScript = false
            resValues = false
            shaders = false
            buildConfig = buildConfigCase
        }
    }

}

What I did was check for every subproject’s name and create the configuration for that, if it’s the :app then there can only be one and everything else was a library, either a Kotlin or Android based one.

You can check this approach in my open source project.

Feature modules (feature folders)

One day you wanted to call a function and have the proper dependencies included?

1
2
3
4
5
6
void applyComposeUIDeps(project) {
    project.dependencies {
        implementation "androidx.compose.ui:ui:$compose_version"
        implementation "androidx.compose.ui:ui-tooling:$compose_version"
    }
}

I gotchu

Drawback

When it comes to feature folders you need to exempt them when you’re using this approach, since you’re basing this approach on names of the modules every name has to be unique, that’s the drawback, sometimes you may end up with longer names.

How to solve the drawback?

1
2
3
4
5
6
7
8
static def excludeParentFoldersFor(project) {
    def name = ""
    def allProjects = project.getAllprojects()
    if (allProjects.size() > 1) {
        name = allProjects.first().name
    }
    return name
}
1
2
3
4
5
6
7
8
9
10
subprojects {

    ///

    //ignore parent folders
        case excludeParentFoldersFor(it):
            break
    }
}

End result

some of your feature build.gradle modules will look really short

You can check this approach in my other open source project.

You can go forward and do many more of these abstractions and forget about copy pasting gradle config once and for all.

Goodbye

Don’t forget to drink water, it’s summer and it’s hot.

Thanks for reading, stay cool and also cool.

This post is licensed under CC BY 4.0 by the author.