ci:cross-compilation linux (#454)

Drew Smirnoff created

Change summary

.github/workflows/nightly.yml | 47 ++++++++++++++++++++++++++++++++++++
.github/workflows/release.yml | 47 ++++++++++++++++++++++++++++++++++++
.goreleaser.nightly.yml       |  4 ++
.goreleaser.yml               |  4 ++
4 files changed, 98 insertions(+), 4 deletions(-)

Detailed changes

.github/workflows/nightly.yml 🔗

@@ -40,15 +40,60 @@ jobs:
           curl -fsSL "$PCSC_URL/pcsclite.h.in" -o "$PCSC_DIR/include/pcsclite.h"
           sed -i '' 's/@VERSION@/1.9.0/' "$PCSC_DIR/include/pcsclite.h"
 
-          # Create pkg-config file (headers-only, no library needed for cross-compilation)
+          # Build stub library — the real libpcsclite is loaded at runtime on the user's system,
+          # but the linker needs symbols to resolve during cross-compilation.
+          cat > "$RUNNER_TEMP/pcsclite_stub.c" << 'STUB'
+          #include <stddef.h>
+          typedef long LONG;
+          typedef unsigned long DWORD;
+          typedef void *LPCVOID;
+          typedef char *LPSTR;
+          typedef const char *LPCSTR;
+          typedef unsigned char *LPBYTE;
+          typedef unsigned char BYTE;
+          typedef LONG SCARDCONTEXT;
+          typedef LONG SCARDHANDLE;
+          typedef struct { DWORD dwProtocol; DWORD cbPciLength; } SCARD_IO_REQUEST;
+          typedef struct { LPCSTR szReader; DWORD dwCurrentState; DWORD dwEventState; DWORD cbAtr; unsigned char rgbAtr[36]; void *pvUserData; } SCARD_READERSTATE;
+          LONG SCardEstablishContext(DWORD s, LPCVOID r1, LPCVOID r2, SCARDCONTEXT *c) { return 0; }
+          LONG SCardReleaseContext(SCARDCONTEXT c) { return 0; }
+          LONG SCardIsValidContext(SCARDCONTEXT c) { return 0; }
+          LONG SCardCancel(SCARDCONTEXT c) { return 0; }
+          LONG SCardConnect(SCARDCONTEXT c, LPCSTR r, DWORD s, DWORD p, SCARDHANDLE *h, DWORD *ap) { return 0; }
+          LONG SCardReconnect(SCARDHANDLE h, DWORD s, DWORD p, DWORD d, DWORD *ap) { return 0; }
+          LONG SCardDisconnect(SCARDHANDLE h, DWORD d) { return 0; }
+          LONG SCardBeginTransaction(SCARDHANDLE h) { return 0; }
+          LONG SCardEndTransaction(SCARDHANDLE h, DWORD d) { return 0; }
+          LONG SCardStatus(SCARDHANDLE h, LPSTR r, DWORD *rl, DWORD *s, DWORD *p, LPBYTE a, DWORD *al) { return 0; }
+          LONG SCardTransmit(SCARDHANDLE h, const SCARD_IO_REQUEST *si, const BYTE *s, DWORD sl, SCARD_IO_REQUEST *ri, BYTE *r, DWORD *rl) { return 0; }
+          LONG SCardControl(SCARDHANDLE h, DWORD c, LPCVOID i, DWORD il, void *o, DWORD ol, DWORD *br) { return 0; }
+          LONG SCardGetAttrib(SCARDHANDLE h, DWORD a, LPBYTE b, DWORD *bl) { return 0; }
+          LONG SCardSetAttrib(SCARDHANDLE h, DWORD a, const BYTE *b, DWORD bl) { return 0; }
+          LONG SCardListReaders(SCARDCONTEXT c, LPCSTR g, LPSTR r, DWORD *rl) { return 0; }
+          LONG SCardListReaderGroups(SCARDCONTEXT c, LPSTR g, DWORD *gl) { return 0; }
+          LONG SCardGetStatusChange(SCARDCONTEXT c, DWORD t, SCARD_READERSTATE *s, DWORD n) { return 0; }
+          LONG SCardFreeMemory(SCARDCONTEXT c, LPCVOID m) { return 0; }
+          const char *pcsc_stringify_error(LONG e) { return "stub"; }
+          STUB
+
+          zig cc -c -target x86_64-linux-musl -o "$RUNNER_TEMP/pcsclite_stub_amd64.o" "$RUNNER_TEMP/pcsclite_stub.c"
+          zig cc -c -target aarch64-linux-musl -o "$RUNNER_TEMP/pcsclite_stub_arm64.o" "$RUNNER_TEMP/pcsclite_stub.c"
+
+          mkdir -p "$PCSC_DIR/lib/amd64" "$PCSC_DIR/lib/arm64"
+          ar rcs "$PCSC_DIR/lib/amd64/libpcsclite.a" "$RUNNER_TEMP/pcsclite_stub_amd64.o"
+          ar rcs "$PCSC_DIR/lib/arm64/libpcsclite.a" "$RUNNER_TEMP/pcsclite_stub_arm64.o"
+
+          # Create pkg-config file — Libs will be overridden per-arch via CGO_LDFLAGS in goreleaser
           cat > "$PCSC_DIR/lib/pkgconfig/libpcsclite.pc" << EOF
           Name: libpcsclite
           Description: PC/SC Lite
           Version: 1.9.0
           Cflags: -I$PCSC_DIR/include
+          Libs: -L$PCSC_DIR/lib/amd64 -lpcsclite
           EOF
 
           echo "PKG_CONFIG_PATH=$PCSC_DIR/lib/pkgconfig" >> $GITHUB_ENV
+          echo "PCSC_DIR=$PCSC_DIR" >> $GITHUB_ENV
 
       - name: Get macOS SDK path
         id: macos_sdk

.github/workflows/release.yml 🔗

@@ -50,15 +50,60 @@ jobs:
           curl -fsSL "$PCSC_URL/pcsclite.h.in" -o "$PCSC_DIR/include/pcsclite.h"
           sed -i '' 's/@VERSION@/1.9.0/' "$PCSC_DIR/include/pcsclite.h"
 
-          # Create pkg-config file (headers-only, no library needed for cross-compilation)
+          # Build stub library — the real libpcsclite is loaded at runtime on the user's system,
+          # but the linker needs symbols to resolve during cross-compilation.
+          cat > "$RUNNER_TEMP/pcsclite_stub.c" << 'STUB'
+          #include <stddef.h>
+          typedef long LONG;
+          typedef unsigned long DWORD;
+          typedef void *LPCVOID;
+          typedef char *LPSTR;
+          typedef const char *LPCSTR;
+          typedef unsigned char *LPBYTE;
+          typedef unsigned char BYTE;
+          typedef LONG SCARDCONTEXT;
+          typedef LONG SCARDHANDLE;
+          typedef struct { DWORD dwProtocol; DWORD cbPciLength; } SCARD_IO_REQUEST;
+          typedef struct { LPCSTR szReader; DWORD dwCurrentState; DWORD dwEventState; DWORD cbAtr; unsigned char rgbAtr[36]; void *pvUserData; } SCARD_READERSTATE;
+          LONG SCardEstablishContext(DWORD s, LPCVOID r1, LPCVOID r2, SCARDCONTEXT *c) { return 0; }
+          LONG SCardReleaseContext(SCARDCONTEXT c) { return 0; }
+          LONG SCardIsValidContext(SCARDCONTEXT c) { return 0; }
+          LONG SCardCancel(SCARDCONTEXT c) { return 0; }
+          LONG SCardConnect(SCARDCONTEXT c, LPCSTR r, DWORD s, DWORD p, SCARDHANDLE *h, DWORD *ap) { return 0; }
+          LONG SCardReconnect(SCARDHANDLE h, DWORD s, DWORD p, DWORD d, DWORD *ap) { return 0; }
+          LONG SCardDisconnect(SCARDHANDLE h, DWORD d) { return 0; }
+          LONG SCardBeginTransaction(SCARDHANDLE h) { return 0; }
+          LONG SCardEndTransaction(SCARDHANDLE h, DWORD d) { return 0; }
+          LONG SCardStatus(SCARDHANDLE h, LPSTR r, DWORD *rl, DWORD *s, DWORD *p, LPBYTE a, DWORD *al) { return 0; }
+          LONG SCardTransmit(SCARDHANDLE h, const SCARD_IO_REQUEST *si, const BYTE *s, DWORD sl, SCARD_IO_REQUEST *ri, BYTE *r, DWORD *rl) { return 0; }
+          LONG SCardControl(SCARDHANDLE h, DWORD c, LPCVOID i, DWORD il, void *o, DWORD ol, DWORD *br) { return 0; }
+          LONG SCardGetAttrib(SCARDHANDLE h, DWORD a, LPBYTE b, DWORD *bl) { return 0; }
+          LONG SCardSetAttrib(SCARDHANDLE h, DWORD a, const BYTE *b, DWORD bl) { return 0; }
+          LONG SCardListReaders(SCARDCONTEXT c, LPCSTR g, LPSTR r, DWORD *rl) { return 0; }
+          LONG SCardListReaderGroups(SCARDCONTEXT c, LPSTR g, DWORD *gl) { return 0; }
+          LONG SCardGetStatusChange(SCARDCONTEXT c, DWORD t, SCARD_READERSTATE *s, DWORD n) { return 0; }
+          LONG SCardFreeMemory(SCARDCONTEXT c, LPCVOID m) { return 0; }
+          const char *pcsc_stringify_error(LONG e) { return "stub"; }
+          STUB
+
+          zig cc -c -target x86_64-linux-musl -o "$RUNNER_TEMP/pcsclite_stub_amd64.o" "$RUNNER_TEMP/pcsclite_stub.c"
+          zig cc -c -target aarch64-linux-musl -o "$RUNNER_TEMP/pcsclite_stub_arm64.o" "$RUNNER_TEMP/pcsclite_stub.c"
+
+          mkdir -p "$PCSC_DIR/lib/amd64" "$PCSC_DIR/lib/arm64"
+          ar rcs "$PCSC_DIR/lib/amd64/libpcsclite.a" "$RUNNER_TEMP/pcsclite_stub_amd64.o"
+          ar rcs "$PCSC_DIR/lib/arm64/libpcsclite.a" "$RUNNER_TEMP/pcsclite_stub_arm64.o"
+
+          # Create pkg-config file
           cat > "$PCSC_DIR/lib/pkgconfig/libpcsclite.pc" << EOF
           Name: libpcsclite
           Description: PC/SC Lite
           Version: 1.9.0
           Cflags: -I$PCSC_DIR/include
+          Libs: -L$PCSC_DIR/lib/amd64 -lpcsclite
           EOF
 
           echo "PKG_CONFIG_PATH=$PCSC_DIR/lib/pkgconfig" >> $GITHUB_ENV
+          echo "PCSC_DIR=$PCSC_DIR" >> $GITHUB_ENV
 
       - name: Get macOS SDK path
         id: macos_sdk

.goreleaser.nightly.yml 🔗

@@ -31,7 +31,9 @@ builds:
       - >-
         {{- if eq .Os "darwin" }}CGO_CFLAGS=-isysroot {{ .Env.SDK_PATH }} -mmacosx-version-min=11.0{{- end }}
       - >-
-        {{- if eq .Os "darwin" }}CGO_LDFLAGS=-isysroot {{ .Env.SDK_PATH }}{{- end }}
+        {{- if eq .Os "darwin" }}CGO_LDFLAGS=-isysroot {{ .Env.SDK_PATH }}
+        {{- else if eq .Os "linux" }}CGO_LDFLAGS=-L{{ .Env.PCSC_DIR }}/lib/{{ .Arch }}
+        {{- end }}
       - >-
         {{- if eq .Os "darwin" }}
           {{- if eq .Arch "amd64"}}CC=clang -arch x86_64 -isysroot {{ .Env.SDK_PATH }}{{- end }}

.goreleaser.yml 🔗

@@ -40,7 +40,9 @@ builds:
       - >-
         {{- if eq .Os "darwin" }}CGO_CFLAGS=-isysroot {{ .Env.SDK_PATH }} -mmacosx-version-min=11.0{{- end }}
       - >-
-        {{- if eq .Os "darwin" }}CGO_LDFLAGS=-isysroot {{ .Env.SDK_PATH }}{{- end }}
+        {{- if eq .Os "darwin" }}CGO_LDFLAGS=-isysroot {{ .Env.SDK_PATH }}
+        {{- else if eq .Os "linux" }}CGO_LDFLAGS=-L{{ .Env.PCSC_DIR }}/lib/{{ .Arch }}
+        {{- end }}
       - >-
         {{- if eq .Os "darwin" }}
           {{- if eq .Arch "amd64"}}CC=clang -arch x86_64 -isysroot {{ .Env.SDK_PATH }}{{- end }}