diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..b1a24a6 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,211 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright The Lance Authors + +name: Create Release + +on: + workflow_dispatch: + inputs: + release_type: + description: 'Version bump type (patch/minor/major bumps version, current keeps it unchanged)' + required: true + default: 'patch' + type: choice + options: + - patch + - minor + - major + - current + release_channel: + description: 'Release channel (preview creates beta tag, stable creates release tag)' + required: true + default: 'preview' + type: choice + options: + - preview + - stable + dry_run: + description: 'Dry run (simulate the release without pushing)' + required: true + default: true + type: boolean + +permissions: + contents: write # commit + tag + push + actions: write # gh workflow run release.yml + +jobs: + create-release: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - name: Output Inputs + run: echo "${{ toJSON(github.event.inputs) }}" + + - name: Checkout repository + uses: actions/checkout@v4 + with: + ref: main + fetch-depth: 0 + # persist-credentials defaults to true → git push uses GITHUB_TOKEN. + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Install cargo-edit + run: cargo install cargo-edit --locked + + - name: Get current version + id: current_version + run: | + CURRENT=$(grep '^version = ' Cargo.toml | head -1 | cut -d '"' -f2) + echo "version=$CURRENT" >>"$GITHUB_OUTPUT" + echo "Current version: $CURRENT" + + - name: Calculate base version + id: base_version + run: | + CURRENT="${{ steps.current_version.outputs.version }}" + # Strip any pre-release suffix (e.g. "0.1.0-beta.3" -> "0.1.0") + BASE_CURRENT="${CURRENT%%-*}" + IFS='.' read -r MAJOR MINOR PATCH <<<"$BASE_CURRENT" + + case "${{ inputs.release_type }}" in + major) + MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 + ;; + minor) + MINOR=$((MINOR + 1)); PATCH=0 + ;; + patch) + PATCH=$((PATCH + 1)) + ;; + current) + # Keep the base version unchanged; useful for cutting another beta + # against the same base (e.g. v0.2.0-beta.2 after v0.2.0-beta.1). + ;; + *) + echo "Unknown release_type: ${{ inputs.release_type }}" >&2 + exit 1 + ;; + esac + + BASE="${MAJOR}.${MINOR}.${PATCH}" + echo "version=$BASE" >>"$GITHUB_OUTPUT" + echo "Base version: $BASE" + + - name: Determine tag and crate version + id: versions + run: | + BASE_VERSION="${{ steps.base_version.outputs.version }}" + CURRENT_VERSION="${{ steps.current_version.outputs.version }}" + if [ "${{ inputs.release_channel }}" = "stable" ]; then + TAG="v${BASE_VERSION}" + CRATE_VERSION="${BASE_VERSION}" + else + # For preview releases, find the next beta number for this base version + BETA_TAGS=$(git tag -l "v${BASE_VERSION}-beta.*" | sort -V) + if [ -z "$BETA_TAGS" ]; then + BETA_NUM=1 + else + LAST_BETA=$(echo "$BETA_TAGS" | tail -n 1) + PREFIX="v${BASE_VERSION}-beta." + LAST_NUM="${LAST_BETA#"$PREFIX"}" + BETA_NUM=$((LAST_NUM + 1)) + fi + TAG="v${BASE_VERSION}-beta.${BETA_NUM}" + CRATE_VERSION="${BASE_VERSION}-beta.${BETA_NUM}" + fi + + if [ "$CURRENT_VERSION" != "$CRATE_VERSION" ]; then + VERSION_CHANGED="true" + else + VERSION_CHANGED="false" + fi + + # Refuse to push if the tag already exists. + if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then + echo "Tag $TAG already exists; aborting." >&2 + exit 1 + fi + + { + echo "tag=$TAG" + echo "crate_version=$CRATE_VERSION" + echo "version_changed=$VERSION_CHANGED" + } >>"$GITHUB_OUTPUT" + echo "Tag will be: $TAG" + echo "Crate version will be: $CRATE_VERSION" + echo "Version changed: $VERSION_CHANGED" + + - name: Update Cargo.toml + Cargo.lock + if: steps.versions.outputs.version_changed == 'true' + run: | + cargo set-version --package lance-c "${{ steps.versions.outputs.crate_version }}" + + - name: Configure git identity + run: | + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + + - name: Create release commit + if: steps.versions.outputs.version_changed == 'true' + run: | + git add Cargo.toml Cargo.lock + git commit -m "chore: bump version to ${{ steps.versions.outputs.crate_version }}" + + - name: Create tag + run: | + git tag -a "${{ steps.versions.outputs.tag }}" \ + -m "Release ${{ steps.versions.outputs.tag }}" + + - name: Push commit + tag (if not dry run) + if: ${{ !inputs.dry_run }} + run: | + if [ "${{ steps.versions.outputs.version_changed }}" = "true" ]; then + git push origin main + fi + git push origin "${{ steps.versions.outputs.tag }}" + + # GITHUB_TOKEN-pushed refs do NOT trigger downstream workflows + # (GitHub's recursion guard). We dispatch the Release workflow at the + # new tag's ref so its publish job (gated on refs/tags/v*) can fire. + - name: Trigger Release workflow on the new tag + if: ${{ !inputs.dry_run }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh workflow run release.yml \ + --ref "${{ steps.versions.outputs.tag }}" \ + -f version="${{ steps.versions.outputs.crate_version }}" + + - name: Summary + run: | + { + echo "## Release Summary" + echo "" + echo "- **Release Type:** ${{ inputs.release_type }}" + echo "- **Release Channel:** ${{ inputs.release_channel }}" + echo "- **Current Version:** ${{ steps.current_version.outputs.version }}" + if [ "${{ steps.versions.outputs.version_changed }}" = "true" ]; then + echo "- **New Version:** ${{ steps.versions.outputs.crate_version }}" + fi + echo "- **Tag:** ${{ steps.versions.outputs.tag }}" + echo "- **Dry Run:** ${{ inputs.dry_run }}" + } >>"$GITHUB_STEP_SUMMARY" + + if [ "${{ inputs.dry_run }}" = "true" ]; then + { + echo "" + echo "⚠️ This was a dry run. No commits, tags, or releases were pushed." + } >>"$GITHUB_STEP_SUMMARY" + else + { + echo "" + echo "✅ Tag pushed and Release workflow dispatched." + echo "" + echo "### Next Steps:" + echo "1. Watch the [Release workflow](https://github.com/${{ github.repository }}/actions/workflows/release.yml) finish (~20 min)." + echo "2. Verify the published assets at the [release page](https://github.com/${{ github.repository }}/releases/tag/${{ steps.versions.outputs.tag }})." + echo "3. Copy the SHA512 snippet from the publish job log into vcpkg/conan recipes." + } >>"$GITHUB_STEP_SUMMARY" + fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 790ff62..070c785 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -128,3 +128,4 @@ jobs: dist/SHA512SUMS generate_release_notes: true fail_on_unmatched_files: true + prerelease: ${{ contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }} diff --git a/README.md b/README.md index c2991e9..e23b13a 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,33 @@ auto ds = lance::Dataset::open("data.lance", {}, /*version=*/42); ## Releasing -Releases are tag-driven via [`release.yml`](.github/workflows/release.yml). +Releases are tag-driven: pushing a `v*.*.*` tag fires [`release.yml`](.github/workflows/release.yml), which builds prebuilt tarballs for `linux-{x86_64,aarch64}` and `macos-{x86_64,aarch64}` and attaches them to a GitHub Release. Beta tags (`v*-beta.*`) are published as pre-releases. + +### Recommended: cut a release via Actions UI + +[`create-release.yml`](.github/workflows/create-release.yml) is a `workflow_dispatch` entry point that bumps `Cargo.toml`, commits, tags, and pushes — replacing the manual edit/commit/tag steps below. + +1. Open Actions → **Create Release** → **Run workflow** on `main`. +2. Choose: + - **release_type**: `patch` / `minor` / `major` (or `current` to cut another beta on the same base, e.g. `v0.2.0-beta.2` after `v0.2.0-beta.1`). + - **release_channel**: `preview` (tags `vX.Y.Z-beta.N`, auto-incremented) or `stable` (tags `vX.Y.Z`). + - **dry_run**: leave on for the first run to preview the computed tag/version without pushing anything. +3. Re-run with **dry_run** off. The workflow: + - Bumps `version = ...` in `Cargo.toml` and refreshes `Cargo.lock` via `cargo set-version` (skipped for `current` if the version is already correct). + - Commits as `github-actions[bot]` with message `chore: bump version to `. + - Pushes the commit to `main` and pushes the tag. + - Dispatches `release.yml` at the new tag's ref. (Direct tag push by `GITHUB_TOKEN` does not trigger workflows — GitHub's recursion guard — so we explicitly `gh workflow run` instead.) +4. `release.yml` builds artifacts. ~20 minutes later the [GitHub Release](https://github.com/lance-format/lance-c/releases) has all four `.tar.xz` artifacts plus a `SHA512SUMS` file. +5. The `publish` job's log emits a paste-ready `set(LANCE_C_SHA512_... "...")` snippet. Copy it into: + - [`ports/lance-c/portfile.cmake`](ports/lance-c/portfile.cmake) (SHA512s) + - [`recipes/lance-c/all/conandata.yml`](recipes/lance-c/all/conandata.yml) (SHA256s, derived from the `.sha256` files in the release assets) +6. Open follow-up PRs to `microsoft/vcpkg` and `conan-io/conan-center-index` mirroring the updated `ports/` and `recipes/` directories. + +> **Branch protection:** if `main` is a protected branch, allow `github-actions[bot]` (or the GitHub Actions integration) to bypass push restrictions, or replace the default `GITHUB_TOKEN` in `create-release.yml` with a PAT that has `contents: write`. + +### Manual fallback + +If you need to cut a release without the workflow (e.g. local tag with extra commits): 1. Decide the new version (semver). Pre-1.0 (`0.x.y`): bump **minor** for breaking changes or new features, **patch** for bug fixes only. 2. On `main`, bump `version = ...` in [`Cargo.toml`](Cargo.toml) and refresh `Cargo.lock`: @@ -202,11 +228,7 @@ Releases are tag-driven via [`release.yml`](.github/workflows/release.yml). git tag v0.2.0 git push origin v0.2.0 ``` -5. `release.yml` fires on the tag push and builds prebuilt tarballs for `linux-{x86_64,aarch64}` and `macos-{x86_64,aarch64}`. ~20 minutes later, the [GitHub Release](https://github.com/lance-format/lance-c/releases) has all four `.tar.xz` artifacts plus a `SHA512SUMS` file. -6. The `publish` job's log emits a paste-ready `set(LANCE_C_SHA512_... "...")` snippet. Copy it into: - - [`ports/lance-c/portfile.cmake`](ports/lance-c/portfile.cmake) (SHA512s) - - [`recipes/lance-c/all/conandata.yml`](recipes/lance-c/all/conandata.yml) (SHA256s, derived from the `.sha256` files in the release assets) -7. Open follow-up PRs to `microsoft/vcpkg` and `conan-io/conan-center-index` mirroring the updated `ports/` and `recipes/` directories. +5. `release.yml` fires on the tag push and builds the four prebuilt tarballs as above. A `workflow_dispatch` trigger on `release.yml` lets you do dry-run builds without cutting a tag — Actions tab → "Release" → "Run workflow" → enter a version like `0.0.1-dev`. The `publish` job is skipped (gated on `refs/tags/v`), but the build matrix runs end-to-end so you can validate it before the real tag.