From d3998cd0795248bd219628c21e69eb9ef21d78ae Mon Sep 17 00:00:00 2001
From: vmarcella
Date: Sun, 28 Sep 2025 14:36:33 -0700
Subject: [PATCH 1/2] [add] gh action to publish new versions.
---
.github/workflows/release.yml | 350 ++++++++++++++++++++++++++++++++++
docs/publishing.md | 149 +++++++++++++++
2 files changed, 499 insertions(+)
create mode 100644 .github/workflows/release.yml
create mode 100644 docs/publishing.md
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..328969bc
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,350 @@
+name: Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ release_date:
+ description: "Override date (UTC YYYY.MM.DD). Leave empty for today."
+ required: false
+ type: string
+ dry_run:
+ description: "Do not push/tag/publish (build only)"
+ required: false
+ type: boolean
+ default: false
+
+permissions:
+ contents: write
+ packages: write
+
+concurrency:
+ group: release-${{ github.ref }}
+ cancel-in-progress: false
+
+env:
+ CARGO_TERM_COLOR: always
+
+jobs:
+ validate:
+ name: Validate (fmt, clippy, tests)
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ lfs: true
+
+ - name: Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Rust cache
+ uses: Swatinem/rust-cache@v2
+
+ - name: Format check
+ run: cargo fmt --all -- --check
+
+ - name: Clippy
+ run: cargo clippy --workspace --all-targets -- -D warnings
+
+ - name: Tests
+ run: cargo test --workspace -- --nocapture
+
+ prepare_version:
+ name: Prepare version, tag, push
+ needs: validate
+ runs-on: ubuntu-latest
+ outputs:
+ version: ${{ steps.setver.outputs.version }}
+ tag: ${{ steps.setver.outputs.tag }}
+ commit_sha: ${{ steps.commit.outputs.sha }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ lfs: true
+
+ - name: Configure git
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+
+ - name: Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Install cargo-workspaces
+ run: cargo install cargo-workspaces --locked
+
+ - name: Compute date-based version
+ id: setver
+ shell: bash
+ run: |
+ set -euo pipefail
+ BASE="${{ inputs.release_date }}"
+ if [[ -z "${BASE}" ]]; then
+ BASE="$(date -u +%Y.%m.%d)"
+ fi
+ git fetch --tags --quiet
+ LAST=$(git tag -l "v${BASE}*" | sed 's/^v//' | sort -V | tail -n1 || true)
+ if [[ -z "${LAST}" ]]; then
+ VERSION="${BASE}"
+ else
+ if [[ "${LAST}" == "${BASE}" ]]; then
+ N=1
+ else
+ SUF="${LAST#${BASE}-}"
+ N=$((10#${SUF} + 1))
+ fi
+ VERSION=$(printf "%s-%02d" "${BASE}" "${N}")
+ fi
+ echo "version=${VERSION}" | tee -a "$GITHUB_OUTPUT"
+ echo "tag=v${VERSION}" | tee -a "$GITHUB_OUTPUT"
+ echo "${VERSION}" > VERSION
+
+ - name: Bump workspace versions
+ if: ${{ !inputs.dry_run }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ # Update all crate versions and internal dependency requirements
+ cargo workspaces version ${{ steps.setver.outputs.version }} \
+ --exact --force --yes --no-git-tag --no-git-commit
+ # Align lockfile
+ cargo update -w
+
+ - name: Commit and tag
+ id: commit
+ if: ${{ !inputs.dry_run }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ git add -A
+ git commit -m "[release] v${{ steps.setver.outputs.version }}"
+ git tag "v${{ steps.setver.outputs.version }}"
+ git push origin HEAD:${{ github.ref_name }}
+ git push origin "v${{ steps.setver.outputs.version }}"
+ echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
+
+ publish_crates:
+ name: Publish to crates.io
+ needs: prepare_version
+ if: ${{ !inputs.dry_run }}
+ runs-on: ubuntu-latest
+ env:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+ steps:
+ - name: Checkout release tag
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ lfs: true
+ ref: ${{ needs.prepare_version.outputs.tag }}
+
+ - name: Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+
+ - name: Install cargo-workspaces
+ run: cargo install cargo-workspaces --locked
+
+ - name: Publish workspace
+ run: |
+ set -euo pipefail
+ # Publish in dependency order, skipping those already on the registry
+ cargo workspaces publish --from-git --yes --skip-published
+
+ build:
+ name: Build artifacts (${{ matrix.os_slug }})
+ needs: prepare_version
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-latest
+ os_slug: linux
+ archive: tar.gz
+ - os: macos-latest
+ os_slug: macos
+ archive: tar.gz
+ - os: windows-latest
+ os_slug: windows
+ archive: zip
+ steps:
+ - name: Checkout release tag
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ lfs: true
+ ref: ${{ needs.prepare_version.outputs.tag }}
+
+ - name: Rust toolchain
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ profile: minimal
+ components: clippy,rustfmt
+
+ - name: Rust cache
+ uses: Swatinem/rust-cache@v2
+
+ - name: Install jq
+ if: ${{ runner.os != 'Windows' }}
+ shell: bash
+ run: |
+ if [ "${{ runner.os }}" = "Linux" ]; then
+ sudo apt-get update && sudo apt-get install -y jq
+ else
+ brew update && brew install jq
+ fi
+
+ - name: Install jq (Windows)
+ if: ${{ runner.os == 'Windows' }}
+ shell: pwsh
+ run: choco install jq -y
+
+ - name: Build binaries
+ shell: bash
+ run: |
+ set -euo pipefail
+ cargo build --workspace --release --bins
+
+ - name: Stage files
+ id: stage
+ shell: bash
+ run: |
+ set -euo pipefail
+ VERSION='${{ needs.prepare_version.outputs.version }}'
+ OUTDIR="stage/lambda-${VERSION}-${{ matrix.os_slug }}"
+ mkdir -p "${OUTDIR}/bin"
+ # List workspace binary targets
+ bins=$(cargo metadata --format-version 1 --no-deps | jq -r '.packages[].targets[] | select(.kind[]=="bin") | .name' | sort -u)
+ for b in $bins; do
+ if [[ "${{ runner.os }}" == "Windows" ]]; then
+ src="target/release/${b}.exe"
+ else
+ src="target/release/${b}"
+ fi
+ if [[ -f "$src" ]]; then
+ cp "$src" "${OUTDIR}/bin/"
+ fi
+ done
+ # Include example 'minimal' if present
+ if [[ -f target/release/examples/minimal ]]; then
+ mkdir -p "${OUTDIR}/examples"
+ cp target/release/examples/minimal "${OUTDIR}/examples/"
+ elif [[ -f target/release/examples/minimal.exe ]]; then
+ mkdir -p "${OUTDIR}/examples"
+ cp target/release/examples/minimal.exe "${OUTDIR}/examples/"
+ fi
+ # Include assets if present
+ if [[ -d crates/lambda-rs/assets ]]; then
+ mkdir -p "${OUTDIR}/assets"
+ cp -R crates/lambda-rs/assets/* "${OUTDIR}/assets/" || true
+ fi
+ # Top-level docs
+ for f in LICENSE LICENSE.md README.md README; do
+ [[ -f "$f" ]] && cp "$f" "${OUTDIR}/" || true
+ done
+ echo "${VERSION}" > "${OUTDIR}/VERSION"
+
+ - name: Package (tar.gz)
+ if: ${{ matrix.archive == 'tar.gz' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ cd stage
+ tar -czf "lambda-${{ needs.prepare_version.outputs.version }}-${{ matrix.os_slug }}.tar.gz" "lambda-${{ needs.prepare_version.outputs.version }}-${{ matrix.os_slug }}"
+ if command -v shasum >/dev/null 2>&1; then
+ shasum -a 256 "lambda-${{ needs.prepare_version.outputs.version }}-${{ matrix.os_slug }}.tar.gz" > "lambda-${{ needs.prepare_version.outputs.version }}-${{ matrix.os_slug }}.tar.gz.sha256"
+ elif command -v sha256sum >/dev/null 2>&1; then
+ sha256sum "lambda-${{ needs.prepare_version.outputs.version }}-${{ matrix.os_slug }}.tar.gz" > "lambda-${{ needs.prepare_version.outputs.version }}-${{ matrix.os_slug }}.tar.gz.sha256"
+ fi
+
+ - name: Package (zip)
+ if: ${{ matrix.archive == 'zip' }}
+ shell: pwsh
+ run: |
+ $v = "${{ needs.prepare_version.outputs.version }}"
+ $slug = "${{ matrix.os_slug }}"
+ $src = "stage/lambda-$v-$slug"
+ $zip = "stage/lambda-$v-$slug.zip"
+ Compress-Archive -Path "$src\*" -DestinationPath $zip -CompressionLevel Optimal
+ (Get-FileHash $zip -Algorithm SHA256).Hash + " *$(Split-Path -Leaf $zip)" | Out-File "$zip.sha256" -Encoding ascii
+
+ - name: Upload artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: release-${{ matrix.os_slug }}
+ path: stage/*
+ if-no-files-found: error
+
+ release:
+ name: Create GitHub release
+ needs: [prepare_version, build, publish_crates]
+ if: ${{ !inputs.dry_run }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout release tag
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ lfs: false
+ ref: ${{ needs.prepare_version.outputs.tag }}
+
+ - name: Generate changelog
+ id: changelog
+ shell: bash
+ run: |
+ set -euo pipefail
+ TAG='${{ needs.prepare_version.outputs.tag }}'
+ VERSION='${{ needs.prepare_version.outputs.version }}'
+ REPO_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}"
+ git fetch --tags --quiet
+ PREV=$(git tag -l 'v*' | sort -V | awk -v t="$TAG" '$0==t{print last; exit} {last=$0}')
+ if [[ -n "${PREV}" ]]; then
+ RANGE="${PREV}..${TAG}"
+ COMPARE_URL="${REPO_URL}/compare/${PREV}...${TAG}"
+ else
+ RANGE="${TAG}"
+ COMPARE_URL="${REPO_URL}/commits/${TAG}"
+ fi
+ FILE="CHANGELOG-v${VERSION}.md"
+ {
+ echo "# Changelog";
+ echo;
+ echo "Version ${TAG}";
+ echo;
+ if [[ -n "${PREV}" ]]; then
+ echo "Changes since ${PREV}";
+ else
+ echo "Initial release";
+ fi
+ echo;
+ echo "Compare: ${COMPARE_URL}";
+ echo;
+ echo "Commits:";
+ echo;
+ } > "${FILE}"
+ git log --no-merges --pretty=format:"- [%h](${REPO_URL}/commit/%H) %s" ${RANGE} >> "${FILE}"
+ echo "path=${FILE}" >> "$GITHUB_OUTPUT"
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: dl
+ merge-multiple: true
+
+ - name: Create release and upload assets
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ needs.prepare_version.outputs.tag }}
+ name: "lambda v${{ needs.prepare_version.outputs.version }}"
+ draft: false
+ prerelease: false
+ body_path: ${{ steps.changelog.outputs.path }}
+ files: |
+ dl/*.tar.gz
+ dl/*.tar.gz.sha256
+ dl/*.zip
+ dl/*.zip.sha256
+ ${{ steps.changelog.outputs.path }}
diff --git a/docs/publishing.md b/docs/publishing.md
new file mode 100644
index 00000000..ba563519
--- /dev/null
+++ b/docs/publishing.md
@@ -0,0 +1,149 @@
+---
+title: "Release and Publishing Guide"
+document_id: "release-guide-2025-09-28"
+status: "living"
+created: "2025-09-28T00:00:00Z"
+last_updated: "2025-09-28T00:00:00Z"
+version: "0.1.0"
+engine_workspace_version: "2023.1.30"
+wgpu_version: "26.0.1"
+shader_backend_default: "naga"
+winit_version: "0.29.10"
+repo_commit: "fda8ee236986"
+owners: ["lambda-sh"]
+reviewers: ["engine", "rendering"]
+tags: ["guide", "releases", "publishing", "ci"]
+---
+
+# Release and Publishing Guide
+
+This guide explains how to cut a release, publish the Rust crates to
+crates.io, and attach compiled artifacts to a GitHub Release using the
+automated workflow in `.github/workflows/release.yml`.
+
+## Versioning Scheme
+
+- Format: `YYYY.MM.DD` for daily releases, with patch suffix `-NN` for
+ same‑day follow‑ups (e.g., `2025.09.28`, `2025.09.28-01`).
+- The workflow auto‑computes the next version by scanning existing tags.
+- Tags are created as `v` (e.g., `v2025.09.28-01`).
+
+## One‑Time Setup
+
+- Add repository secret `CARGO_REGISTRY_TOKEN` with a crates.io token that has
+ publish rights for all workspace crates.
+- Ensure branch protections allow the GitHub Actions bot to push to the main
+ branch, or ask us to switch the workflow to open a PR instead of pushing.
+- Run `scripts/setup.sh` once locally to install hooks and LFS, and prefer
+ storing large assets through git‑lfs.
+
+## How the Workflow Works
+
+Jobs run in this order when manually triggered:
+
+1) Validate
+- Runs `cargo fmt --check`, `cargo clippy -D warnings`, and `cargo test` on
+ Ubuntu.
+
+2) Prepare version, tag, push
+- Computes version from the provided date (or UTC today), auto‑increments
+ `-NN` if a same‑day release already exists.
+- Bumps all workspace crate versions and their internal dependency pins using
+ `cargo workspaces version --exact`.
+- Commits `[release] v` and pushes to the current branch; pushes tag
+ `v`.
+
+3) Publish to crates.io
+- Uses `cargo workspaces publish --from-git --skip-published` to push all
+ changed crates in dependency order. Already published versions are skipped.
+
+4) Build tri‑platform artifacts
+- Builds all binary targets in release mode for Linux, macOS, and Windows.
+- Packages archives with:
+ - `bin/` containing all workspace bin targets
+ - `examples/minimal` if present
+ - `crates/lambda-rs/assets/` if present
+ - `README`, `LICENSE` when present
+ - `VERSION` file and `.sha256` checksums
+
+5) Create GitHub Release
+- Creates a release for the tag and uploads the archives and checksums.
+- Generates a markdown changelog with a compare link and a bullet list of
+ commit links between the previous and current tag; used as the release body
+ and attached as an asset.
+
+## Running a Release
+
+1) Pre‑flight
+- Ensure CI is green on main. Locally, you can run:
+ - `cargo fmt --all`
+ - `cargo clippy --workspace --all-targets -- -D warnings`
+ - `cargo test --workspace`
+
+2) Dry run (safe test)
+- Go to GitHub → Actions → `Release` → `Run workflow`.
+- Set `dry_run: true` and leave the date blank to use today. This builds and
+ packages artifacts but does not bump, tag, push, or publish.
+
+3) Real release
+- Run the workflow with `dry_run: false` (default). Optionally set
+ `release_date` in `YYYY.MM.DD` if you need to back/forward‑date.
+- The workflow updates versions on the branch you run it from (typically
+ `main`), pushes the release commit and tag, publishes crates, builds, and
+ creates a GitHub Release.
+
+## Hotfix / Patch Releases
+
+- To ship a same‑day fix, re‑run the workflow the same day. It will detect the
+ previous tag and produce the next `-NN` suffix (e.g., `-01`, `-02`).
+- Fixes should be ordinary commits on the branch; the release job will include
+ them in the new tag.
+
+## Changelog Details
+
+- The workflow determines the previous `v*` tag and compares it with the new
+ tag. If none exists, it marks the release as initial.
+- The generated markdown contains:
+ - A compare link (`/compare/prev...new`)
+ - A list of commit subjects with links to each commit
+- You can edit the release notes on GitHub after the run if you want to add
+ highlights or screenshots.
+
+## Troubleshooting
+
+- Branch protections reject pushes
+ - Symptom: The prepare step fails on `git push`.
+ - Fix: Allow GitHub Actions to push to the branch, or switch the workflow to
+ open a PR for the version bump.
+
+- crates.io publish failures
+ - Symptom: Network/registry hiccups yield partial publish.
+ - Fix: Re‑run the `publish_crates` job or the entire workflow. Already
+ published crates are skipped.
+
+- Packaging misses a binary
+ - Ensure the target is declared as a `[[bin]]` in its crate. The packager
+ enumerates binary targets via `cargo metadata`.
+
+- Assets not included
+ - Only `crates/lambda-rs/assets` is packaged by default. If you need more
+ assets included, expand the staging step in the workflow.
+
+## Releasing New Crates in the Workspace
+
+- Make sure new crates have `license`, `repository`, and `categories` fields
+ set in `Cargo.toml`, and are members of the workspace.
+- The workflow bumps versions for all workspace crates and publishes only those
+ with changes present in the tag (`--from-git`).
+
+## Manual Verification (optional)
+
+- Before cutting a release, you can verify examples locally:
+ - `cargo run --example minimal`
+- For native engine builds:
+ - `scripts/compile_lambda.sh --build Debug`
+ - `scripts/compile_and_test.sh --os MacOS`
+
+## Changelog
+
+- 0.1.0 – Initial authoring of the guide and workflow documentation.
From cf34729e34c4c08ab3b01769787907b5620e2ea6 Mon Sep 17 00:00:00 2001
From: vmarcella
Date: Sun, 28 Sep 2025 14:38:42 -0700
Subject: [PATCH 2/2] [update] readme.
---
README.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/README.md b/README.md
index 910d8671..feb72f56 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
[](https://github.com/lambda-sh/lambda/actions/workflows/compile_lambda_rs.yml)
+[](https://github.com/lambda-sh/lambda/actions/workflows/release.yml)


@@ -18,6 +19,7 @@
1. [Getting started](#get_started)
1. [Examples](#examples)
1. [Planned additions](#plans)
+1. [Releases & Publishing](#publishing)
1. [How to contribute](#contribute)
1. [Resources](#resources)
## Description
@@ -177,6 +179,13 @@ cargo run --example triangles
- [ ] Unit tests.
- [ ] Nightly builds.
+## Releases & Publishing
+For cutting releases, publishing crates to crates.io, and attaching
+multi-platform artifacts to GitHub Releases, see:
+
+- docs/publishing.md
+
+
## How to contribute
Fork the current repository and then make the changes that you'd like to
said fork. Stable releases will happen within the main branch requiring that