mirror of
https://github.com/GayPizzaSpecifications/darwin-apk.git
synced 2025-08-03 13:31:32 +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