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

tools.aqua.turnkey.plugin.analysis.DependencyGraph.kt Maven / Gradle / Ivy

Go to download

Plugin for creating TurnKey projects that provides access to native library and Java rewriting tools.

The newest version!
/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright 2019-2024 The TurnKey Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package tools.aqua.turnkey.plugin.analysis

import kotlin.reflect.KClass
import org.jgrapht.graph.AbstractGraph
import org.jgrapht.graph.AsSubgraph
import org.jgrapht.graph.DefaultEdge
import org.jgrapht.graph.DirectedAcyclicGraph
import org.jgrapht.traverse.TopologicalOrderIterator
import tools.aqua.turnkey.plugin.util.reachSet

/**
 * Analyzes dependencies between native libraries of type [L] (analyzable) and [S] (speculative)
 * with linkage names of type [T]. This class can identify loading roots and load orders as well as
 * disambiguate bundled and system libraries.
 */
internal class DependencyGraph<
    T, L : AnalyzableNativeLibrary, S : SpeculativeNamedLibrary>(
    private val graph: AbstractGraph, DefaultEdge>,
    private val analyzableClass: KClass,
    private val speculativeClass: KClass
) {
  /** Holder for factory functions. */
  companion object {
    /**
     * Create a dependency graph for the given [libraries] with reified classes [analyzableClass]
     * and [speculativeClass].
     *
     * @throws IllegalArgumentException if a dependency loop is defined by the libraries.
     */
    fun , S : SpeculativeNamedLibrary> of(
        libraries: Iterable,
        analyzableClass: KClass,
        speculativeClass: KClass
    ): DependencyGraph {
      // populate the lookup with all existing libraries
      val existingLookup =
          buildMap> {
            libraries.forEach { new ->
              merge(new.fuzzyName, new) { old, _ ->
                val diff = old.fuzzyMismatch - new.fuzzyMismatch
                require(diff != 0) {
                  "equally fuzzy libraries $old and $new with identical fuzzy name added"
                }
                if (diff < 0) old else new
              }
            }
          }
      val lookup = existingLookup.toMutableMap>()

      // populate the graph with all existing libraries
      val graph = DirectedAcyclicGraph, DefaultEdge>(DefaultEdge::class.java)
      libraries.forEach(graph::addVertex)

      libraries.forEach { library ->
        library.libraryDependencies.forEach { dependency ->
          // if the library is already present in the lookup (as analyzable or speculative form),
          // use that
          // if not, add the speculative form present to the lookup and graph and use it
          val real =
              lookup.computeIfAbsent(dependency.fuzzyName) { dependency.also(graph::addVertex) }

          // add the dependency edge
          graph.addEdge(library, real)
        }
      }

      return DependencyGraph(graph, analyzableClass, speculativeClass)
    }

    /** Create a dependency graph for the given [libraries]. */
    inline fun <
        T, reified L : AnalyzableNativeLibrary, reified S : SpeculativeNamedLibrary> of(
        libraries: Iterable
    ): DependencyGraph = of(libraries, L::class, S::class)

    /** Create a dependency graph for the given [libraries]. */
    inline fun <
        T, reified L : AnalyzableNativeLibrary, reified S : SpeculativeNamedLibrary> of(
        vararg libraries: L
    ): DependencyGraph = of(libraries.asIterable())
  }

  /**
   * Compute a dependency subgraph containing only the given [roots] and their transitive
   * dependencies.
   */
  fun subgraphFrom(roots: Set>): DependencyGraph =
      DependencyGraph(AsSubgraph(graph, graph.reachSet(roots)), analyzableClass, speculativeClass)

  /**
   * Compute a dependency subgraph containing only the given [roots] and their transitive
   * dependencies.
   */
  fun subgraphFrom(vararg roots: NativeLibrary): DependencyGraph =
      subgraphFrom(roots.toSet())

  /** The libraries that are not dependencies of any other library in this graph. */
  val linkageRoots: Set
    get() =
        graph
            .vertexSet()
            .asSequence()
            .filter { graph.inDegreeOf(it) == 0 }
            .filterIsInstance(analyzableClass.java)
            .toSet()

  /**
   * An ordering over the bundled libraries contained in this graph that maintains load order: if
   * any library `a` depends (transitively) on `b`, this will contain `a` before `b`.
   */
  val localLibrariesInLoadOrder: List
    get() =
        TopologicalOrderIterator(graph).asSequence().filterIsInstance(analyzableClass.java).toList()

  /** The system libraries contained in this graph, in no particular order. */
  val systemLibraries: Set
    get() = graph.vertexSet().asSequence().filterIsInstance(speculativeClass.java).toSet()
}