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
|