diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml index f54aaeb..8d6dcff 100644 --- a/.forgejo/workflows/release.yaml +++ b/.forgejo/workflows/release.yaml @@ -156,6 +156,34 @@ jobs: echo "" > /tmp/release_notes.md fi + - name: Commit changelog to main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.version.outputs.version }}" + CHANGELOG_UPDATED="${{ steps.changelog.outputs.changelog_updated }}" + + git config user.name "Forgejo Actions" + git config user.email "actions@forge.eblu.me" + + if [ "$CHANGELOG_UPDATED" != "true" ]; then + echo "No changelog changes to commit" + exit 0 + fi + + # Consumed towncrier fragments must land on main so the next release + # does not re-process them. main stays at version 0.0.0; the version + # bump lives only on the release tag (see "Bump version and tag + # release" below, whose commit is a child of this one). + git add CHANGELOG.md docs/changelog.d/ + if git diff --cached --quiet; then + echo "No changes to commit" + else + git commit -m "Update changelog for $VERSION [skip ci]" + git push origin HEAD:main + echo "Changelog changes committed and pushed to main" + fi + - name: Build docs run: | VERSION="${{ steps.version.outputs.version }}" @@ -181,6 +209,35 @@ jobs: echo "Release asset inventory:" find release-assets -maxdepth 1 -type f -print | sort || true + - name: Bump version and tag release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + VERSION="${{ steps.version.outputs.version }}" # vX.Y.Z + BARE="${VERSION#v}" # X.Y.Z + + git config user.name "Forgejo Actions" + git config user.email "actions@forge.eblu.me" + + # Bake the release version into the manifests on a commit that ONLY + # the tag will point at. main deliberately stays at 0.0.0; this is the + # version that `cargo install --git --tag ` compiles into + # `--version`. Cargo.lock must match Cargo.toml for `--locked` + # installs: exactly the five workspace crates are at 0.0.0, so the + # anchored substitution is unambiguous (verified — no third-party + # dependency is pinned to 0.0.0). + sed -i 's/^version = "0.0.0"$/version = "'"$BARE"'"/' Cargo.toml Cargo.lock + git add Cargo.toml Cargo.lock + git commit -m "Release $VERSION [skip ci]" + + # Tag the bump commit and push ONLY the tag, so the versioned commit + # is reachable via the tag without advancing main. The Create release + # step below then attaches a release to this existing tag. + git tag "$VERSION" + git push origin "refs/tags/$VERSION" + echo "Tagged $VERSION at $(git rev-parse --short HEAD); main left at 0.0.0" + - name: Create release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -190,6 +247,8 @@ jobs: CHANGELOG_UPDATED="${{ steps.changelog.outputs.changelog_updated }}" FORGE_URL="${{ github.server_url }}/api/v1/repos/${{ github.repository }}" + # The tag was already created and pushed by "Bump version and tag + # release"; this attaches a Forgejo release to that existing tag. echo "Creating release $VERSION..." { @@ -263,31 +322,6 @@ jobs: echo "" echo "Release created successfully!" - - name: Commit changelog changes - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - VERSION="${{ steps.version.outputs.version }}" - CHANGELOG_UPDATED="${{ steps.changelog.outputs.changelog_updated }}" - - if [ "$CHANGELOG_UPDATED" != "true" ]; then - echo "No changelog changes to commit" - exit 0 - fi - - git config user.name "Forgejo Actions" - git config user.email "actions@forge.eblu.me" - - git add CHANGELOG.md docs/changelog.d/ - - if git diff --cached --quiet; then - echo "No changes to commit" - else - git commit -m "Update changelog for $VERSION [skip ci]" - git push origin HEAD:main - echo "Changelog changes committed and pushed" - fi - - name: Summary run: | VERSION="${{ steps.version.outputs.version }}" diff --git a/crates/heph-core/build.rs b/crates/heph-core/build.rs new file mode 100644 index 0000000..f0fe636 --- /dev/null +++ b/crates/heph-core/build.rs @@ -0,0 +1,44 @@ +//! Build script: capture the short git SHA this build came from so the CLI +//! surfaces can report ` ()`. The crate version itself comes from +//! `Cargo.toml` (bumped to the release version on the release tag — see +//! `.forgejo/workflows/release.yaml`); this only adds the provenance suffix. +//! +//! Resolution order: the `HEPH_BUILD_SHA` env override (an escape hatch for +//! builds with no `.git`, e.g. a source tarball) → `git rev-parse` → `unknown`. + +use std::process::Command; + +fn main() { + println!("cargo:rerun-if-env-changed=HEPH_BUILD_SHA"); + // Rebuild when HEAD moves so the embedded SHA stays accurate. Best-effort: + // a missing path here is harmless (cargo just won't watch it). + println!("cargo:rerun-if-changed=../../.git/HEAD"); + + let sha = std::env::var("HEPH_BUILD_SHA") + .ok() + .filter(|s| !s.is_empty()) + .or_else(git_short_sha) + .unwrap_or_else(|| "unknown".to_string()); + + println!("cargo:rustc-env=HEPH_BUILD_SHA={sha}"); +} + +fn git_short_sha() -> Option { + let out = Command::new("git") + .args(["rev-parse", "--short=9", "HEAD"]) + .output() + .ok()?; + if !out.status.success() { + return None; + } + let sha = String::from_utf8(out.stdout).ok()?.trim().to_string(); + if sha.is_empty() { + return None; + } + let dirty = Command::new("git") + .args(["status", "--porcelain"]) + .output() + .map(|o| !o.stdout.is_empty()) + .unwrap_or(false); + Some(if dirty { format!("{sha}-dirty") } else { sha }) +} diff --git a/crates/heph-core/src/lib.rs b/crates/heph-core/src/lib.rs index d9b9ecd..e5ff637 100644 --- a/crates/heph-core/src/lib.rs +++ b/crates/heph-core/src/lib.rs @@ -8,6 +8,12 @@ //! injected [`Clock`](clock::Clock) (tech-spec §2) so ranking and recurrence are //! deterministic. +/// Full version string for the CLI surfaces: `" ()"`, +/// e.g. `"1.0.0 (ab6701d12)"`. The version is the workspace version baked into +/// the release tag's manifest (`main` stays `0.0.0`); the short SHA is captured +/// at build time by `build.rs`. Used by `heph`/`hephd` for `--version`. +pub const VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), " (", env!("HEPH_BUILD_SHA"), ")"); + pub mod clock; mod crdt; pub mod error; diff --git a/crates/heph/src/main.rs b/crates/heph/src/main.rs index 505aa93..735bfe1 100644 --- a/crates/heph/src/main.rs +++ b/crates/heph/src/main.rs @@ -18,7 +18,7 @@ use hephd::{datespec, default_socket_path, Client, DeviceFlow, KeyringTokenStore mod service; #[derive(Parser, Debug)] -#[command(name = "heph", version, about)] +#[command(name = "heph", version = heph_core::VERSION, about)] struct Cli { /// Path to the hephd unix socket (defaults to the standard runtime path). #[arg(long, global = true)] diff --git a/crates/hephd/src/main.rs b/crates/hephd/src/main.rs index ee07cf6..62df200 100644 --- a/crates/hephd/src/main.rs +++ b/crates/hephd/src/main.rs @@ -38,7 +38,7 @@ enum Mode { /// The Hephaestus per-device daemon. #[derive(Parser, Debug)] -#[command(name = "hephd", version, about)] +#[command(name = "hephd", version = heph_core::VERSION, about)] struct Cli { /// Runtime mode. #[arg(long, value_enum, default_value_t = Mode::Local)] diff --git a/docs/changelog.d/release-version-bump.bugfix.md b/docs/changelog.d/release-version-bump.bugfix.md new file mode 100644 index 0000000..d29a48c --- /dev/null +++ b/docs/changelog.d/release-version-bump.bugfix.md @@ -0,0 +1 @@ +`heph --version` / `hephd --version` now report the release version plus the build commit (e.g. `1.0.0 (ab6701d12)`) instead of a bare `0.0.0`. The short git SHA is captured at build time, so any build — released, branch, or dirty working tree — is traceable to its commit. diff --git a/docs/changelog.d/release-version-bump.infra.md b/docs/changelog.d/release-version-bump.infra.md new file mode 100644 index 0000000..d3f1a21 --- /dev/null +++ b/docs/changelog.d/release-version-bump.infra.md @@ -0,0 +1 @@ +The release workflow now bakes the release version into `Cargo.toml` + `Cargo.lock` on a commit that only the release tag points at (tagging it manually), so `cargo install --git --tag vX.Y.Z` reports the real version while `main` stays at `0.0.0`.