diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..237be4bfd2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,190 @@ +# Builds wicked-sqlc for every supported platform, runs each binary on its own +# platform, and attaches the archives, checksums.txt and build provenance to the +# GitHub release of the tag. Install with scripts/install.sh. +# +# A tag push builds that tag. To add binaries to an existing release, run the +# workflow by hand with the tag (the tag's source is built, with this file's +# steps). +name: release + +on: + push: + tags: ["v*"] + workflow_dispatch: + inputs: + tag: + description: "Existing tag to build, e.g. v2.4.0" + required: true + +permissions: + contents: read + +concurrency: + group: release-${{ inputs.tag || github.ref_name }} + cancel-in-progress: false + +env: + TAG: ${{ inputs.tag || github.ref_name }} + # Go release used for the binaries: the newest patch of go.mod's version. + GO_VERSION: "1.26" + +jobs: + build: + name: build ${{ matrix.goos }}/${{ matrix.goarch }} + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: true + matrix: + include: + # Linux: CGO (native PostgreSQL parser) linked statically against + # musl in Alpine, so the binary runs on any distribution. The amd64 + # job also cross-builds Windows without CGO (WebAssembly parser). + - { goos: linux, goarch: amd64, runner: ubuntu-24.04 } + - { goos: linux, goarch: arm64, runner: ubuntu-24.04-arm } + # macOS: CGO, native runners, runs on macOS 12 and later. + - { goos: darwin, goarch: amd64, runner: macos-15-intel } + - { goos: darwin, goarch: arm64, runner: macos-15 } + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ env.TAG }} + persist-credentials: false + + - name: Version + run: | + echo "$TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$' || { echo "not a release tag: $TAG" >&2; exit 1; } + echo "VERSION=${TAG#v}" >> "$GITHUB_ENV" + # Same version string as `make build` from the tag. + echo "LDFLAGS=-s -w -X github.com/sqlc-dev/sqlc/internal/info.Version=${TAG}-wicked-fork" >> "$GITHUB_ENV" + + - name: Build (Linux, Alpine) + if: matrix.goos == 'linux' + run: | + docker run --rm -v "$PWD:/src" -w /src \ + -e LDFLAGS -e GOFLAGS=-buildvcs=false -e WINDOWS="${{ matrix.goarch == 'amd64' }}" \ + "golang:${GO_VERSION}-alpine" sh -euc ' + apk add --no-cache build-base >/dev/null + CGO_ENABLED=1 go build -trimpath \ + -ldflags="$LDFLAGS -linkmode external -extldflags -static" -o dist/sqlc ./cmd/sqlc + if [ "$WINDOWS" = true ]; then + CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath \ + -ldflags="$LDFLAGS" -o dist/windows/sqlc.exe ./cmd/sqlc + fi + chown -R "$(stat -c %u:%g /src)" dist' + file dist/sqlc | grep -q 'statically linked' + + - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + if: matrix.goos == 'darwin' + with: + go-version: ${{ env.GO_VERSION }} + cache: false + + - name: Build (macOS) + if: matrix.goos == 'darwin' + env: + CGO_ENABLED: "1" + MACOSX_DEPLOYMENT_TARGET: "12.0" + run: go build -trimpath -ldflags="$LDFLAGS" -o dist/sqlc ./cmd/sqlc + + - name: Smoke test + run: | + got="$(dist/sqlc version)" + test "$got" = "${TAG}-wicked-fork" || { echo "sqlc version = $got" >&2; exit 1; } + cp -R internal/endtoend/testdata/wicked_compat "$RUNNER_TEMP/smoke" + (cd "$RUNNER_TEMP/smoke" && "$GITHUB_WORKSPACE/dist/sqlc" compile && "$GITHUB_WORKSPACE/dist/sqlc" generate) + + - name: Package + run: | + mkdir -p out + name="sqlc_${VERSION}_${{ matrix.goos }}_${{ matrix.goarch }}" + cp LICENSE dist/ + tar -C dist -czf "out/${name}.tar.gz" sqlc LICENSE + if [ -f dist/windows/sqlc.exe ]; then + cp LICENSE dist/windows/ + (cd dist/windows && zip -q "../../out/sqlc_${VERSION}_windows_amd64.zip" sqlc.exe LICENSE) + fi + ls -l out + + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: sqlc-${{ matrix.goos }}-${{ matrix.goarch }} + path: out/* + if-no-files-found: error + retention-days: 7 + + windows: + name: test windows/amd64 + needs: build + runs-on: windows-2025 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ env.TAG }} + persist-credentials: false + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: sqlc-linux-amd64 + path: out + - name: Smoke test + shell: bash + run: | + unzip -q out/sqlc_*_windows_amd64.zip -d "$RUNNER_TEMP/bin" + sqlc="$RUNNER_TEMP/bin/sqlc.exe" + got="$("$sqlc" version | tr -d '\r')" + test "$got" = "${TAG}-wicked-fork" || { echo "sqlc version = $got" >&2; exit 1; } + cp -R internal/endtoend/testdata/wicked_compat "$RUNNER_TEMP/smoke" + (cd "$RUNNER_TEMP/smoke" && "$sqlc" compile && "$sqlc" generate) + + publish: + needs: [build, windows] + runs-on: ubuntu-24.04 + permissions: + contents: write # upload to the release + id-token: write # build provenance + attestations: write + steps: + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + pattern: sqlc-* + path: out + merge-multiple: true + + - name: Checksums + working-directory: out + run: | + sha256sum sqlc_* > checksums.txt + cat checksums.txt + + - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2 + with: + subject-path: out/sqlc_* + + - name: Upload to the release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + if ! gh release view "$TAG" >/dev/null 2>&1; then + gh release create "$TAG" --verify-tag --title "$TAG" --generate-notes + fi + gh release upload "$TAG" out/* --clobber + + install: + name: install on ${{ matrix.runner }} + needs: publish + runs-on: ${{ matrix.runner }} + strategy: + matrix: + runner: [ubuntu-24.04, ubuntu-24.04-arm, macos-15-intel, macos-15] + steps: + # scripts/install.sh from this workflow's commit (an older tag may not have it). + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Install the release with scripts/install.sh + env: + INSTALL_DIR: ${{ runner.temp }}/bin + run: | + sh scripts/install.sh "$TAG" + got="$("$INSTALL_DIR/sqlc" version)" + test "$got" = "${TAG}-wicked-fork" || { echo "sqlc version = $got" >&2; exit 1; } diff --git a/GUIDE.md b/GUIDE.md index 17a6b73a94..03a7469981 100644 --- a/GUIDE.md +++ b/GUIDE.md @@ -52,6 +52,29 @@ your customers are disappointed, or (2) before the product is launched, writing ## Install +Releases from v2.4.1 on ship binaries for Linux (amd64, arm64; static, any +distribution), macOS (amd64, arm64; 12 or later) and Windows (amd64), with a +`checksums.txt` and build provenance. `scripts/install.sh` downloads one, +checks its SHA-256 and installs it into `$(go env GOPATH)/bin` (or +`INSTALL_DIR`): + +```bash +curl -fsSL https://raw.githubusercontent.com/Stumble/sqlc/main/scripts/install.sh | sh -s -- v2.4.1 +sqlc version # v2.4.1-wicked-fork +``` + +Set `SQLC_SHA256` to the archive's checksum to accept only that archive (a +project that pins sqlc should do this). `gh attestation verify -R +Stumble/sqlc` checks that an archive was built by this repository's release +workflow. On Windows, download `sqlc__windows_amd64.zip` from the +release. + +The Linux and macOS binaries use the native PostgreSQL parser (CGO). The +Windows binary, and builds with `CGO_ENABLED=0`, use its WebAssembly build: +same output, about two seconds slower per run. + +### Build from source + Build this fork from source using the Go version required by [go.mod](go.mod). The default build uses CGO and requires a C compiler. Ensure Go's installation directory (`GOBIN`, or `GOPATH/bin` when unset) is on your `PATH`. diff --git a/README.md b/README.md index 3c3e8b7e56..c708429c9a 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,13 @@ the wicked backend. ## Use it -Build the current `main` branch with the Go version specified in [go.mod](go.mod): +Install a release binary (Linux, macOS; the SHA-256 is checked): + +```sh +curl -fsSL https://raw.githubusercontent.com/Stumble/sqlc/main/scripts/install.sh | sh -s -- v2.4.1 +``` + +Or build the current `main` branch with the Go version specified in [go.mod](go.mod): ```sh git clone https://github.com/Stumble/sqlc.git @@ -42,6 +48,8 @@ cd sqlc make install ``` +See [GUIDE.md](GUIDE.md#install) for platforms, checksums and Windows. + Select the backend in `sqlc.yaml`: ```yaml diff --git a/docs/changelogs/2026-11-04-release-binaries.md b/docs/changelogs/2026-11-04-release-binaries.md new file mode 100644 index 0000000000..1c6b923bcd --- /dev/null +++ b/docs/changelogs/2026-11-04-release-binaries.md @@ -0,0 +1,88 @@ +# Release binaries with checksums and an install script + +Tracking: alva-ai/mono-meta#1115. Yumin, 2026-11-04: developers should not +have to clone this repository and run `make` to get the right sqlc; publish +binaries for several platforms with an install script that checks the SHA-256. + +## 1. Current State + +- Releases carry no binaries. The only way to get a binary that reports its + version is `make build` / `make install` in a clone: `internal/info.Version` + is `v1.31.1` in the source, and the Makefile sets `-wicked-fork` + through `-ldflags`. +- Builds without the Makefile (`go install ./cmd/sqlc`, as ggsrc/ggsrv-layout's + CI did) write `sqlc v1.31.1` into generated headers. +- The PostgreSQL engine uses pg_query_go with CGO and its WebAssembly build + (wasilibs/go-pgquery) without CGO. Measured on v2.4.0: identical output on + `wicked_compat`, ggsrv-layout's repositories and Stumble/bookstore; ~60–80 ms + with CGO, ~2.2 s without (WebAssembly start-up). + +## 2. Intended Behavior + +- B1 A tag push (or a manual run with an existing tag) builds `sqlc` for + linux/amd64, linux/arm64, darwin/amd64, darwin/arm64 and windows/amd64 and + attaches `sqlc___.tar.gz` (`.zip` on Windows), each with + `sqlc` and `LICENSE`, plus `checksums.txt` (SHA-256) to the tag's release. +- B2 Each binary reports `-wicked-fork`, like `make build` from the tag. +- B3 Every binary runs `sqlc version`, `sqlc compile` and `sqlc generate` on its + own platform before anything is published; after publishing, + `scripts/install.sh` installs the release on Linux and macOS runners. +- B4 Linux binaries are static (CGO, musl) and run on any distribution; macOS + binaries use CGO and run on macOS 12 or later; the Windows binary has no CGO. +- B5 `scripts/install.sh [version]` picks the platform, downloads the archive, + checks it against `SQLC_SHA256` or the release's `checksums.txt`, and installs + it into `INSTALL_DIR` (default `$(go env GOPATH)/bin`, else `~/.local/bin`). +- B6 Build provenance is attested (`gh attestation verify`). +- F1 A wrong checksum, a missing archive or a tag that is not `vX.Y.Z[-pre]` + stops without installing or publishing. + +## 3. Decisions and Risks + +- D1 CGO where it is available (Linux via a static musl build in Alpine, macOS + on native runners): 30× faster runs than the WebAssembly parser, same output. +- D2 Native runners for every platform, so each binary is executed where it + will run. Windows is cross-built on Linux and run on a Windows runner. +- D3 The build steps live in the workflow, not the Makefile, so a manual run + can build tags older than this change (v2.4.0); the version flag matches the + Makefile's. +- D4 Go: newest patch of 1.26 (go.mod's version). +- R1 GitHub runner labels (`macos-15-intel`, `ubuntu-24.04-arm`) are checked + only when the workflow first runs. + +## 4. Changes + +- `.github/workflows/release.yml` (new): B1–B4, B6. +- `scripts/install.sh` (new): B5, F1. +- `GUIDE.md`, `README.md`: install from a release. + +## 5. Verification + +Local, v2.4.0 source, with the workflow's Linux build command: + +- `golang:1.26-alpine`, CGO, `-extldflags -static`: `statically linked`; + `sqlc version` = `v2.4.0-wicked-fork`; `compile` and `generate` on + `wicked_compat` pass. The binary runs on Debian 11, Ubuntu 20.04 and Alpine + 3.20 and generates ggsrv-layout's repositories identically to a CGO + `make build` (except the version header, which the old build got wrong). +- Cross builds with `CGO_ENABLED=0` succeed for all five platforms. +- `scripts/install.sh` against a local copy of the release layout, on Ubuntu + 20.04 (dash) and Alpine 3.20 (busybox): latest-release lookup, explicit + version, pinned `SQLC_SHA256`, wrong checksum (refused), unknown version + (404, nothing installed). +- ShellCheck (`install.sh`) and actionlint (`release.yml`): clean. + +## 6. Human Decisions + +- Yumin, 2026-11-04 (request above). +- Yumin, 2026-11-04: govulncheck failed on `main`; fix the dependencies first + (#22) and publish the first binaries as v2.4.1 rather than attaching + binaries built from v2.4.0's vulnerable dependencies. + +## 7. Outcome + +- [ ] Merged. +- [ ] v2.4.1 released by a tag push, with binaries. + +## 8. Remaining Work + +- None. diff --git a/docs/releases/v2.4.1.md b/docs/releases/v2.4.1.md new file mode 100644 index 0000000000..5048d951cb --- /dev/null +++ b/docs/releases/v2.4.1.md @@ -0,0 +1,33 @@ +# wicked-sqlc v2.4.1 + +This patch release publishes prebuilt binaries for the first time and updates +Go and dependencies flagged by govulncheck. Generated code is the same as v2.4.0 +apart from the version comment. + +## Changes + +- Binaries for Linux (amd64, arm64; static, any distribution), macOS (amd64, + arm64; 12 or later) and Windows (amd64), with `checksums.txt` and build + provenance. +- `scripts/install.sh` installs a release after checking its SHA-256. +- Go 1.26.10 (was 1.26.8), golang.org/x/net v0.60.0, golang.org/x/text v0.42.0 + and google.golang.org/grpc v1.83.1 fix 14 vulnerabilities govulncheck reports + as reachable in v2.4.0. + +## Upgrade + +```sh +curl -fsSL https://raw.githubusercontent.com/Stumble/sqlc/main/scripts/install.sh | sh -s -- v2.4.1 +sqlc version +# v2.4.1-wicked-fork +``` + +Set `SQLC_SHA256` to the archive's value in `checksums.txt` to accept only that +archive. `gh attestation verify -R Stumble/sqlc` checks that an +archive was built by this repository's release workflow. On Windows, download +`sqlc_2.4.1_windows_amd64.zip` from the release. Building from source with +`make install` now requires Go 1.26.10. + +Regenerate with `sqlc generate`; only the version comments change. + +See the [guide](https://github.com/Stumble/sqlc/blob/main/GUIDE.md#install). diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000000..c78b9aba63 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,82 @@ +#!/bin/sh +# Install a wicked-sqlc release binary after checking its SHA-256. +# +# scripts/install.sh # latest release +# scripts/install.sh v2.4.1 # a given release +# curl -fsSL https://raw.githubusercontent.com/Stumble/sqlc/main/scripts/install.sh | sh -s -- v2.4.1 +# +# Environment: +# INSTALL_DIR where to put sqlc (default: $(go env GOPATH)/bin, else ~/.local/bin) +# SQLC_SHA256 expected SHA-256 of the archive; pin it to trust only that +# archive. Without it the release's checksums.txt is used. +# +# Linux (amd64, arm64; static binary) and macOS (amd64, arm64, 12+). On +# Windows download sqlc__windows_amd64.zip from the release. +set -eu + +REPO="${SQLC_REPO:-Stumble/sqlc}" +BASE_URL="${SQLC_BASE_URL:-https://github.com/$REPO/releases}" +VERSION="${1:-${SQLC_VERSION:-}}" + +die() { echo "install.sh: $*" >&2; exit 1; } +need() { command -v "$1" >/dev/null 2>&1 || die "$1 is required"; } +need curl +need tar + +case "$(uname -s)" in + Linux) os=linux ;; + Darwin) os=darwin ;; + *) die "unsupported OS $(uname -s); download the archive from $BASE_URL" ;; +esac +case "$(uname -m)" in + x86_64 | amd64) arch=amd64 ;; + aarch64 | arm64) arch=arm64 ;; + *) die "unsupported architecture $(uname -m)" ;; +esac + +if [ -z "$VERSION" ]; then + # .../releases/latest redirects to .../releases/tag/ + VERSION="$(curl -fsSLo /dev/null -w '%{url_effective}' "$BASE_URL/latest")" + VERSION="${VERSION##*/}" +fi +case "$VERSION" in v*) ;; *) VERSION="v$VERSION" ;; esac + +if [ -z "${INSTALL_DIR:-}" ]; then + if command -v go >/dev/null 2>&1; then + INSTALL_DIR="$(go env GOPATH)/bin" + else + INSTALL_DIR="$HOME/.local/bin" + fi +fi + +archive="sqlc_${VERSION#v}_${os}_${arch}.tar.gz" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +echo "Downloading $archive ($VERSION)" +curl -fsSL -o "$tmp/$archive" "$BASE_URL/download/$VERSION/$archive" + +if [ -n "${SQLC_SHA256:-}" ]; then + want="$SQLC_SHA256" +else + curl -fsSL -o "$tmp/checksums.txt" "$BASE_URL/download/$VERSION/checksums.txt" + want="$(awk -v f="$archive" '$2 == f { print $1 }' "$tmp/checksums.txt")" + [ -n "$want" ] || die "$archive is not in checksums.txt" +fi +if command -v sha256sum >/dev/null 2>&1; then + got="$(sha256sum "$tmp/$archive" | awk '{ print $1 }')" +else + need shasum + got="$(shasum -a 256 "$tmp/$archive" | awk '{ print $1 }')" +fi +[ "$got" = "$want" ] || die "SHA-256 mismatch for $archive: got $got, want $want" + +tar -xzf "$tmp/$archive" -C "$tmp" sqlc +mkdir -p "$INSTALL_DIR" +mv "$tmp/sqlc" "$INSTALL_DIR/sqlc" +chmod 0755 "$INSTALL_DIR/sqlc" +echo "Installed $INSTALL_DIR/sqlc: $("$INSTALL_DIR/sqlc" version)" +case ":$PATH:" in + *":$INSTALL_DIR:"*) ;; + *) echo "Note: $INSTALL_DIR is not on PATH" ;; +esac