## 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 ... 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 Search the index (names and descriptions) muslkit show 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 Sysroot (default $XDG_DATA_HOME/muslkit/sysroot) -b, --branch Alpine branch (default v3.21; `edge` for rolling) -a, --arch Target arch (default x86_64) -m, --mirror Mirror base (default https://dl-cdn.alpinelinux.org/alpine) --repo Comma-separated repositories (default main,community) -l, --lib Add -l to emitted flags (repeatable) --cc 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