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

com.autonomousapps.tasks.ComputeAdviceTask.kt Maven / Gradle / Ivy

There is a newer version: 2.0.2
Show newest version
package com.autonomousapps.tasks

import com.autonomousapps.TASK_GROUP_DEP_INTERNAL
import com.autonomousapps.advice.PluginAdvice
import com.autonomousapps.extension.DependenciesHandler
import com.autonomousapps.internal.Bundles
import com.autonomousapps.internal.utils.*
import com.autonomousapps.model.*
import com.autonomousapps.model.declaration.Bucket
import com.autonomousapps.model.declaration.Configurations
import com.autonomousapps.model.declaration.Declaration
import com.autonomousapps.model.intermediates.*
import com.autonomousapps.transform.StandardTransform
import org.gradle.api.DefaultTask
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.tasks.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import javax.inject.Inject

/**
 * Takes [usage][com.autonomousapps.model.intermediates.Usage] information from [ComputeUsagesTask] and emits the set of
 * transforms a user should perform to have correct and simple dependency declarations. I.e., produces the advice.
 */
@CacheableTask
abstract class ComputeAdviceTask @Inject constructor(
  private val workerExecutor: WorkerExecutor
) : DefaultTask() {

  init {
    group = TASK_GROUP_DEP_INTERNAL
    description = "Merges dependency usage reports from variant-specific computations"
  }

  @get:Input
  abstract val projectPath: Property

  @get:Input
  abstract val buildPath: Property

  @get:PathSensitive(PathSensitivity.RELATIVE)
  @get:InputFiles
  abstract val dependencyUsageReports: ListProperty

  @get:PathSensitive(PathSensitivity.RELATIVE)
  @get:InputFiles
  abstract val dependencyGraphViews: ListProperty

  @get:PathSensitive(PathSensitivity.RELATIVE)
  @get:InputFiles
  abstract val androidScoreReports: ListProperty

  @get:PathSensitive(PathSensitivity.NONE)
  @get:InputFile
  abstract val declarations: RegularFileProperty

  @get:Nested
  abstract val bundles: Property

  @get:Input
  abstract val supportedSourceSets: SetProperty

  @get:Input
  abstract val ignoreKtx: Property

  @get:Input
  abstract val kapt: Property

  @get:Optional
  @get:PathSensitive(PathSensitivity.NONE)
  @get:InputFile
  abstract val redundantJvmPluginReport: RegularFileProperty

  @get:OutputFile
  abstract val output: RegularFileProperty

  @get:OutputFile
  abstract val dependencyUsages: RegularFileProperty

  @get:OutputFile
  abstract val annotationProcessorUsages: RegularFileProperty

  @get:OutputFile
  abstract val bundledTraces: RegularFileProperty

  @TaskAction fun action() {
    workerExecutor.noIsolation().submit(ComputeAdviceAction::class.java) {
      projectPath.set([email protected])
      buildPath.set([email protected])
      dependencyUsageReports.set([email protected])
      dependencyGraphViews.set([email protected])
      androidScoreReports.set([email protected])
      declarations.set([email protected])
      bundles.set([email protected])
      supportedSourceSets.set([email protected])
      ignoreKtx.set([email protected])
      kapt.set([email protected])
      redundantPluginReport.set([email protected])
      output.set([email protected])
      dependencyUsages.set([email protected])
      annotationProcessorUsages.set([email protected])
      bundledTraces.set([email protected])
    }
  }

  interface ComputeAdviceParameters : WorkParameters {
    val projectPath: Property
    val buildPath: Property
    val dependencyUsageReports: ListProperty
    val dependencyGraphViews: ListProperty
    val androidScoreReports: ListProperty
    val declarations: RegularFileProperty
    val bundles: Property
    val supportedSourceSets: SetProperty
    val ignoreKtx: Property
    val kapt: Property
    val redundantPluginReport: RegularFileProperty
    val output: RegularFileProperty
    val dependencyUsages: RegularFileProperty
    val annotationProcessorUsages: RegularFileProperty
    val bundledTraces: RegularFileProperty
  }

  abstract class ComputeAdviceAction : WorkAction {

    override fun execute() {
      val output = parameters.output.getAndDelete()
      val dependencyUsagesOut = parameters.dependencyUsages.getAndDelete()
      val annotationProcessorUsagesOut = parameters.annotationProcessorUsages.getAndDelete()
      val bundleTraces = parameters.bundledTraces.getAndDelete()

      val projectPath = parameters.projectPath.get()
      val buildPath = parameters.buildPath.get()
      val declarations = parameters.declarations.fromJsonSet()
      val dependencyGraph = parameters.dependencyGraphViews.get()
        .map { it.fromJson() }
        .associateBy { it.name }
      val androidScore = parameters.androidScoreReports.get()
        .map { it.fromJson() }
        .run { AndroidScore.ofVariants(this) }
        .toSetOrEmpty()
      val bundleRules = parameters.bundles.get()
      val reports = parameters.dependencyUsageReports.get().mapToSet { it.fromJson() }
      val usageBuilder = UsageBuilder(
        reports = reports,
        // TODO: it would be clearer to get this from a SyntheticProject
        variants = dependencyGraph.values.map { it.variant }
      )
      val dependencyUsages = usageBuilder.dependencyUsages
      val annotationProcessorUsages = usageBuilder.annotationProcessingUsages
      val supportedSourceSets = parameters.supportedSourceSets.get()
      val isKaptApplied = parameters.kapt.get()

      val bundles = Bundles.of(
        projectPath = projectPath,
        dependencyGraph = dependencyGraph,
        bundleRules = bundleRules,
        dependencyUsages = dependencyUsages,
        ignoreKtx = parameters.ignoreKtx.get()
      )

      val dependencyAdviceBuilder = DependencyAdviceBuilder(
        projectPath = projectPath,
        buildPath = buildPath,
        bundles = bundles,
        dependencyUsages = dependencyUsages,
        annotationProcessorUsages = annotationProcessorUsages,
        declarations = declarations,
        supportedSourceSets = supportedSourceSets,
        isKaptApplied = isKaptApplied
      )

      val pluginAdviceBuilder = PluginAdviceBuilder(
        isKaptApplied = isKaptApplied,
        redundantPlugins = parameters.redundantPluginReport.fromNullableJsonSet(),
        annotationProcessorUsages = annotationProcessorUsages
      )

      val projectAdvice = ProjectAdvice(
        projectPath = projectPath,
        dependencyAdvice = dependencyAdviceBuilder.advice,
        pluginAdvice = pluginAdviceBuilder.getPluginAdvice(),
        moduleAdvice = androidScore
      )

      output.bufferWriteJson(projectAdvice)
      // These must be transformed so that the Coordinates are Strings for serialization
      dependencyUsagesOut.bufferWriteJsonMap(dependencyUsages.toStringCoordinates(buildPath))
      annotationProcessorUsagesOut.bufferWriteJsonMap(annotationProcessorUsages.toStringCoordinates(buildPath))
      // TODO consider centralizing this logic in a separate PR
      bundleTraces.bufferWriteJsonSet(dependencyAdviceBuilder.bundledTraces)
    }
  }
}

