mirror of
				https://github.com/GayPizzaSpecifications/darwin-apk.git
				synced 2025-11-04 07:59:38 +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
 | 
			
		||||
 | 
			
		||||
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":
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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 {
 | 
			
		||||
      print(package)
 | 
			
		||||
      print("\(package.name):", package.dependencies)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user