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

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

There is a newer version: 2.6.1
Show newest version
// Copyright (c) 2024. Tony Robalik.
// SPDX-License-Identifier: Apache-2.0
package com.autonomousapps.tasks

import com.autonomousapps.internal.utils.bufferWriteJson
import com.autonomousapps.internal.utils.fromJson
import com.autonomousapps.internal.utils.fromJsonSet
import com.autonomousapps.internal.utils.fromNullableJsonSet
import com.autonomousapps.model.*
import com.autonomousapps.model.internal.Capability
import com.autonomousapps.model.internal.Dependency
import com.autonomousapps.model.internal.FlatDependency
import com.autonomousapps.model.internal.IncludedBuildDependency
import com.autonomousapps.model.internal.ModuleDependency
import com.autonomousapps.model.internal.PhysicalArtifact
import com.autonomousapps.model.internal.ProjectDependency
import com.autonomousapps.model.internal.intermediates.producer.ExplodedJar
import com.autonomousapps.model.internal.intermediates.AndroidAssetDependency
import com.autonomousapps.model.internal.intermediates.AndroidManifestDependency
import com.autonomousapps.model.internal.intermediates.AndroidResDependency
import com.autonomousapps.model.internal.intermediates.AnnotationProcessorDependency
import com.autonomousapps.model.internal.intermediates.DependencyView
import com.autonomousapps.model.internal.intermediates.InlineMemberDependency
import com.autonomousapps.model.internal.intermediates.NativeLibDependency
import com.autonomousapps.model.internal.intermediates.ServiceLoaderDependency
import com.autonomousapps.model.internal.intermediates.TypealiasDependency
import com.autonomousapps.services.InMemoryCache
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.gradle.workers.WorkerExecutor
import java.io.File
import javax.inject.Inject

