An experimental Gradle Plugin that automatically maps and includes modules in your builds
Magic Modules
An experimental Gradle Plugin that automatically maps and includes modules in your builds.
What is this?
// Blog post with the full motivation to come! Stay tunned
For large Android projects hosted in mono repos, management for module names might be a real pain, specially when we have lots of moving parts under a structure driven by nested Gradle subprojects.
This experimental plugin attemps to solve that. It parses a project tree like this
.
├── app
│ └── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── res
├── build.gradle
├── buildSrc
│ ├── build.gradle.kts
│ └── src
│ └── main
│ └── kotlin
├── common
│ ├── core
│ │ ├── build.gradle
│ │ └── src
│ │ └── main
│ └── utils
│ ├── build.gradle.kts
│ └── src
│ └── main
├── features
│ ├── home
│ │ ├── build.gradle
│ │ └── src
│ │ └── main
│ └── login
│ ├── build.gradle
│ └── src
│ └── main
|
|
└── settings.gradle
and
- it automatically includes all founded modules in
settings.gradle
- it writes 2 Kotlin files under your
buildSrc/src/main/kotlin
:
// Generated by MagicModules plugin. Mind your Linters!
import kotlin.String
import kotlin.collections.List
object Libraries {
const val FEATURES_HOME: String = ":features:home"
const val FEATURES_LOGIN: String = ":features:login"
const val COMMON_CORE: String = ":common:core"
const val COMMON_UTILS: String = ":common:utils"
val allAvailable: List<String> =
listOf(
FEATURES_HOME,
FEATURES_LOGIN,
COMMON_CORE,
COMMON_UTILS
)
}
// Generated by MagicModules plugin. Mind your Linters!
import kotlin.String
import kotlin.collections.List
object Applications {
const val APP: String = ":app"
val allAvailable: List<String> =
listOf(
APP
)
}
In this way, refactors around the project structure will become a bit easier, since build.gradle
configuration
dependencies {
implementation project(Libraries.COMMON_UTILS)
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation ...
}
will break if common/utils
moves around. The new constant under buildSrc
will be mapped and will be ready to use.
We can also add all the libraries to one monotlithic app easily
dependencies {
Libraries.allAvailable.each { implementation project(it) }
}
Setup
To try this plugin out, you can grab a snapshot build from Jitpack. Add this snippet in your settings.gradle
file
buildscript {
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
dependencies {
classpath 'com.github.dotanuki-labs:magic-modules:<plugin-version>'
}
}
apply plugin: "io.labs.dotanuki.magicmodules"
and remove all include
statements
include 'app'
include 'featureA'
include 'featureB'
include 'featureC'
include ...
They are not needed anymore.
If your project uses a multi-application layout, with standalone apps for your features/screens, you can opt-in to not include all com.android.application
modules in order to reduce configuration times locally and eventually build times on CI.
rootProject.name='awesome-project'
apply plugin: "io.labs.dotanuki.magicmodules"
magicModules {
includeApps = false
}
include ':app'
Matching Gradle build files
This plugin walks your project tree and inspect all the build.gradle
and build.gradle.kts
files in order to learn if the related module matches an Android library, a JVM library or an Android application. This means that Magic Modules
is sensitive on how you apply plugins in your Gradle build scripts, for instance using
apply plugin: 'com.android.library'
or
plugins {
kotlin("jvm")
}
This plugin does a best-effort attempt in order to catch all the common cases, but it might not work at all if you
- (1) have some strategy to share build logic accross Gradle modules and
- (2) applied the
application
orlibrary
plugin using such shared build logic for your modules
Building and testing
To build this plugin and publish it locally for testing
./gradlew publishToMavenLocal
To run all the checks, including integration tests
./gradlew ktlintCheck test
To check logs generated by this plugin and learn how this plugin works, we have a sample project available
cd sample
./gradlew clean app:assembleDebug --info | grep MagicModulesPlugin
Limitations
The main limitation I've found with this approach is that - right now - the plugin generates the Libraries.kt
and Applications.kt
under the main source set of buildSrc
, which means eventually issues with linters that run for buildSrc
files.
I need more time in order to figure out if we can have such generated files under buildSrc/build
somehow.
Further work
I realised that
- It might be useful to configure the output folder/package for
Libraries.kt
andApplications.kt
- It might be useful to grab more Gradle build script matchers using the plugin configuration
Author
Coded by Ubiratan Soares (follow me on Twitter)