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.nim · 213 lines · 8.0 KBNim Blame HistoryRaw
muslkit: musl static libraries from Alpine, without Alpine 5feb050 nandi 20h ago1## muslkit — musl-linked static libraries from Alpine, without Alpine.
2##
3## muslkit add openssl-libs-static zlib-static
4## muslkit nimflags
5## eval "$(muslkit env)"
6##
7## It fetches Alpine's APKINDEX, resolves a dependency closure, downloads the
8## .apk files (they are just tarballs) and unpacks them into a sysroot
9## directory. Nothing is installed system-wide and nothing needs root.
10
11import muslkit/[flags, index, repo, sysroot]
12export flags, index, repo, sysroot
13
14when isMainModule:
15 import std/[os, sequtils, strutils, tables]
16
17 const usageText = """
18muslkit — musl static libraries from Alpine, without Alpine
19
20Usage:
21 muslkit add <pkg>... Download packages and unpack into the sysroot
22 muslkit list Show what the sysroot holds
23 muslkit libs Show the static libraries in the sysroot
24 muslkit search <text> Search the index (names and descriptions)
25 muslkit show <pkg> Index record for one package
26 muslkit nimflags [-l lib] Print nim flags for a static build
27 muslkit ccflags [-l lib] Print cc/clang flags
28 muslkit nimcfg [-o file] Write a nim.cfg fragment (adds --cc when zigcc is set)
29 muslkit zigcc [-o file] Write a `zig cc -target …-musl` wrapper script
30 muslkit env Shell exports (PKG_CONFIG_*, MUSLKIT_ROOT)
31 muslkit path Print the sysroot path
32 muslkit clean Remove the sysroot (cache is kept)
33
34Options:
35 -r, --root <dir> Sysroot (default $XDG_DATA_HOME/muslkit/sysroot)
36 -b, --branch <ver> Alpine branch (default v3.21; `edge` for rolling)
37 -a, --arch <arch> Target arch (default x86_64)
38 -m, --mirror <url> Mirror base (default https://dl-cdn.alpinelinux.org/alpine)
39 --repo <names> Comma-separated repositories (default main,community)
40 -l, --lib <name> Add -l<name> to emitted flags (repeatable)
41 --cc <path> Compiler for `nimcfg` (e.g. the script `muslkit zigcc` writes)
42 --no-deps Do not pull dependencies
43 --refresh Re-fetch the index even if it is fresh
44 -q, --quiet No progress on stderr
45 -h, --help Show help
46
47Packages are downloaded over HTTPS and unpacked as-is; muslkit does not check
48Alpine's signatures, so treat a sysroot as build input, not as a trust root.
49
50Examples:
51 muslkit add openssl-libs-static
52 nim c -d:ssl $(muslkit nimflags) --cc:clang --clang.exe:./zigcc app.nim
53 muslkit add --branch edge --arch aarch64 zlib-static
54"""
55
56 type Opts = object
57 root, mirror, branch, arch, cc, output: string
58 repos, libs, args: seq[string]
59 noDeps, refresh, quiet, help: bool
60
61 proc defaultRoot(): string = getDataDir() / "muslkit" / "sysroot"
62
63 proc die(msg: string) =
64 stderr.writeLine "muslkit: " & msg
65 quit(1)
66
67 proc parseOpts(): Opts =
68 ## Hand-rolled so `--root dir`, `--root=dir` and `-r dir` all work; parseopt
69 ## only accepts the attached forms.
70 result = Opts(root: getEnv("MUSLKIT_ROOT", defaultRoot()),
71 mirror: defaultMirror, branch: defaultBranch,
72 arch: defaultArch, repos: defaultRepos)
73 let argv = commandLineParams()
74 var i = 0
75 while i < argv.len:
76 var arg = argv[i]
77 if not arg.startsWith("-") or arg == "-":
78 result.args.add arg
79 inc i
80 continue
81 var
82 key = arg.strip(leading = true, trailing = false, chars = {'-'})
83 val = ""
84 attached = false
85 for sep in ['=', ':']:
86 let at = key.find(sep)
87 if at >= 0:
88 val = key[at + 1 .. ^1]
89 key = key[0 ..< at]
90 attached = true
91 break
92
93 proc takeValue(): string =
94 if attached: return val
95 inc i
96 if i >= argv.len: die "option --" & key & " needs a value"
97 argv[i]
98
99 case key
100 of "r", "root": result.root = takeValue()
101 of "b", "branch": result.branch = takeValue()
102 of "a", "arch": result.arch = takeValue()
103 of "m", "mirror": result.mirror = takeValue()
104 of "repo": result.repos = takeValue().split(',').filterIt(it.len > 0)
105 of "l", "lib": result.libs.add takeValue()
106 of "o", "output": result.output = takeValue()
107 of "cc": result.cc = takeValue()
108 of "no-deps": result.noDeps = true
109 of "refresh": result.refresh = true
110 of "q", "quiet": result.quiet = true
111 of "h", "help": result.help = true
112 else: die "unknown option: " & arg
113 inc i
114
115 proc emit(o: Opts, text: string) =
116 if o.output.len > 0:
117 createDir o.output.parentDir
118 writeFile(o.output, text)
119 if not o.quiet: stderr.writeLine "wrote " & o.output
120 else:
121 stdout.write text
122
123 proc cmdAdd(o: Opts, remote: Remote) =
124 if o.args.len == 0: die "add: name at least one package"
125 let
126 idx = remote.fetchIndex(o.refresh)
127 wanted = idx.resolve(o.args, withDeps = not o.noDeps)
128 for pkg in wanted:
129 let archive = remote.fetchPackage(pkg)
130 unpack(archive, o.root)
131 record(o.root, wanted)
132 if not o.quiet:
133 stderr.writeLine "unpacked " & $wanted.len & " package(s) into " & o.root
134 let libs = staticLibs(o.root)
135 if libs.len > 0:
136 stderr.writeLine "static libs: " &
137 libs.mapIt(it.extractFilename).join(" ")
138
139 proc cmdList(o: Opts) =
140 let entries = readManifest(o.root)
141 if entries.len == 0:
142 stderr.writeLine "nothing installed in " & o.root
143 return
144 for e in entries:
145 echo e.name.alignLeft(32), e.version.alignLeft(20), e.repo
146
147 proc cmdSearch(o: Opts, remote: Remote) =
148 if o.args.len == 0: die "search: give some text"
149 let
150 idx = remote.fetchIndex(o.refresh)
151 needle = o.args.join(" ").toLowerAscii
152 var hits = 0
153 for name, pkg in idx.packages:
154 if needle in name.toLowerAscii or needle in pkg.description.toLowerAscii:
155 echo name.alignLeft(34), pkg.version.alignLeft(16), pkg.description
156 inc hits
157 if hits == 0: stderr.writeLine "no matches for " & needle
158
159 proc cmdShow(o: Opts, remote: Remote) =
160 if o.args.len == 0: die "show: name a package"
161 let idx = remote.fetchIndex(o.refresh)
162 for want in o.args:
163 let name = idx.find(want)
164 if name.len == 0: die "no package provides '" & want & "'"
165 let pkg = idx.packages[name]
166 echo "name: ", pkg.name
167 echo "version: ", pkg.version
168 echo "repository: ", pkg.repo, "/", remote.arch
169 echo "size: ", pkg.size div 1024, " KiB"
170 echo "description: ", pkg.description
171 if pkg.depends.len > 0: echo "depends: ", pkg.depends.join(" ")
172 if pkg.provides.len > 0: echo "provides: ", pkg.provides.join(" ")
173 echo "url: ", remote.repoUrl(pkg.repo), "/", apkFile(pkg)
174
175 proc main() =
176 let o = parseOpts()
177 if o.help or o.args.len == 0:
178 stdout.write usageText
179 quit(if o.help: 0 else: 1)
180 let
181 cmd = o.args[0]
182 rest = Opts(root: o.root, mirror: o.mirror, branch: o.branch,
183 arch: o.arch, cc: o.cc, output: o.output, repos: o.repos,
184 libs: o.libs, args: o.args[1 .. ^1], noDeps: o.noDeps,
185 refresh: o.refresh, quiet: o.quiet)
186 remote = initRemote(o.mirror, o.branch, o.arch, o.repos, quiet = o.quiet)
187 case cmd
188 of "add": cmdAdd(rest, remote)
189 of "list": cmdList(rest)
190 of "libs": stdout.write describe(rest.root)
191 of "search": cmdSearch(rest, remote)
192 of "show": cmdShow(rest, remote)
193 of "nimflags": echo nimFlags(rest.root, rest.libs).join(" ")
194 of "ccflags": echo ccFlags(rest.root, rest.libs).join(" ")
195 of "nimcfg": rest.emit nimCfg(rest.root, rest.cc, rest.libs)
196 of "zigcc":
197 let path = if rest.output.len > 0: rest.output else: "zigcc"
198 writeZigCc(path, rest.arch & "-linux-musl")
199 if not rest.quiet: stderr.writeLine "wrote " & path
200 of "env":
201 for kv in pkgConfigEnv(rest.root):
202 echo "export ", kv
203 echo "export MUSLKIT_ROOT=", rest.root
204 of "path": echo rest.root
205 of "clean":
206 removeDir rest.root
207 if not rest.quiet: stderr.writeLine "removed " & rest.root
208 else: die "unknown command: " & cmd & " (try --help)"
209
210 try:
211 main()
212 except CatchableError as e:
213 die e.msg