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

repo.nim · 95 lines · 3.5 KBNim Blame HistoryRaw
muslkit: musl static libraries from Alpine, without Alpine 5feb050 nandi 21h ago1## Talking to an Alpine mirror: index fetch, package download, local caching.
2##
3## Everything lands in a cache directory first, so a re-run of `muslkit add` is
4## offline and a sysroot can be rebuilt without touching the network.
5
6import std/[httpclient, os, strutils, times]
7import ./index
8
9const
10 defaultMirror* = "https://dl-cdn.alpinelinux.org/alpine"
11 defaultBranch* = "v3.21"
12 defaultRepos* = @["main", "community"]
13 defaultArch* = "x86_64"
14 indexMaxAge* = initDuration(hours = 24)
15
16type Remote* = object
17 mirror*, branch*, arch*: string
18 repos*: seq[string]
19 cacheDir*: string
20 quiet*: bool
21
22proc initRemote*(mirror = defaultMirror, branch = defaultBranch,
23 arch = defaultArch, repos = defaultRepos,
24 cacheDir = "", quiet = false): Remote =
25 Remote(mirror: mirror, branch: branch, arch: arch, repos: repos,
26 cacheDir: if cacheDir.len > 0: cacheDir
27 else: getCacheDir() / "muslkit",
28 quiet: quiet)
29
30proc repoUrl*(r: Remote, repo: string): string =
31 r.mirror & "/" & r.branch & "/" & repo & "/" & r.arch
32
33proc note(r: Remote, msg: string) =
34 if not r.quiet: stderr.writeLine msg
35
36proc download(r: Remote, url, dest: string) =
37 createDir dest.parentDir
38 let tmp = dest & ".part"
39 var client = newHttpClient(timeout = 60_000)
40 defer: client.close()
41 try:
42 client.downloadFile(url, tmp)
43 except CatchableError as e:
44 removeFile tmp
45 raise newException(IOError, "fetching " & url & " failed: " & e.msg)
46 moveFile(tmp, dest)
47
48proc needsRefresh(path: string, maxAge: Duration): bool =
49 if not fileExists(path): return true
50 getTime() - path.getLastModificationTime() > maxAge
51
52proc untar(archive, dest: string, members: openArray[string] = []) =
53 ## Shell out to tar: an .apk is concatenated gzip streams, which GNU tar
54 ## already reads, and vendoring a decompressor would be the larger evil.
55 createDir dest
56 var cmd = "tar -xzf " & quoteShell(archive) & " -C " & quoteShell(dest)
57 for m in members:
58 cmd.add " " & quoteShell(m)
59 # apk metadata entries are not part of the package's file list.
60 cmd.add " --exclude=.PKGINFO --exclude=.SIGN.* --warning=no-unknown-keyword"
61 if execShellCmd(cmd) != 0:
62 raise newException(IOError, "tar failed on " & archive)
63
64proc fetchIndex*(r: Remote, refresh = false): Index =
65 ## Fetch (or reuse) every configured repository's APKINDEX, merged.
66 for repo in r.repos:
67 let
68 dir = r.cacheDir / r.branch / repo / r.arch
69 gz = dir / "APKINDEX.tar.gz"
70 plain = dir / "APKINDEX"
71 if refresh or needsRefresh(gz, indexMaxAge):
72 r.note "fetching index: " & repo & "/" & r.arch
73 r.download(r.repoUrl(repo) & "/APKINDEX.tar.gz", gz)
74 removeFile plain
75 if not fileExists(plain):
76 untar(gz, dir, ["APKINDEX"])
77 result.merge parseIndex(readFile(plain), repo)
78
79proc apkFile*(pkg: Pkg): string = pkg.name & "-" & pkg.version & ".apk"
80
81proc fetchPackage*(r: Remote, pkg: Pkg): string =
82 ## Download one package if it is not already cached; returns the local path.
83 let dest = r.cacheDir / r.branch / pkg.repo / r.arch / apkFile(pkg)
84 if fileExists(dest) and getFileSize(dest) == pkg.size:
85 return dest
86 r.note "downloading " & pkg.name & " " & pkg.version &
87 " (" & $(pkg.size div 1024) & " KiB)"
88 r.download(r.repoUrl(pkg.repo) & "/" & apkFile(pkg), dest)
89 if pkg.size > 0 and getFileSize(dest) != pkg.size:
90 removeFile dest
91 raise newException(IOError, pkg.name & ": size mismatch against the index")
92 dest
93
94proc unpack*(archive, sysroot: string) =
95 untar(archive, sysroot)