mirror of
https://github.com/GayPizzaSpecifications/darwin-apk.git
synced 2025-08-03 21:41:31 +00:00
Implement dependency wrapping & version spec
This commit is contained in:
117
Sources/apk/Common/ApkRequirement.swift
Normal file
117
Sources/apk/Common/ApkRequirement.swift
Normal file
@ -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[..<nameEnd])
|
||||||
|
return (name, spec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ApkRequirement {
|
||||||
|
enum ParseError: Error, LocalizedError {
|
||||||
|
case brokenSpec
|
||||||
|
|
||||||
|
var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .brokenSpec: "Invalid version specification"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: - Private Implementation
|
||||||
|
|
||||||
|
fileprivate extension ApkRequirement {
|
||||||
|
struct ComparatorBits: OptionSet {
|
||||||
|
let rawValue: UInt8
|
||||||
|
|
||||||
|
static let equals: Self = Self(rawValue: 1 << 0)
|
||||||
|
static let less: Self = Self(rawValue: 1 << 1)
|
||||||
|
static let greater: Self = Self(rawValue: 1 << 2)
|
||||||
|
static let fuzzy: Self = Self(rawValue: 1 << 3)
|
||||||
|
static let conflict: Self = Self(rawValue: 1 << 4)
|
||||||
|
|
||||||
|
static let any: Self = [ .equals, .less, .greater ]
|
||||||
|
static let checksum: Self = [ .less, .greater ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension ApkVersionSpecification {
|
||||||
|
init(_ bits: ApkRequirement.ComparatorBits, version: Substring) throws(ApkRequirement.ParseError) {
|
||||||
|
if bits == [ .conflict ] {
|
||||||
|
self = .conflict
|
||||||
|
} else {
|
||||||
|
if bits.contains(.conflict) {
|
||||||
|
throw .brokenSpec
|
||||||
|
} else if bits == [ .any ] {
|
||||||
|
self = .any
|
||||||
|
} else {
|
||||||
|
self = .constraint(op: try .init(bits), version: String(version))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension ApkVersionSpecification.Operator {
|
||||||
|
init(_ bits: ApkRequirement.ComparatorBits) throws(ApkRequirement.ParseError) {
|
||||||
|
self = switch bits.subtracting(.conflict) {
|
||||||
|
case .equals: .equals
|
||||||
|
case .less: .less
|
||||||
|
case .greater: .greater
|
||||||
|
//case .checksum: .checksum
|
||||||
|
case [ .equals, .less ]: .lessEqual
|
||||||
|
case [ .equals, .greater ]: .greaterEqual
|
||||||
|
case [ .fuzzy, .equals ], .fuzzy: .fuzzyEquals
|
||||||
|
case [ .fuzzy, .equals, .less]: .lessFuzzy
|
||||||
|
case [ .fuzzy, .equals, .greater]: .greaterFuzzy
|
||||||
|
default: throw .brokenSpec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate extension Substring {
|
||||||
|
func firstRange(where predicate: (Character) throws -> Bool) rethrows -> Range<Self.Index>? {
|
||||||
|
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..<idx
|
||||||
|
}
|
||||||
|
}
|
46
Sources/apk/Common/ApkVersionSpecification.swift
Normal file
46
Sources/apk/Common/ApkVersionSpecification.swift
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
enum ApkVersionSpecification: Equatable {
|
||||||
|
case any
|
||||||
|
case constraint(op: Operator, version: String)
|
||||||
|
case conflict
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ApkVersionSpecification: CustomStringConvertible {
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
case .any: ""
|
||||||
|
case .conflict: "!"
|
||||||
|
case .constraint(let op, let version): "\(op)\(version)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ApkVersionSpecification {
|
||||||
|
enum Operator: Equatable {
|
||||||
|
case equals
|
||||||
|
case fuzzyEquals
|
||||||
|
case greater
|
||||||
|
case less
|
||||||
|
case greaterEqual
|
||||||
|
case lessEqual
|
||||||
|
case greaterFuzzy
|
||||||
|
case lessFuzzy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ApkVersionSpecification.Operator: CustomStringConvertible {
|
||||||
|
var description: String {
|
||||||
|
switch self {
|
||||||
|
//case .checksum: "><"
|
||||||
|
case .lessEqual: "<="
|
||||||
|
case .greaterEqual: ">="
|
||||||
|
case .lessFuzzy: "<~"
|
||||||
|
case .greaterFuzzy: ">~"
|
||||||
|
case .equals: "="
|
||||||
|
case .less: "<"
|
||||||
|
case .greater: ">"
|
||||||
|
case .fuzzyEquals: "~"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Sources/apk/Index/ApkIndexDependency.swift
Normal file
22
Sources/apk/Index/ApkIndexDependency.swift
Normal file
@ -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)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
Sources/apk/Index/ApkIndexInstallIf.swift
Normal file
10
Sources/apk/Index/ApkIndexInstallIf.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct ApkIndexPackage {
|
struct ApkIndexPackage: Hashable {
|
||||||
let indexChecksum: String //TODO: Decode cus why not
|
let indexChecksum: String //TODO: Decode cus why not
|
||||||
let name: String
|
let name: String
|
||||||
let version: String
|
let version: String
|
||||||
@ -17,9 +17,9 @@ struct ApkIndexPackage {
|
|||||||
let buildTime: Date?
|
let buildTime: Date?
|
||||||
let commit: String?
|
let commit: String?
|
||||||
let providerPriority: UInt16?
|
let providerPriority: UInt16?
|
||||||
let dependencies: [String] //TODO: stuff
|
let dependencies: [ApkIndexDependency]
|
||||||
let provides: [String] //TODO: stuff
|
let provides: [ApkIndexProvides]
|
||||||
let installIf: [String] //TODO: stuff
|
let installIf: [ApkIndexInstallIf]
|
||||||
|
|
||||||
var downloadFilename: String { "\(self.name)-\(version).apk" }
|
var downloadFilename: String { "\(self.name)-\(version).apk" }
|
||||||
|
|
||||||
@ -39,9 +39,9 @@ extension ApkIndexPackage {
|
|||||||
var packageSize: UInt64? = nil
|
var packageSize: UInt64? = nil
|
||||||
var installedSize: UInt64? = nil
|
var installedSize: UInt64? = nil
|
||||||
|
|
||||||
var dependencies = [String]()
|
var dependencies = [ApkIndexDependency]()
|
||||||
var provides = [String]()
|
var provides = [ApkIndexProvides]()
|
||||||
var installIf = [String]()
|
var installIf = [ApkIndexInstallIf]()
|
||||||
|
|
||||||
// Optional fields
|
// Optional fields
|
||||||
var architecture: String? = nil
|
var architecture: String? = nil
|
||||||
@ -67,7 +67,8 @@ extension ApkIndexPackage {
|
|||||||
case "A":
|
case "A":
|
||||||
architecture = record.value
|
architecture = record.value
|
||||||
case "D":
|
case "D":
|
||||||
dependencies = record.value.components(separatedBy: " ")
|
do { dependencies = try ApkIndexDependency.extract(record.value) }
|
||||||
|
catch { throw .badValue(key: record.key) }
|
||||||
case "C":
|
case "C":
|
||||||
indexChecksum = record.value // base64-encoded SHA1 hash prefixed with "Q1"
|
indexChecksum = record.value // base64-encoded SHA1 hash prefixed with "Q1"
|
||||||
case "S":
|
case "S":
|
||||||
@ -81,9 +82,11 @@ extension ApkIndexPackage {
|
|||||||
}
|
}
|
||||||
installedSize = value
|
installedSize = value
|
||||||
case "p":
|
case "p":
|
||||||
provides = record.value.components(separatedBy: " ")
|
do { provides = try ApkIndexProvides.extract(record.value) }
|
||||||
|
catch { throw .badValue(key: record.key) }
|
||||||
case "i":
|
case "i":
|
||||||
installIf = record.value.components(separatedBy: " ")
|
do { installIf = try ApkIndexInstallIf.extract(record.value) }
|
||||||
|
catch { throw .badValue(key: record.key) }
|
||||||
case "o":
|
case "o":
|
||||||
origin = record.value
|
origin = record.value
|
||||||
case "m":
|
case "m":
|
||||||
|
9
Sources/apk/Index/ApkIndexProvides.swift
Normal file
9
Sources/apk/Index/ApkIndexProvides.swift
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
30
Sources/apk/Index/ApkIndexRequirementRef.swift
Normal file
30
Sources/apk/Index/ApkIndexRequirementRef.swift
Normal file
@ -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<T: ApkIndexRequirementRef>(_ blob: String) throws(ApkRequirement.ParseError) -> [T] {
|
||||||
|
return try blob.components(separatedBy: " ")
|
||||||
|
.map { token throws(ApkRequirement.ParseError) in
|
||||||
|
try .init(extract: token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -47,7 +47,7 @@ public struct ApkIndexUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for package in index.packages {
|
for package in index.packages {
|
||||||
print(package)
|
print("\(package.name):", package.dependencies)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user