Skip to main content

xtask_todo_lib/devshell/vm/
workspace_host.rs

1//! Host workspace paths shared by γ (Lima) and β (`devshell-vm` over socket / TCP / Windows stdio), without pulling in `limactl` / Lima session.
2//!
3//! Used on **all targets** when `beta-vm` is enabled (Windows β uses TCP); γ continues to use the same
4//! resolution via `session_gamma`.
5
6use std::path::{Path, PathBuf};
7use std::process::Command;
8
9use serde_json::Value;
10
11use super::super::sandbox;
12use super::guest_fs_ops::guest_project_dir_on_guest;
13
14// Keep in sync with `session_gamma/env.rs`.
15const ENV_DEVSHELL_VM_WORKSPACE_PARENT: &str = "DEVSHELL_VM_WORKSPACE_PARENT";
16const ENV_DEVSHELL_VM_WORKSPACE_USE_CARGO_ROOT: &str = "DEVSHELL_VM_WORKSPACE_USE_CARGO_ROOT";
17
18fn cargo_metadata_workspace_and_target(cwd: &Path) -> Result<(PathBuf, PathBuf), ()> {
19    let out = Command::new("cargo")
20        .args(["metadata", "--format-version", "1", "--no-deps"])
21        .current_dir(cwd)
22        .output()
23        .map_err(|_| ())?;
24    if !out.status.success() {
25        return Err(());
26    }
27    let v: Value = serde_json::from_slice(&out.stdout).map_err(|_| ())?;
28    let wr = v
29        .get("workspace_root")
30        .and_then(|x| x.as_str())
31        .map(PathBuf::from)
32        .ok_or(())?;
33    let td = v
34        .get("target_directory")
35        .and_then(|x| x.as_str())
36        .map(PathBuf::from)
37        .ok_or(())?;
38    Ok((wr, td))
39}
40
41fn sanitize_instance_segment(name: &str) -> String {
42    name.chars()
43        .map(|c| {
44            if c.is_ascii_alphanumeric() || c == '-' || c == '_' {
45                c
46            } else {
47                '_'
48            }
49        })
50        .collect()
51}
52
53/// Host workspace root shared with the VM / β staging tree (see `docs/devshell-vm-gamma.md`).
54#[must_use]
55pub fn workspace_parent_for_instance(instance: &str) -> PathBuf {
56    if let Ok(p) = std::env::var(ENV_DEVSHELL_VM_WORKSPACE_PARENT) {
57        let p = p.trim();
58        if !p.is_empty() {
59            return PathBuf::from(p);
60        }
61    }
62    let use_cargo = std::env::var(ENV_DEVSHELL_VM_WORKSPACE_USE_CARGO_ROOT).map_or(true, |s| {
63        let s = s.trim();
64        if s.is_empty() {
65            true
66        } else {
67            !(s == "0"
68                || s.eq_ignore_ascii_case("false")
69                || s.eq_ignore_ascii_case("no")
70                || s.eq_ignore_ascii_case("off"))
71        }
72    });
73    if use_cargo {
74        if let Ok(cwd) = std::env::current_dir() {
75            if let Ok((wr, _)) = cargo_metadata_workspace_and_target(&cwd) {
76                return wr.canonicalize().unwrap_or(wr);
77            }
78        }
79    }
80    let seg = sanitize_instance_segment(instance);
81    sandbox::devshell_export_parent_dir()
82        .join("vm-workspace")
83        .join(seg)
84}
85
86/// Guest working directory for VFS cwd (mount + last path segment).
87#[must_use]
88pub fn guest_dir_for_vfs_cwd(guest_mount: &str, vfs_cwd: &str) -> String {
89    guest_project_dir_on_guest(guest_mount, vfs_cwd)
90}