diff --git a/Sources/apk/Graph/ApkPackageGraph.swift b/Sources/apk/Graph/ApkPackageGraph.swift index 2e65127..1539a5d 100644 --- a/Sources/apk/Graph/ApkPackageGraph.swift +++ b/Sources/apk/Graph/ApkPackageGraph.swift @@ -50,6 +50,62 @@ public class ApkPackageGraph { } } +extension ApkPackageGraph { + public func orderSort(breakCycles: Bool = true) throws(SortError) -> [ApkPackageGraphNode] { + var stack = [ApkPackageGraphNode]() + var resolving = Set() + var visited = Set() + var ignoring = Set() + for node in self.shallowIsolates { + try orderSort(node, &stack, &resolving, &visited, &ignoring, breakCycles) + } + return stack + } + + internal func orderSort( + _ node: ApkPackageGraphNode, + _ stack: inout [ApkPackageGraphNode], + _ resolving: inout Set, + _ visited: inout Set, + _ ignoring: inout Set, + _ breakCycles: Bool + ) throws(SortError) { + for dep in node.children { + let depID = dep.packageID + guard !ignoring.contains(depID) else { + continue + } + + guard !resolving.contains(depID) else { + throw .cyclicDependency(cycles: "\(node) -> \(dep)") + } + + if !visited.contains(depID) { + resolving.insert(depID) + let depNode = self._nodes[depID] + do { + try orderSort(depNode, &stack, &resolving, &visited, &ignoring, breakCycles) + } catch { + guard breakCycles else { + throw error + } + + stack.append(depNode) + ignoring.insert(depID) + try orderSort(depNode, &stack, &resolving, &visited, &ignoring, breakCycles) + ignoring.remove(depID) + } + resolving.remove(depID) + visited.insert(depID) + } + } + + if !stack.contains(node) { + stack.append(node) + } + } +} + extension ApkPackageGraph { func findDependencyCycle(node: ApkPackageGraphNode) -> (ApkPackageGraphNode, ApkPackageGraphNode)? { var resolving = Set() diff --git a/Sources/dpk-cli/Subcommands/DpkGraphCommand.swift b/Sources/dpk-cli/Subcommands/DpkGraphCommand.swift index 49018a5..1c0d90d 100644 --- a/Sources/dpk-cli/Subcommands/DpkGraphCommand.swift +++ b/Sources/dpk-cli/Subcommands/DpkGraphCommand.swift @@ -30,7 +30,11 @@ struct DpkGraphCommand: AsyncParsableCommand { try graph.deepIsolates.map { $0.package.nameDescription }.joined(separator: "\n") .write(to: URL(filePath: "deepIsolates.txt"), atomically: false, encoding: .utf8) + timerStart = DispatchTime.now() +#if false let sorted = try graph.parallelOrderSort() + print("Parallel sort took \(timerStart.distance(to: .now()).seconds) seconds") + if var out = TextFileWriter(URL(filePath: "sorted.txt")) { for (i, set) in sorted.enumerated() { print("\(i):\n", to: &out) @@ -39,6 +43,13 @@ struct DpkGraphCommand: AsyncParsableCommand { } } } +#else + let sorted = try graph.orderSort() + print("Order sort took \(timerStart.distance(to: .now()).seconds) seconds") + + try sorted.map(String.init).joined(separator: "\n") + .write(to: URL(filePath: "sorted.txt"), atomically: false, encoding: .utf8) +#endif } catch { fatalError(error.localizedDescription) }