Support linking the SDKs against secretspec-ffi via pkg-config #438
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "Haskell SDK" | |
| # Builds and tests the Haskell SDK (secretspec-hs) against a freshly built | |
| # secretspec-ffi staticlib. The SDK statically links the C ABI archive at build | |
| # time, so the Rust resolver is embedded in the binary and there is no runtime | |
| # loader path (no LD_LIBRARY_PATH). | |
| # | |
| # The Windows job cannot use devenv (no Nix on Windows runners), so it sets up | |
| # rustup + MSYS2 + GHC directly. GHC on Windows links with a MinGW toolchain, | |
| # so the staticlib comes from the x86_64-pc-windows-gnu Rust target (declared | |
| # in rust-toolchain.toml); the MSYS2 mingw gcc compiles the archive's C deps | |
| # (aws-lc-sys, sqlite, zstd) and NASM assembles aws-lc's x86_64 assembly. Some | |
| # of the archive's link libraries are import libraries shipped inside cargo | |
| # registry crates rather than in any MinGW distribution -- those are staged | |
| # next to the archive (scripts/copy-mingw-import-libs.sh) so GHC's linker | |
| # finds them. | |
| on: | |
| workflow_call: | |
| secrets: | |
| HACKAGE_TOKEN: | |
| description: Hackage API token used only by the tag-push publish job | |
| required: false | |
| workflow_dispatch: | |
| pull_request: | |
| paths: | |
| - "secretspec-hs/**" | |
| - "secretspec-ffi/**" | |
| - "secretspec/**" | |
| - ".github/workflows/haskell-build.yml" | |
| - "scripts/sync-sdk-versions.sh" | |
| - "scripts/copy-mingw-import-libs.sh" | |
| push: | |
| branches: [main] | |
| tags: | |
| - v** | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Sync SDK package versions | |
| run: bash scripts/sync-sdk-versions.sh | |
| # The cdylib + CLI debug build (AWS/GCP/Bitwarden providers) plus the | |
| # Nix store overflows the ~14GB free on a hosted runner. | |
| - name: Free up disk space | |
| run: | | |
| sudo rm -rf /usr/share/dotnet /usr/local/lib/android \ | |
| /opt/hostedtoolcache/CodeQL /usr/local/.ghcup /opt/ghc \ | |
| /usr/local/share/boost /usr/local/share/powershell | |
| sudo docker image prune --all --force | |
| df -h / | |
| - uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 | |
| - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 | |
| with: | |
| name: devenv | |
| - name: Install devenv.sh | |
| run: nix profile install nixpkgs#devenv | |
| - name: Build staticlib + CLI and run the Haskell SDK test-suite | |
| run: | | |
| devenv shell -- bash -c ' | |
| set -euo pipefail | |
| cargo build -p secretspec-ffi -p secretspec | |
| target_dir="$(cargo metadata --no-deps --format-version 1 \ | |
| | grep -o "\"target_directory\":\"[^\"]*\"" | head -1 | sed "s/.*:\"\(.*\)\"/\1/")" | |
| export SECRETSPEC_BIN="$target_dir/debug/secretspec" | |
| # Capture the staticlib archive plus its transitive native deps. Stage | |
| # the .a alone so -lsecretspec_ffi resolves to the archive (target/debug | |
| # also holds the .so) and the resolver is embedded with no loader path. | |
| native_libs="$(cargo rustc -q -p secretspec-ffi --crate-type staticlib -- \ | |
| --print native-static-libs 2>&1 | sed -n "s/^note: native-static-libs: //p" | tail -1)" | |
| hs_lib_dir="$(mktemp -d)" | |
| cp "$target_dir/debug/libsecretspec_ffi.a" "$hs_lib_dir/" | |
| cd secretspec-hs | |
| cabal update | |
| # --write-ghc-environment-files lets the codegen test compile the | |
| # quicktype-generated module; SECRETSPEC_BIN lets it run the CLI. | |
| # All -optl flags go in ONE --ghc-options occurrence: cabal reverses | |
| # the order of repeated occurrences, which breaks `-framework X` | |
| # pairs on macOS. | |
| cabal test --extra-lib-dirs="$hs_lib_dir" \ | |
| --ghc-options="-optl${native_libs// / -optl}" \ | |
| --write-ghc-environment-files=always --test-show-details=streaming | |
| ffi_prefix="$(mktemp -d)" | |
| bash ../secretspec-ffi/scripts/cinstall.sh "$ffi_prefix" | |
| PKG_CONFIG_PATH="$ffi_prefix/lib/pkgconfig" \ | |
| pkg-config --print-errors --exists secretspec_ffi | |
| PKG_CONFIG_PATH="$ffi_prefix/lib/pkgconfig" cabal test -f use-pkg-config \ | |
| --write-ghc-environment-files=always --test-show-details=streaming | |
| ' | |
| build-windows: | |
| name: build (windows) | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Sync SDK package versions | |
| run: bash scripts/sync-sdk-versions.sh | |
| - name: Install Rust (pinned by rust-toolchain.toml) | |
| run: rustup toolchain install | |
| - name: Cache Rust builds | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| - name: Install NASM (aws-lc-sys assembly) | |
| uses: ilammy/setup-nasm@72793074d3c8cdda771dba85f6deafe00623038b # v1.5.2 | |
| # MINGW64 (msvcrt-based) matches the CRT the x86_64-pc-windows-gnu Rust | |
| # target links, so every object in the archive agrees on one C runtime. | |
| - name: Install MSYS2 MinGW toolchain (compiles the staticlib's C deps) | |
| uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0 | |
| with: | |
| msystem: MINGW64 | |
| path-type: inherit | |
| install: mingw-w64-x86_64-gcc mingw-w64-x86_64-binutils | |
| - name: Install GHC and cabal | |
| uses: haskell-actions/setup@6037f33647c3f17758a2356c80fc4a53d7e0685d # v2.12.0 | |
| with: | |
| ghc-version: "9.6" | |
| - name: Build staticlib + CLI and run the Haskell SDK test-suite | |
| shell: msys2 {0} | |
| run: | | |
| set -euo pipefail | |
| # The archive for GHC's MinGW linker; --crate-type overrides the | |
| # crate's list so the unused cdylib is never linked. Release like | |
| # the shipped artifacts: a debug archive carries sectionless .dwo | |
| # members that objcopy below refuses to process. | |
| cargo rustc -p secretspec-ffi --release --target x86_64-pc-windows-gnu \ | |
| --crate-type staticlib | |
| # The CLI (for the test-suite's end-to-end codegen test) builds for | |
| # the default MSVC host target, same as the released binaries. | |
| cargo build -p secretspec | |
| export SECRETSPEC_BIN="$(cygpath -w "$PWD/target/debug/secretspec.exe")" | |
| native_libs="$(cargo rustc -q -p secretspec-ffi --release \ | |
| --target x86_64-pc-windows-gnu --crate-type staticlib -- \ | |
| --print native-static-libs 2>&1 \ | |
| | sed -n 's/^note: native-static-libs: //p' | tail -1)" | |
| # Stage the .a alone (target/ also holds rlibs) plus the import | |
| # libraries that ship inside cargo registry crates (see header). | |
| hs_lib_dir="$(mktemp -d)" | |
| cp target/x86_64-pc-windows-gnu/release/libsecretspec_ffi.a "$hs_lib_dir/" | |
| # Two archive fixups for GHC's older bundled toolchain, which links | |
| # the final test binary: | |
| # * rustc's windows-gnu objects carry `-exclude-symbols` .drectve | |
| # directives (DLL export hygiene) that GHC 9.6's ld.lld rejects | |
| # inside .drectve (LLVM allows them only from 17); meaningless | |
| # for a static executable link, so strip the section. | |
| # * the MSYS2-compiled aws-lc objects call nanosleep through its | |
| # time64 asm alias (nanosleep64), which GHC's older winpthreads | |
| # does not export; on x86_64 time_t is 64-bit either way, so | |
| # alias it back and GHC's winpthreads import library satisfies | |
| # it. (Linking MSYS2's winpthreads statically instead collides: | |
| # GHC's driver always links its own winpthreads import library, | |
| # and the two define overlapping internals.) | |
| # objcopy refuses archives holding sectionless split-debuginfo | |
| # members (the prebuilt std ships .dwo members a linker never | |
| # selects), so drop those first. | |
| ar t "$hs_lib_dir/libsecretspec_ffi.a" | grep '\.dwo$' | while read -r m; do | |
| ar d "$hs_lib_dir/libsecretspec_ffi.a" "$m" | |
| done | |
| objcopy --remove-section=.drectve --redefine-sym nanosleep64=nanosleep \ | |
| "$hs_lib_dir/libsecretspec_ffi.a" | |
| printf '%s\n' "$native_libs" > "$hs_lib_dir/native-static-libs.txt" | |
| bash scripts/copy-mingw-import-libs.sh \ | |
| "$hs_lib_dir/native-static-libs.txt" "$hs_lib_dir" | |
| # Resolving pthread symbols from GHC's winpthreads import library | |
| # makes the test binary import libwinpthread-1.dll at runtime, and | |
| # cabal does not put GHC's bundled toolchain bin on PATH | |
| # (STATUS_DLL_NOT_FOUND when the suite starts). Add it ourselves; | |
| # the ls fails the job early if the GHC layout ever changes. | |
| mingw_bin="$(cygpath -u "$(ghc --print-libdir)")/../mingw/bin" | |
| ls "$mingw_bin"/libwinpthread-1.dll | |
| export PATH="$PATH:$mingw_bin" | |
| cd secretspec-hs | |
| cabal update | |
| cabal test --extra-lib-dirs="$(cygpath -w "$hs_lib_dir")" \ | |
| --ghc-options="-optl${native_libs// / -optl}" \ | |
| --write-ghc-environment-files=always --test-show-details=streaming || { | |
| # cabal keeps the suite's output in a log file; surface it, and | |
| # run the binary directly to expose a load-time failure's status. | |
| echo "=== test suite log ===" | |
| find dist-newstyle -name "*.log" -path "*secretspec-test*" -exec cat {} + | |
| exe="$(find dist-newstyle -name "secretspec-test.exe" | head -1)" | |
| echo "=== imported DLLs ===" | |
| objdump -p "$exe" | grep -i "DLL Name" || true | |
| ldd "$exe" || true | |
| echo "=== direct run: $exe ===" | |
| "$exe" || echo "direct run exit code: $?" | |
| exit 1 | |
| } | |
| publish: | |
| name: publish to Hackage | |
| if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') | |
| needs: [build, build-windows] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Sync SDK package versions | |
| run: bash scripts/sync-sdk-versions.sh | |
| - uses: cachix/install-nix-action@630ae543ea3a38a9a4166f03376c02c50f408342 # v31.11.0 | |
| - uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17 | |
| with: | |
| name: devenv | |
| - name: Install devenv.sh | |
| run: nix profile install nixpkgs#devenv | |
| - name: sdist and upload to Hackage | |
| # Requires the HACKAGE_TOKEN secret. The package statically links | |
| # secretspec-ffi at build time, so Hackage's build bots cannot compile it | |
| # (no staticlib, no Rust toolchain); the upload still succeeds and the | |
| # README documents the link requirement. | |
| env: | |
| HACKAGE_TOKEN: ${{ secrets.HACKAGE_TOKEN }} | |
| run: | | |
| devenv shell -- bash -c ' | |
| set -euo pipefail | |
| cd secretspec-hs | |
| cabal sdist | |
| cabal upload --publish --token="$HACKAGE_TOKEN" \ | |
| dist-newstyle/sdist/secretspec-*.tar.gz | |
| ' |