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 · 18h ago
muslkit.nim · 213 lines · 8.0 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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
## muslkit — musl-linked static libraries from Alpine, without Alpine.
##
##   muslkit add openssl-libs-static zlib-static
##   muslkit nimflags
##   eval "$(muslkit env)"
##
## It fetches Alpine's APKINDEX, resolves a dependency closure, downloads the
## .apk files (they are just tarballs) and unpacks them into a sysroot
## directory. Nothing is installed system-wide and nothing needs root.

import muslkit/[flags, index, repo, sysroot]
export flags, index, repo, sysroot

when isMainModule:
  import std/[os, sequtils, strutils, tables]

  const usageText = """
muslkit — musl static libraries from Alpine, without Alpine

Usage:
  muslkit add <pkg>...        Download packages and unpack into the sysroot
  muslkit list                Show what the sysroot holds
  muslkit libs                Show the static libraries in the sysroot
  muslkit search <text>       Search the index (names and descriptions)
  muslkit show <pkg>          Index record for one package
  muslkit nimflags [-l lib]   Print nim flags for a static build
  muslkit ccflags [-l lib]    Print cc/clang flags
  muslkit nimcfg [-o file]    Write a nim.cfg fragment (adds --cc when zigcc is set)
  muslkit zigcc [-o file]     Write a `zig cc -target …-musl` wrapper script
  muslkit env                 Shell exports (PKG_CONFIG_*, MUSLKIT_ROOT)
  muslkit path                Print the sysroot path
  muslkit clean               Remove the sysroot (cache is kept)

Options:
  -r, --root <dir>     Sysroot (default $XDG_DATA_HOME/muslkit/sysroot)
  -b, --branch <ver>   Alpine branch (default v3.21; `edge` for rolling)
  -a, --arch <arch>    Target arch (default x86_64)
  -m, --mirror <url>   Mirror base (default https://dl-cdn.alpinelinux.org/alpine)
      --repo <names>   Comma-separated repositories (default main,community)
  -l, --lib <name>     Add -l<name> to emitted flags (repeatable)
      --cc <path>      Compiler for `nimcfg` (e.g. the script `muslkit zigcc` writes)
      --no-deps        Do not pull dependencies
      --refresh        Re-fetch the index even if it is fresh
  -q, --quiet          No progress on stderr
  -h, --help           Show help

Packages are downloaded over HTTPS and unpacked as-is; muslkit does not check
Alpine's signatures, so treat a sysroot as build input, not as a trust root.

Examples:
  muslkit add openssl-libs-static
  nim c -d:ssl $(muslkit nimflags) --cc:clang --clang.exe:./zigcc app.nim
  muslkit add --branch edge --arch aarch64 zlib-static
"""

  type Opts = object
    root, mirror, branch, arch, cc, output: string
    repos, libs, args: seq[string]
    noDeps, refresh, quiet, help: bool

  proc defaultRoot(): string = getDataDir() / "muslkit" / "sysroot"

  proc die(msg: string) =
    stderr.writeLine "muslkit: " & msg
    quit(1)

  proc parseOpts(): Opts =
    ## Hand-rolled so `--root dir`, `--root=dir` and `-r dir` all work; parseopt
    ## only accepts the attached forms.
    result = Opts(root: getEnv("MUSLKIT_ROOT", defaultRoot()),
                  mirror: defaultMirror, branch: defaultBranch,
                  arch: defaultArch, repos: defaultRepos)
    let argv = commandLineParams()
    var i = 0
    while i < argv.len:
      var arg = argv[i]
      if not arg.startsWith("-") or arg == "-":
        result.args.add arg
        inc i
        continue
      var
        key = arg.strip(leading = true, trailing = false, chars = {'-'})
        val = ""
        attached = false
      for sep in ['=', ':']:
        let at = key.find(sep)
        if at >= 0:
          val = key[at + 1 .. ^1]
          key = key[0 ..< at]
          attached = true
          break

      proc takeValue(): string =
        if attached: return val
        inc i
        if i >= argv.len: die "option --" & key & " needs a value"
        argv[i]

      case key
      of "r", "root": result.root = takeValue()
      of "b", "branch": result.branch = takeValue()
      of "a", "arch": result.arch = takeValue()
      of "m", "mirror": result.mirror = takeValue()
      of "repo": result.repos = takeValue().split(',').filterIt(it.len > 0)
      of "l", "lib": result.libs.add takeValue()
      of "o", "output": result.output = takeValue()
      of "cc": result.cc = takeValue()
      of "no-deps": result.noDeps = true
      of "refresh": result.refresh = true
      of "q", "quiet": result.quiet = true
      of "h", "help": result.help = true
      else: die "unknown option: " & arg
      inc i

  proc emit(o: Opts, text: string) =
    if o.output.len > 0:
      createDir o.output.parentDir
      writeFile(o.output, text)
      if not o.quiet: stderr.writeLine "wrote " & o.output
    else:
      stdout.write text

  proc cmdAdd(o: Opts, remote: Remote) =
    if o.args.len == 0: die "add: name at least one package"
    let
      idx = remote.fetchIndex(o.refresh)
      wanted = idx.resolve(o.args, withDeps = not o.noDeps)
    for pkg in wanted:
      let archive = remote.fetchPackage(pkg)
      unpack(archive, o.root)
    record(o.root, wanted)
    if not o.quiet:
      stderr.writeLine "unpacked " & $wanted.len & " package(s) into " & o.root
      let libs = staticLibs(o.root)
      if libs.len > 0:
        stderr.writeLine "static libs: " &
          libs.mapIt(it.extractFilename).join(" ")

  proc cmdList(o: Opts) =
    let entries = readManifest(o.root)
    if entries.len == 0:
      stderr.writeLine "nothing installed in " & o.root
      return
    for e in entries:
      echo e.name.alignLeft(32), e.version.alignLeft(20), e.repo

  proc cmdSearch(o: Opts, remote: Remote) =
    if o.args.len == 0: die "search: give some text"
    let
      idx = remote.fetchIndex(o.refresh)
      needle = o.args.join(" ").toLowerAscii
    var hits = 0
    for name, pkg in idx.packages:
      if needle in name.toLowerAscii or needle in pkg.description.toLowerAscii:
        echo name.alignLeft(34), pkg.version.alignLeft(16), pkg.description
        inc hits
    if hits == 0: stderr.writeLine "no matches for " & needle

  proc cmdShow(o: Opts, remote: Remote) =
    if o.args.len == 0: die "show: name a package"
    let idx = remote.fetchIndex(o.refresh)
    for want in o.args:
      let name = idx.find(want)
      if name.len == 0: die "no package provides '" & want & "'"
      let pkg = idx.packages[name]
      echo "name:        ", pkg.name
      echo "version:     ", pkg.version
      echo "repository:  ", pkg.repo, "/", remote.arch
      echo "size:        ", pkg.size div 1024, " KiB"
      echo "description: ", pkg.description
      if pkg.depends.len > 0: echo "depends:     ", pkg.depends.join(" ")
      if pkg.provides.len > 0: echo "provides:    ", pkg.provides.join(" ")
      echo "url:         ", remote.repoUrl(pkg.repo), "/", apkFile(pkg)

  proc main() =
    let o = parseOpts()
    if o.help or o.args.len == 0:
      stdout.write usageText
      quit(if o.help: 0 else: 1)
    let
      cmd = o.args[0]
      rest = Opts(root: o.root, mirror: o.mirror, branch: o.branch,
                  arch: o.arch, cc: o.cc, output: o.output, repos: o.repos,
                  libs: o.libs, args: o.args[1 .. ^1], noDeps: o.noDeps,
                  refresh: o.refresh, quiet: o.quiet)
      remote = initRemote(o.mirror, o.branch, o.arch, o.repos, quiet = o.quiet)
    case cmd
    of "add": cmdAdd(rest, remote)
    of "list": cmdList(rest)
    of "libs": stdout.write describe(rest.root)
    of "search": cmdSearch(rest, remote)
    of "show": cmdShow(rest, remote)
    of "nimflags": echo nimFlags(rest.root, rest.libs).join(" ")
    of "ccflags": echo ccFlags(rest.root, rest.libs).join(" ")
    of "nimcfg": rest.emit nimCfg(rest.root, rest.cc, rest.libs)
    of "zigcc":
      let path = if rest.output.len > 0: rest.output else: "zigcc"
      writeZigCc(path, rest.arch & "-linux-musl")
      if not rest.quiet: stderr.writeLine "wrote " & path
    of "env":
      for kv in pkgConfigEnv(rest.root):
        echo "export ", kv
      echo "export MUSLKIT_ROOT=", rest.root
    of "path": echo rest.root
    of "clean":
      removeDir rest.root
      if not rest.quiet: stderr.writeLine "removed " & rest.root
    else: die "unknown command: " & cmd & " (try --help)"

  try:
    main()
  except CatchableError as e:
    die e.msg