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.autonomousapps.internal.ArtifactAttributes
import com.autonomousapps.internal.OutputPaths
import com.autonomousapps.internal.android.AndroidGradlePluginFactory
import com.autonomousapps.internal.artifactsFor
import com.autonomousapps.internal.utils.capitalizeSafely
import com.autonomousapps.internal.utils.namedOrNull
import com.autonomousapps.model.declaration.SourceSetKind
import com.autonomousapps.services.InMemoryCache
import com.autonomousapps.tasks.*
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.file.FileCollection
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 buildType: String = variant.buildType.name
  final override val kind: SourceSetKind = variantSourceSet.variant.kind
  final override val variantNameCapitalized: String = variantName.capitalizeSafely()
  final override val taskNameSuffix: String = computeTaskNameSuffix()
  final override val compileConfigurationName = variantSourceSet.compileClasspathConfigurationName
  final override val runtimeConfigurationName = variantSourceSet.runtimeClasspathConfigurationName
  final override val kaptConfigurationName = kaptConfName()
  final override val annotationProcessorConfigurationName = "${variantName}AnnotationProcessorClasspath"
  final override val kotlinSourceFiles: FileCollection = getKotlinSources()
  final override val javaSourceFiles: FileCollection = getJavaSources()
  final override val groovySourceFiles: FileCollection = getGroovySources()
  final override val scalaSourceFiles: FileCollection = getScalaSources()

  // TODO looks like this will break with AGP >4. Seriously, check this against 7+
  final override val attributeValueJar =
    if (agpVersion.startsWith("4.")) ArtifactAttributes.ANDROID_CLASSES_JAR_4
    else ArtifactAttributes.ANDROID_CLASSES_JAR

  final override val isDataBindingEnabled: Boolean = dataBindingEnabled
  final override val isViewBindingEnabled: Boolean = viewBindingEnabled

  final override val outputPaths = OutputPaths(project, "$variantName${kind.taskNameSuffix}")

  final override val testJavaCompileName: String = "compile${variantNameCapitalized}UnitTestJavaWithJavac"
  final override val testKotlinCompileName: String = "compile${variantNameCapitalized}UnitTestKotlin"

  final override fun registerByteCodeSourceExploderTask(): TaskProvider {
    return project.tasks.register("explodeByteCodeSource$taskNameSuffix") {
      classes.setFrom(project.files())
      kotlinCompileTask()?.let { kotlinClasses.from(it.get().outputs.files.asFileTree) }
      javaClasses.from(javaCompileTask().get().outputs.files.asFileTree)

      output.set(outputPaths.explodingBytecodePath)
    }
  }

  final override fun registerManifestComponentsExtractionTask(): TaskProvider {
    return project.tasks.register(
      "extractPackageNameFromManifest$taskNameSuffix"
    ) {
      setArtifacts(project.configurations[compileConfigurationName].artifactsFor("android-manifest"))
      namespace.set(agp.namespace())
      output.set(outputPaths.manifestPackagesPath)
    }
  }

  final override fun registerFindAndroidResTask(): TaskProvider {
    return project.tasks.register("findAndroidResImports$taskNameSuffix") {
      setAndroidSymbols(
        project.configurations[compileConfigurationName].artifactsFor("android-symbol-with-package-name")
      )
      setAndroidPublicRes(project.configurations[compileConfigurationName].artifactsFor("android-public-res"))
      output.set(outputPaths.androidResPath)
    }
  }

  final override fun registerExplodeXmlSourceTask(): TaskProvider {
    return project.tasks.register("explodeXmlSource$taskNameSuffix") {
      androidLocalRes.setFrom(getAndroidRes())
      layouts(variant.sourceSets.flatMap { it.resDirectories })
      manifestFiles.setFrom(variant.sourceSets.map { it.manifestFile })
      namespace.set(agp.namespace())
      output.set(outputPaths.androidResToResUsagePath)
    }
  }

  final override fun registerExplodeAssetSourceTask(): TaskProvider {
    return project.tasks.register("explodeAssetSource$taskNameSuffix") {
      androidLocalAssets.setFrom(getAndroidAssets())
      output.set(outputPaths.androidAssetSourcePath)
    }
  }

  final override fun registerFindNativeLibsTask(): TaskProvider {
    return project.tasks.register("findNativeLibs$taskNameSuffix") {
      setAndroidJni(project.configurations[compileConfigurationName].artifactsFor(ArtifactAttributes.ANDROID_JNI))
      output.set(outputPaths.nativeDependenciesPath)
    }
  }

  final override fun registerFindAndroidLintersTask(): TaskProvider =
    project.tasks.register("findAndroidLinters$taskNameSuffix") {
      setLintJars(project.configurations[compileConfigurationName].artifactsFor(ArtifactAttributes.ANDROID_LINT))
      output.set(outputPaths.androidLintersPath)
    }

  final override fun registerFindAndroidAssetProvidersTask(): TaskProvider =
    project.tasks.register("findAndroidAssetProviders$taskNameSuffix") {
      setAssets(project.configurations[runtimeConfigurationName].artifactsFor(ArtifactAttributes.ANDROID_ASSETS))
      output.set(outputPaths.androidAssetsPath)
    }

  final override fun registerFindDeclaredProcsTask(): TaskProvider =
    project.tasks.register("findDeclaredProcs$taskNameSuffix") {
      inMemoryCacheProvider.set(InMemoryCache.register(project))
      kaptConf()?.let {
        setKaptArtifacts(it.incoming.artifacts)
      }
      annotationProcessorConf()?.let {
        setAnnotationProcessorArtifacts(it.incoming.artifacts)
      }

      output.set(outputPaths.declaredProcPath)
    }

  private fun kaptConfName(): String {
    return when (variantSourceSet.variant.kind) {
      SourceSetKind.MAIN -> "kapt$variantNameCapitalized"
      SourceSetKind.TEST -> "kaptTest"
      SourceSetKind.ANDROID_TEST -> "kaptAndroidTest"
      SourceSetKind.CUSTOM_JVM -> error("Custom JVM source sets are not supported in Android context")
    }
  }

  // Known to exist in Kotlin 1.3.61.
  private fun kotlinCompileTask(): TaskProvider? {
    return when (variantSourceSet.variant.kind) {
      SourceSetKind.MAIN -> project.tasks.namedOrNull("compile${variantNameCapitalized}Kotlin")
      SourceSetKind.TEST -> project.tasks.namedOrNull("compile${variantNameCapitalized}UnitTestKotlin")
      SourceSetKind.ANDROID_TEST -> project.tasks.namedOrNull("compile${variantNameCapitalized}AndroidTestKotlin")
      SourceSetKind.CUSTOM_JVM -> error("Custom JVM source sets are not supported in Android context")
    }
  }

  // Known to exist in AGP 3.5, 3.6, and 4.0, albeit with different backing classes (AndroidJavaCompile,
  // JavaCompile)
  private fun javaCompileTask(): TaskProvider {
    return when (variantSourceSet.variant.kind) {
      SourceSetKind.MAIN -> project.tasks.named("compile${variantNameCapitalized}JavaWithJavac")
      SourceSetKind.TEST -> project.tasks.named("compile${variantNameCapitalized}UnitTestJavaWithJavac")
      SourceSetKind.ANDROID_TEST -> project.tasks.named("compile${variantNameCapitalized}AndroidTestJavaWithJavac")
      SourceSetKind.CUSTOM_JVM -> error("Custom JVM source sets are not supported in Android context")
    }
  }

  private fun computeTaskNameSuffix(): String {
    return if (variantSourceSet.variant.kind == SourceSetKind.MAIN) {
      // "flavorDebug" -> "FlavorDebug"
      variantName.capitalizeSafely()
    } else {
      // "flavorDebug" + "Test" -> "FlavorDebugTest"
      variantName.capitalizeSafely() + variantSourceSet.variant.kind.taskNameSuffix
    }
  }

  private fun getGroovySources(): FileCollection = getSourceDirectories().matching(Language.filterOf(Language.GROOVY))
  private fun getJavaSources(): FileCollection = getSourceDirectories().matching(Language.filterOf(Language.JAVA))
  private fun getKotlinSources(): FileCollection = getSourceDirectories().matching(Language.filterOf(Language.KOTLIN))
  private fun getScalaSources(): FileCollection = getSourceDirectories().matching(Language.filterOf(Language.SCALA))

  private fun getSourceDirectories(): FileTree {
    // Java dirs regardless of whether they exist
    val javaDirs = variantSourceSet.androidSourceSets.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).asFileTree
  }

  private fun getAndroidRes(): FileTree {
    val resDirs = variant.sourceSets.flatMap {
      it.resDirectories
    }.filter { it.exists() }

    return project.files(resDirs).asFileTree.matching {
      include("**/*.xml")
    }
  }

  private fun getAndroidAssets(): FileCollection {
    val assetsDirs = variant.sourceSets.flatMap {
      it.assetsDirectories
    }.filter { it.exists() }

    return project.files(assetsDirs).asFileTree
  }
}

