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.

muslkit: musl static libraries from Alpine, without Alpine 5feb050 · on 5feb05094be9eb20a8a0588b44117d22c7dc3cac · nandi · 20h ago
repo.nim · 95 lines · 3.5 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
## Talking to an Alpine mirror: index fetch, package download, local caching.
##
## Everything lands in a cache directory first, so a re-run of `muslkit add` is
## offline and a sysroot can be rebuilt without touching the network.

import std/[httpclient, os, strutils, times]
import ./index

const
  defaultMirror* = "https://dl-cdn.alpinelinux.org/alpine"
  defaultBranch* = "v3.21"
  defaultRepos* = @["main", "community"]
  defaultArch* = "x86_64"
  indexMaxAge* = initDuration(hours = 24)

type Remote* = object
  mirror*, branch*, arch*: string
  repos*: seq[string]
  cacheDir*: string
  quiet*: bool

proc initRemote*(mirror = defaultMirror, branch = defaultBranch,
                 arch = defaultArch, repos = defaultRepos,
                 cacheDir = "", quiet = false): Remote =
  Remote(mirror: mirror, branch: branch, arch: arch, repos: repos,
         cacheDir: if cacheDir.len > 0: cacheDir
                   else: getCacheDir() / "muslkit",
         quiet: quiet)

proc repoUrl*(r: Remote, repo: string): string =
  r.mirror & "/" & r.branch & "/" & repo & "/" & r.arch

proc note(r: Remote, msg: string) =
  if not r.quiet: stderr.writeLine msg

proc download(r: Remote, url, dest: string) =
  createDir dest.parentDir
  let tmp = dest & ".part"
  var client = newHttpClient(timeout = 60_000)
  defer: client.close()
  try:
    client.downloadFile(url, tmp)
  except CatchableError as e:
    removeFile tmp
    raise newException(IOError, "fetching " & url & " failed: " & e.msg)
  moveFile(tmp, dest)

proc needsRefresh(path: string, maxAge: Duration): bool =
  if not fileExists(path): return true
  getTime() - path.getLastModificationTime() > maxAge

proc untar(archive, dest: string, members: openArray[string] = []) =
  ## Shell out to tar: an .apk is concatenated gzip streams, which GNU tar
  ## already reads, and vendoring a decompressor would be the larger evil.
  createDir dest
  var cmd = "tar -xzf " & quoteShell(archive) & " -C " & quoteShell(dest)
  for m in members:
    cmd.add " " & quoteShell(m)
  # apk metadata entries are not part of the package's file list.
  cmd.add " --exclude=.PKGINFO --exclude=.SIGN.* --warning=no-unknown-keyword"
  if execShellCmd(cmd) != 0:
    raise newException(IOError, "tar failed on " & archive)

proc fetchIndex*(r: Remote, refresh = false): Index =
  ## Fetch (or reuse) every configured repository's APKINDEX, merged.
  for repo in r.repos:
    let
      dir = r.cacheDir / r.branch / repo / r.arch
      gz = dir / "APKINDEX.tar.gz"
      plain = dir / "APKINDEX"
    if refresh or needsRefresh(gz, indexMaxAge):
      r.note "fetching index: " & repo & "/" & r.arch
      r.download(r.repoUrl(repo) & "/APKINDEX.tar.gz", gz)
      removeFile plain
    if not fileExists(plain):
      untar(gz, dir, ["APKINDEX"])
    result.merge parseIndex(readFile(plain), repo)

proc apkFile*(pkg: Pkg): string = pkg.name & "-" & pkg.version & ".apk"

proc fetchPackage*(r: Remote, pkg: Pkg): string =
  ## Download one package if it is not already cached; returns the local path.
  let dest = r.cacheDir / r.branch / pkg.repo / r.arch / apkFile(pkg)
  if fileExists(dest) and getFileSize(dest) == pkg.size:
    return dest
  r.note "downloading " & pkg.name & " " & pkg.version &
         " (" & $(pkg.size div 1024) & " KiB)"
  r.download(r.repoUrl(pkg.repo) & "/" & apkFile(pkg), dest)
  if pkg.size > 0 and getFileSize(dest) != pkg.size:
    removeFile dest
    raise newException(IOError, pkg.name & ": size mismatch against the index")
  dest

proc unpack*(archive, sysroot: string) =
  untar(archive, sysroot)