mirror of
				https://github.com/GayPizzaSpecifications/darwin-apk.git
				synced 2025-11-03 23:49: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
 | 
					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