internal class AndroidAppAnalyzer(
  project: Project,
  variant: BaseVariant,
  agpVersion: String,
  variantSourceSet: VariantSourceSet
) : AndroidAnalyzer(
  project = project,
  variant = variant,
  variantSourceSet = variantSourceSet,
  agpVersion = agpVersion
)

internal class AndroidLibAnalyzer(
  project: Project,
  variant: BaseVariant,
  agpVersion: String,
  variantSourceSet: VariantSourceSet
) : AndroidAnalyzer(
  project = project,
  variant = variant,
  variantSourceSet = variantSourceSet,
  agpVersion = agpVersion
) {

  override fun registerAbiAnalysisTask(abiExclusions: Provider): TaskProvider {
    return project.tasks.register("abiAnalysis$taskNameSuffix") {
      jar.set(getBundleTaskOutput())
      exclusions.set(abiExclusions)
      output.set(outputPaths.abiAnalysisPath)
      abiDump.set(outputPaths.abiDumpPath)
    }
  }

  // We register this here because the concept of an "AndroidScore" for Android apps is useless. Android apps will never
  // be candidates for conversion to JVM libraries.
  override fun registerAndroidScoreTask(
    synthesizeDependenciesTask: TaskProvider,
    synthesizeProjectViewTask: TaskProvider
  ): TaskProvider {
    return project.tasks.register("computeAndroidScore$taskNameSuffix") {
      dependencies.set(synthesizeDependenciesTask.flatMap { it.outputDir })
      syntheticProject.set(synthesizeProjectViewTask.flatMap { it.output })
      output.set(outputPaths.androidScorePath)
    }
  }

  // TODO stop using bundleTask directly. Fragile.
  private fun getBundleTaskOutput(): Provider = agp.getBundleTaskOutput(variantNameCapitalized)
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy