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

com.autonomousapps.internal.graph.GraphViewBuilder.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.internal.graph

import com.autonomousapps.internal.isJavaPlatform
import com.autonomousapps.internal.utils.rootCoordinates
import com.autonomousapps.internal.utils.toCoordinates
import com.autonomousapps.model.Coordinates
import com.autonomousapps.model.DependencyGraphView
import com.google.common.graph.Graph
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult

/**
 * Walks the resolved dependency graph to create a dependency graph rooted on the current project in a configuration
 * cache-compatible way.
 */
@Suppress("UnstableApiUsage") // Guava Graph
internal class GraphViewBuilder(
  root: ResolvedComponentResult,
  fileCoordinates: Set,
  private val localOnly: Boolean = false,
) {

  val graph: Graph

  private val graphBuilder = DependencyGraphView.newGraphBuilder()
  private val visited = mutableSetOf()
  private val componentFilter: (ResolvedDependencyResult) -> Boolean = {
    !localOnly || it.selected.id is ProjectComponentIdentifier
  }

  init {
    val rootId = root.rootCoordinates()

    walkFileDeps(fileCoordinates, rootId)
    walk(root, rootId)

    graph = graphBuilder.build()
  }

  private fun walkFileDeps(fileCoordinates: Set, rootId: Coordinates) {
    graphBuilder.addNode(rootId)

    // the only way to get flat jar file dependencies
    fileCoordinates.forEach { id ->
      graphBuilder.putEdge(rootId, id)
    }
  }

  private fun walk(root: ResolvedComponentResult, rootId: Coordinates) {
    root.dependencies.asSequence()
      // Only resolved dependencies
      .filterIsInstance()
      .filter(componentFilter)
      // AGP adds all runtime dependencies as constraints to the compile classpath, and these show
      // up in the resolution result. Filter them out.
      .filterNot { it.isConstraint }
      // For similar reasons as above
      .filterNot { it.isJavaPlatform() }
      // Sometimes there is a self-dependency?
      .filterNot { it.toCoordinates() == rootId }
      .map {
        // Might be from an included build, in which case the coordinates reflect the _requested_ dependency instead of
        // the _resolved_ dependency.
        Pair(it, it.toCoordinates())
      }
      // make reproducible output friendly to compare between executions
      .sortedWith(
        compareBy> { pair -> pair.second.javaClass.simpleName }
          .thenComparing { pair -> pair.second.identifier }
      )
      .forEach { (dependencyResult, depId) ->
        // add an edge
        graphBuilder.putEdge(rootId, depId)

        if (!visited.contains(depId)) {
          visited.add(depId)
          // recursively walk the graph in a depth-first pattern
          walk(dependencyResult.selected, depId)
        }
      }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy