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
40 changes: 36 additions & 4 deletions cmux-tui/crates/cmux-tui-core/src/release.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,27 @@ use std::path::{Component, Path};
/// test build, or a raw R2 tree artifact stamped `0.0.0-r2.sha-…` that the
/// app bundles) reports the one cmux-next version, the workspace crate
/// version, which `scripts/cmux-next/check-version-surfaces.sh` requires.
pub const DISTRIBUTION_VERSION: &str = match option_env!("CMUX_TUI_DISTRIBUTION_VERSION") {
Some(version) if is_release(version) => version,
_ => env!("CARGO_PKG_VERSION"),
};
/// The binary stamps it (`CMUX_TUI_DISTRIBUTION_VERSION`) and installs it at
/// start ([`install_distribution_version`]), so a new stamp (every build of
/// the Linux pool) recompiles the binary and not this crate.
pub fn distribution_version() -> &'static str {
INSTALLED_DISTRIBUTION_VERSION.get().copied().unwrap_or(env!("CARGO_PKG_VERSION"))
}

static INSTALLED_DISTRIBUTION_VERSION: std::sync::OnceLock<&'static str> =
std::sync::OnceLock::new();

/// Records the binary's stamped release, `None` when it has none. A placeholder
/// stamp is not a release ([`is_release`]). The first call wins.
pub fn install_distribution_version(stamped: Option<&'static str>) {
let _ = INSTALLED_DISTRIBUTION_VERSION.set(release_or_workspace(stamped));
}

/// A release stamp as the version to report: the workspace crate version for
/// none or a placeholder.
fn release_or_workspace(stamped: Option<&str>) -> &str {
stamped.filter(|version| is_release(version)).unwrap_or(env!("CARGO_PKG_VERSION"))
}

/// A release version: not empty and not a `0.0.0-…` placeholder stamp.
pub const fn is_release(version: &str) -> bool {
Expand Down Expand Up @@ -127,3 +144,18 @@ pub fn update_hint_for(install: Option<&str>) -> String {
),
}
}

