mirror of
				https://github.com/GayPizzaSpecifications/darwin-apk.git
				synced 2025-11-03 23:49:38 +00:00 
			
		
		
		
	Quick n' dirty SwiftGraph
This commit is contained in:
		@ -8,10 +8,14 @@ let package = Package(
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  dependencies: [
 | 
					  dependencies: [
 | 
				
			||||||
    .package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.1"),
 | 
					    .package(url: "https://github.com/apple/swift-argument-parser", from: "1.6.1"),
 | 
				
			||||||
 | 
					    .package(url: "https://github.com/davecom/SwiftGraph", from: "3.1.0"),
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  targets: [
 | 
					  targets: [
 | 
				
			||||||
    .target(
 | 
					    .target(
 | 
				
			||||||
      name: "darwin-apk",
 | 
					      name: "darwin-apk",
 | 
				
			||||||
 | 
					      dependencies: [
 | 
				
			||||||
 | 
					        .product(name: "SwiftGraph", package: "SwiftGraph"),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
      path: "Sources/apk",
 | 
					      path: "Sources/apk",
 | 
				
			||||||
    ),
 | 
					    ),
 | 
				
			||||||
    .testTarget(
 | 
					    .testTarget(
 | 
				
			||||||
 | 
				
			|||||||
@ -4,103 +4,71 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import Foundation
 | 
					import Foundation
 | 
				
			||||||
 | 
					import SwiftGraph
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class ApkPackageGraph {
 | 
					public class ApkPackageGraph {
 | 
				
			||||||
  public let pkgIndex: ApkIndex
 | 
					  let graph: UnweightedGraph<ApkIndex.Index>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private var _nodes = [ApkPackageGraphNode]()
 | 
					  public var shallowIsolates: [ApkIndex.Index] {
 | 
				
			||||||
 | 
					    self.graph.indices.lazy.filter { index in !self.graph.edges.contains { edge in edge.endIndex == index } }
 | 
				
			||||||
  public var nodes: [ApkPackageGraphNode] { self._nodes }
 | 
					      .map { index in self.graph.vertexAtIndex(index) }
 | 
				
			||||||
  public var shallowIsolates: [ApkPackageGraphNode] { self._nodes.filter(\.parents.isEmpty) }
 | 
					  }
 | 
				
			||||||
  public var deepIsolates: [ApkPackageGraphNode] { self._nodes.filter(\.children.isEmpty) }
 | 
					  public var deepIsolates: [ApkIndex.Index] {
 | 
				
			||||||
 | 
					    self.graph.indices.lazy.filter { index in self.graph.edgesForIndex(index).isEmpty }
 | 
				
			||||||
  public init(index: ApkIndex) {
 | 
					      .map { index in self.graph.vertexAtIndex(index) }
 | 
				
			||||||
    self.pkgIndex = index
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public func buildGraphNode() {
 | 
					  public init(from pkgIndex: inout ApkIndex) throws(GraphError) {
 | 
				
			||||||
    var provides = [String: Int]()
 | 
					    self.graph = UnweightedGraph<ApkIndex.Index>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (idx, package) in self.pkgIndex.packages.enumerated() {
 | 
					    // Add each package to the graph
 | 
				
			||||||
      provides[package.name] = idx
 | 
					    for pkgIdx in pkgIndex.packages.indices {
 | 
				
			||||||
      for provision in package.provides {
 | 
					      // Skip packages already added by requirements
 | 
				
			||||||
        if !provides.keys.contains(provision.name) {
 | 
					      guard !self.graph.vertexInGraph(vertex: pkgIdx) else {
 | 
				
			||||||
          provides[provision.name] = idx
 | 
					        continue
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					      // Add package ID as a vertex
 | 
				
			||||||
 | 
					      let u = self.graph.addVertex(pkgIdx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (id, package) in pkgIndex.packages.enumerated() {
 | 
					      // Add dependent packages to the graphs and link them via edges
 | 
				
			||||||
      let children: [ApkIndexRequirementRef] = package.dependencies.compactMap { dependency in
 | 
					      let pkg = pkgIndex.packages[pkgIdx]
 | 
				
			||||||
        guard !dependency.requirement.versionSpec.conflict,
 | 
					      for dep in pkg.dependencies {
 | 
				
			||||||
            let id = provides[dependency.requirement.name] else {
 | 
					        // Resolve package dependency
 | 
				
			||||||
          return nil
 | 
					        guard let depIdx = pkgIndex.resolveIndex(requirement: dep.requirement) else {
 | 
				
			||||||
 | 
					          // It's okay to skip missing conflicts
 | 
				
			||||||
 | 
					          if dep.requirement.versionSpec.isConflict {
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          // Didn't find a satisfactory dependency in the index
 | 
				
			||||||
 | 
					          //throw .missingDependency(dep.requirement, pkg)
 | 
				
			||||||
 | 
					          print("WARN: Couldn't satisfy \"\(dep.requirement)\" required by \"\(pkg.nameDescription)\"")
 | 
				
			||||||
 | 
					          continue
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return .init(self, id: id, constraint: .dep(version: dependency.requirement.versionSpec))
 | 
					
 | 
				
			||||||
      } + package.installIf.compactMap { installIf in
 | 
					        // Get the graph vertex of dependency, or add it to the graph if it doesn't exist
 | 
				
			||||||
        guard let id = provides[installIf.requirement.name] else {
 | 
					        let v = self.graph.indexOfVertex(depIdx) ?? self.graph.addVertex(depIdx)
 | 
				
			||||||
          return nil
 | 
					
 | 
				
			||||||
        }
 | 
					        self.graph.addEdge(fromIndex: u, toIndex: v, directed: true)
 | 
				
			||||||
        return .init(self, id: id, constraint: .installIf(version: installIf.requirement.versionSpec ))
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      self._nodes.append(.init(self,
 | 
					 | 
				
			||||||
        id: id,
 | 
					 | 
				
			||||||
        children: children
 | 
					 | 
				
			||||||
      ))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var reverseDependencies = [ApkIndexRequirementRef: [ApkIndexRequirementRef]]()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (index, node) in self._nodes.enumerated() {
 | 
					 | 
				
			||||||
      for child in node.children {
 | 
					 | 
				
			||||||
        reverseDependencies[child, default: []].append(
 | 
					 | 
				
			||||||
          .init(self, id: index, constraint: child.constraint)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (ref, parents) in reverseDependencies {
 | 
					 | 
				
			||||||
      self._nodes[ref.packageID].parents = parents
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extension ApkPackageGraph {
 | 
					extension ApkPackageGraph {
 | 
				
			||||||
  func findDependencyCycle(node: ApkPackageGraphNode) -> (ApkPackageGraphNode, ApkPackageGraphNode)? {
 | 
					  public func sorted(breakCycles: Bool = true) throws(SortError) -> [ApkIndex.Index] {
 | 
				
			||||||
    var resolving = Set<Int>()
 | 
					    if !breakCycles {
 | 
				
			||||||
    var visited = Set<Int>()
 | 
					      guard let sorted = self.graph.topologicalSort() else {
 | 
				
			||||||
    return self.findDependencyCycle(node: node, &resolving, &visited)
 | 
					        throw .cyclicDependency(cycles: self.graph.detectCycles().description)
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  func findDependencyCycle(
 | 
					 | 
				
			||||||
    node: ApkPackageGraphNode,
 | 
					 | 
				
			||||||
    _ resolving: inout Set<Int>,
 | 
					 | 
				
			||||||
    _ visited: inout Set<Int>
 | 
					 | 
				
			||||||
  ) -> (ApkPackageGraphNode, ApkPackageGraphNode)? {
 | 
					 | 
				
			||||||
    for dependency in node.children {
 | 
					 | 
				
			||||||
      let depNode = self._nodes[dependency.packageID]
 | 
					 | 
				
			||||||
      if resolving.contains(depNode.packageID) {
 | 
					 | 
				
			||||||
        return (node, depNode)
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if !visited.contains(depNode.packageID) {
 | 
					 | 
				
			||||||
        resolving.insert(depNode.packageID)
 | 
					 | 
				
			||||||
        if let cycle = findDependencyCycle(node: depNode, &resolving, &visited) {
 | 
					 | 
				
			||||||
          return cycle
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        resolving.remove(depNode.packageID)
 | 
					 | 
				
			||||||
        visited.insert(depNode.packageID)
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      return sorted.reversed()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    fatalError("Not yet implemented")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return nil
 | 
					    /*
 | 
				
			||||||
  }
 | 
					    var results = [[ApkIndex.Index]]()
 | 
				
			||||||
 | 
					 | 
				
			||||||
  public func parallelOrderSort(breakCycles: Bool = true) throws(SortError) -> [[ApkPackageGraphNode]] {
 | 
					 | 
				
			||||||
    var results = [[ApkPackageGraphNode]]()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Map all nodes to all of their children, remove any self dependencies
 | 
					    // Map all nodes to all of their children, remove any self dependencies
 | 
				
			||||||
 | 
					    var working = self.graph.isDAG
 | 
				
			||||||
    var working = self._nodes.reduce(into: [ApkPackageGraphNode: Set<ApkPackageGraphNode>]()) { d, node in
 | 
					    var working = self._nodes.reduce(into: [ApkPackageGraphNode: Set<ApkPackageGraphNode>]()) { d, node in
 | 
				
			||||||
      d[node] = Set(node.children.filter { child in
 | 
					      d[node] = Set(node.children.filter { child in
 | 
				
			||||||
        if case .dep(let version) = child.constraint {
 | 
					        if case .dep(let version) = child.constraint {
 | 
				
			||||||
@ -134,9 +102,7 @@ extension ApkPackageGraph {
 | 
				
			|||||||
          break
 | 
					          break
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let cycles = working.keys.compactMap { node in
 | 
					        let cycles = self.graph.detectCycles()
 | 
				
			||||||
          self.findDependencyCycle(node: node)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Error if cycle breaking is turned off
 | 
					        // Error if cycle breaking is turned off
 | 
				
			||||||
        if !breakCycles {
 | 
					        if !breakCycles {
 | 
				
			||||||
@ -159,14 +125,25 @@ extension ApkPackageGraph {
 | 
				
			|||||||
        d[node.key] = node.value.subtracting(set)
 | 
					        d[node.key] = node.value.subtracting(set)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return results
 | 
					extension ApkPackageGraph {
 | 
				
			||||||
 | 
					  public enum GraphError: Error, LocalizedError {
 | 
				
			||||||
 | 
					    case missingDependency(ApkVersionRequirement, ApkIndexPackage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public var errorDescription: String? {
 | 
				
			||||||
 | 
					      switch self {
 | 
				
			||||||
 | 
					      case .missingDependency(let r, let p): "Couldn't satisfy \"\(r)\" required by \"\(p.nameDescription)\""
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public enum SortError: Error, LocalizedError {
 | 
					  public enum SortError: Error, LocalizedError {
 | 
				
			||||||
    case cyclicDependency(cycles: String)
 | 
					    case cyclicDependency(cycles: String)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var errorDescription: String {
 | 
					    public var errorDescription: String? {
 | 
				
			||||||
      switch self {
 | 
					      switch self {
 | 
				
			||||||
      case .cyclicDependency(let cycles): "Dependency cycles found:\n\(cycles)"
 | 
					      case .cyclicDependency(let cycles): "Dependency cycles found:\n\(cycles)"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
				
			|||||||
@ -5,6 +5,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import Foundation
 | 
					import Foundation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
public class ApkPackageGraphNode {
 | 
					public class ApkPackageGraphNode {
 | 
				
			||||||
  private weak var _graph: ApkPackageGraph?
 | 
					  private weak var _graph: ApkPackageGraph?
 | 
				
			||||||
  let packageID: Int
 | 
					  let packageID: Int
 | 
				
			||||||
@ -48,3 +49,4 @@ extension ApkPackageGraphNode: CustomStringConvertible {
 | 
				
			|||||||
    return result
 | 
					    return result
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
				
			|||||||
@ -3,6 +3,7 @@
 | 
				
			|||||||
 * SPDX-License-Identifier: Apache-2.0
 | 
					 * SPDX-License-Identifier: Apache-2.0
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
struct ApkIndexRequirementRef {
 | 
					struct ApkIndexRequirementRef {
 | 
				
			||||||
  private weak var _graph: ApkPackageGraph?
 | 
					  private weak var _graph: ApkPackageGraph?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -62,3 +63,4 @@ extension ApkIndexRequirementRef: CustomStringConvertible {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
				
			|||||||
@ -14,28 +14,41 @@ struct DpkGraphCommand: AsyncParsableCommand {
 | 
				
			|||||||
    let graph: ApkPackageGraph
 | 
					    let graph: ApkPackageGraph
 | 
				
			||||||
    do {
 | 
					    do {
 | 
				
			||||||
      let localRepositories = try await ApkRepositoriesConfig()
 | 
					      let localRepositories = try await ApkRepositoriesConfig()
 | 
				
			||||||
      graph = ApkPackageGraph(index: try await ApkIndexReader.resolve(localRepositories.repositories, fetch: .lazy))
 | 
					 | 
				
			||||||
      graph.buildGraphNode()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      try graph.pkgIndex.description.write(to: URL(filePath: "packages.txt"), atomically: false, encoding: .utf8)
 | 
					      var timerStart = DispatchTime.now()
 | 
				
			||||||
 | 
					      var pkgIndex = try await ApkIndexReader.resolve(localRepositories.repositories, fetch: .lazy)
 | 
				
			||||||
 | 
					      print("Index build took \(timerStart.distance(to: .now()).seconds) seconds")
 | 
				
			||||||
 | 
					      try pkgIndex.description.write(to: URL(filePath: "packages.txt"), atomically: false, encoding: .utf8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      timerStart = DispatchTime.now()
 | 
				
			||||||
 | 
					      try graph = ApkPackageGraph(from: &pkgIndex)
 | 
				
			||||||
 | 
					      print("Graph build took \(timerStart.distance(to: .now()).seconds) seconds")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      try graph.shallowIsolates.map { pkgIndex.packages[$0].nameDescription }.joined(separator: "\n")
 | 
				
			||||||
 | 
					        .write(to: URL(filePath: "shallowIsolates.txt"), atomically: false, encoding: .utf8)
 | 
				
			||||||
 | 
					      try graph.deepIsolates.map { pkgIndex.packages[$0].nameDescription }.joined(separator: "\n")
 | 
				
			||||||
 | 
					        .write(to: URL(filePath: "deepIsolates.txt"), atomically: false, encoding: .utf8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let sorted = try graph.sorted(breakCycles: false)
 | 
				
			||||||
 | 
					      try sorted.map { pkgIndex.packages[$0].nameDescription }.joined(separator: "\n")
 | 
				
			||||||
 | 
					        .write(to: URL(filePath: "sorted.txt"), atomically: false, encoding: .utf8)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    } catch {
 | 
					    } catch {
 | 
				
			||||||
      fatalError(error.localizedDescription)
 | 
					      fatalError(error.localizedDescription)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
#if false
 | 
					}
 | 
				
			||||||
    if var out = TextFileWriter(URL(filePath: "shallowIsolates.txt")) {
 | 
					
 | 
				
			||||||
      for node in graph.shallowIsolates { print(node, to: &out) }
 | 
					fileprivate extension DispatchTimeInterval {
 | 
				
			||||||
    }
 | 
					  var seconds: Double {
 | 
				
			||||||
    if var out = TextFileWriter(URL(filePath: "deepIsolates.txt")) {
 | 
					    switch self {
 | 
				
			||||||
      for node in graph.deepIsolates { print(node, to: &out) }
 | 
					    case .seconds(let value):      Double(value)
 | 
				
			||||||
    }
 | 
					    case .milliseconds(let value): Double(value) / 1_000
 | 
				
			||||||
#else
 | 
					    case .microseconds(let value): Double(value) / 1_000_000
 | 
				
			||||||
    do {
 | 
					    case .nanoseconds(let value):  Double(value) / 1_000_000_000
 | 
				
			||||||
      let sorted = try graph.parallelOrderSort()
 | 
					    case .never: .infinity
 | 
				
			||||||
      print(sorted)
 | 
					    @unknown default:
 | 
				
			||||||
    } catch {
 | 
					    fatalError("Unsupported")
 | 
				
			||||||
      fatalError(error.localizedDescription)
 | 
					    }
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
#endif
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user