internal class PluginAdviceBuilder(
  isKaptApplied: Boolean,
  redundantPlugins: Set,
  annotationProcessorUsages: Map>
) {

  private val pluginAdvice = mutableSetOf()

  fun getPluginAdvice(): Set = pluginAdvice

  init {
    pluginAdvice.addAll(redundantPlugins)

    if (isKaptApplied) {
      val usedProcs = annotationProcessorUsages.asSequence()
        .filter { (_, usages) -> usages.any { it.bucket == Bucket.ANNOTATION_PROCESSOR } }
        .map { it.key }
        .toSet()

      // kapt is unused
      if (usedProcs.isEmpty()) {
        pluginAdvice.add(PluginAdvice.redundantKapt())
      }
    }
  }
}

internal class DependencyAdviceBuilder(
  projectPath: String,
  private val buildPath: String,
  private val bundles: Bundles,
  private val dependencyUsages: Map>,
  private val annotationProcessorUsages: Map>,
  private val declarations: Set,
  private val supportedSourceSets: Set,
  private val isKaptApplied: Boolean
) {

  /** The unfiltered advice. */
  val advice: Set

  /** Dependencies that are removed from [advice] because they match a bundle rule. Used by **Reason**. */
  val bundledTraces = mutableSetOf()

  init {
    advice = computeDependencyAdvice(projectPath)
      .plus(computeAnnotationProcessorAdvice())
      .toSortedSet()
  }

  private fun computeDependencyAdvice(projectPath: String): Sequence {
    val declarations = declarations.filterToSet { Configurations.isForRegularDependency(it.configurationName) }
    return dependencyUsages.asSequence()
      .flatMap { (coordinates, usages) ->
        StandardTransform(coordinates, declarations, supportedSourceSets, buildPath).reduce(usages).map { it to coordinates }
      }
      // "null" removes the advice
      .mapNotNull { (advice, originalCoordinates) ->
        // Make sure to do all operations here based on the originalCoordinates used in the graph.
        // The 'advice.coordinates' may be reduced - e.g. contain less capabilities in the GradleVariantIdentifier.
        // TODO could improve performance by merging has... with find...
        when {
          // The user cannot change this one:
          // https://github.com/gradle/gradle/blob/d9303339298e6206182fd1f5c7e51f11e4bdff30/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaTestFixturesPlugin.java#L68
          advice.coordinates.identifier == projectPath && advice.fromConfiguration?.equals("testFixturesApi") ?: false -> {
            null
          }
          // https://github.com/gradle/gradle/blob/d9303339298e6206182fd1f5c7e51f11e4bdff30/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaTestFixturesPlugin.java#L70
          advice.coordinates.identifier == projectPath && advice.fromConfiguration?.lowercase()?.endsWith("testimplementation") ?: false -> {
            null
          }
          advice.isAdd() && bundles.hasParentInBundle(originalCoordinates) -> {
            val parent = bundles.findParentInBundle(originalCoordinates)!!
            bundledTraces += BundleTrace.DeclaredParent(parent = parent, child = originalCoordinates)
            null
          }
          // Optionally map given advice to "primary" advice, if bundle has a primary
          advice.isAdd() -> {
            val p = bundles.maybePrimary(advice, originalCoordinates)
            if (p != advice) {
              bundledTraces += BundleTrace.PrimaryMap(primary = p.coordinates, subordinate = originalCoordinates)
            }
            p
          }
          advice.isRemove() && bundles.hasUsedChild(originalCoordinates) -> {
            val child = bundles.findUsedChild(originalCoordinates)!!
            bundledTraces += BundleTrace.UsedChild(parent = originalCoordinates, child = child)
            null
          }
          // If the advice has a used child, don't change it
          advice.isAnyChange() && bundles.hasUsedChild(originalCoordinates) -> {
            val child = bundles.findUsedChild(originalCoordinates)!!
            bundledTraces += BundleTrace.UsedChild(parent = originalCoordinates, child = child)
            null
          }
          else -> advice
        }
      }
  }

  // nb: no bundle support for annotation processors
  private fun computeAnnotationProcessorAdvice(): Sequence {
    val declarations = declarations.filterToSet { Configurations.isForAnnotationProcessor(it.configurationName) }
    return annotationProcessorUsages.asSequence()
      .flatMap { (coordinates, usages) ->
        StandardTransform(coordinates, declarations, supportedSourceSets, buildPath, isKaptApplied).reduce(usages)
      }
  }
}

/**
 * Equivalent to
 * ```
 * someBoolean.also { b ->
 *   if (b) block()
 * }
 * ```
 */
internal inline fun Boolean.andIfTrue(block: () -> Unit): Boolean {
  if (this) {
    block()
  }
  return this
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy