mirror of
				https://github.com/GayPizzaSpecifications/darwin-apk.git
				synced 2025-11-03 23:49:38 +00:00 
			
		
		
		
	Factor out resolution responsibilities
This commit is contained in:
		@ -6,31 +6,27 @@
 | 
			
		||||
import Foundation
 | 
			
		||||
 | 
			
		||||
public class ApkPackageGraph {
 | 
			
		||||
  public var pkgIndex: ApkIndex
 | 
			
		||||
 | 
			
		||||
  private var _nodes = [ApkPackageGraphNode]()
 | 
			
		||||
 | 
			
		||||
  public var nodes: [ApkPackageGraphNode] { self._nodes }
 | 
			
		||||
  public var shallowIsolates: [ApkPackageGraphNode] { self._nodes.filter(\.isShallow) }
 | 
			
		||||
  public var deepIsolates: [ApkPackageGraphNode] { self._nodes.filter(\.isDeep) }
 | 
			
		||||
 | 
			
		||||
  public init(index: ApkIndex) {
 | 
			
		||||
    self.pkgIndex = index
 | 
			
		||||
  }
 | 
			
		||||
  public init() {}
 | 
			
		||||
 | 
			
		||||
  public func buildGraphNode() {
 | 
			
		||||
  public func buildGraphNode(index pkgIndex: ApkIndex, providers: ApkIndexProviderCache) {
 | 
			
		||||
    for (packageID, package) in pkgIndex.packages.enumerated() {
 | 
			
		||||
      let children: [ApkPackageGraphNode.ChildRef] = package.dependencies.compactMap { dependency in
 | 
			
		||||
        guard let providerID = pkgIndex.resolveIndex(requirement: dependency.requirement) else {
 | 
			
		||||
        guard let providerID = providers.resolve(index: pkgIndex, requirement: dependency.requirement) else {
 | 
			
		||||
          return nil
 | 
			
		||||
        }
 | 
			
		||||
        return .init(constraint: .dependency, packageID: providerID, versionSpec: dependency.requirement.versionSpec)
 | 
			
		||||
      } + package.installIf.compactMap { installIf in
 | 
			
		||||
        guard let prvID = pkgIndex.resolveIndex(requirement: installIf.requirement) else {
 | 
			
		||||
      } /* + package.installIf.compactMap { installIf in
 | 
			
		||||
        guard let prvID = providers.resolve(index: pkgIndex, requirement: installIf.requirement) else {
 | 
			
		||||
          return nil
 | 
			
		||||
        }
 | 
			
		||||
        return .init(constraint: .installIf, packageID: prvID, versionSpec: installIf.requirement.versionSpec)
 | 
			
		||||
      }
 | 
			
		||||
      } */
 | 
			
		||||
      self._nodes.append(.init(self,
 | 
			
		||||
        id: packageID,
 | 
			
		||||
        children: children
 | 
			
		||||
 | 
			
		||||
@ -10,23 +10,10 @@ public class ApkPackageGraphNode {
 | 
			
		||||
  public var parentIDs = [ApkIndex.Index]()
 | 
			
		||||
  public var children: [ChildRef]
 | 
			
		||||
 | 
			
		||||
  private weak var _graph: ApkPackageGraph?
 | 
			
		||||
 | 
			
		||||
  public var package: ApkIndexPackage {
 | 
			
		||||
    self._graph!.pkgIndex.packages[self.packageID]
 | 
			
		||||
  }
 | 
			
		||||
  public var parents: [ApkIndexPackage] {
 | 
			
		||||
    self.parentIDs.map { index in self._graph!.pkgIndex.packages[index] }
 | 
			
		||||
  }
 | 
			
		||||
  public var childPackages: [ApkIndexPackage] {
 | 
			
		||||
    self.children.map { child in self._graph!.pkgIndex.packages[child.packageID] }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @inlinable public var isShallow: Bool { self.parentIDs.isEmpty }
 | 
			
		||||
  @inlinable public var isDeep: Bool { self.children.isEmpty }
 | 
			
		||||
 | 
			
		||||
  internal init(_ graph: ApkPackageGraph, id: Int, children: [ChildRef]) {
 | 
			
		||||
    self._graph = graph
 | 
			
		||||
    self.packageID = id
 | 
			
		||||
    self.children = children
 | 
			
		||||
  }
 | 
			
		||||
@ -54,31 +41,8 @@ extension ApkPackageGraphNode {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension ApkPackageGraphNode: CustomStringConvertible {
 | 
			
		||||
  public var description: String {
 | 
			
		||||
    let package = self.package
 | 
			
		||||
    var result = "  \(package.nameDescription):\n"
 | 
			
		||||
    if !self.parentIDs.isEmpty {
 | 
			
		||||
      result += "    parents:\n"
 | 
			
		||||
      for parent in self.parents {
 | 
			
		||||
        result += "      \(parent.nameDescription)\n"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if !self.children.isEmpty {
 | 
			
		||||
      result += "    children:\n"
 | 
			
		||||
      for child in self.children {
 | 
			
		||||
        let childPackage = self._graph!.pkgIndex.packages[child.packageID]
 | 
			
		||||
        result += "      "
 | 
			
		||||
        switch child.constraint {
 | 
			
		||||
        case .dependency: result += "dep="
 | 
			
		||||
        case .installIf: result += "installIf="
 | 
			
		||||
        }
 | 
			
		||||
        result += childPackage.nameDescription
 | 
			
		||||
        result += ", "
 | 
			
		||||
        result += child.versionSpec.description
 | 
			
		||||
        result += "\n"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return result
 | 
			
		||||
extension ApkIndex {
 | 
			
		||||
  public func at(node: ApkPackageGraphNode) -> ApkIndexPackage {
 | 
			
		||||
    self.packages[node.packageID]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,12 +8,6 @@ import Foundation
 | 
			
		||||
public struct ApkIndex: Sendable {
 | 
			
		||||
  public let packages: [ApkIndexPackage]
 | 
			
		||||
  public typealias Index = Array<ApkIndexPackage>.Index
 | 
			
		||||
 | 
			
		||||
  lazy var providers: [(ApkIndexProvides, Index)] = {
 | 
			
		||||
    self.packages.enumerated().flatMap { index, pkg in
 | 
			
		||||
      [ (.specific(name: pkg.name, version: pkg.version), index) ] + pkg.provides.map { ($0, index) }
 | 
			
		||||
    }
 | 
			
		||||
  }()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public extension ApkIndex {
 | 
			
		||||
@ -28,24 +22,8 @@ public extension ApkIndex {
 | 
			
		||||
      $0.name == name
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mutating func resolve(requirement: ApkVersionRequirement) -> ApkIndexPackage? {
 | 
			
		||||
    self.providers.filter { prv in prv.0.satisfies(requirement) }
 | 
			
		||||
      .map { self.packages[$1] }.max()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  mutating func resolveIndex(requirement: ApkVersionRequirement) -> Index? {
 | 
			
		||||
    self.providers.filter { prv in prv.0.satisfies(requirement) }
 | 
			
		||||
      .max { self.packages[$0.1] < self.packages[$1.1] }?.1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension ApkIndexPackage: Comparable {
 | 
			
		||||
  public static func < (lhs: Self, rhs: Self) -> Bool {
 | 
			
		||||
    // Prefer highest declared provider priority
 | 
			
		||||
    lhs.providerPriority ?? 0 < rhs.providerPriority ?? 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public extension ApkIndex {
 | 
			
		||||
  static func merge<S: Sequence>(_ tables: S) -> Self where S.Element == Self {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										37
									
								
								Sources/apk/Index/ApkIndexProviderCache.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								Sources/apk/Index/ApkIndexProviderCache.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 * darwin-apk © 2025 Gay Pizza Specifications
 | 
			
		||||
 * SPDX-License-Identifier: Apache-2.0
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import Foundation
 | 
			
		||||
 | 
			
		||||
public struct ApkIndexProviderCache {
 | 
			
		||||
  private var _providers = [(ApkIndexProvides, ApkIndex.Index)]()
 | 
			
		||||
  //private var _installIfCache = [(ApkIndexInstallIf, Set<ApkIndex.Index>)]()
 | 
			
		||||
 | 
			
		||||
  public init(index pkgIndex: ApkIndex) {
 | 
			
		||||
    for (index, pkg) in pkgIndex.packages.enumerated() {
 | 
			
		||||
      self._providers.append((.specific(name: pkg.name, version: pkg.version), index))
 | 
			
		||||
      for provision in pkg.provides {
 | 
			
		||||
        self._providers.append((provision, index))
 | 
			
		||||
      }
 | 
			
		||||
      //for installIf in pkg.installIf {
 | 
			
		||||
      //  self._installIfCache.append((installIf, index))
 | 
			
		||||
      //}
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension ApkIndexProviderCache {
 | 
			
		||||
  func resolve(index pkgIndex: ApkIndex, requirement: ApkVersionRequirement) -> ApkIndex.Index? {
 | 
			
		||||
    self._providers.filter { prv in prv.0.satisfies(requirement) }
 | 
			
		||||
      .max { pkgIndex.packages[$0.1] < pkgIndex.packages[$1.1] }?.1
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
extension ApkIndexPackage: Comparable {
 | 
			
		||||
  public static func < (lhs: Self, rhs: Self) -> Bool {
 | 
			
		||||
    // Prefer highest declared provider priority
 | 
			
		||||
    lhs.providerPriority ?? 0 < rhs.providerPriority ?? 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -11,23 +11,23 @@ struct DpkGraphCommand: AsyncParsableCommand {
 | 
			
		||||
  static let configuration = CommandConfiguration(commandName: "graph")
 | 
			
		||||
 | 
			
		||||
  func run() async throws(ExitCode) {
 | 
			
		||||
    let graph: ApkPackageGraph
 | 
			
		||||
    do {
 | 
			
		||||
      let localRepositories = try await ApkRepositoriesConfig()
 | 
			
		||||
 | 
			
		||||
      var timerStart = DispatchTime.now()
 | 
			
		||||
      graph = ApkPackageGraph(index:
 | 
			
		||||
        try await ApkIndexReader.resolve(localRepositories.repositories, fetch: .lazy))
 | 
			
		||||
      let pkgIndex = try await ApkIndexReader.resolve(localRepositories.repositories, fetch: .lazy)
 | 
			
		||||
      print("Index build took \(timerStart.distance(to: .now()).seconds) seconds")
 | 
			
		||||
      try graph.pkgIndex.description.write(to: URL(filePath: "packages.txt"), atomically: false, encoding: .utf8)
 | 
			
		||||
      try pkgIndex.description.write(to: URL(filePath: "packages.txt"), atomically: false, encoding: .utf8)
 | 
			
		||||
 | 
			
		||||
      timerStart = DispatchTime.now()
 | 
			
		||||
      graph.buildGraphNode()
 | 
			
		||||
      let providerCache = ApkIndexProviderCache(index: pkgIndex)
 | 
			
		||||
      let graph = ApkPackageGraph()
 | 
			
		||||
      graph.buildGraphNode(index: pkgIndex, providers: providerCache)
 | 
			
		||||
      print("Graph build took \(timerStart.distance(to: .now()).seconds) seconds")
 | 
			
		||||
 | 
			
		||||
      try graph.shallowIsolates.map { $0.package.nameDescription }.joined(separator: "\n")
 | 
			
		||||
      try graph.shallowIsolates.map { pkgIndex.at(node: $0).nameDescription }.joined(separator: "\n")
 | 
			
		||||
        .write(to: URL(filePath: "shallowIsolates.txt"), atomically: false, encoding: .utf8)
 | 
			
		||||
      try graph.deepIsolates.map { $0.package.nameDescription }.joined(separator: "\n")
 | 
			
		||||
      try graph.deepIsolates.map { pkgIndex.at(node: $0).nameDescription }.joined(separator: "\n")
 | 
			
		||||
        .write(to: URL(filePath: "deepIsolates.txt"), atomically: false, encoding: .utf8)
 | 
			
		||||
 | 
			
		||||
      timerStart = DispatchTime.now()
 | 
			
		||||
@ -37,17 +37,19 @@ struct DpkGraphCommand: AsyncParsableCommand {
 | 
			
		||||
 | 
			
		||||
      if var out = TextFileWriter(URL(filePath: "sorted.txt")) {
 | 
			
		||||
        for (i, set) in sorted.enumerated() {
 | 
			
		||||
          print("\(i):\n", to: &out)
 | 
			
		||||
          print("\(i):", to: &out)
 | 
			
		||||
          for item in set {
 | 
			
		||||
            print("\(item.description)", to: &out)
 | 
			
		||||
            let pkg = pkgIndex.at(node: item)
 | 
			
		||||
            print("  \(pkg.nameDescription)", to: &out)
 | 
			
		||||
          }
 | 
			
		||||
          print(to: &out)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
#else
 | 
			
		||||
      let sorted = try graph.orderSort()
 | 
			
		||||
      print("Order sort took \(timerStart.distance(to: .now()).seconds) seconds")
 | 
			
		||||
 | 
			
		||||
      try sorted.map(String.init).joined(separator: "\n")
 | 
			
		||||
      try sorted.map { node in pkgIndex.at(node: node).nameDescription }.joined(separator: "\n")
 | 
			
		||||
        .write(to: URL(filePath: "sorted.txt"), atomically: false, encoding: .utf8)
 | 
			
		||||
#endif
 | 
			
		||||
    } catch {
 | 
			
		||||
@ -64,8 +66,7 @@ fileprivate extension DispatchTimeInterval {
 | 
			
		||||
    case .microseconds(let value): Double(value) / 1_000_000
 | 
			
		||||
    case .nanoseconds(let value):  Double(value) / 1_000_000_000
 | 
			
		||||
    case .never: .infinity
 | 
			
		||||
    @unknown default:
 | 
			
		||||
    fatalError("Unsupported")
 | 
			
		||||
    @unknown default: fatalError("Unsupported")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user