| muslkit: musl static libraries from Alpine, without Alpine 5feb050 nandi 21h ago | 1 | ## Turning a sysroot into compiler flags. |
| 2 | ## |
| 3 | ## Nim's OpenSSL binding is the one place a static build needs more than paths: |
| 4 | ## `-d:ssl` makes Nim dlopen libssl at runtime, which a static binary cannot do, |
| 5 | ## and OpenSSL 3 dropped a symbol Nim still references. Both fixes are emitted |
| 6 | ## here rather than left as folklore in someone's shell history. |
| 7 | |
| 8 | import std/[os, strutils] |
| 9 | import ./sysroot |
| 10 | |
| 11 | type Toolchain* = enum |
| 12 | tcNim = "nim", tcCc = "cc", tcPkgConfig = "pkg-config" |
| 13 | |
| 14 | proc includeDir*(root: string): string = root / "usr" / "include" |
| 15 | proc libDir*(root: string): string = root / "usr" / "lib" |
| 16 | |
| 17 | proc hasLib(root, name: string): bool = |
| 18 | fileExists(libDir(root) / ("lib" & name & ".a")) |
| 19 | |
| 20 | proc nimFlags*(root: string, libs: openArray[string] = [], |
| 21 | opensslCompat = true): seq[string] = |
| 22 | result = @["--passC:-I" & includeDir(root), |
| 23 | "--passL:-L" & libDir(root), |
| 24 | "--passL:-static"] |
| 25 | if hasLib(root, "ssl") and hasLib(root, "crypto"): |
| 26 | # Stop Nim dlopen'ing OpenSSL, and link the archives instead. |
| 27 | result.add "--dynlibOverride:ssl" |
| 28 | result.add "--dynlibOverride:crypto" |
| 29 | result.add "--passL:" & libDir(root) / "libssl.a" |
| 30 | result.add "--passL:" & libDir(root) / "libcrypto.a" |
| 31 | if opensslCompat: |
| 32 | # Removed in OpenSSL 3.0; Nim's wrapper still names it. |
| 33 | result.add "--passC:-DSSL_get_peer_certificate=SSL_get1_peer_certificate" |
| 34 | for lib in libs: |
| 35 | result.add "--passL:-l" & lib |
| 36 | |
| 37 | proc ccFlags*(root: string, libs: openArray[string] = []): seq[string] = |
| 38 | result = @["-I" & includeDir(root), "-L" & libDir(root), "-static"] |
| 39 | for lib in libs: |
| 40 | result.add "-l" & lib |
| 41 | |
| 42 | proc pkgConfigEnv*(root: string): seq[string] = |
| 43 | @["PKG_CONFIG_SYSROOT_DIR=" & root, |
| 44 | "PKG_CONFIG_LIBDIR=" & libDir(root) / "pkgconfig"] |
| 45 | |
| 46 | proc nimCfg*(root, cc: string, libs: openArray[string] = []): string = |
| 47 | ## A nim.cfg fragment: cross-compiler plus every flag from `nimFlags`. |
| 48 | result = "# Generated by muslkit — static musl build against " & root & |
| 49 | "\n# Regenerate with: muslkit nimcfg --root " & root & "\n" |
| 50 | if cc.len > 0: |
| 51 | result.add "--cc:clang\n" |
| 52 | result.add "--clang.exe:\"" & cc & "\"\n" |
| 53 | result.add "--clang.linkerexe:\"" & cc & "\"\n" |
| 54 | for f in nimFlags(root, libs): |
| 55 | result.add f & "\n" |
| 56 | |
| 57 | const zigccTemplate* = """#!/bin/sh |
| 58 | # Generated by muslkit: zig as a musl cross-compiler. |
| 59 | exec zig cc -target @TARGET@ "$@" |
| 60 | """ |
| 61 | |
| 62 | proc zigccScript*(target: string): string = |
| 63 | zigccTemplate.replace("@TARGET@", target) |
| 64 | |
| 65 | proc writeZigCc*(path, target: string) = |
| 66 | writeFile(path, zigccScript(target)) |
| 67 | setFilePermissions(path, {fpUserRead, fpUserWrite, fpUserExec, |
| 68 | fpGroupRead, fpGroupExec, |
| 69 | fpOthersRead, fpOthersExec}) |
| 70 | |
| 71 | proc describe*(root: string): string = |
| 72 | let libs = staticLibs(root) |
| 73 | if libs.len == 0: |
| 74 | return "no static libraries in " & root |
| 75 | result = $libs.len & " static libraries in " & libDir(root) & ":\n" |
| 76 | for l in libs: |
| 77 | result.add " " & l.extractFilename & " (" & |
| 78 | $(getFileSize(l) div 1024) & " KiB)\n" |