Skip to content

Commit eed9002

Browse files
authored
chore: derive build version from git tags for all components (#305)
Compute version strings from git describe in openshell-core's build.rs using the guess-next-dev scheme (e.g. 0.0.4-dev.6+g2bf9969). All binary crates and the TUI splash screen now use the shared openshell_core::VERSION constant instead of CARGO_PKG_VERSION. In Docker/CI builds where .git is absent, falls back to CARGO_PKG_VERSION which is already set correctly by the sed-patch pipeline. Also adds OPENSHELL_CARGO_VERSION support to the cluster image and fast-deploy supervisor builds for parity with the gateway.
1 parent f606acd commit eed9002

File tree

12 files changed

+119
-26
lines changed

12 files changed

+119
-26
lines changed

crates/openshell-cli/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ const DOCTOR_HELP: &str = "\x1b[1mALIAS\x1b[0m
298298
/// `OpenShell` CLI - agent execution and management.
299299
#[derive(Parser, Debug)]
300300
#[command(name = "openshell")]
301-
#[command(author, version, about, long_about = None)]
301+
#[command(author, version = openshell_core::VERSION, about, long_about = None)]
302302
#[command(propagate_version = true)]
303303
#[command(help_template = HELP_TEMPLATE)]
304304
#[command(disable_help_subcommand = true)]

crates/openshell-core/build.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44
use std::env;
55

66
fn main() -> Result<(), Box<dyn std::error::Error>> {
7+
// --- Git-derived version ---
8+
// Compute a version from `git describe` for local builds. In Docker/CI
9+
// builds where .git is absent, this silently does nothing and the binary
10+
// falls back to CARGO_PKG_VERSION (which is already sed-patched by the
11+
// build pipeline).
12+
println!("cargo:rerun-if-changed=../../.git/HEAD");
13+
println!("cargo:rerun-if-changed=../../.git/refs/tags");
14+
15+
if let Some(version) = git_version() {
16+
println!("cargo:rustc-env=OPENSHELL_GIT_VERSION={version}");
17+
}
18+
19+
// --- Protobuf compilation ---
720
// Use bundled protoc from protobuf-src
821
// SAFETY: This is run at build time in a single-threaded build script context.
922
// No other threads are reading environment variables concurrently.
@@ -33,3 +46,48 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
3346

3447
Ok(())
3548
}
49+
50+
/// Derive a version string from `git describe --tags`.
51+
///
52+
/// Implements the "guess-next-dev" convention used by the release pipeline
53+
/// (`setuptools-scm`): when there are commits past the last tag, the patch
54+
/// version is bumped and `-dev.<N>+g<sha>` is appended.
55+
///
56+
/// Examples:
57+
/// on tag v0.0.3 → "0.0.3"
58+
/// 3 commits past v0.0.3 → "0.0.4-dev.3+g2bf9969"
59+
///
60+
/// Returns `None` when git is unavailable or the repo has no matching tags.
61+
fn git_version() -> Option<String> {
62+
let output = std::process::Command::new("git")
63+
.args(["describe", "--tags", "--long", "--match", "v*"])
64+
.output()
65+
.ok()?;
66+
67+
if !output.status.success() {
68+
return None;
69+
}
70+
71+
let desc = String::from_utf8(output.stdout).ok()?;
72+
let desc = desc.trim();
73+
let desc = desc.strip_prefix('v').unwrap_or(desc);
74+
75+
// `git describe --long` format: <tag>-<N>-g<sha>
76+
// Split from the right to handle tags that contain hyphens.
77+
let (rest, sha) = desc.rsplit_once('-')?;
78+
let (tag, commits_str) = rest.rsplit_once('-')?;
79+
let commits: u32 = commits_str.parse().ok()?;
80+
81+
if commits == 0 {
82+
// Exactly on a tag — use the tag version as-is.
83+
return Some(tag.to_string());
84+
}
85+
86+
// Bump patch version (guess-next-dev scheme).
87+
let mut parts = tag.splitn(3, '.');
88+
let major = parts.next()?;
89+
let minor = parts.next()?;
90+
let patch: u32 = parts.next()?.parse().ok()?;
91+
92+
Some(format!("{major}.{minor}.{}-dev.{commits}+{sha}", patch + 1))
93+
}

crates/openshell-core/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//! - Protocol buffer definitions and generated code
88
//! - Configuration management
99
//! - Common error types
10+
//! - Build version metadata
1011
1112
pub mod config;
1213
pub mod error;
@@ -16,3 +17,14 @@ pub mod proto;
1617

1718
pub use config::{Config, TlsConfig};
1819
pub use error::{Error, Result};
20+
21+
/// Build version string derived from git metadata.
22+
///
23+
/// For local builds this is computed by `build.rs` via `git describe` using
24+
/// the guess-next-dev scheme (e.g. `0.0.4-dev.6+g2bf9969`). In Docker/CI
25+
/// builds where `.git` is absent, falls back to `CARGO_PKG_VERSION` which
26+
/// is already set correctly by the build pipeline's sed patch.
27+
pub const VERSION: &str = match option_env!("OPENSHELL_GIT_VERSION") {
28+
Some(v) => v,
29+
None => env!("CARGO_PKG_VERSION"),
30+
};

crates/openshell-sandbox/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use openshell_sandbox::run_sandbox;
1414
/// OpenShell Sandbox - process isolation and monitoring.
1515
#[derive(Parser, Debug)]
1616
#[command(name = "openshell-sandbox")]
17+
#[command(version = openshell_core::VERSION)]
1718
#[command(about = "Process sandbox and monitor", long_about = None)]
1819
struct Args {
1920
/// Command to execute in the sandbox.

crates/openshell-server/src/auth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ fn render_connect_page(
121121
.replace('<', "\\x3c")
122122
.replace('>', "\\x3e");
123123

124-
let version = env!("CARGO_PKG_VERSION");
124+
let version = openshell_core::VERSION;
125125

126126
format!(
127127
r#"<!DOCTYPE html>

crates/openshell-server/src/grpc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ impl OpenShell for OpenShellService {
134134
) -> Result<Response<HealthResponse>, Status> {
135135
Ok(Response::new(HealthResponse {
136136
status: ServiceStatus::Healthy.into(),
137-
version: env!("CARGO_PKG_VERSION").to_string(),
137+
version: openshell_core::VERSION.to_string(),
138138
}))
139139
}
140140

crates/openshell-server/src/http.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ async fn healthz() -> impl IntoResponse {
3131
async fn readyz() -> impl IntoResponse {
3232
let response = HealthResponse {
3333
status: "healthy",
34-
version: env!("CARGO_PKG_VERSION"),
34+
version: openshell_core::VERSION,
3535
};
3636

3737
(StatusCode::OK, Json(response))

crates/openshell-server/src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use openshell_server::{run_server, tracing_bus::TracingLogBus};
1515
/// `OpenShell` Server - gRPC and HTTP server with protocol multiplexing.
1616
#[derive(Parser, Debug)]
1717
#[command(name = "openshell-server")]
18+
#[command(version = openshell_core::VERSION)]
1819
#[command(about = "OpenShell gRPC/HTTP server", long_about = None)]
1920
struct Args {
2021
/// Port to bind the server to (all interfaces).

crates/openshell-tui/src/ui/splash.rs

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ const ART_WIDTH: u16 = 40;
4545
/// Total content lines: 6 (OPEN) + 6 (SHELL) + 1 (blank) + 1 (tagline) = 14.
4646
const CONTENT_LINES: u16 = 14;
4747

48-
// Border (2) + top/bottom inner padding (2) + content + blank before footer (1) + footer (1).
49-
const MODAL_HEIGHT: u16 = CONTENT_LINES + 6;
48+
// Border (2) + top/bottom inner padding (2) + content + blank before footer (1) + footer (2).
49+
const MODAL_HEIGHT: u16 = CONTENT_LINES + 7;
5050

5151
// Art width + left/right padding (3+3) + borders (2).
5252
const MODAL_WIDTH: u16 = ART_WIDTH + 8;
@@ -73,13 +73,13 @@ pub fn draw(frame: &mut Frame<'_>, area: Rect, theme: &crate::theme::Theme) {
7373
let inner = block.inner(popup);
7474
frame.render_widget(block, popup);
7575

76-
// Split inner area: art content + spacer + footer.
76+
// Split inner area: art content + spacer + footer (2 lines).
7777
let chunks = Layout::default()
7878
.direction(Direction::Vertical)
7979
.constraints([
8080
Constraint::Length(CONTENT_LINES), // OPEN + SHELL + blank + tagline
8181
Constraint::Min(0), // spacer
82-
Constraint::Length(1), // footer
82+
Constraint::Length(2), // footer (version + prompt)
8383
])
8484
.split(inner);
8585

@@ -102,27 +102,20 @@ pub fn draw(frame: &mut Frame<'_>, area: Rect, theme: &crate::theme::Theme) {
102102

103103
frame.render_widget(Paragraph::new(content_lines), chunks[0]);
104104

105-
// -- Footer: version + ALPHA badge (left) + prompt (right) --
106-
let version = format!("v{}", env!("CARGO_PKG_VERSION"));
107-
let spacer = " ";
105+
// -- Footer: version + ALPHA badge on line 1, prompt on line 2 --
106+
let version = format!("v{}", openshell_core::VERSION);
108107
let alpha_badge = "ALPHA";
109-
let prompt_text = "press any key";
110-
111-
// Pad between left group and prompt to fill the line.
112-
let used = version.len() + spacer.len() + alpha_badge.len() + prompt_text.len() + 2; // +2 for ░ and space
113-
let footer_width = chunks[2].width as usize;
114-
let gap = footer_width.saturating_sub(used);
115-
116-
let footer = Line::from(vec![
117-
Span::styled(version, t.accent),
118-
Span::styled(spacer, t.muted),
119-
Span::styled(alpha_badge, t.title_bar),
120-
Span::styled(" ".repeat(gap), t.muted),
121-
Span::styled(prompt_text, t.muted),
122-
Span::styled(" ░", t.muted),
108+
109+
let footer = Paragraph::new(vec![
110+
Line::from(vec![
111+
Span::styled(version, t.accent),
112+
Span::styled(" ", t.muted),
113+
Span::styled(alpha_badge, t.title_bar),
114+
]),
115+
Line::from(Span::styled("press any key ░", t.muted)),
123116
]);
124117

125-
frame.render_widget(Paragraph::new(footer), chunks[2]);
118+
frame.render_widget(footer, chunks[2]);
126119
}
127120

128121
fn centered_rect(width: u16, height: u16, area: Rect) -> Rect {

deploy/docker/Dockerfile.cluster

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certifi
7676
FROM --platform=$BUILDPLATFORM rust:1.88-slim AS supervisor-builder
7777
ARG TARGETARCH
7878
ARG BUILDARCH
79+
ARG OPENSHELL_CARGO_VERSION
7980
ARG CARGO_TARGET_CACHE_SCOPE=default
8081
ARG SCCACHE_MEMCACHED_ENDPOINT
8182

@@ -135,6 +136,9 @@ RUN --mount=type=cache,id=cargo-registry-supervisor-${TARGETARCH},sharing=locked
135136
--mount=type=cache,id=cargo-target-supervisor-${TARGETARCH}-${CARGO_TARGET_CACHE_SCOPE},sharing=locked,target=/build/target \
136137
--mount=type=cache,id=sccache-supervisor-${TARGETARCH},sharing=locked,target=/tmp/sccache \
137138
. cross-build.sh && \
139+
if [ -n "${OPENSHELL_CARGO_VERSION:-}" ]; then \
140+
sed -i -E '/^\[workspace\.package\]/,/^\[/{s/^version[[:space:]]*=[[:space:]]*".*"/version = "'"${OPENSHELL_CARGO_VERSION}"'"/}' Cargo.toml; \
141+
fi && \
138142
cargo_cross_build --release -p openshell-sandbox && \
139143
mkdir -p /build/out && \
140144
cp "$(cross_output_dir release)/openshell-sandbox" /build/out/

0 commit comments

Comments
 (0)