#[derive(Debug, Clone)]
pub struct SpawnOptions {
pub port: u16,
pub wait_for_bind: bool,
pub memory_limit: String,
pub server_memory_limit: String,
pub executable_name: String,
pub executable_path: Option<String>,
pub extra_args: Vec<String>,
pub extra_env: Vec<(String, String)>,
pub hide_welcome_screen: bool,
pub detach_process: bool,
}
const RERUN_BINARY: &str = "rerun";
impl Default for SpawnOptions {
fn default() -> Self {
Self {
port: re_grpc_server::DEFAULT_SERVER_PORT,
wait_for_bind: false,
memory_limit: "75%".into(),
server_memory_limit: "0B".into(),
executable_name: RERUN_BINARY.into(),
executable_path: None,
extra_args: Vec::new(),
extra_env: Vec::new(),
hide_welcome_screen: false,
detach_process: true,
}
}
}
impl SpawnOptions {
pub fn connect_addr(&self) -> std::net::SocketAddr {
std::net::SocketAddr::new(
std::net::IpAddr::V4(std::net::Ipv4Addr::new(127, 0, 0, 1)),
self.port,
)
}
pub fn listen_addr(&self) -> std::net::SocketAddr {
std::net::SocketAddr::new(
std::net::IpAddr::V4(std::net::Ipv4Addr::new(0, 0, 0, 0)),
self.port,
)
}
pub fn executable_path(&self) -> String {
if let Some(path) = self.executable_path.as_deref() {
return path.to_owned();
}
#[cfg(debug_assertions)]
{
let cargo_target_dir =
std::env::var("CARGO_TARGET_DIR").unwrap_or_else(|_| "target".to_owned());
let local_build_path = format!(
"{cargo_target_dir}/debug/{}{}",
self.executable_name,
std::env::consts::EXE_SUFFIX
);
if std::fs::metadata(&local_build_path).is_ok() {
re_log::info!("Spawning the locally built rerun at {local_build_path}");
return local_build_path;
} else {
re_log::info!(
"No locally built rerun found at {local_build_path:?}, using executable named {:?} from PATH.",
self.executable_name
);
}
}
self.executable_name.clone()
}
}
#[derive(thiserror::Error)]
pub enum SpawnError {
#[error("Failed to find Rerun Viewer executable in PATH.\n{message}\nPATH={search_path:?}")]
ExecutableNotFoundInPath {
message: String,
executable_name: String,
search_path: String,
},
#[error("Failed to find Rerun Viewer executable at {executable_path:?}")]
ExecutableNotFound {
executable_path: String,
},
#[error("Failed to spawn the Rerun Viewer process: {0}")]
Io(#[from] std::io::Error),
}
impl std::fmt::Debug for SpawnError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<Self as std::fmt::Display>::fmt(self, f)
}
}
pub fn spawn(opts: &SpawnOptions) -> Result<(), SpawnError> {
#[cfg(target_family = "unix")]
use std::os::unix::process::CommandExt as _;
use std::{net::TcpStream, process::Command, time::Duration};
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)
* Via direct download from our release assets: https://github.com/rerun-io/rerun/releases/latest/
* Using `pip`: `pip3 install rerun-sdk`
For more information, refer to our complete install documentation over at:
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__`
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;
let server_memory_limit = &opts.server_memory_limit;
let executable_path = opts.executable_path();
if TcpStream::connect_timeout(&connect_addr, Duration::from_secs(1)).is_ok() {
re_log::info!(
addr = %opts.listen_addr(),
"A process is already listening at this address. Assuming it's a Rerun Viewer."
);
return Ok(());
}
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 {
let sdk_version = re_build_info::build_info!().version;
SpawnError::ExecutableNotFoundInPath {
message: if sdk_version.is_release() {
MSG_INSTALL_HOW_TO_VERSIONED
.replace("__VIEWER_VERSION__", &sdk_version.to_string())
} else {
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()
}
};
let viewer_version = Command::new(&executable_path)
.arg("--version")
.output()
.ok()
.and_then(|output| {
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())
);
if sdk_version.is_release() {
eprintln!(
"{}",
MSG_INSTALL_HOW_TO_VERSIONED
.replace("__VIEWER_VERSION__", &sdk_version.to_string())
);
} else {
eprintln!();
}
}
}
let mut rerun_bin = Command::new(&executable_path);
rerun_bin
.stdin(std::process::Stdio::null())
.arg(format!("--port={port}"))
.arg(format!("--memory-limit={memory_limit}"))
.arg(format!("--server-memory-limit={server_memory_limit}"))
.arg("--expect-data-soon");
if opts.hide_welcome_screen {
rerun_bin.arg("--hide-welcome-screen");
}
rerun_bin.args(opts.extra_args.clone());
rerun_bin.envs(opts.extra_env.clone());
if opts.detach_process {
#[cfg(target_family = "unix")]
#[expect(unsafe_code)]
unsafe {
rerun_bin.pre_exec(|| {
libc::setsid();
Ok(())
})
};
}
rerun_bin.spawn().map_err(map_err)?;
if opts.wait_for_bind {
for i in 0..5 {
re_log::debug!("connection attempt {}", i + 1);
if TcpStream::connect_timeout(&connect_addr, Duration::from_secs(1)).is_ok() {
break;
}
std::thread::sleep(Duration::from_millis(100));
}
}
_ = rerun_bin;
Ok(())
}