#[cfg(test)]
mod distribution_tests {
/// What the binary installs is what `DISTRIBUTION_VERSION` was when this
/// crate read the stamp itself: a release as stamped, anything else the
/// workspace crate version.
#[test]
fn only_a_release_stamp_is_reported_as_the_release() {
use super::release_or_workspace;
assert_eq!(release_or_workspace(Some("0.13.4")), "0.13.4");
assert_eq!(release_or_workspace(Some("0.0.0-r2.sha-abc")), env!("CARGO_PKG_VERSION"));
assert_eq!(release_or_workspace(Some("")), env!("CARGO_PKG_VERSION"));
assert_eq!(release_or_workspace(None), env!("CARGO_PKG_VERSION"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ pub(super) fn bind(
writer,
id,
json!({"app": "cmux-tui", "protocol": super::super::PROTOCOL_VERSION,
"version": crate::release::DISTRIBUTION_VERSION, "generation": mux.registry_identity().1,
"version": crate::release::distribution_version(), "generation": mux.registry_identity().1,
"capabilities": ["agent-session-attach-v1", "agent-session-observe-v1"]}),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub(super) fn too_old_text(why: &str, version: Option<&str>, install: Option<&st
let release = version.map_or_else(String::new, |version| format!(" (cmux {version})"));
format!(
"this client{release} is too old for this cmux-tui daemon (cmux {}): {why}. To fix it, {}",
crate::release::DISTRIBUTION_VERSION,
crate::release::distribution_version(),
crate::release::update_hint_for(install)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ fn api_revision_too_old_refusal_names_the_clients_update_command() {
);

let identity = run(json!({"cmd":"identify","revisions":true})).unwrap();
assert_eq!(identity["version"], crate::release::DISTRIBUTION_VERSION);
assert_eq!(identity["version"], crate::release::distribution_version());
assert!(identity["install"].is_string());
mux.shutdown();
}
2 changes: 1 addition & 1 deletion cmux-tui/crates/cmux-tui-core/src/server/cmd_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub(super) fn identify(
let mut identity = json!({
"app": "cmux-tui",
// The npm/PyPI release (the crate version in the app and local builds).
"version": crate::release::DISTRIBUTION_VERSION,
"version": crate::release::distribution_version(),
"build_commit": stamped_build_commit(),
"build_id": build_id,
"cli_path": cli_path,
Expand Down
1 change: 1 addition & 0 deletions cmux-tui/crates/cmux-tui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1710,6 +1710,7 @@ pub fn main() -> std::process::ExitCode {
unsafe { startup_env::take_link_token_from_env() };
// Before any mode can serve `identify` or `ping`.
cmux_tui_core::server::install_build_stamp(build_stamp());
cmux_tui_core::release::install_distribution_version(version_report::DISTRIBUTION_STAMP);
// `cmux` (CLI and mux) and `acpmux` (a symlink) are one binary, one version.
#[cfg(unix)]
if std::env::args_os()
Expand Down
3 changes: 2 additions & 1 deletion cmux-tui/crates/cmux-tui/src/session/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ fn validate_remote_identity(ident: &Value) -> anyhow::Result<()> {
/// The refusal for a version skew, naming the exact update command of the
/// side that is too old (plans/cmux-next/tui-tester-rollout.md #4).
fn skew_refusal(ident: &Value, protocol: u64, client_too_old: bool) -> String {
use cmux_tui_core::release::{DISTRIBUTION_VERSION, InstallChannel, update_hint_for};
use crate::version_report::{DISTRIBUTION_VERSION, InstallChannel};
use cmux_tui_core::release::update_hint_for;
let server = ident.get("version").and_then(Value::as_str).unwrap_or("unknown");
if client_too_old {
format!(
Expand Down
2 changes: 1 addition & 1 deletion cmux-tui/crates/cmux-tui/src/session/remote/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ impl RemoteSession {
// how we were installed, so it can translate for us or name our
// update command when it refuses (server/api_revision_wire.rs).
if ident.get("api_revision").is_some() {
use cmux_tui_core::release::{DISTRIBUTION_VERSION, InstallChannel};
use crate::version_report::{DISTRIBUTION_VERSION, InstallChannel};
client_info.set(
"api_revision",
json!(
Expand Down
16 changes: 14 additions & 2 deletions cmux-tui/crates/cmux-tui/src/version_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,24 @@

use std::path::Path;

pub(crate) use cmux_tui_core::release::InstallChannel;
use cmux_tui_core::release::is_release;
pub(crate) use cmux_tui_core::release::{DISTRIBUTION_VERSION, InstallChannel};

/// This binary's release stamp, `None` for a build that has none. Read only
/// here: cmux-tui-core gets it at start (`install_distribution_version`), so a
/// new stamp recompiles this crate and not the libraries.
pub(crate) const DISTRIBUTION_STAMP: Option<&str> = option_env!("CMUX_TUI_DISTRIBUTION_VERSION");

/// The npm/PyPI release that contains this binary, as
/// `cmux_tui_core::release::distribution_version` reports it once installed.
pub(crate) const DISTRIBUTION_VERSION: &str = match DISTRIBUTION_STAMP {
Some(version) if is_release(version) => version,
_ => env!("CARGO_PKG_VERSION"),
};

/// Whether this binary was stamped as an npm/PyPI release (not a `0.0.0-…`
/// placeholder): crash forwarding and the update check run only then.
pub(crate) const RELEASE_STAMPED: bool = match option_env!("CMUX_TUI_DISTRIBUTION_VERSION") {
pub(crate) const RELEASE_STAMPED: bool = match DISTRIBUTION_STAMP {
Some(version) => is_release(version),
None => false,
};
Expand Down
Loading