From 5e4cf1bbc969b0546c366e4f46896faac02357ee Mon Sep 17 00:00:00 2001 From: a dinosaur Date: Sun, 10 Nov 2024 03:30:55 +1100 Subject: [PATCH] Implement dependency wrapping & version spec --- Sources/apk/Common/ApkRequirement.swift | 117 ++++++++++++++++++ .../apk/Common/ApkVersionSpecification.swift | 46 +++++++ Sources/apk/Index/ApkIndexDependency.swift | 22 ++++ Sources/apk/Index/ApkIndexInstallIf.swift | 10 ++ Sources/apk/Index/ApkIndexPackage.swift | 23 ++-- Sources/apk/Index/ApkIndexProvides.swift | 9 ++ .../apk/Index/ApkIndexRequirementRef.swift | 30 +++++ Sources/apk/Index/ApkIndexUpdate.swift | 2 +- 8 files changed, 248 insertions(+), 11 deletions(-) create mode 100644 Sources/apk/Common/ApkRequirement.swift create mode 100644 Sources/apk/Common/ApkVersionSpecification.swift create mode 100644 Sources/apk/Index/ApkIndexDependency.swift create mode 100644 Sources/apk/Index/ApkIndexInstallIf.swift create mode 100644 Sources/apk/Index/ApkIndexProvides.swift create mode 100644 Sources/apk/Index/ApkIndexRequirementRef.swift diff --git a/Sources/apk/Common/ApkRequirement.swift b/Sources/apk/Common/ApkRequirement.swift new file mode 100644 index 0000000..26467fd --- /dev/null +++ b/Sources/apk/Common/ApkRequirement.swift @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation + +internal struct ApkRequirement { + static func extract(blob extract: String) throws(ParseError) -> (String, ApkVersionSpecification) { + var comparer: ComparatorBits = [] + var dependStr = extract[...] + let nameEnd: String.Index, versionStart: String.Index + + // Check for bang prefix to indicate a conflict + if dependStr.first == "!" { + comparer.insert(.conflict) + dependStr = dependStr[dependStr.index(after: dependStr.startIndex)...] + } + + // Match comparator + if let range = dependStr.firstRange(where: { [ "<", "=", ">", "~" ].contains($0) }) { + for c in dependStr[range] { + switch c { + case "<": comparer.insert(.less) + case "=": comparer.insert(.equals) + case ">": comparer.insert(.greater) + case "~": comparer.formUnion([ .fuzzy, .equals ]) + default: break + } + } + (nameEnd, versionStart) = (range.lowerBound, range.upperBound) + } else { + // + if !comparer.contains(.conflict) { + comparer.formUnion(.any) + } + (nameEnd, versionStart) = (dependStr.endIndex, dependStr.endIndex) + } + + // Parse version specification + let spec = try ApkVersionSpecification(comparer, version: dependStr[versionStart...]) + let name = String(dependStr[.. Bool) rethrows -> Range? { + guard let start = try self.firstIndex(where: predicate) else { + return nil + } + var idx = start + repeat { + idx = self.index(after: idx) + } while try idx != self.endIndex && predicate(self[idx]) + return start..<" + case .lessEqual: "<=" + case .greaterEqual: ">=" + case .lessFuzzy: "<~" + case .greaterFuzzy: ">~" + case .equals: "=" + case .less: "<" + case .greater: ">" + case .fuzzyEquals: "~" + } + } +} diff --git a/Sources/apk/Index/ApkIndexDependency.swift b/Sources/apk/Index/ApkIndexDependency.swift new file mode 100644 index 0000000..59e021b --- /dev/null +++ b/Sources/apk/Index/ApkIndexDependency.swift @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 + +import Foundation + +struct ApkIndexDependency: ApkIndexRequirementRef { + let name: String + let versionSpec: ApkVersionSpecification + + init(extract: String) throws(ApkRequirement.ParseError) { + (self.name, self.versionSpec) = try ApkRequirement.extract(blob: extract) + } +} + +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)" + } + } +} diff --git a/Sources/apk/Index/ApkIndexInstallIf.swift b/Sources/apk/Index/ApkIndexInstallIf.swift new file mode 100644 index 0000000..85bbaac --- /dev/null +++ b/Sources/apk/Index/ApkIndexInstallIf.swift @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: Apache-2.0 + +struct ApkIndexInstallIf: ApkIndexRequirementRef { + let name: String + let versionSpec: ApkVersionSpecification + + init(extract: String) throws(ApkRequirement.ParseError) { + (self.name, self.versionSpec) = try ApkRequirement.extract(blob: extract) + } +} diff --git a/Sources/apk/Index/ApkIndexPackage.swift b/Sources/apk/Index/ApkIndexPackage.swift index 98892c4..5ac68de 100644 --- a/Sources/apk/Index/ApkIndexPackage.swift +++ b/Sources/apk/Index/ApkIndexPackage.swift @@ -2,7 +2,7 @@ import Foundation -struct ApkIndexPackage { +struct ApkIndexPackage: Hashable { let indexChecksum: String //TODO: Decode cus why not let name: String let version: String @@ -17,9 +17,9 @@ struct ApkIndexPackage { let buildTime: Date? let commit: String? let providerPriority: UInt16? - let dependencies: [String] //TODO: stuff - let provides: [String] //TODO: stuff - let installIf: [String] //TODO: stuff + let dependencies: [ApkIndexDependency] + let provides: [ApkIndexProvides] + let installIf: [ApkIndexInstallIf] var downloadFilename: String { "\(self.name)-\(version).apk" } @@ -39,9 +39,9 @@ extension ApkIndexPackage { var packageSize: UInt64? = nil var installedSize: UInt64? = nil - var dependencies = [String]() - var provides = [String]() - var installIf = [String]() + var dependencies = [ApkIndexDependency]() + var provides = [ApkIndexProvides]() + var installIf = [ApkIndexInstallIf]() // Optional fields var architecture: String? = nil @@ -67,7 +67,8 @@ extension ApkIndexPackage { case "A": architecture = record.value case "D": - dependencies = record.value.components(separatedBy: " ") + do { dependencies = try ApkIndexDependency.extract(record.value) } + catch { throw .badValue(key: record.key) } case "C": indexChecksum = record.value // base64-encoded SHA1 hash prefixed with "Q1" case "S": @@ -81,9 +82,11 @@ extension ApkIndexPackage { } installedSize = value case "p": - provides = record.value.components(separatedBy: " ") + do { provides = try ApkIndexProvides.extract(record.value) } + catch { throw .badValue(key: record.key) } case "i": - installIf = record.value.components(separatedBy: " ") + do { installIf = try ApkIndexInstallIf.extract(record.value) } + catch { throw .badValue(key: record.key) } case "o": origin = record.value case "m": diff --git a/Sources/apk/Index/ApkIndexProvides.swift b/Sources/apk/Index/ApkIndexProvides.swift new file mode 100644 index 0000000..eb54e05 --- /dev/null +++ b/Sources/apk/Index/ApkIndexProvides.swift @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 + +struct ApkIndexProvides: ApkIndexRequirementRef { + let name: String + + init(extract: String) throws(ApkRequirement.ParseError) { + (self.name, _) = try ApkRequirement.extract(blob: extract) + } +} diff --git a/Sources/apk/Index/ApkIndexRequirementRef.swift b/Sources/apk/Index/ApkIndexRequirementRef.swift new file mode 100644 index 0000000..d7136f4 --- /dev/null +++ b/Sources/apk/Index/ApkIndexRequirementRef.swift @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: Apache-2.0 + +protocol ApkIndexRequirementRef: Equatable, Hashable { + var name: String { get } + var invert: Bool { get } + + init(extract: String) throws(ApkRequirement.ParseError) + + func satisfied(by other: ApkIndexPackage) -> Bool +} + +extension ApkIndexRequirementRef { + var invert: Bool { false } + func satisfied(by _: ApkIndexPackage) -> Bool { true } + + static func == (lhs: Self, rhs: Self) -> Bool { + return !(lhs.name != rhs.name && !lhs.invert) + } + + func hash(into hasher: inout Hasher) { + self.name.hash(into: &hasher) + } + + static func extract(_ blob: String) throws(ApkRequirement.ParseError) -> [T] { + return try blob.components(separatedBy: " ") + .map { token throws(ApkRequirement.ParseError) in + try .init(extract: token) + } + } +} diff --git a/Sources/apk/Index/ApkIndexUpdate.swift b/Sources/apk/Index/ApkIndexUpdate.swift index d5cd989..caadb65 100644 --- a/Sources/apk/Index/ApkIndexUpdate.swift +++ b/Sources/apk/Index/ApkIndexUpdate.swift @@ -47,7 +47,7 @@ public struct ApkIndexUpdater { } for package in index.packages { - print(package) + print("\(package.name):", package.dependencies) } }