WIP parallel sort (BROKEN)

This commit is contained in:
2024-11-11 23:08:01 +11:00
parent d850b9998d
commit d8a17ffd24
3 changed files with 144 additions and 7 deletions

View File

@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
import Foundation
public class ApkPackageGraph {
public let pkgIndex: ApkIndex
@ -26,9 +28,9 @@ public class ApkPackageGraph {
}
}
for package in pkgIndex.packages {
self._nodes.append(.init(
package: package,
for (id, package) in pkgIndex.packages.enumerated() {
self._nodes.append(.init(self,
id: id,
children: package.dependencies.compactMap { dependency in
guard let id = provides[dependency.requirement.name] else {
return nil
@ -62,3 +64,114 @@ public class ApkPackageGraph {
}
}
}
extension ApkPackageGraph {
func findDependencyCycle(node: ApkPackageGraphNode) -> (ApkPackageGraphNode, ApkPackageGraphNode)? {
var resolving = Set<ApkPackageGraphNode>()
var visited = Set<ApkPackageGraphNode>()
return self.findDependencyCycle(node: node, &resolving, &visited)
}
func findDependencyCycle(
node: ApkPackageGraphNode,
_ resolving: inout Set<ApkPackageGraphNode>,
_ visited: inout Set<ApkPackageGraphNode>
) -> (ApkPackageGraphNode, ApkPackageGraphNode)? {
for dependency in node.children {
let depNode = self._nodes[dependency.packageID]
if resolving.contains(depNode) {
return (node, depNode)
}
if !visited.contains(depNode) {
resolving.insert(depNode)
if let cycle = findDependencyCycle(node: depNode, &resolving, &visited) {
return cycle
}
resolving.remove(depNode)
visited.insert(depNode)
}
}
return nil
}
public func parallelOrderSort(breakCycles: Bool = true) throws(SortError) -> [[ApkPackageGraphNode]] {
var results = [[ApkPackageGraphNode]]()
// Map all nodes to all of their children, remove any self dependencies
var working = self._nodes.reduce(into: [ApkPackageGraphNode: Set<ApkPackageGraphNode>]()) { d, node in
d[node] = Set(node.children.filter { child in
if case .dep(let version) = child.constraint {
version != .conflict && child.packageID != node.packageID
} else { false }
}.map { self._nodes[$0.packageID] })
}
// Collect all child nodes that aren't already in the map
// This should be empty every time
let extras = working.values.reduce(Set<ApkPackageGraphNode>()) { a, b in
a.union(b)
}.subtracting(working.keys)
assert(extras.isEmpty, "Dangling nodes in the graph")
// Put all extra nodes into the working map, with an empty set
extras.forEach {
working[$0] = .init()
}
while true {
// Set of all nodes now with satisfied dependencies
var set = working
.filter { _, children in children.isEmpty }
.map(\.key)
// If nothing was satisfied in this loop, check for cycles
// If no cycles exist and the working set is empty, resolve is complete
if set.isEmpty {
if working.isEmpty {
break
}
let cycles = working.keys.compactMap { node in
self.findDependencyCycle(node: node)
}
// Error if cycle breaking is turned off
if !breakCycles {
throw .cyclicDependency(cycles: cycles.map { node, dependency in
"\(node) -> \(dependency)"
}.joined(separator: "\n"))
}
// Bread cycles by setting the new resolution set to dependencies that cycled
set = cycles.map(\.1)
}
// Add installation set to list of installation sets
results.append(set)
// Filter the working set for anything that wasn't dealt with this iteration
working = working.filter { node, _ in
!set.contains(node)
}.reduce(into: [ApkPackageGraphNode: Set<ApkPackageGraphNode>]()) { d, node in
d[node.key] = node.value.subtracting(set)
}
}
print(working)
return results
}
public enum SortError: Error, LocalizedError {
case cyclicDependency(cycles: String)
var errorDescription: String {
switch self {
case .cyclicDependency(let cycles): "Dependency cycles found:\n\(cycles)"
}
}
}
}

View File

@ -6,20 +6,35 @@
import Foundation
public class ApkPackageGraphNode {
private weak var graph: ApkPackageGraph!
let package: ApkIndexPackage
private weak var _graph: ApkPackageGraph?
let packageID: Int
//private var _parents = NSHashTable<ApkPackageGraphNode>.weakObjects()
//private var _children = NSHashTable<ApkPackageGraphNode>.weakObjects()
var parents = [ApkIndexRequirementRef]()
var children: [ApkIndexRequirementRef]
internal init(package: ApkIndexPackage, children: [ApkIndexRequirementRef]) {
self.package = package
var package: ApkIndexPackage {
self._graph!.pkgIndex.packages[self.packageID]
}
internal init(_ graph: ApkPackageGraph, id: Int, children: [ApkIndexRequirementRef]) {
self._graph = graph
self.packageID = id
self.children = children
}
}
extension ApkPackageGraphNode: Equatable, Hashable {
public static func == (lhs: ApkPackageGraphNode, rhs: ApkPackageGraphNode) -> Bool {
lhs.packageID == rhs.packageID
}
public func hash(into hasher: inout Hasher) {
self.packageID.hash(into: &hasher)
}
}
extension ApkPackageGraphNode: CustomStringConvertible {
public var description: String {
var result = "node[\(self.package.name)]"

View File

@ -22,11 +22,20 @@ struct DpkGraphCommand: AsyncParsableCommand {
fatalError(error.localizedDescription)
}
#if false
if var out = TextFileWriter(URL(filePath: "shallowIsolates.txt")) {
for node in graph.shallowIsolates { print(node, to: &out) }
}
if var out = TextFileWriter(URL(filePath: "deepIsolates.txt")) {
for node in graph.deepIsolates { print(node, to: &out) }
}
#else
do {
let sorted = try graph.parallelOrderSort()
print(sorted)
} catch {
fatalError(error.localizedDescription)
}
#endif
}
}