mirror of
https://github.com/GayPizzaSpecifications/darwin-apk.git
synced 2025-08-06 14:41:35 +00:00
Port index changes to parallel sort
This commit is contained in:
@ -6,61 +6,46 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class ApkPackageGraph {
|
public class ApkPackageGraph {
|
||||||
public let pkgIndex: ApkIndex
|
public var pkgIndex: ApkIndex
|
||||||
|
|
||||||
private var _nodes = [ApkPackageGraphNode]()
|
private var _nodes = [ApkPackageGraphNode]()
|
||||||
|
|
||||||
public var nodes: [ApkPackageGraphNode] { self._nodes }
|
public var nodes: [ApkPackageGraphNode] { self._nodes }
|
||||||
public var shallowIsolates: [ApkPackageGraphNode] { self._nodes.filter(\.parents.isEmpty) }
|
public var shallowIsolates: [ApkPackageGraphNode] { self._nodes.filter(\.isShallow) }
|
||||||
public var deepIsolates: [ApkPackageGraphNode] { self._nodes.filter(\.children.isEmpty) }
|
public var deepIsolates: [ApkPackageGraphNode] { self._nodes.filter(\.isDeep) }
|
||||||
|
|
||||||
public init(index: ApkIndex) {
|
public init(index: ApkIndex) {
|
||||||
self.pkgIndex = index
|
self.pkgIndex = index
|
||||||
}
|
}
|
||||||
|
|
||||||
public func buildGraphNode() {
|
public func buildGraphNode() {
|
||||||
var provides = [String: Int]()
|
for (packageID, package) in pkgIndex.packages.enumerated() {
|
||||||
|
let children: [ApkPackageGraphNode.ChildRef] = package.dependencies.compactMap { dependency in
|
||||||
for (idx, package) in self.pkgIndex.packages.enumerated() {
|
guard let providerID = pkgIndex.resolveIndex(requirement: dependency.requirement) else {
|
||||||
provides[package.name] = idx
|
|
||||||
for provision in package.provides {
|
|
||||||
if !provides.keys.contains(provision.name) {
|
|
||||||
provides[provision.name] = idx
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (id, package) in pkgIndex.packages.enumerated() {
|
|
||||||
let children: [ApkIndexRequirementRef] = package.dependencies.compactMap { dependency in
|
|
||||||
guard !dependency.requirement.versionSpec.conflict,
|
|
||||||
let id = provides[dependency.requirement.name] else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .init(self, id: id, constraint: .dep(version: dependency.requirement.versionSpec))
|
return .init(constraint: .dependency, packageID: providerID, versionSpec: dependency.requirement.versionSpec)
|
||||||
} + package.installIf.compactMap { installIf in
|
} + package.installIf.compactMap { installIf in
|
||||||
guard let id = provides[installIf.requirement.name] else {
|
guard let prvID = pkgIndex.resolveIndex(requirement: installIf.requirement) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return .init(self, id: id, constraint: .installIf(version: installIf.requirement.versionSpec ))
|
return .init(constraint: .installIf, packageID: prvID, versionSpec: installIf.requirement.versionSpec)
|
||||||
}
|
}
|
||||||
self._nodes.append(.init(self,
|
self._nodes.append(.init(self,
|
||||||
id: id,
|
id: packageID,
|
||||||
children: children
|
children: children
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
var reverseDependencies = [ApkIndexRequirementRef: [ApkIndexRequirementRef]]()
|
var reverseDependencies = [ApkIndex.Index: [ApkIndex.Index]]()
|
||||||
|
|
||||||
for (index, node) in self._nodes.enumerated() {
|
for (index, node) in self._nodes.enumerated() {
|
||||||
for child in node.children {
|
for child in node.children {
|
||||||
reverseDependencies[child, default: []].append(
|
reverseDependencies[child.packageID, default: []].append(index)
|
||||||
.init(self, id: index, constraint: child.constraint)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (ref, parents) in reverseDependencies {
|
for (ref, parents) in reverseDependencies {
|
||||||
self._nodes[ref.packageID].parents = parents
|
self._nodes[ref].parentIDs = parents
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,8 +59,8 @@ extension ApkPackageGraph {
|
|||||||
|
|
||||||
func findDependencyCycle(
|
func findDependencyCycle(
|
||||||
node: ApkPackageGraphNode,
|
node: ApkPackageGraphNode,
|
||||||
_ resolving: inout Set<Int>,
|
_ resolving: inout Set<ApkIndex.Index>,
|
||||||
_ visited: inout Set<Int>
|
_ visited: inout Set<ApkIndex.Index>
|
||||||
) -> (ApkPackageGraphNode, ApkPackageGraphNode)? {
|
) -> (ApkPackageGraphNode, ApkPackageGraphNode)? {
|
||||||
for dependency in node.children {
|
for dependency in node.children {
|
||||||
let depNode = self._nodes[dependency.packageID]
|
let depNode = self._nodes[dependency.packageID]
|
||||||
@ -103,8 +88,8 @@ extension ApkPackageGraph {
|
|||||||
// 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._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 .dependency = child.constraint {
|
||||||
!version.conflict && child.packageID != node.packageID
|
!child.versionSpec.isConflict && child.packageID != node.packageID
|
||||||
} else { false }
|
} else { false }
|
||||||
}.map { self._nodes[$0.packageID] })
|
}.map { self._nodes[$0.packageID] })
|
||||||
}
|
}
|
||||||
@ -118,7 +103,7 @@ extension ApkPackageGraph {
|
|||||||
|
|
||||||
// Put all extra nodes into the working map, with an empty set
|
// Put all extra nodes into the working map, with an empty set
|
||||||
extras.forEach {
|
extras.forEach {
|
||||||
working[$0] = .init()
|
working[$0] = []
|
||||||
}
|
}
|
||||||
|
|
||||||
while !working.isEmpty {
|
while !working.isEmpty {
|
||||||
|
@ -1,24 +1,31 @@
|
|||||||
/*
|
/*
|
||||||
* darwin-apk © 2024 Gay Pizza Specifications
|
* darwin-apk © 2024, 2025 Gay Pizza Specifications
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class ApkPackageGraphNode {
|
public class ApkPackageGraphNode {
|
||||||
|
public let packageID: ApkIndex.Index
|
||||||
|
public var parentIDs = [ApkIndex.Index]()
|
||||||
|
public var children: [ChildRef]
|
||||||
|
|
||||||
private weak var _graph: ApkPackageGraph?
|
private weak var _graph: ApkPackageGraph?
|
||||||
let packageID: Int
|
|
||||||
|
|
||||||
//private var _parents = NSHashTable<ApkPackageGraphNode>.weakObjects()
|
public var package: ApkIndexPackage {
|
||||||
//private var _children = NSHashTable<ApkPackageGraphNode>.weakObjects()
|
|
||||||
var parents = [ApkIndexRequirementRef]()
|
|
||||||
var children: [ApkIndexRequirementRef]
|
|
||||||
|
|
||||||
var package: ApkIndexPackage {
|
|
||||||
self._graph!.pkgIndex.packages[self.packageID]
|
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] }
|
||||||
|
}
|
||||||
|
|
||||||
internal init(_ graph: ApkPackageGraph, id: Int, children: [ApkIndexRequirementRef]) {
|
@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._graph = graph
|
||||||
self.packageID = id
|
self.packageID = id
|
||||||
self.children = children
|
self.children = children
|
||||||
@ -35,15 +42,42 @@ extension ApkPackageGraphNode: Equatable, Hashable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ApkPackageGraphNode {
|
||||||
|
public struct ChildRef {
|
||||||
|
let constraint: Constraint
|
||||||
|
let packageID: Int
|
||||||
|
let versionSpec: ApkVersionSpecification
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Constraint {
|
||||||
|
case dependency, installIf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ApkPackageGraphNode: CustomStringConvertible {
|
extension ApkPackageGraphNode: CustomStringConvertible {
|
||||||
public var description: String {
|
public var description: String {
|
||||||
var result = "node[\(self.package.name)]"
|
let package = self.package
|
||||||
if !self.parents.isEmpty {
|
var result = " \(package.nameDescription):\n"
|
||||||
result += ", parents[\(self.parents.lazy.map(\.description).joined(separator: ", "))]"
|
if !self.parentIDs.isEmpty {
|
||||||
|
result += " parents:\n"
|
||||||
|
for parent in self.parents {
|
||||||
|
result += " \(parent.nameDescription)\n"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !self.children.isEmpty {
|
if !self.children.isEmpty {
|
||||||
result += ", children[\(self.children.lazy.map(\.description).joined(separator: ", "))]"
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
* darwin-apk © 2024 Gay Pizza Specifications
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
struct ApkIndexRequirementRef {
|
|
||||||
private weak var _graph: ApkPackageGraph?
|
|
||||||
|
|
||||||
let packageID: Int
|
|
||||||
let constraint: Constraint
|
|
||||||
|
|
||||||
init(_ graph: ApkPackageGraph, id: Int, constraint: Constraint) {
|
|
||||||
self._graph = graph
|
|
||||||
self.packageID = id
|
|
||||||
self.constraint = constraint
|
|
||||||
}
|
|
||||||
|
|
||||||
var package: ApkIndexPackage {
|
|
||||||
self._graph!.pkgIndex.packages[self.packageID]
|
|
||||||
}
|
|
||||||
|
|
||||||
func satisfied(by other: ApkVersionRequirement) -> Bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalize() -> ApkIndexRequirementRef {
|
|
||||||
.init(self._graph!, id: self.packageID, constraint: .dep(version: .any()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ApkIndexRequirementRef: Equatable, Hashable {
|
|
||||||
static func == (lhs: Self, rhs: Self) -> Bool {
|
|
||||||
lhs.packageID == rhs.packageID && lhs.constraint == rhs.constraint
|
|
||||||
}
|
|
||||||
|
|
||||||
func hash(into hasher: inout Hasher) {
|
|
||||||
self.packageID.hash(into: &hasher)
|
|
||||||
self.constraint.hash(into: &hasher)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ApkIndexRequirementRef {
|
|
||||||
enum Constraint: Hashable {
|
|
||||||
case dep(version: ApkVersionSpecification)
|
|
||||||
case provision
|
|
||||||
case installIf(version: ApkVersionSpecification)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension ApkIndexRequirementRef: CustomStringConvertible {
|
|
||||||
var description: String {
|
|
||||||
guard let package = self._graph?.pkgIndex.packages[self.packageID] else {
|
|
||||||
return String()
|
|
||||||
}
|
|
||||||
return switch self.constraint {
|
|
||||||
case .dep(let version):
|
|
||||||
"dep=\(ApkVersionRequirement(name: package.name, spec: version))"
|
|
||||||
case .provision:
|
|
||||||
"provides=\(package.name)"
|
|
||||||
case .installIf(let version):
|
|
||||||
"installIf=\(ApkVersionRequirement(name: package.name, spec: version))"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* darwin-apk © 2024 Gay Pizza Specifications
|
* darwin-apk © 2024, 2025 Gay Pizza Specifications
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -92,3 +92,14 @@ extension ApkVersionSpecification.Operator: CustomStringConvertible {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ApkVersionSpecification: CustomStringConvertible {
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .any(invert: false): "depend=any"
|
||||||
|
case .any(invert: true): "conflict=any"
|
||||||
|
case .constraint(invert: false, let op, let version): "depend\(op)\(version)"
|
||||||
|
case .constraint(invert: true, let op, let version): "conflict\(op)\(version)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* darwin-apk © 2024 Gay Pizza Specifications
|
* darwin-apk © 2024, 2025 Gay Pizza Specifications
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -14,28 +14,47 @@ 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()
|
|
||||||
|
|
||||||
|
var timerStart = DispatchTime.now()
|
||||||
|
graph = ApkPackageGraph(index:
|
||||||
|
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 graph.pkgIndex.description.write(to: URL(filePath: "packages.txt"), atomically: false, encoding: .utf8)
|
||||||
} catch {
|
|
||||||
fatalError(error.localizedDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
#if false
|
timerStart = DispatchTime.now()
|
||||||
if var out = TextFileWriter(URL(filePath: "shallowIsolates.txt")) {
|
graph.buildGraphNode()
|
||||||
for node in graph.shallowIsolates { print(node, to: &out) }
|
print("Graph build took \(timerStart.distance(to: .now()).seconds) seconds")
|
||||||
}
|
|
||||||
if var out = TextFileWriter(URL(filePath: "deepIsolates.txt")) {
|
try graph.shallowIsolates.map { $0.package.nameDescription }.joined(separator: "\n")
|
||||||
for node in graph.deepIsolates { print(node, to: &out) }
|
.write(to: URL(filePath: "shallowIsolates.txt"), atomically: false, encoding: .utf8)
|
||||||
}
|
try graph.deepIsolates.map { $0.package.nameDescription }.joined(separator: "\n")
|
||||||
#else
|
.write(to: URL(filePath: "deepIsolates.txt"), atomically: false, encoding: .utf8)
|
||||||
do {
|
|
||||||
let sorted = try graph.parallelOrderSort()
|
let sorted = try graph.parallelOrderSort()
|
||||||
print(sorted)
|
if var out = TextFileWriter(URL(filePath: "sorted.txt")) {
|
||||||
|
for (i, set) in sorted.enumerated() {
|
||||||
|
print("\(i):\n", to: &out)
|
||||||
|
for item in set {
|
||||||
|
print("\(item.description)", to: &out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
fatalError(error.localizedDescription)
|
fatalError(error.localizedDescription)
|
||||||
}
|
}
|
||||||
#endif
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension DispatchTimeInterval {
|
||||||
|
var seconds: Double {
|
||||||
|
switch self {
|
||||||
|
case .seconds(let value): Double(value)
|
||||||
|
case .milliseconds(let value): Double(value) / 1_000
|
||||||
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user