All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.autonomousapps.internal.analyzer.AndroidProjectAnalyzer.kt Maven / Gradle / Ivy

There is a newer version: 2.0.2
Show newest version
@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)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy