1#[derive(Debug, Clone)]
15pub struct SpawnOptions {
16 pub port: u16,
20
21 pub wait_for_bind: bool,
24
25 pub memory_limit: String,
31
32 pub executable_name: String,
38
39 pub executable_path: Option<String>,
44
45 pub extra_args: Vec<String>,
47
48 pub extra_env: Vec<(String, String)>,
50
51 pub hide_welcome_screen: bool,
53
54 pub detach_process: bool,
56}
57
58const RERUN_BINARY: &str = "rerun";
60
61impl Default for SpawnOptions {
62 fn default() -> Self {
63 Self {
64 port: re_grpc_server::DEFAULT_SERVER_PORT,
65 wait_for_bind: false,
66 memory_limit: "75%".into(),
67 executable_name: RERUN_BINARY.into(),
68 executable_path: None,
69 extra_args: Vec::new(),
70 extra_env: Vec::new(),
71 hide_welcome_screen: false,
72 detach_process: true,
73 }
74 }
75}
76
77impl SpawnOptions {
78 pub fn connect_addr(&self) -> std::net::SocketAddr {
80 std::net::SocketAddr::new("127.0.0.1".parse().unwrap(), self.port)
81 }
82
83 pub fn listen_addr(&self) -> std::net::SocketAddr {
85 std::net::SocketAddr::new("0.0.0.0".parse().unwrap(), self.port)
86 }
87
88 pub fn executable_path(&self) -> String {
90 if let Some(path) = self.executable_path.as_deref() {
91 return path.to_owned();
92 }
93
94 #[cfg(debug_assertions)]
95 {
96 let local_build_path = format!("target/debug/{RERUN_BINARY}");
97 if std::fs::metadata(&local_build_path).is_ok() {
98 re_log::info!("Spawning the locally built rerun at {local_build_path}");
99 return local_build_path;
100 }
101 }
102
103 self.executable_name.clone()
104 }
105}
106
107#[derive(thiserror::Error)]
109pub enum SpawnError {
110 #[error("Failed to find Rerun Viewer executable in PATH.\n{message}\nPATH={search_path:?}")]
112 ExecutableNotFoundInPath {
113 message: String,
115
116 executable_name: String,
118
119 search_path: String,
121 },
122
123 #[error("Failed to find Rerun Viewer executable at {executable_path:?}")]
125 ExecutableNotFound {
126 executable_path: String,
128 },
129
130 #[error("Failed to spawn the Rerun Viewer process: {0}")]
132 Io(#[from] std::io::Error),
133}
134
135impl std::fmt::Debug for SpawnError {
136 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137 <Self as std::fmt::Display>::fmt(self, f)
144 }
145}
146
147#[allow(unsafe_code)]
157pub fn spawn(opts: &SpawnOptions) -> Result<(), SpawnError> {
158 #[cfg(target_family = "unix")]
159 use std::os::unix::process::CommandExt as _;
160
161 use std::{net::TcpStream, process::Command, time::Duration};
162
163 const MSG_INSTALL_HOW_TO: &str = "
167 You can install binary releases of the Rerun Viewer:
168 * Using `cargo`: `cargo binstall rerun-cli` (see https://github.com/cargo-bins/cargo-binstall)
169 * Via direct download from our release assets: https://github.com/rerun-io/rerun/releases/latest/
170 * Using `pip`: `pip3 install rerun-sdk`
171
172 For more information, refer to our complete install documentation over at:
173 https://rerun.io/docs/getting-started/installing-viewer
174 ";
175
176 const MSG_INSTALL_HOW_TO_VERSIONED: &str = "
178 You can install an appropriate version of the Rerun Viewer via binary releases:
179 * Using `cargo`: `cargo binstall --force rerun-cli@__VIEWER_VERSION__` (see https://github.com/cargo-bins/cargo-binstall)
180 * Via direct download from our release assets: https://github.com/rerun-io/rerun/releases/__VIEWER_VERSION__/
181 * Using `pip`: `pip3 install rerun-sdk==__VIEWER_VERSION__`
182
183 For more information, refer to our complete install documentation over at:
184 https://rerun.io/docs/getting-started/installing-viewer
185 ";
186
187 const MSG_VERSION_MISMATCH: &str = "
189 ⚠ The version of the Rerun Viewer available on your PATH does not match the version of your Rerun SDK ⚠
190
191 Rerun does not make any kind of backwards/forwards compatibility guarantee yet: this can lead to (subtle) bugs.
192
193 > Rerun Viewer: v__VIEWER_VERSION__ (executable: \"__VIEWER_PATH__\")
194 > Rerun SDK: v__SDK_VERSION__";
195
196 let port = opts.port;
197 let connect_addr = opts.connect_addr();
198 let memory_limit = &opts.memory_limit;
199 let executable_path = opts.executable_path();
200
201 if TcpStream::connect_timeout(&connect_addr, Duration::from_secs(1)).is_ok() {
203 re_log::info!(
204 addr = %opts.listen_addr(),
205 "A process is already listening at this address. Assuming it's a Rerun Viewer."
206 );
207 return Ok(());
208 }
209
210 let map_err = |err: std::io::Error| -> SpawnError {
211 if err.kind() == std::io::ErrorKind::NotFound {
212 if let Some(executable_path) = opts.executable_path.as_ref() {
213 SpawnError::ExecutableNotFound {
214 executable_path: executable_path.clone(),
215 }
216 } else {
217 let sdk_version = re_build_info::build_info!().version;
218 SpawnError::ExecutableNotFoundInPath {
219 message: if sdk_version.is_release() {
221 MSG_INSTALL_HOW_TO_VERSIONED
222 .replace("__VIEWER_VERSION__", &sdk_version.to_string())
223 } else {
224 MSG_INSTALL_HOW_TO.to_owned()
225 },
226 executable_name: opts.executable_name.clone(),
227 search_path: std::env::var("PATH").unwrap_or_else(|_| String::new()),
228 }
229 }
230 } else {
231 err.into()
232 }
233 };
234
235 let viewer_version = Command::new(&executable_path)
238 .arg("--version")
239 .output()
240 .ok()
241 .and_then(|output| {
242 let output = String::from_utf8_lossy(&output.stdout);
243 re_build_info::CrateVersion::try_parse_from_build_info_string(output).ok()
244 });
245
246 if let Some(viewer_version) = viewer_version {
247 let sdk_version = re_build_info::build_info!().version;
248
249 if !viewer_version.is_compatible_with(sdk_version) {
250 eprintln!(
251 "{}",
252 MSG_VERSION_MISMATCH
253 .replace("__VIEWER_VERSION__", &viewer_version.to_string())
254 .replace("__VIEWER_PATH__", &executable_path)
255 .replace("__SDK_VERSION__", &sdk_version.to_string())
256 );
257
258 if sdk_version.is_release() {
261 eprintln!(
262 "{}",
263 MSG_INSTALL_HOW_TO_VERSIONED
264 .replace("__VIEWER_VERSION__", &sdk_version.to_string())
265 );
266 } else {
267 eprintln!();
268 }
269 }
270 }
271
272 let mut rerun_bin = Command::new(&executable_path);
273
274 rerun_bin
278 .stdin(std::process::Stdio::null())
279 .arg(format!("--port={port}"))
280 .arg(format!("--memory-limit={memory_limit}"))
281 .arg("--expect-data-soon");
282
283 if opts.hide_welcome_screen {
284 rerun_bin.arg("--hide-welcome-screen");
285 }
286
287 rerun_bin.args(opts.extra_args.clone());
288 rerun_bin.envs(opts.extra_env.clone());
289
290 if opts.detach_process {
291 #[cfg(target_family = "unix")]
294 unsafe {
295 rerun_bin.pre_exec(|| {
296 libc::setsid();
300
301 Ok(())
302 })
303 };
304 }
305
306 rerun_bin.spawn().map_err(map_err)?;
307
308 if opts.wait_for_bind {
309 for i in 0..5 {
315 re_log::debug!("connection attempt {}", i + 1);
316 if TcpStream::connect_timeout(&connect_addr, Duration::from_secs(1)).is_ok() {
317 break;
318 }
319 std::thread::sleep(Duration::from_millis(100));
320 }
321 }
322
323 _ = rerun_bin;
325
326 Ok(())
327}