@CacheableTask
abstract class SynthesizeDependenciesTask @Inject constructor(
  private val workerExecutor: WorkerExecutor
) : DefaultTask() {

  init {
    description = "Re-synthesize dependencies from analysis"
  }

  @get:Internal
  abstract val inMemoryCache: Property

  /** Needed to disambiguate other projects that might have otherwise identical inputs. */
  @get:Input
  abstract val projectPath: Property

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

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

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

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

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

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

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

  /*
   * Android-specific and therefore optional.
   */

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

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

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

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

  @get:OutputDirectory
  abstract val outputDir: DirectoryProperty

  @TaskAction fun action() {
    workerExecutor.noIsolation().submit(SynthesizeDependenciesWorkAction::class.java) {
      inMemoryCache.set([email protected])
      compileDependencies.set([email protected])
      physicalArtifacts.set([email protected])
      explodedJars.set([email protected])
      inlineMembers.set([email protected])
      typealiases.set([email protected])
      serviceLoaders.set([email protected])
      annotationProcessors.set([email protected])
      manifestComponents.set([email protected])
      androidRes.set([email protected])
      nativeLibs.set([email protected])
      androidAssets.set([email protected])
      outputDir.set([email protected])
    }
  }

  interface SynthesizeDependenciesParameters : WorkParameters {
    val inMemoryCache: Property
    val compileDependencies: RegularFileProperty
    val physicalArtifacts: RegularFileProperty
    val explodedJars: RegularFileProperty
    val inlineMembers: RegularFileProperty
    val typealiases: RegularFileProperty
    val serviceLoaders: RegularFileProperty
    val annotationProcessors: RegularFileProperty

    // Android-specific and therefore optional
    val manifestComponents: RegularFileProperty
    val androidRes: RegularFileProperty
    val nativeLibs: RegularFileProperty
    val androidAssets: RegularFileProperty

    val outputDir: DirectoryProperty
  }

  abstract class SynthesizeDependenciesWorkAction : WorkAction {

    private val builders = sortedMapOf()

    override fun execute() {
      val outputDir = parameters.outputDir

      val dependencies = parameters.compileDependencies.fromJson().coordinates
      val physicalArtifacts = parameters.physicalArtifacts.fromJsonSet()
      val explodedJars = parameters.explodedJars.fromJsonSet()
      val inlineMembers = parameters.inlineMembers.fromJsonSet()
      val typealiases = parameters.typealiases.fromJsonSet()
      val serviceLoaders = parameters.serviceLoaders.fromJsonSet()
      val annotationProcessors = parameters.annotationProcessors.fromJsonSet()
      // Android-specific and therefore optional
      val manifestComponents = parameters.manifestComponents.fromNullableJsonSet()
      val androidRes = parameters.androidRes.fromNullableJsonSet()
      val nativeLibs = parameters.nativeLibs.fromNullableJsonSet()
      val androidAssets = parameters.androidAssets.fromNullableJsonSet()

      physicalArtifacts.forEach { artifact ->
        builders.merge(
          artifact.coordinates,
          DependencyBuilder(artifact.coordinates).apply { files.add(artifact.file) },
          DependencyBuilder::concat
        )
      }

      // A dependency can appear in the graph even though it's just a .pom (.module) file. E.g., kotlinx-coroutines-core.
      // This is a fallback so all such dependencies have a file written to disk.
      dependencies.forEach { dependency ->
        // Do not add dependencies that are already known again
        val coordinatesAlreadyKnown = builders.values.any {
          it.coordinates == dependency
          // TODO(tsr): including the following in the check is too aggressive and can lead to failures in the
          //  ProjectVariant::dependencies function when looking for a file. This can happen, I think, when a dependency
          //  is specified as an external dependency but ends up getting resolved as a local project dependency instead.
          //  The simplest thing is to include the json file for this dep twice. Wastes some disk space (etc), but
          //  solves the problem. I doubt this is the best solution to the problem.
          // If the dependency is pointing at a project, there might already be an artifact
          // stored under matching IncludedBuildCoordinates.
          // || (it.coordinates is IncludedBuildCoordinates
          // && dependency.identifier == it.coordinates.resolvedProject.identifier
          // && dependency.gradleVariantIdentification.variantMatches(it.coordinates.resolvedProject))
        }
        if (!coordinatesAlreadyKnown) {
          builders.merge(
            dependency,
            DependencyBuilder(dependency),
            DependencyBuilder::concat
          )
        }
      }

      merge(explodedJars)
      merge(inlineMembers)
      merge(typealiases)
      merge(serviceLoaders)
      merge(annotationProcessors)
      merge(manifestComponents)
      merge(androidRes)
      merge(nativeLibs)
      merge(androidAssets)

      // Write every dependency to its own file in the output directory
      builders.values.asSequence()
        .map { it.build() }
        .forEach { dependency ->
          val coordinates = dependency.coordinates
          outputDir.file(coordinates.toFileName()).get().asFile.bufferWriteJson(dependency)
        }
    }

    private fun > merge(dependencies: Set) {
      dependencies.forEach {
        builders.merge(
          it.coordinates,
          DependencyBuilder(it.coordinates).apply { capabilities.addAll(it.toCapabilities()) },
          DependencyBuilder::concat
        )
      }
    }
  }

  private class DependencyBuilder(val coordinates: Coordinates) {

    val capabilities: MutableList = mutableListOf()
    val files: MutableList = mutableListOf()

    fun concat(other: DependencyBuilder): DependencyBuilder {
      files.addAll(other.files)
      other.capabilities.forEach { otherCapability ->
        val existing = capabilities.find { it.javaClass.canonicalName == otherCapability.javaClass.canonicalName }
        if (existing != null) {
          val merged = existing.merge(otherCapability)
          capabilities.remove(existing)
          capabilities.add(merged)
        } else {
          capabilities.add(otherCapability)
        }
      }
      return this
    }

    fun build(): Dependency {
      val capabilities: Map = capabilities.associateBy { it.javaClass.canonicalName }
      return when (coordinates) {
        is ProjectCoordinates -> ProjectDependency(coordinates, capabilities, files)
        is ModuleCoordinates -> ModuleDependency(coordinates, capabilities, files)
        is FlatCoordinates -> FlatDependency(coordinates, capabilities, files)
        is IncludedBuildCoordinates -> IncludedBuildDependency(coordinates, capabilities, files)
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy