Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

spawn version compatibility check #4031

Merged
merged 4 commits into from
Oct 27, 2023
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
51 changes: 51 additions & 0 deletions crates/re_build_info/src/build_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,54 @@ impl std::fmt::Display for BuildInfo {
Ok(())
}
}

// ---

use crate::CrateVersion;

impl CrateVersion {
/// Attempts to parse a [`CrateVersion`] from a [`BuildInfo`]'s string representation (`rerun --version`).
///
/// Refer to `BuildInfo as std::fmt::Display>::fmt` to see what the string representation is
/// expected to look like. Roughly:
/// ```ignore
/// <name> <semver> [<rust_info>] <target> <branch> <commit> <build_date>
/// ```
pub fn try_parse_from_build_info_string(s: impl AsRef<str>) -> Result<CrateVersion, String> {
let s = s.as_ref();
let parts = s.split_whitespace().collect::<Vec<_>>();
if parts.len() < 2 {
return Err(format!("{s:?} is not a valid BuildInfo string"));
}
CrateVersion::try_parse(parts[1]).map_err(ToOwned::to_owned)
}
}

#[test]
fn crate_version_from_build_info_string() {
let build_info = BuildInfo {
crate_name: "re_build_info",
version: CrateVersion {
major: 0,
minor: 10,
patch: 0,
meta: Some(crate::crate_version::Meta::DevAlpha(7)),
},
rustc_version: "1.72.1 (d5c2e9c34 2023-09-13)",
llvm_version: "16.0.5",
git_hash: "",
git_branch: "",
is_in_rerun_workspace: true,
target_triple: "x86_64-unknown-linux-gnu",
datetime: "",
};

let build_info_str = build_info.to_string();

{
let expected_crate_version = build_info.version;
let crate_version = CrateVersion::try_parse_from_build_info_string(build_info_str).unwrap();

assert_eq!(expected_crate_version, crate_version);
}
}
8 changes: 4 additions & 4 deletions crates/re_build_info/src/crate_version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ mod meta {
/// - `00000000` -> none of the above
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CrateVersion {
major: u8,
minor: u8,
patch: u8,
meta: Option<Meta>,
pub major: u8,
pub minor: u8,
pub patch: u8,
pub meta: Option<Meta>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
Expand Down
98 changes: 77 additions & 21 deletions crates/re_sdk/src/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,9 @@ impl std::fmt::Debug for SpawnError {
pub fn spawn(opts: &SpawnOptions) -> Result<(), SpawnError> {
use std::{net::TcpStream, process::Command, time::Duration};

// NOTE: It's indented on purpose, it just looks better and reads easier.
const EXECUTABLE_NOT_FOUND: &str = //
// NOTE: These are indented on purpose, it just looks better and reads easier.

const MSG_INSTALL_HOW_TO: &str = //
"
You can install binary releases of the Rerun Viewer:
* Using `cargo`: `cargo binstall rerun-cli` (see https://github.com/cargo-bins/cargo-binstall)
Expand All @@ -142,6 +143,26 @@ pub fn spawn(opts: &SpawnOptions) -> Result<(), SpawnError> {
https://rerun.io/docs/getting-started/installing-viewer
";

const MSG_INSTALL_HOW_TO_VERSIONED: &str = //
"
You can install an appropriate version of the Rerun Viewer via binary releases:
* Using `cargo`: `cargo binstall --force rerun-cli@__VIEWER_VERSION__` (see https://github.com/cargo-bins/cargo-binstall)
* Via direct download from our release assets: https://github.com/rerun-io/rerun/releases/__VIEWER_VERSION__/
* Using `pip`: `pip3 install rerun-sdk==__VIEWER_VERSION__` (warning: pip version has slower start times!)

For more information, refer to our complete install documentation over at:
https://rerun.io/docs/getting-started/installing-viewer
";

const MSG_VERSION_MISMATCH: &str = //
"
⚠ The version of the Rerun Viewer available on your PATH does not match the version of your Rerun SDK ⚠

Rerun does not make any kind of backwards/forwards compatibility guarantee yet: this can lead to (subtle) bugs.

> Rerun Viewer: v__VIEWER_VERSION__ (executable: \"__VIEWER_PATH__\")
> Rerun SDK: v__SDK_VERSION__";

let port = opts.port;
let connect_addr = opts.connect_addr();
let memory_limit = &opts.memory_limit;
Expand All @@ -156,33 +177,68 @@ pub fn spawn(opts: &SpawnOptions) -> Result<(), SpawnError> {
return Ok(());
}

let res = Command::new(executable_path)
.arg(format!("--port={port}"))
.arg(format!("--memory-limit={memory_limit}"))
.arg("--skip-welcome-screen")
.args(opts.extra_args.clone())
.spawn();

let rerun_bin = match res {
Ok(rerun_bin) => rerun_bin,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return if let Some(executable_path) = opts.executable_path.as_ref() {
Err(SpawnError::ExecutableNotFound {
let map_err = |err: std::io::Error| -> SpawnError {
if err.kind() == std::io::ErrorKind::NotFound {
if let Some(executable_path) = opts.executable_path.as_ref() {
SpawnError::ExecutableNotFound {
executable_path: executable_path.clone(),
})
}
} else {
Err(SpawnError::ExecutableNotFoundInPath {
message: EXECUTABLE_NOT_FOUND.to_owned(),
SpawnError::ExecutableNotFoundInPath {
message: MSG_INSTALL_HOW_TO.to_owned(),
executable_name: opts.executable_name.clone(),
search_path: std::env::var("PATH").unwrap_or_else(|_| String::new()),
})
}
}
} else {
err.into()
}
Err(err) => {
return Err(err.into());
}
};

let viewer_version = {
let output = Command::new(&executable_path)
.arg("--version")
.output()
.map_err(map_err)?;

let output = String::from_utf8_lossy(&output.stdout);
re_build_info::CrateVersion::try_parse_from_build_info_string(output).ok()
};

if let Some(viewer_version) = viewer_version {
let sdk_version = re_build_info::build_info!().version;

if !viewer_version.is_compatible_with(sdk_version) {
eprintln!(
"{}",
MSG_VERSION_MISMATCH
.replace("__VIEWER_VERSION__", &viewer_version.to_string())
.replace("__VIEWER_PATH__", &executable_path)
.replace("__SDK_VERSION__", &sdk_version.to_string())
);

// Don't recommend installing stuff through registries if the user is running some
// weird version.
if sdk_version.meta.is_none() {
eprintln!(
"{}",
MSG_INSTALL_HOW_TO_VERSIONED
.replace("__VIEWER_VERSION__", &sdk_version.to_string())
);
} else {
eprintln!();
}
}
}

let rerun_bin = Command::new(&executable_path)
.arg(format!("--port={port}"))
.arg(format!("--memory-limit={memory_limit}"))
.arg("--skip-welcome-screen")
.args(opts.extra_args.clone())
.spawn()
.map_err(map_err);

// Simply forget about the child process, we want it to outlive the parent process if needed.
_ = rerun_bin;

Expand Down
Loading