crabc 0.7.8

A Rust musl-compatible libc with a dynamic linker
# Copyright (c) 2026 Meng Zhuo
# SPDX-License-Identifier: BSD-3-Clause

name: Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write   # create the GitHub release
  id-token: write   # mint the short-lived crates.io token via OIDC

concurrency:
  group: release-${{ github.ref }}
  cancel-in-progress: false

jobs:
  test:
    strategy:
      fail-fast: false
      matrix:
        include:
          - arch: x86_64
            os: ubuntu-latest
          - arch: aarch64
            os: ubuntu-24.04-arm
    runs-on: ${{ matrix.os }}
    timeout-minutes: 360
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust nightly
        uses: dtolnay/rust-toolchain@nightly

      - name: Install musl-tools
        run: sudo apt-get update && sudo apt-get install -y musl-tools

      - name: Build all artifacts
        run: cargo build --workspace

      - name: Run integration tests
        run: |
          set -o pipefail
          cargo test --workspace --no-fail-fast 2>&1 | tee /tmp/integration-tests.log

      - name: Report failed tests
        if: failure()
        run: |
          if [ -f /tmp/integration-tests.log ]; then
            grep -E '^test .* FAILED|FAILED|panicked at|error: test failed' /tmp/integration-tests.log | tail -80 | while IFS= read -r line; do
              echo "::error::$line"
            done
          fi

  macro-parity:
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7

      - name: Install cross compilers
        run: |
          sudo apt-get update
          sudo apt-get install -y gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu g++-aarch64-linux-gnu g++-riscv64-linux-gnu make curl

      - name: Cache musl 1.2.6 headers
        uses: actions/cache@v6
        with:
          path: ~/.cache/crabc
          key: musl-1.2.6-headers-v1

      - name: Check macro expansion parity
        run: ./scripts/macro-parity.sh

      - name: Check headers against Rust constants
        run: python3 tests/header_rust_consistency.py --include include

  publish:
    needs: [test, macro-parity]
    runs-on: ubuntu-latest
    # Must match the "Environment" field of the trusted publisher configured
    # on crates.io for crabc, crabc-libc and crabc-ldso.
    environment: release
    steps:
      - uses: actions/checkout@v7

      - name: Install Rust nightly
        uses: dtolnay/rust-toolchain@nightly

      - name: Verify tag matches workspace versions
        run: |
          set -euo pipefail
          version="${GITHUB_REF_NAME#v}"
          cargo metadata --no-deps --format-version 1 > /tmp/metadata.json
          for pkg in crabc crabc-libc crabc-ldso crabc-utils; do
            got=$(jq -r --arg p "$pkg" '.packages[] | select(.name == $p) | .version' /tmp/metadata.json)
            if [ "$got" != "$version" ]; then
              echo "::error::$pkg is version $got but tag $GITHUB_REF_NAME expects $version"
              exit 1
            fi
          done
          echo "Publishing version $version"

      - name: Install musl, cross toolchains and targets
        run: |
          sudo apt-get update
          sudo apt-get install -y musl-tools gcc-aarch64-linux-gnu gcc-riscv64-linux-gnu
          rustup target add \
            x86_64-unknown-linux-musl \
            aarch64-unknown-linux-musl \
            aarch64-unknown-linux-gnu \
            riscv64gc-unknown-linux-musl

      - name: Build release artifacts
        run: |
          set -eux
          cargo build --release -p crabc-libc -p crabc-ldso --target x86_64-unknown-linux-gnu
          CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc \
            cargo build --release -p crabc --target x86_64-unknown-linux-musl
          CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
            cargo build --release -p crabc-libc -p crabc-ldso --target aarch64-unknown-linux-gnu
          CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=aarch64-linux-gnu-gcc \
            cargo build --release -p crabc --target aarch64-unknown-linux-musl
          CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_LINKER=riscv64-linux-gnu-gcc \
            cargo build --release --workspace --target riscv64gc-unknown-linux-musl
          lld="$(rustc --print sysroot)/lib/rustlib/$(rustc -vV | sed -n 's/^host: //p')/bin/rust-lld"
          RUSTFLAGS="-C target-feature=+crt-static -C link-self-contained=yes -C linker=$lld" \
            CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_MUSL_LINKER="$lld" \
            cargo build --release -p crabc --target riscv64gc-unknown-linux-musl

      - name: Build Rust CRT objects and crabc-utils
        run: |
          set -o pipefail
          ./scripts/build-crt.sh > /tmp/crt-build.log 2>&1 || {
            tail -40 /tmp/crt-build.log | sed 's/^/::error::/'
            exit 1
          }
          ./scripts/build-utils.sh > /tmp/utils-build.log 2>&1 || {
            tail -10 /tmp/utils-build.log | sed 's/^/::error::/'
            exit 1
          }

      - name: Build Alpine packages
        run: ./scripts/package-apk.sh

      - name: Smoke-test crabc-utils
        env:
          VERSION: ${{ github.ref_name }}
        run: |
          set -eux
          version="${VERSION#v}"
          docker run --rm -v "$PWD/dist:/dist:ro" alpine:3.23 sh -c "
            apk add --allow-untrusted \
              /dist/crabc-${version}-r0.x86_64.apk \
              /dist/crabc-utils-${version}-r0.x86_64.apk
            crabc-utils getconf PAGESIZE >/dev/null
            ldd /bin/busybox >/dev/null
          "

      - name: Smoke-test the packaged libc
        env:
          VERSION: ${{ github.ref_name }}
        run: |
          set -eux
          version="${VERSION#v}"
          musl-gcc -fPIE -pie -D_GNU_SOURCE -I include \
            -Wl,--dynamic-linker=/usr/lib/crabc/libldso.so \
            -L target/x86_64-unknown-linux-gnu/release \
            tests/fixtures/compat_test.c -Wl,--allow-shlib-undefined -lpthread -lc \
            -o /tmp/compat_apk
          docker run --rm --network=none \
            -v "$PWD/dist:/dist:ro" -v "/tmp:/work:ro" alpine:3.23 sh -c "
              apk --no-network add --allow-untrusted /dist/crabc-${version}-r0.x86_64.apk
              mkdir -p /tmp/crabtest && touch /tmp/crabtest/seed
              LD_LIBRARY_PATH=/usr/lib/crabc /work/compat_apk
            "

      - name: Authenticate with crates.io
        id: auth
        uses: rust-lang/crates-io-auth-action@v1

      # Publishing is idempotent: re-running the workflow for a tag whose
      # crates are already on crates.io skips them instead of failing.
      - name: Publish crabc-libc
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: |
          set -euo pipefail
          version="${GITHUB_REF_NAME#v}"
          code=$(curl -s -o /dev/null -w '%{http_code}' \
            -A 'crabc-release (+https://github.com/mengzhuo/crabc)' \
            "https://crates.io/api/v1/crates/crabc-libc/$version")
          case "$code" in
            200) echo "crabc-libc $version is already published; skipping" ;;
            404) RUSTFLAGS='-C panic=abort' cargo publish -p crabc-libc --registry crates-io ;;
            *) echo "::error::unexpected crates.io status $code"; exit 1 ;;
          esac

      - name: Publish crabc-ldso
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: |
          set -euo pipefail
          version="${GITHUB_REF_NAME#v}"
          code=$(curl -s -o /dev/null -w '%{http_code}' \
            -A 'crabc-release (+https://github.com/mengzhuo/crabc)' \
            "https://crates.io/api/v1/crates/crabc-ldso/$version")
          case "$code" in
            200) echo "crabc-ldso $version is already published; skipping" ;;
            404) RUSTFLAGS='-C panic=abort' cargo publish -p crabc-ldso --registry crates-io ;;
            *) echo "::error::unexpected crates.io status $code"; exit 1 ;;
          esac

      - name: Publish crabc
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
        run: |
          set -euo pipefail
          version="${GITHUB_REF_NAME#v}"
          code=$(curl -s -o /dev/null -w '%{http_code}' \
            -A 'crabc-release (+https://github.com/mengzhuo/crabc)' \
            "https://crates.io/api/v1/crates/crabc/$version")
          case "$code" in
            200) echo "crabc $version is already published; skipping" ;;
            404) cargo publish -p crabc --registry crates-io ;;
            *) echo "::error::unexpected crates.io status $code"; exit 1 ;;
          esac

      - name: Create GitHub release
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 || \
            gh release create "$GITHUB_REF_NAME" --generate-notes --verify-tag
          gh release upload "$GITHUB_REF_NAME" dist/*.apk dist/SHA256SUMS --clobber