Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .android-dynamic-modules.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Project-specific defaults used by the plugin when this repository is opened in the sandbox IDE.
applicationModule=:app
requiredModules=:app
hiddenModules=:hiddenmodule
dynamicFeaturePrefixes=df_
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
gradlew text eol=lf
gradlew.bat text eol=crlf
18 changes: 18 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: 2

updates:
- package-ecosystem: gradle
directory: /
schedule:
interval: weekly
groups:
gradle-dependencies:
patterns: ["*"]

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns: ["*"]
51 changes: 51 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Build

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
quality:
name: Test, build, and verify
runs-on: ubuntu-latest
steps:
- name: Check out sources
uses: actions/checkout@v6

- name: Set up JDK 21
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 21

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v6

- name: Run quality gates
run: ./gradlew check buildPlugin verifyPlugin --stacktrace

- name: Upload plugin distribution
uses: actions/upload-artifact@v7
with:
name: android-dynamic-modules
path: build/distributions/*.zip
if-no-files-found: error

- name: Upload reports on failure
if: failure()
uses: actions/upload-artifact@v7
with:
name: verification-reports
path: |
build/reports/tests
build/reports/pluginVerifier
build/reports/problems
if-no-files-found: ignore
13 changes: 8 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
.gradle
.intellijPlatform/
.kotlin/
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/

### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
.idea/
*.iws
*.iml
*.ipr
Expand Down Expand Up @@ -39,4 +38,8 @@ bin/
.vscode/

### Mac OS ###
.DS_Store
.DS_Store

### Plugin sandbox ###
build/idea-sandbox/
*.hprof
3 changes: 0 additions & 3 deletions .idea/.gitignore

This file was deleted.

1 change: 0 additions & 1 deletion .idea/.name

This file was deleted.

19 changes: 0 additions & 19 deletions .idea/gradle.xml

This file was deleted.

10 changes: 0 additions & 10 deletions .idea/inspectionProfiles/Project_Default.xml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/kotlinc.xml

This file was deleted.

7 changes: 0 additions & 7 deletions .idea/misc.xml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/vcs.xml

This file was deleted.

25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Changelog

All notable changes to this project are documented in this file.

## 1.0.0 - Unreleased

### Added

- Safe parsing for Groovy and Kotlin Gradle settings.
- Dynamic-feature synchronization for Groovy and Kotlin application build files.
- Project configuration through `.android-dynamic-modules.properties`.
- Required and hidden modules, filterable UI, and light/default presets.
- Unit and regression tests plus GitHub Actions verification.

### Changed

- Migrated to IntelliJ Platform Gradle Plugin 2.x, Gradle 9.5, and JDK 21.
- Replaced placeholder plugin metadata and package names.
- Applied module changes as one validated IDE write command.

### Fixed

- Prevented missing or unreadable Gradle files from being replaced with empty content.
- Added consistent support for `build.gradle.kts` and `build.gradle`.
- Removed substring-based module matching and destructive comment replacement.
21 changes: 21 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Contributing

## Development workflow

1. Create a branch from `main`.
2. Keep Gradle parsing and rendering logic independent from IntelliJ UI classes.
3. Add regression tests for every supported syntax or fixed failure mode.
4. Run `./gradlew check buildPlugin verifyPlugin` with JDK 21.
5. Open a pull request describing the affected Gradle syntax and safety behavior.

## Safety rules

- Never turn a read or parse error into empty file content.
- Prepare and validate all changes before mutating an IDE document.
- Preserve files byte-for-byte when a declaration is unsupported.
- Normalize and compare complete Gradle module paths; do not use substring matching.
- Keep file I/O away from the Swing event-dispatch thread.

## Tests

Parser fixtures should cover both Kotlin and Groovy DSL when applicable. Include cases for comments, multiline declarations, nested modules, empty dynamic-feature sets, and repeated application of the renderer.
114 changes: 112 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,113 @@
# Android Dynamic Modules Plugin
# Android Dynamic Modules

This repo is a template for Medium Guide on how to reduce Android compilation time that you can find here: https://medium.com/@fazi.ruben/from-frustration-to-efficiency-how-i-reduced-android-compilation-time-by-70-e0a57fd0bb09
An IntelliJ Platform plugin that lets Android developers enable or disable Gradle modules from the IDE. When selected modules are Android dynamic features, the plugin also keeps the base application's `dynamicFeatures` declaration aligned and starts a Gradle sync.

This repository accompanies [How I Reduced Android Compilation Time by 70%](https://medium.com/@fazi.ruben/from-frustration-to-efficiency-how-i-reduced-android-compilation-time-by-70-e0a57fd0bb09), but the implementation has been hardened for use outside the original sample project.

## What it supports

- `settings.gradle.kts` and `settings.gradle`.
- Kotlin and Groovy `include(...)` syntax.
- Multiple and multiline module declarations.
- Nested paths such as `:feature:payments`.
- Kotlin and Groovy Android `dynamicFeatures` declarations.
- Required modules, hidden modules, custom application directories, and configurable dynamic-feature prefixes.
- A fail-closed change plan: every target is parsed and validated before any IDE document is changed.
- One grouped IDE write command followed by Gradle sync only when files changed.

## Usage

1. Open a Gradle project in Android Studio or IntelliJ IDEA with the Gradle plugin installed.
2. Select **Tools → Manage Android Modules**.
3. Choose the modules to keep enabled, or apply one of the presets.
4. Select **Apply and Sync**.

The plugin normalizes module paths to Gradle's `:module:path` form. Required modules cannot be deselected. Hidden modules preserve their current state and are not shown in the dialog.

The Gradle files are version-controlled project files. Review or revert their changes with the IDE or Git before committing them.

## Project configuration

Add an optional `.android-dynamic-modules.properties` file to the project root:

```properties
applicationModule=:app
# Optional when the application module has a custom projectDir.
# applicationModuleDirectory=applications/mobile
requiredModules=:app,:core
hiddenModules=:build-logic
dynamicFeaturePrefixes=df_,dynamic_
```

All module lists are comma-separated. Without this file the defaults are:

- application and required module: `:app`;
- no hidden modules;
- dynamic-feature prefix: `df_`.

A module already listed in the application's `dynamicFeatures` declaration is recognized as dynamic even when it does not match a configured prefix.

## Safe editing model

The plugin never writes an empty fallback after a read error. Its workflow is:

1. resolve exactly one settings file and, when required, exactly one application build file;
2. parse all literal module declarations;
3. prepare the complete changes in memory;
4. verify that open IDE documents still match the loaded snapshot;
5. update the documents as one IDE command;
6. save and start Gradle sync.

If a file changed while the dialog was open, a dynamic-feature declaration is missing, both Groovy and Kotlin variants exist, or a statement is structurally invalid, the operation stops without applying the plan.

## Supported declaration examples

```kotlin
include(":app", ":feature:login")
// include(":optional")

android {
dynamicFeatures += setOf(":df_payments", ":df_profile")
}
```

```groovy
include ':app', ':feature:login'

android {
dynamicFeatures = [':df_payments', ':df_profile']
}
```

Only literal quoted module paths are managed. Computed includes, version-catalog logic, custom functions, and arbitrary Gradle expressions are intentionally left untouched.

## Development

Requirements:

- JDK 21;
- Gradle Wrapper included in this repository.

Run the quality gates:

```shell
./gradlew check buildPlugin verifyPlugin
```

Useful tasks:

```shell
./gradlew runIde
./gradlew test
./gradlew buildPlugin
```

The `app`, `optional`, and `hiddenmodule` subprojects are lightweight sandbox fixtures. Their behavior is described by the repository's `.android-dynamic-modules.properties` file.

## Compatibility

The plugin is built with IntelliJ Platform Gradle Plugin 2.x, targets IntelliJ Platform build `242` or newer, and declares a required dependency on the bundled Gradle plugin. Compatibility is checked by CI with the IntelliJ Plugin Verifier.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). Please include tests for parser, renderer, or file-planning changes.
20 changes: 2 additions & 18 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,21 +1,5 @@
plugins {
kotlin("jvm")
base
}

group = "com.example"
version = "1.0-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
testImplementation(kotlin("test"))
}

tasks.test {
useJUnitPlatform()
}
kotlin {
jvmToolchain(21)
}
description = "Required sample module used by the plugin development sandbox"
16 changes: 0 additions & 16 deletions app/src/main/kotlin/Main.kt

This file was deleted.

Loading