nandi/nimstaticpublic Fork 0
main
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.

index.nim · 113 lines · 3.7 KBNim Blame HistoryRaw
muslkit: musl static libraries from Alpine, without Alpine 5feb050 nandi 23h ago1## APKINDEX parsing and dependency resolution.
2##
3## An APKINDEX is blank-line-separated records of single-letter fields:
4##
5## P:openssl-dev
6## V:3.3.7-r1
7## D:libcrypto3=3.3.7-r1 libssl3=3.3.7-r1 pkgconfig
8## p:openssl3-dev=3.3.7-r1 pc:libssl=3.3.7
9##
10## `D:` depends and `p:` provides both carry optional version constraints, and a
11## dependency may name a *provide* (`so:libssl.so.3`) rather than a package.
12
13import std/[sets, strutils, tables]
14
15type
16 Pkg* = object
17 name*, version*, arch*, description*, origin*, license*: string
18 size*: int ## download size in bytes, from `S:`
19 checksum*: string ## `C:` — apk's control-segment hash, not the file's
20 depends*: seq[string]
21 provides*: seq[string]
22 repo*: string ## main | community | …, filled in by the caller
23
24 Index* = object
25 packages*: OrderedTable[string, Pkg]
26 providers*: Table[string, string] ## provide name → package name
27
28func stripConstraint*(dep: string): string =
29 ## `musl=1.2.5-r11` → `musl`; `so:libssl.so.3=3` → `so:libssl.so.3`.
30 ## Alpine writes constraints as =, >=, <=, >, < or ~.
31 for i, c in dep:
32 if c in {'=', '>', '<', '~'}:
33 return dep[0 ..< i]
34 dep
35
36func isConflict*(dep: string): bool = dep.startsWith("!")
37
38proc parseIndex*(text: string, repo = ""): Index =
39 ## Parse one APKINDEX body. Later records win, matching apk's own behavior of
40 ## letting a repository shadow an earlier one.
41 var
42 idx: Index
43 cur: Pkg
44
45 proc flush(idx: var Index, cur: var Pkg, repo: string) =
46 if cur.name.len > 0:
47 cur.repo = repo
48 idx.packages[cur.name] = cur
49 idx.providers[cur.name] = cur.name
50 for p in cur.provides:
51 idx.providers[stripConstraint(p)] = cur.name
52 cur = Pkg()
53
54 for rawLine in text.splitLines:
55 let line = rawLine.strip(leading = false)
56 if line.len == 0:
57 flush(idx, cur, repo)
58 continue
59 if line.len < 2 or line[1] != ':':
60 continue
61 let val = line[2 .. ^1]
62 case line[0]
63 of 'P': cur.name = val
64 of 'V': cur.version = val
65 of 'A': cur.arch = val
66 of 'T': cur.description = val
67 of 'L': cur.license = val
68 of 'o': cur.origin = val
69 of 'C': cur.checksum = val
70 of 'S': cur.size = try: parseInt(val) except ValueError: 0
71 of 'D': cur.depends = val.splitWhitespace()
72 of 'p': cur.provides = val.splitWhitespace()
73 else: discard
74 flush(idx, cur, repo)
75 idx
76
77proc merge*(a: var Index, b: Index) =
78 ## Fold another repository's index in. `b` wins on collisions.
79 for name, pkg in b.packages:
80 a.packages[name] = pkg
81 for provide, name in b.providers:
82 a.providers[provide] = name
83
84proc find*(idx: Index, want: string): string =
85 ## Resolve a name-or-provide to a package name, or "" if nothing supplies it.
86 let key = stripConstraint(want)
87 if key in idx.packages: return key
88 idx.providers.getOrDefault(key, "")
89
90proc resolve*(idx: Index, wanted: openArray[string], withDeps = true): seq[Pkg] =
91 ## Depth-first closure over `wanted`, in install order (dependencies first).
92 ## Raises KeyError naming the first request nothing in the index supplies.
93 var seen: HashSet[string]
94 var order: seq[Pkg]
95
96 proc visit(want, requestedBy: string) =
97 if isConflict(want): return
98 let name = idx.find(want)
99 if name.len == 0:
100 let ctx = if requestedBy.len > 0: " (needed by " & requestedBy & ")" else: ""
101 raise newException(KeyError, "no package provides '" &
102 stripConstraint(want) & "'" & ctx)
103 if name in seen: return
104 seen.incl name
105 let pkg = idx.packages[name]
106 if withDeps:
107 for dep in pkg.depends:
108 visit(dep, name)
109 order.add pkg
110
111 for want in wanted:
112 visit(want, "")
113 order