diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 235616c..81b5b70 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,3 +110,9 @@ jobs: buf generate cargo fmt --all git diff --exit-code -- src + + prebuilt: + permissions: + contents: read + name: Verify prebuilt distribution + uses: ./.github/workflows/prebuilt.yml diff --git a/.github/workflows/prebuilt.yml b/.github/workflows/prebuilt.yml new file mode 100644 index 0000000..afe3fa0 --- /dev/null +++ b/.github/workflows/prebuilt.yml @@ -0,0 +1,214 @@ +name: Prebuilt binaries + +on: + workflow_call: + inputs: + ref: + type: string + default: "" + verify-install: + type: boolean + default: false + +permissions: + contents: read + +defaults: + run: + shell: bash + +env: + CARGO_TERM_COLOR: always + MANIFEST: Cargo.toml + CARGO_PACKAGE: sqlc-gen-sqlx + BINARY_NAME: sqlc-gen-sqlx + RELEASE_PREFIX: v + +jobs: + build: + if: ${{ !inputs.verify-install }} + permissions: + contents: read + name: Build ${{ matrix.target }} + runs-on: ${{ matrix.build_runner }} + timeout-minutes: 90 + strategy: + fail-fast: false + matrix: &targets + include: + - target: x86_64-apple-darwin + runner: macos-15-intel + build_runner: macos-15-intel + - target: aarch64-apple-darwin + runner: macos-15 + build_runner: macos-15 + - target: x86_64-unknown-linux-gnu + runner: ubuntu-22.04 + build_runner: ubuntu-22.04 + - target: aarch64-unknown-linux-gnu + runner: ubuntu-22.04-arm + build_runner: ubuntu-22.04-arm + - target: x86_64-unknown-linux-musl + runner: ubuntu-22.04 + build_runner: ubuntu-22.04 + - target: aarch64-unknown-linux-musl + runner: ubuntu-22.04-arm + build_runner: ubuntu-22.04-arm + - target: x86_64-pc-windows-msvc + runner: windows-2025 + build_runner: windows-2025 + - target: aarch64-pc-windows-msvc + runner: windows-11-arm + build_runner: windows-11-arm + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: ${{ inputs.ref || github.sha }} + - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 + with: + install_args: rust + cache: false + - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2 + with: + key: release-${{ matrix.target }} + - name: Install Linux build dependencies + if: runner.os == 'Linux' + run: sudo apt-get update && sudo apt-get install -y build-essential pkg-config musl-tools libdbus-1-dev + - name: Configure target linking + env: + TARGET: ${{ matrix.target }} + run: | + target_env="$(printf '%s' "$TARGET" | tr '[:lower:]-' '[:upper:]_')" + if [[ "$TARGET" == *musl ]]; then + echo "CARGO_TARGET_${target_env}_RUSTFLAGS=-C target-feature=+crt-static" + # Rust supplies its musl runtime; musl-gcc is for C dependencies. + echo "CC_${TARGET//-/_}=musl-gcc" + elif [[ "$TARGET" == *windows* ]]; then + echo "CARGO_TARGET_${target_env}_RUSTFLAGS=-C target-feature=+crt-static" + fi >> "$GITHUB_ENV" + + - name: Build and package executable + env: + TARGET: ${{ matrix.target }} + run: | + version="$(awk -F'"' '/^version = / { print $2; exit }' "$MANIFEST")" + test -n "$version" + rustup target add "$TARGET" + cargo build --locked --release -p "$CARGO_PACKAGE" --target "$TARGET" + mkdir -p dist + if [[ "$TARGET" == *windows* ]]; then + archive="$(pwd)/dist/$CARGO_PACKAGE-$version-$TARGET.zip" + (cd "target/$TARGET/release" && 7z a -tzip "$archive" "$BINARY_NAME.exe") + else + tar -C "target/$TARGET/release" -czf "dist/$CARGO_PACKAGE-$version-$TARGET.tar.gz" "$BINARY_NAME" + fi + cd dist + for archive in *.tar.gz *.zip; do + [[ -f "$archive" ]] || continue + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$archive" > "$archive.sha256" + else + shasum -a 256 "$archive" > "$archive.sha256" + fi + done + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: binary-${{ matrix.target }} + path: dist/* + if-no-files-found: error + + smoke: + if: ${{ !inputs.verify-install }} + permissions: + contents: read + needs: build + name: Run archive ${{ matrix.target }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: *targets + steps: + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: binary-${{ matrix.target }} + path: dist + - name: Verify checksum and run extracted executable + env: + TARGET: ${{ matrix.target }} + run: | + cd dist + mkdir unpack + if [[ "$TARGET" == *windows* ]]; then + archive=("$CARGO_PACKAGE"-*.zip) + binary="$BINARY_NAME.exe" + else + archive=("$CARGO_PACKAGE"-*.tar.gz) + binary="$BINARY_NAME" + fi + test "${#archive[@]}" -eq 1 + if command -v sha256sum >/dev/null 2>&1; then + sha256sum -c "${archive[0]}.sha256" + else + shasum -a 256 -c "${archive[0]}.sha256" + fi + if [[ "$TARGET" == *windows* ]]; then + 7z x "${archive[0]}" -ounpack + else + tar -xzf "${archive[0]}" -C unpack + fi + if [[ "$CARGO_PACKAGE" == protoc-gen-* || "$CARGO_PACKAGE" == sqlc-gen-sqlx ]]; then + "unpack/$binary" < /dev/null > smoke.out + else + "unpack/$binary" --help > smoke.out + fi + test -s smoke.out + + install: + if: inputs.verify-install + permissions: + contents: read + name: Install without compiling ${{ matrix.target }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: *targets + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + ref: ${{ inputs.ref || github.sha }} + - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 + with: + install_args: cargo-binstall + cache: false + - name: Install published binaries through binstall and mise + env: + TARGET: ${{ matrix.target }} + GH_TOKEN: ${{ github.token }} + run: | + version="$(awk -F'"' '/^version = / { print $2; exit }' "$MANIFEST")" + test -n "$version" + binary="$BINARY_NAME" + archive="$CARGO_PACKAGE-$version-$TARGET.tar.gz" + if [[ "$TARGET" == *windows* ]]; then + binary="$binary.exe" + archive="$CARGO_PACKAGE-$version-$TARGET.zip" + fi + cargo-binstall "$CARGO_PACKAGE@$version" --no-confirm --disable-telemetry \ + --disable-strategies compile,quick-install --targets "$TARGET" \ + --install-path "$RUNNER_TEMP/binstall" --no-track + mise install-into \ + "github:${GITHUB_REPOSITORY}[asset_pattern=${archive}]@${RELEASE_PREFIX}${version}" \ + "$RUNNER_TEMP/mise" + for prefix in "$RUNNER_TEMP/binstall" "$RUNNER_TEMP/mise"; do + if [[ "$CARGO_PACKAGE" == protoc-gen-* || "$CARGO_PACKAGE" == sqlc-gen-sqlx ]]; then + "$prefix/$binary" < /dev/null > "$RUNNER_TEMP/smoke.out" + else + "$prefix/$binary" --help > "$RUNNER_TEMP/smoke.out" + fi + test -s "$RUNNER_TEMP/smoke.out" + done + cmp "$RUNNER_TEMP/binstall/$binary" "$RUNNER_TEMP/mise/$binary" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 81cf04a..6f2ae46 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,6 +15,9 @@ concurrency: group: release-${{ github.workflow }}-${{ inputs.tag || github.ref }} cancel-in-progress: false +permissions: + contents: read + jobs: verify: runs-on: ubuntu-latest @@ -71,13 +74,23 @@ jobs: env: DATABASE_URL: postgres://sqlc:sqlc@localhost:5432/sqlc_test + prebuilt: + permissions: + contents: read + needs: verify + uses: ./.github/workflows/prebuilt.yml + with: + ref: ${{ inputs.tag || github.sha }} + publish-release: environment: release - needs: verify + needs: [verify, prebuilt] runs-on: ubuntu-latest timeout-minutes: 15 permissions: contents: write + id-token: write + attestations: write env: RELEASE_TAG: ${{ inputs.tag || github.ref_name }} @@ -88,6 +101,13 @@ jobs: persist-credentials: false ref: ${{ inputs.tag || github.ref }} + - name: Download native archives + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: binary-* + path: dist + merge-multiple: true + - name: Install Rust stable uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable with: @@ -106,6 +126,11 @@ jobs: shasum -a 256 dist/sqlc-gen-sqlx.wasm > dist/sqlc-gen-sqlx.wasm.sha256 fi + - name: Attest native archives + uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: dist/* + - name: Ensure GitHub release exists env: GH_TOKEN: ${{ github.token }} @@ -131,8 +156,7 @@ jobs: GH_TOKEN: ${{ github.token }} run: | gh release upload "${RELEASE_TAG}" \ - dist/sqlc-gen-sqlx.wasm \ - dist/sqlc-gen-sqlx.wasm.sha256 \ + dist/* \ --clobber publish-crate: @@ -165,3 +189,13 @@ jobs: run: cargo publish --locked env: CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }} + + verify-install: + permissions: + contents: read + name: Verify published installations + needs: [publish-crate] + uses: ./.github/workflows/prebuilt.yml + with: + verify-install: true + ref: ${{ inputs.tag || github.sha }} diff --git a/.gitignore b/.gitignore index 4e31434..3002281 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target/ _sqlc_dev.yaml .memsearch +/dist/ diff --git a/Cargo.toml b/Cargo.toml index 832230e..ced9862 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,19 @@ description = "A sqlc plugin that generates type-safe sqlx Rust code from SQL qu repository = "https://github.com/mathematic-inc/sqlc-gen-sqlx" license = "MIT OR Apache-2.0" +[package.metadata.binstall] +pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }.tar.gz" +bin-dir = "{ bin }{ binary-ext }" +pkg-fmt = "tgz" + +[package.metadata.binstall.overrides.x86_64-pc-windows-msvc] +pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }.zip" +pkg-fmt = "zip" + +[package.metadata.binstall.overrides.aarch64-pc-windows-msvc] +pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ version }-{ target }.zip" +pkg-fmt = "zip" + [workspace] resolver = "2" members = [".", "examples/*"] diff --git a/README.md b/README.md index 6f96c1d..021817a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,42 @@ A [sqlc](https://sqlc.dev) plugin that generates type-safe [sqlx](https://github.com/transact-rs/sqlx) Rust code from SQL queries. +## Prebuilt installation + +Release archives contain the executable and install without a Rust compiler. +Install with cargo-binstall, with source compilation disabled: + +```sh +cargo binstall --disable-strategies compile sqlc-gen-sqlx +``` + +Or declare the GitHub release directly in `mise.toml`: + +```toml +[tools] +"github:mathematic-inc/sqlc-gen-sqlx" = "latest" +``` + +Run `mise install` to download and activate the executable. No custom mise plugin +is required. The Cargo backend (`cargo:sqlc-gen-sqlx`) also supports these releases; +set `cargo.binstall_only = true` to reject source compilation. + +| Platform | Architectures | Archive | +| --- | --- | --- | +| macOS | x64, ARM64 | `.tar.gz` | +| Linux GNU (glibc 2.35 or newer) | x64, ARM64 | `.tar.gz` | +| Linux musl | x64, ARM64 | `.tar.gz` | +| Windows MSVC | x64, ARM64 | `.zip` | + +Every archive includes SHA-256 checksums and GitHub build provenance. CI builds all +eight targets and runs the extracted executables on the matching architecture. +After publication, the release workflow installs through cargo-binstall and mise +and runs both installations. A missing prebuilt binary fails the release checks. + +The release also includes `sqlc-gen-sqlx.wasm`. Continue using `plugins[].wasm` +with its URL and checksum for sqlc's WASM plugin mode. The native executable +installed above is used through `plugins[].process.cmd: sqlc-gen-sqlx` instead. + ## What it generates For each SQL query annotated with a sqlc command, the plugin emits: diff --git a/mise.toml b/mise.toml index 9068e16..8048c78 100644 --- a/mise.toml +++ b/mise.toml @@ -6,6 +6,7 @@ npm.package_manager = "npm" [tools] actionlint = "1.7.12" +cargo-binstall = "1.22.0" buf = "1.72.0" ghalint = "1.5.6" gitleaks = "8.30.1"