/dependency-analysis-android-gradle-plugin

Analyze dependency usage in Android and Java/Kotlin projects, with a Gradle plugin

Primary LanguageKotlinApache License 2.0Apache-2.0

Use cases

  1. Produces an "advice" report which indicates:
    • Unused dependencies which should be removed.
    • Declared dependencies which are on the wrong configuration (api vs implementation)
    • Transitively used dependencies which ought to be declared directly, and on which configuration.
    • [Experimental] Dependencies which could be declared on the compileOnly configuration, as they're not required at runtime. This new features uses a heuristic for finding compileOnly candidates. Please see the KDoc on the AnalyzedJar task for details.

This is printed to console in a narrative form, and also written to disk as JSON. The JSON output has three components (see the Advice model class):

  1. Dependency (identifier + resolved version)
  2. "fromConfiguration", which is the configuration on which the dependency is currently declared. Typically "api" or "implementation". If this field is not present, that means it is null and the dependency is transitive. It should be declared directly.
  3. "toConfiguration", which is the configuration on which the dependency should be declared. If this field is not present, that means it is null and the dependency should be removed.

Compatibilities

  1. Android Gradle Plugin: this plugin is built with AGP 3.6.1. It is tested to work with AGP 3.5.3, 3.6.1, 4.0.0-beta03, and 4.1.0-alpha04 (com.android.library and com.android.application projects only).
  2. Kotlin plugin: tested with Kotlin 1.3.x (specifically 1.3.5x-7x).
  3. Java Library Plugin: tested with the java-library plugin bundled with Gradle 5.6.4, 6.0.1, 6.1.1, 6.2.1, and 6.3.
  4. Gradle: this plugin is built with Gradle 6.3. It is tested against Gradle 5.6.4, 6.0.1, 6.1.1, 6.2.1, and 6.3.
  5. It works with Java, Kotlin, and Kapt. Both multi-module JVM and Android projects.

Limitations

Given a multi-project build with two subprojects, A and B, and A depends on B (A --> B), the plugin will emit a false positive indicating B is unused in A (inaccurately) in the following scenario:

  1. Where A only uses Android R references from B and those references are not namespaced (you do not have android.namespacedRClass=true in your gradle.properties file).

This limitation may eventually be lifted.

How to use

Add to your root project. See https://plugins.gradle.org/plugin/com.autonomousapps.dependency-analysis for instructions.

plugins {
    id("com.autonomousapps.dependency-analysis") version "${latest_version}"
}

Aggregate tasks

There will be a task on the root project with the name buildHealth. Running that task will execute all tasks in all projects, and then produce the advice report, aggregated across all subprojects. The path to this report will be printed to the console.

Customizing behavior

The plugin provides a dependencyAnalysis {} extension (com.autonomousapps.DependencyAnalysisExtension) for configuration.

Customizing variants to analyze

If your Android project uses flavors or custom build types, you may wish to change the default variant that is analyzed. By default, this plugin will analyze the debug variant for Android, and the main source set for Java. To customize this, add the following to your root build.gradle[.kts]

dependencyAnalysis {
  setVariants("my", "custom", "variants")
}

If the plugin cannot find any variants by these names, it will first fallback to the defaults ("debug" and "main"), and then simply ignore the given subproject.

Failure conditions

By default, the plugin's tasks will not fail a build upon detection of dependency issues; they simply print results to console and to disk. If you would prefer your build to fail if there are issues, you can configure the plugin as follows:

dependencyAnalysis {
  issues {
    // Default for all issue types is "warn"
    // Can set behavior for all issue types
    onAny { 
      fail() // or...
      warn() // or...
      ignore() 
    }
    // Or configure behavior per-type
    onUnusedDependencies { ... }
    onUsedTransitiveDependencies { ... }
    onIncorrectConfiguration { ... }
  }
}

It is also possible to tell the plugin to ignore any issue relating to specified dependencies. Both the fail() and warn() except a String varargs or Iterable<String>. For example:

dependencyAnalysis {
  issues {
    onUnusedDependencies {
      fail("org.jetbrains.kotlin:kotlin-stdlib-jdk7", "androidx.core:core-ktx")
    }
  }
}

Please note that the ignore() method takes no argument, as it already tells the plugin to ignore everything.

If your build fails, the plugin will print the reason why to console, along with the path to the report. Please see Use cases, above, for help on understanding the report.

Per-project tasks

You can also run some tasks on individual projects.

For the advice report,

  1. Run the task ./gradlew my-project:adviceDebug, where "Debug" is the variant you're interested in. This will be "Main" for java-library projects (where the variant is based on source set name). It will produce advice reports in the build/reports/dependency-analysis/<variant>/ directory.

At this time, that is the only recommended task for end-users. If you are interested in the other tasks, please run ./gradlew tasks --group dependency-analysis or ./gradlew my-project:tasks --group dependency-analysis

Flowchart

This flowchart was built with Mermaid and is experimental. It's an attempt to provide some high-level documentation additional reference.

Flowchart