com.autonomousapps.internal.analyzer.AndroidProjectAnalyzer.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dependency-analysis-gradle-plugin Show documentation
Show all versions of dependency-analysis-gradle-plugin Show documentation
Analyzes dependency usage in Android and JVM projects
@file:Suppress("UnstableApiUsage")
package com.autonomousapps.internal.analyzer
import com.android.build.gradle.api.BaseVariant
import com.android.build.gradle.internal.publishing.AndroidArtifacts
import com.autonomousapps.internal.OutputPaths
import com.autonomousapps.internal.android.AndroidGradlePluginFactory
import com.autonomousapps.internal.utils.capitalizeSafely
import com.autonomousapps.internal.utils.namedOrNull
import com.autonomousapps.services.InMemoryCache
import com.autonomousapps.tasks.*
import org.gradle.api.Action
import org.gradle.api.Project
import org.gradle.api.UnknownDomainObjectException
import org.gradle.api.artifacts.ArtifactView
import org.gradle.api.artifacts.Configuration
import org.gradle.api.attributes.Attribute
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileTree
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.TaskProvider
import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.register
import java.io.File
/**
* Base class for analyzing an Android project (com.android.application or com.android.library only).
*/
internal abstract class AndroidAnalyzer(
project: Project,
protected val variant: BaseVariant,
protected val variantSourceSet: VariantSourceSet,
agpVersion: String
) : AbstractDependencyAnalyzer(project) {
protected val agp = AndroidGradlePluginFactory(project, agpVersion).newAdapter()
private val dataBindingEnabled = agp.isDataBindingEnabled()
private val viewBindingEnabled = agp.isViewBindingEnabled()
final override val flavorName: String = variant.flavorName
final override val variantName: String = variant.name
final override val variantNameCapitalized: String = variantName.capitalizeSafely()
final override val compileConfigurationName = "${variantName}CompileClasspath"
final override val runtimeConfigurationName = "${variantName}RuntimeClasspath"
final override val attribute: Attribute = AndroidArtifacts.ARTIFACT_TYPE
final override val kotlinSourceFiles: FileTree = getKotlinSources()
final override val javaSourceFiles: FileTree = getJavaSources()
final override val javaAndKotlinSourceFiles: FileTree = getJavaAndKotlinSources()
final override val attributeValue = if (agpVersion.startsWith("4.")) {
"android-classes-jar"
} else {
"android-classes"
}
final override val isDataBindingEnabled: Boolean = dataBindingEnabled
final override val isViewBindingEnabled: Boolean = viewBindingEnabled
protected val outputPaths = OutputPaths(project, variantName)
// For AGP 3.5.x, this does not return any module dependencies
override val attributeValueRes = "android-symbol-with-package-name"
private val manifestArtifactView: Action =
Action {
attributes.attribute(AndroidArtifacts.ARTIFACT_TYPE, "android-manifest")
}
final override val testJavaCompileName: String = "compile${variantNameCapitalized}UnitTestJavaWithJavac"
final override val testKotlinCompileName: String = "compile${variantNameCapitalized}UnitTestKotlin"
override fun registerManifestPackageExtractionTask(): TaskProvider =
project.tasks.register("extractPackageNameFromManifest$variantNameCapitalized") {
setArtifacts(project.configurations[compileConfigurationName].incoming.artifactView(manifestArtifactView).artifacts)
output.set(outputPaths.manifestPackagesPath)
}
override fun registerAndroidResToSourceAnalysisTask(
manifestPackageExtractionTask: TaskProvider
): TaskProvider =
project.tasks.register(
"findAndroidResBySourceUsage$variantNameCapitalized"
) {
val resourceArtifacts = project.configurations[compileConfigurationName]
.incoming.artifactView {
attributes.attribute(attribute, attributeValueRes)
}.artifacts
manifestPackages.set(manifestPackageExtractionTask.flatMap { it.output })
setResources(resourceArtifacts)
javaAndKotlinSourceFiles.setFrom([email protected])
output.set(outputPaths.androidResToSourceUsagePath)
}
override fun registerAndroidResToResAnalysisTask(): TaskProvider {
return project.tasks.register(
"findAndroidResByResUsage$variantNameCapitalized"
) {
setAndroidPublicRes(project.configurations[compileConfigurationName]
.incoming.artifactView {
attributes.attribute(attribute, "android-public-res")
}.artifacts)
androidLocalRes.setFrom(getAndroidRes())
output.set(outputPaths.androidResToResUsagePath)
}
}
override fun registerFindNativeLibsTask(
locateDependenciesTask: TaskProvider
): TaskProvider? {
return project.tasks.register("findNativeLibs$variantNameCapitalized") {
val jni = project.configurations[compileConfigurationName].incoming.artifactView {
attributes.attribute(attribute, "android-jni")
}.artifacts
setArtifacts(jni)
dependencyConfigurations.set(locateDependenciesTask.flatMap { it.output })
output.set(outputPaths.nativeDependenciesPath)
}
}
override fun registerFindDeclaredProcsTask(
inMemoryCacheProvider: Provider,
locateDependenciesTask: TaskProvider
): TaskProvider {
return project.tasks.register(
"findDeclaredProcs$variantNameCapitalized"
) {
kaptConf()?.let {
setKaptArtifacts(it.incoming.artifacts)
}
annotationProcessorConf()?.let {
setAnnotationProcessorArtifacts(it.incoming.artifacts)
}
dependencyConfigurations.set(locateDependenciesTask.flatMap { it.output })
output.set(outputPaths.declaredProcPath)
outputPretty.set(outputPaths.declaredProcPrettyPath)
this.inMemoryCacheProvider.set(inMemoryCacheProvider)
}
}
private fun kaptConf(): Configuration? = try {
project.configurations["kapt$variantNameCapitalized"]
} catch (_: UnknownDomainObjectException) {
null
}
private fun annotationProcessorConf(): Configuration? = try {
project.configurations["${variantName}AnnotationProcessorClasspath"]
} catch (_: UnknownDomainObjectException) {
null
}
private fun getKotlinSources(): FileTree = getSourceDirectories().asFileTree.matching {
include("**/*.kt")
exclude("**/*.java")
}
private fun getJavaSources(): FileTree = getSourceDirectories().asFileTree.matching {
include("**/*.java")
exclude("**/*.kt")
}
private fun getJavaAndKotlinSources(): FileTree = getSourceDirectories().asFileTree
.matching {
include("**/*.java")
include("**/*.kt")
}
private fun getSourceDirectories(): ConfigurableFileCollection {
// Java dirs regardless of whether they exist
val javaDirs = variant.sourceSets.flatMap {
it.javaDirectories
}
// Kotlin dirs, only if they exist. If we filtered the above for existence, and there was no
// Java dir, then this would also be empty.
val kotlinDirs = javaDirs
.map { it.path }
.map { it.removeSuffix("java") + "kotlin" }
.map { File(it) }
.filter { it.exists() }
// Now finally filter Java dirs for existence
return project.files(javaDirs.filter { it.exists() } + kotlinDirs)
}
private fun getAndroidRes(): FileTree {
val resDirs = variant.sourceSets.flatMap {
it.resDirectories
}.filter { it.exists() }
return project.files(resDirs).asFileTree.matching {
include("**/*.xml")
}
}
override fun registerCreateVariantFilesTask(): TaskProvider {
return project.tasks.register("createVariantFiles$variantNameCapitalized") {
val androidSourceSets = variantSourceSet.androidSourceSets
val kotlinSourceSets = variantSourceSet.kotlinSourceSets ?: emptySet()
val namedJavaDirs = mutableMapOf()
val namedXmlDirs = mutableMapOf()
androidSourceSets.forEach {
namedJavaDirs[it.name] = CollectionHolder(project.files(it.javaDirectories))
namedXmlDirs[it.name] = CollectionHolder(project.files(it.resDirectories))
}
val namedKotlinDirs = kotlinSourceSets.map {
it.name to CollectionHolder(project.files(it.kotlin.srcDirs))
}.toMap()
this.namedJavaDirs.putAll(namedJavaDirs)
this.namedKotlinDirs.putAll(namedKotlinDirs)
this.namedXmlDirs.putAll(namedXmlDirs)
output.set(outputPaths.variantFilesPath)
}
}
}
internal class AndroidAppAnalyzer(
project: Project, variant: BaseVariant, agpVersion: String, variantSourceSet: VariantSourceSet
) : AndroidAnalyzer(
project = project,
variant = variant,
variantSourceSet = variantSourceSet,
agpVersion = agpVersion
) {
override fun registerClassAnalysisTask(createVariantFiles: TaskProvider): TaskProvider {
return project.tasks.register("analyzeClassUsage$variantNameCapitalized") {
kotlinCompileTask()?.let { kotlinClasses.from(it.get().outputs.files.asFileTree) }
javaClasses.from(javaCompileTask().get().outputs.files.asFileTree)
variantFiles.set(createVariantFiles.flatMap { it.output })
testJavaCompile?.let { javaCompile ->
testJavaClassesDir.set(javaCompile.flatMap { it.destinationDirectory })
}
testKotlinCompile?.let { kotlinCompile ->
testKotlinClassesDir.set(kotlinCompile.flatMap { it.destinationDirectory })
}
layouts(variant.sourceSets.flatMap { it.resDirectories })
output.set(outputPaths.allUsedClassesPath)
outputPretty.set(outputPaths.allUsedClassesPrettyPath)
}
}
override fun registerFindUnusedProcsTask(
findDeclaredProcs: TaskProvider,
importFinder: TaskProvider
): TaskProvider {
return project.tasks.register(
"findUnusedProcs$variantNameCapitalized"
) {
kotlinCompileTask()?.let { kotlinClasses.from(it.get().outputs.files.asFileTree) }
javaClasses.from(javaCompileTask().get().outputs.files.asFileTree)
imports.set(importFinder.flatMap { it.importsReport })
annotationProcessorsProperty.set(findDeclaredProcs.flatMap { it.output })
output.set(outputPaths.unusedProcPath)
}
}
// Known to exist in Kotlin 1.3.61.
private fun kotlinCompileTask() =
project.tasks.namedOrNull("compile${variantNameCapitalized}Kotlin")
// Known to exist in AGP 3.5, 3.6, and 4.0, albeit with different backing classes (AndroidJavaCompile,
// JavaCompile)
private fun javaCompileTask() =
project.tasks.named("compile${variantNameCapitalized}JavaWithJavac")
}
internal class AndroidLibAnalyzer(
project: Project, variant: BaseVariant, agpVersion: String, variantSourceSet: VariantSourceSet
) : AndroidAnalyzer(
project = project,
variant = variant,
variantSourceSet = variantSourceSet,
agpVersion = agpVersion
) {
override fun registerClassAnalysisTask(createVariantFiles: TaskProvider): TaskProvider =
project.tasks.register("analyzeClassUsage$variantNameCapitalized") {
variantFiles.set(createVariantFiles.flatMap { it.output })
jar.set(getBundleTaskOutput())
testJavaCompile?.let { javaCompile ->
testJavaClassesDir.set(javaCompile.flatMap { it.destinationDirectory })
}
testKotlinCompile?.let { kotlinCompile ->
testKotlinClassesDir.set(kotlinCompile.flatMap { it.destinationDirectory })
}
layouts(variant.sourceSets.flatMap { it.resDirectories })
output.set(outputPaths.allUsedClassesPath)
outputPretty.set(outputPaths.allUsedClassesPrettyPath)
}
override fun registerAbiAnalysisTask(
findClassesTask: TaskProvider,
abiExclusions: Provider
): TaskProvider =
project.tasks.register("abiAnalysis$variantNameCapitalized") {
jar.set(getBundleTaskOutput())
dependencies.set(findClassesTask.flatMap { it.allComponentsReport })
exclusions.set(abiExclusions)
output.set(outputPaths.abiAnalysisPath)
abiDump.set(outputPaths.abiDumpPath)
}
override fun registerFindUnusedProcsTask(
findDeclaredProcs: TaskProvider,
importFinder: TaskProvider
): TaskProvider {
return project.tasks.register(
"findUnusedProcs$variantNameCapitalized"
) {
jar.set(getBundleTaskOutput())
imports.set(importFinder.flatMap { it.importsReport })
annotationProcessorsProperty.set(findDeclaredProcs.flatMap { it.output })
output.set(outputPaths.unusedProcPath)
}
}
private fun getBundleTaskOutput(): Provider =
agp.getBundleTaskOutput(variantNameCapitalized)
}