Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions crates/code_assistant/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::types::ToolSyntax;
use clap::{Parser, Subcommand, ValueEnum};
use crate::version;
use clap::{CommandFactory, FromArgMatches, Parser, Subcommand, ValueEnum};
use llm::provider_config::ConfigurationSystem;
use sandbox::SandboxPolicy;
use std::path::PathBuf;
Expand Down Expand Up @@ -66,7 +67,7 @@ pub enum Mode {

/// Define the application arguments
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
#[command(about, long_about = None)]
pub struct Args {
#[command(subcommand)]
pub mode: Option<Mode>,
Expand Down Expand Up @@ -138,7 +139,17 @@ pub struct Args {

impl Args {
pub fn parse() -> Self {
<Args as Parser>::parse()
// Build the command with goreleaser-style version strings captured at
// compile time: `-V` prints the one-line summary, `--version` prints
// the full commit/build/toolchain block. Leaked to `&'static str`
// because clap wants an owned static; `parse` runs once at startup.
let short: &'static str = Box::leak(version::short().into_boxed_str());
let long: &'static str = Box::leak(version::long().into_boxed_str());
let command = <Args as CommandFactory>::command()
.version(short)
.long_version(long);
let matches = command.get_matches();
<Args as FromArgMatches>::from_arg_matches(&matches).unwrap_or_else(|err| err.exit())
}

/// Resolve a model name, ensuring it exists in the configuration and providing a fallback.
Expand Down
2 changes: 1 addition & 1 deletion crates/code_assistant/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod logging;
#[allow(unused_imports)]
pub(crate) use code_assistant_core::{
agent, config, config_dir, persistence, plugins, session, skills, tool_dialects, tools, types,
ui, utils,
ui, utils, version,
};

use crate::cli::{Args, Mode};
Expand Down
4 changes: 4 additions & 0 deletions crates/code_assistant_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ transmutation = { version = "0.3", features = ["office"], optional = true }
# Prevent system sleep while agents are running
keepawake = "0.6"

[build-dependencies]
# Format the build timestamp as RFC3339 UTC in build.rs.
chrono = "0.4"

[dev-dependencies]
axum = "0.7"
# test-util enables the paused clock (`start_paused`) for timer tests
Expand Down
66 changes: 66 additions & 0 deletions crates/code_assistant_core/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//! Build script that captures version/build information at compile time and
//! exposes it to the crate via `cargo:rustc-env` variables. The values mirror
//! the kind of metadata goreleaser embeds into Go binaries (commit, dirty
//! tree, build timestamp, toolchain, target triple).
//!
//! The corresponding runtime accessors live in `src/version.rs`.

use std::process::Command;

fn main() {
// Re-run the build script when the git state that feeds the commit/dirty
// fields changes. Working-tree edits that don't touch these files won't
// force a rerun, so the dirty flag / build timestamp reflect the last
// build that observed a relevant change — accurate for clean CI/release
// builds (which always start from a fresh checkout).
for path in [
"../../.git/HEAD",
"../../.git/index",
"../../.git/refs",
"build.rs",
] {
println!("cargo:rerun-if-changed={path}");
}

// Short git commit hash (full SHA). "unknown" when git is unavailable
// (e.g. building from a source tarball without a .git directory).
let commit = run_git(&["rev-parse", "HEAD"]).unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=BUILD_GIT_COMMIT={commit}");

// Whether the working tree had uncommitted changes at build time.
let dirty = run_git(&["status", "--porcelain"])
.map(|out| !out.trim().is_empty())
.unwrap_or(false);
println!(
"cargo:rustc-env=BUILD_GIT_DIRTY={}",
if dirty { "dirty" } else { "clean" }
);

// Build timestamp in RFC3339 UTC (e.g. 2026-08-20T17:01:00Z).
let built = chrono::Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
println!("cargo:rustc-env=BUILD_TIMESTAMP={built}");

// Rust compiler version, e.g. "rustc 1.83.0 (90b35a623 2024-11-26)".
let rustc = std::env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string());
let rustc_version = Command::new(&rustc)
.arg("--version")
.output()
.ok()
.filter(|o| o.status.success())
.map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
.unwrap_or_else(|| "unknown".to_string());
println!("cargo:rustc-env=BUILD_RUSTC_VERSION={rustc_version}");

// Target triple the binary is being built for, e.g. "aarch64-apple-darwin".
let target = std::env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
println!("cargo:rustc-env=BUILD_TARGET={target}");
}

/// Run a git subcommand and return its trimmed stdout on success.
fn run_git(args: &[&str]) -> Option<String> {
let output = Command::new("git").args(args).output().ok()?;
if !output.status.success() {
return None;
}
Some(String::from_utf8_lossy(&output.stdout).trim().to_string())
}
1 change: 1 addition & 0 deletions crates/code_assistant_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod tools;
pub mod types;
pub mod ui;
pub mod utils;
pub mod version;

// Mock building blocks (LLM provider, UI, project manager, tool fixtures).
// Compiled for our own tests and, behind the `test-utils` feature, for
Expand Down
134 changes: 134 additions & 0 deletions crates/code_assistant_core/src/version.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//! Build/version information captured at compile time by `build.rs`.
//!
//! Mirrors the metadata a Go binary built with goreleaser exposes: crate
//! version, git commit + clean/dirty tree state, build timestamp, and the
//! Rust toolchain / target triple. Consumed by the CLI `--version` output and
//! the GPUI "About" dialog.

/// Crate (semantic) version, e.g. `0.2.16`.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

/// Full git commit SHA the binary was built from, or `unknown`.
pub const GIT_COMMIT: &str = env!("BUILD_GIT_COMMIT");

/// Working-tree state at build time: `clean` or `dirty`.
pub const GIT_DIRTY: &str = env!("BUILD_GIT_DIRTY");

/// Build timestamp in RFC3339 UTC, e.g. `2026-08-20T17:01:00Z`.
pub const BUILD_TIMESTAMP: &str = env!("BUILD_TIMESTAMP");

/// Rust compiler version string, e.g. `rustc 1.83.0 (90b35a623 2024-11-26)`.
pub const RUSTC_VERSION: &str = env!("BUILD_RUSTC_VERSION");

/// Target triple, e.g. `aarch64-apple-darwin`.
pub const TARGET: &str = env!("BUILD_TARGET");

/// `true` for optimized (non-debug) builds.
pub fn is_release_build() -> bool {
!cfg!(debug_assertions)
}

/// `true` when the git working tree was clean at build time.
pub fn git_tree_clean() -> bool {
GIT_DIRTY == "clean"
}

/// Human label for the build profile, e.g. `release build` / `debug build`.
pub fn build_profile() -> &'static str {
if is_release_build() {
"release build"
} else {
"debug build"
}
}

/// Human label for the git tree state, e.g. `clean git tree` / `dirty git tree`.
pub fn git_tree_label() -> &'static str {
if git_tree_clean() {
"clean git tree"
} else {
"dirty git tree"
}
}

/// Single-line version summary as consumed by clap's `-V` output. clap
/// prepends the binary name, so this is just `v0.2.16 (release build)` and the
/// printed line becomes `code-assistant v0.2.16 (release build)`.
pub fn short() -> String {
format!("v{VERSION} ({})", build_profile())
}

/// Multi-line version block for clap's `--version` output (clap prepends the
/// binary name to the first line), mirroring goreleaser-style metadata:
///
/// ```text
/// code-assistant v0.2.16 (release build)
/// commit: 6e24def… (clean git tree)
/// built: 2026-08-20T17:01:00Z
/// rust: rustc 1.83.0 (90b35a623 2024-11-26) aarch64-apple-darwin
/// ```
pub fn long() -> String {
format!(
"{header}\n\
commit: {commit} ({tree})\n\
built: {built}\n\
rust: {rustc} {target}",
header = short(),
commit = GIT_COMMIT,
tree = git_tree_label(),
built = BUILD_TIMESTAMP,
rustc = RUSTC_VERSION,
target = TARGET,
)
}

/// Self-contained multi-line version block including the application name on
/// the first line (e.g. for clipboard copy from the About dialog).
pub fn long_with_name() -> String {
format!("code-assistant {}", long())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn short_starts_with_version() {
let s = short();
assert!(s.starts_with(&format!("v{VERSION}")), "unexpected: {s}");
assert!(s.ends_with("build)"), "unexpected: {s}");
}

#[test]
fn long_has_all_fields() {
let l = long();
assert!(
l.lines()
.next()
.unwrap()
.starts_with(&format!("v{VERSION}"))
);
assert!(l.contains("commit: "));
assert!(l.contains("built: "));
assert!(l.contains("rust: "));
assert!(l.contains(TARGET));
}

#[test]
fn long_with_name_prefixes_app_name() {
assert!(long_with_name().starts_with("code-assistant v"));
}

#[test]
fn labels_match_flags() {
assert_eq!(
build_profile(),
if cfg!(debug_assertions) {
"debug build"
} else {
"release build"
}
);
assert!(matches!(GIT_DIRTY, "clean" | "dirty"));
}
}
5 changes: 5 additions & 0 deletions crates/ui_gpui/assets/icons/info.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading