diff --git a/Sources/apk/Common/ApkRequirement.swift b/Sources/apk/Common/ApkRequirement.swift index 244ca1b..29f99d1 100644 --- a/Sources/apk/Common/ApkRequirement.swift +++ b/Sources/apk/Common/ApkRequirement.swift @@ -5,8 +5,16 @@ import Foundation -internal struct ApkRequirement { - static func extract(blob extract: String) throws(ParseError) -> (String, ApkVersionSpecification) { +internal struct ApkRequirement: Hashable { + let name: String + let versionSpec: ApkVersionSpecification + + init(name: String, spec: ApkVersionSpecification) { + self.name = name + self.versionSpec = spec + } + + init(extract: String) throws(ParseError) { var comparer: ComparatorBits = [] var dependStr = extract[...] let nameEnd: String.Index, versionStart: String.Index @@ -30,7 +38,7 @@ internal struct ApkRequirement { } (nameEnd, versionStart) = (range.lowerBound, range.upperBound) } else { - // + // Lack of conflict flag indicates any version if !comparer.contains(.conflict) { comparer.formUnion(.any) } @@ -38,9 +46,18 @@ internal struct ApkRequirement { } // Parse version specification - let spec = try ApkVersionSpecification(comparer, version: dependStr[versionStart...]) - let name = String(dependStr[...weakObjects() + //private var _children = NSHashTable.weakObjects() + var parents = [ApkIndexRequirementRef]() + var children: [ApkIndexRequirementRef] + + internal init(package: ApkIndexPackage, children: [ApkIndexRequirementRef]) { + self.package = package + self.children = children + } +} + +extension ApkPackageGraphNode: CustomStringConvertible { + var description: String { + var result = "node[\(self.package.name)]" + if !self.parents.isEmpty { + result += ", parents[\(self.parents.lazy.map(\.description).joined(separator: ", "))]" + } + if !self.children.isEmpty { + result += ", children[\(self.children.lazy.map(\.description).joined(separator: ", "))]" + + } + return result + } +} diff --git a/Sources/apk/Index/ApkIndexDependency.swift b/Sources/apk/Index/ApkIndexDependency.swift index 6514e35..740cc0f 100644 --- a/Sources/apk/Index/ApkIndexDependency.swift +++ b/Sources/apk/Index/ApkIndexDependency.swift @@ -5,22 +5,10 @@ import Foundation -struct ApkIndexDependency: ApkIndexRequirementRef { - let name: String - let versionSpec: ApkVersionSpecification +struct ApkIndexDependency: Hashable { + let requirement: ApkRequirement - init(name: String, version spec: ApkVersionSpecification) { - self.name = name - self.versionSpec = spec - } -} - -extension ApkIndexDependency: CustomStringConvertible { - var description: String { - switch self.versionSpec { - case .any: self.name - case .conflict: "!\(self.name)" - case .constraint(let op, let version): "\(self.name)\(op)\(version)" - } + init(requirement: ApkRequirement) { + self.requirement = requirement } } diff --git a/Sources/apk/Index/ApkIndexInstallIf.swift b/Sources/apk/Index/ApkIndexInstallIf.swift index fcade26..7cd382b 100644 --- a/Sources/apk/Index/ApkIndexInstallIf.swift +++ b/Sources/apk/Index/ApkIndexInstallIf.swift @@ -3,12 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -struct ApkIndexInstallIf: ApkIndexRequirementRef { - let name: String - let versionSpec: ApkVersionSpecification +struct ApkIndexInstallIf: Hashable { + let requirement: ApkRequirement - init(name: String, version spec: ApkVersionSpecification) { - self.name = name - self.versionSpec = spec + init(requirement: ApkRequirement) { + self.requirement = requirement } } diff --git a/Sources/apk/Index/ApkIndexPackage.swift b/Sources/apk/Index/ApkIndexPackage.swift index daa0765..1959637 100644 --- a/Sources/apk/Index/ApkIndexPackage.swift +++ b/Sources/apk/Index/ApkIndexPackage.swift @@ -5,7 +5,7 @@ import Foundation -struct ApkIndexPackage: ApkIndexRequirementRef { +struct ApkIndexPackage: Hashable { let indexChecksum: ApkIndexDigest let name: String let version: String @@ -74,8 +74,10 @@ extension ApkIndexPackage { case "A": architecture = record.value case "D": - do { dependencies = try ApkIndexDependency.extract(record.value) } - catch { throw .badValue(key: record.key) } + do { + dependencies = try record.value.components(separatedBy: " ") + .map { .init(requirement: try .init(extract: $0)) } + } catch { throw .badValue(key: record.key) } case "C": guard let digest = ApkIndexDigest(decode: record.value) else { throw .badValue(key: record.key) @@ -92,11 +94,15 @@ extension ApkIndexPackage { } installedSize = value case "p": - do { provides = try ApkIndexProvides.extract(record.value) } - catch { throw .badValue(key: record.key) } + do { + provides = try record.value.components(separatedBy: " ") + .map { .init(requirement: try .init(extract: $0)) } + } catch { throw .badValue(key: record.key) } case "i": - do { installIf = try ApkIndexInstallIf.extract(record.value) } - catch { throw .badValue(key: record.key) } + do { + installIf = try record.value.components(separatedBy: " ") + .map { .init(requirement: try .init(extract: $0)) } + } catch { throw .badValue(key: record.key) } case "o": origin = record.value case "m": @@ -191,13 +197,13 @@ extension ApkIndexPackage: CustomStringConvertible { s += "provider prio: \(providerPrio)\n" } if !self.dependencies.isEmpty { - s += "dependencies: - \(self.dependencies.map(String.init).joined(separator: " "))\n" + s += "dependencies: - \(self.dependencies.map(\.requirement.description).joined(separator: " "))\n" } if !self.provides.isEmpty { - s += "provides: ----- \(self.provides.map { $0.name }.joined(separator: " "))\n" + s += "provides: ----- \(self.provides.map(\.name).joined(separator: " "))\n" } if !self.installIf.isEmpty { - s += "install if: --- \(self.installIf.map { $0.name }.joined(separator: " "))\n" + s += "install if: --- \(self.installIf.map(\.requirement.description).joined(separator: " "))\n" } return s } diff --git a/Sources/apk/Index/ApkIndexProvides.swift b/Sources/apk/Index/ApkIndexProvides.swift index 0e86438..759ee11 100644 --- a/Sources/apk/Index/ApkIndexProvides.swift +++ b/Sources/apk/Index/ApkIndexProvides.swift @@ -3,10 +3,10 @@ * SPDX-License-Identifier: Apache-2.0 */ -struct ApkIndexProvides: ApkIndexRequirementRef { +struct ApkIndexProvides: Hashable { let name: String - init(name: String, version _: ApkVersionSpecification) { - self.name = name + init(requirement: ApkRequirement) { + self.name = requirement.name } } diff --git a/Sources/apk/Index/ApkIndexRequirementRef.swift b/Sources/apk/Index/ApkIndexRequirementRef.swift index 4b527bd..e0c1ec4 100644 --- a/Sources/apk/Index/ApkIndexRequirementRef.swift +++ b/Sources/apk/Index/ApkIndexRequirementRef.swift @@ -3,24 +3,58 @@ * SPDX-License-Identifier: Apache-2.0 */ -protocol ApkIndexRequirementRef: Equatable, Hashable { - var name: String { get } - var invert: Bool { get } +struct ApkIndexRequirementRef { + private weak var _graph: ApkPackageGraph? - init(name: String, version spec: ApkVersionSpecification) + let packageID: Int + let constraint: Constraint - func satisfied(by other: ApkIndexPackage) -> Bool + 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: ApkRequirement) -> Bool { + true + } +} + +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 { - var invert: Bool { false } - func satisfied(by _: ApkIndexPackage) -> Bool { true } - - static func extract(_ blob: String) throws(ApkRequirement.ParseError) -> [T] { - return try blob.components(separatedBy: " ") - .map { token throws(ApkRequirement.ParseError) in - let (name, versionSpec) = try ApkRequirement.extract(blob: token) - return .init(name: name, version: versionSpec) - } + 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=\(ApkRequirement(name: package.name, spec: version))" + case .provision: + "provides=\(package.name)" + case .installIf(let version): + "installIf=\(ApkRequirement(name: package.name, spec: version))" + } } } diff --git a/Sources/apk/Index/ApkIndexUpdate.swift b/Sources/apk/Index/ApkIndexUpdate.swift index b3b618f..19f1557 100644 --- a/Sources/apk/Index/ApkIndexUpdate.swift +++ b/Sources/apk/Index/ApkIndexUpdate.swift @@ -13,11 +13,11 @@ public struct ApkIndexUpdater { public init() { self.repositories = [ - "https://dl-cdn.alpinelinux.org/alpine/v3.21/main", - "https://dl-cdn.alpinelinux.org/alpine/edge/community" + "https://dl-cdn.alpinelinux.org/alpine/v3.20/main", + "https://dl-cdn.alpinelinux.org/alpine/v3.20/community" ] // other archs: "armhf", "armv7", "loongarch64", "ppc64le", "riscv64", "s390x", "x86" - self.architectures = [ "aarch64", "x86_64" ] + self.architectures = [ "aarch64" /*, "x86_64" */ ] } public func update() { @@ -41,14 +41,23 @@ public struct ApkIndexUpdater { } } - let index: ApkIndex + let graph: ApkPackageGraph do { let tables = try repositories.map { try readIndex(URL(filePath: $0.localName)) } - index = ApkIndex.merge(tables) - try index.description.write(to: URL(fileURLWithPath: "packages.txt"), atomically: false, encoding: .utf8) + graph = ApkPackageGraph(index: ApkIndex.merge(tables)) + graph.buildGraphNode() + + try graph.pkgIndex.description.write(to: URL(filePath: "packages.txt"), atomically: false, encoding: .utf8) } catch { fatalError(error.localizedDescription) } + + 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) } + } } private func readIndex(_ indexURL: URL) throws -> ApkIndex { diff --git a/Sources/apk/Utility/TextFileWriter.swift b/Sources/apk/Utility/TextFileWriter.swift new file mode 100644 index 0000000..0a3d1a4 --- /dev/null +++ b/Sources/apk/Utility/TextFileWriter.swift @@ -0,0 +1,28 @@ +/* + * darwin-apk © 2024 Gay Pizza Specifications + * SPDX-License-Identifier: Apache-2.0 + */ + +import Foundation + +struct TextFileWriter: TextOutputStream { + private var _hnd: FileHandle + + init?(_ to: URL) { + let file = open(to.path(), O_WRONLY | O_CREAT | O_TRUNC | O_SYNC, 0o644) + guard file >= 0 else { + return nil + } + self._hnd = FileHandle(fileDescriptor: file, closeOnDealloc: true) + } + + mutating func write(_ string: String) { + if let data = string.data(using: .utf8) { + try? self._hnd.write(contentsOf: data) + } + } + + mutating func close() throws { + try self._hnd.close() + } +}