nandi/nimstaticpublic Fork 0
159b56cbf22ffa7841a22e6a0c595a25ba3b70f2
Commits
Clone
git clone https://git.rickub.com/nandi/nimstatic.git
git clone ssh://git@rickub.com/nandi/nimstatic.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

temp: raw size probe 159b56c · on 159b56cbf22ffa7841a22e6a0c595a25ba3b70f2 · nandi · 12h ago
index.nim · 113 lines · 3.7 KBNim Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
## APKINDEX parsing and dependency resolution.
##
## An APKINDEX is blank-line-separated records of single-letter fields:
##
##   P:openssl-dev
##   V:3.3.7-r1
##   D:libcrypto3=3.3.7-r1 libssl3=3.3.7-r1 pkgconfig
##   p:openssl3-dev=3.3.7-r1 pc:libssl=3.3.7
##
## `D:` depends and `p:` provides both carry optional version constraints, and a
## dependency may name a *provide* (`so:libssl.so.3`) rather than a package.

import std/[sets, strutils, tables]

type
  Pkg* = object
    name*, version*, arch*, description*, origin*, license*: string
    size*: int              ## download size in bytes, from `S:`
    checksum*: string       ## `C:` — apk's control-segment hash, not the file's
    depends*: seq[string]
    provides*: seq[string]
    repo*: string           ## main | community | …, filled in by the caller

  Index* = object
    packages*: OrderedTable[string, Pkg]
    providers*: Table[string, string]  ## provide name → package name

func stripConstraint*(dep: string): string =
  ## `musl=1.2.5-r11` → `musl`; `so:libssl.so.3=3` → `so:libssl.so.3`.
  ## Alpine writes constraints as =, >=, <=, >, < or ~.
  for i, c in dep:
    if c in {'=', '>', '<', '~'}:
      return dep[0 ..< i]
  dep

func isConflict*(dep: string): bool = dep.startsWith("!")

proc parseIndex*(text: string, repo = ""): Index =
  ## Parse one APKINDEX body. Later records win, matching apk's own behavior of
  ## letting a repository shadow an earlier one.
  var
    idx: Index
    cur: Pkg

  proc flush(idx: var Index, cur: var Pkg, repo: string) =
    if cur.name.len > 0:
      cur.repo = repo
      idx.packages[cur.name] = cur
      idx.providers[cur.name] = cur.name
      for p in cur.provides:
        idx.providers[stripConstraint(p)] = cur.name
    cur = Pkg()

  for rawLine in text.splitLines:
    let line = rawLine.strip(leading = false)
    if line.len == 0:
      flush(idx, cur, repo)
      continue
    if line.len < 2 or line[1] != ':':
      continue
    let val = line[2 .. ^1]
    case line[0]
    of 'P': cur.name = val
    of 'V': cur.version = val
    of 'A': cur.arch = val
    of 'T': cur.description = val
    of 'L': cur.license = val
    of 'o': cur.origin = val
    of 'C': cur.checksum = val
    of 'S': cur.size = try: parseInt(val) except ValueError: 0
    of 'D': cur.depends = val.splitWhitespace()
    of 'p': cur.provides = val.splitWhitespace()
    else: discard
  flush(idx, cur, repo)
  idx

proc merge*(a: var Index, b: Index) =
  ## Fold another repository's index in. `b` wins on collisions.
  for name, pkg in b.packages:
    a.packages[name] = pkg
  for provide, name in b.providers:
    a.providers[provide] = name

proc find*(idx: Index, want: string): string =
  ## Resolve a name-or-provide to a package name, or "" if nothing supplies it.
  let key = stripConstraint(want)
  if key in idx.packages: return key
  idx.providers.getOrDefault(key, "")

proc resolve*(idx: Index, wanted: openArray[string], withDeps = true): seq[Pkg] =
  ## Depth-first closure over `wanted`, in install order (dependencies first).
  ## Raises KeyError naming the first request nothing in the index supplies.
  var seen: HashSet[string]
  var order: seq[Pkg]

  proc visit(want, requestedBy: string) =
    if isConflict(want): return
    let name = idx.find(want)
    if name.len == 0:
      let ctx = if requestedBy.len > 0: " (needed by " & requestedBy & ")" else: ""
      raise newException(KeyError, "no package provides '" &
                         stripConstraint(want) & "'" & ctx)
    if name in seen: return
    seen.incl name
    let pkg = idx.packages[name]
    if withDeps:
      for dep in pkg.depends:
        visit(dep, name)
    order.add pkg

  for want in wanted:
    visit(want, "")
  order