Skip to main content

xil/
workers.rs

1//! Where the Python ML worker scripts live.
2//!
3//! Chatterbox Turbo, Whisper and MMAudio stay Python, each in its own venv;
4//! `xil` only speaks to them over JSON lines. Their scripts ship inside the
5//! `xil_pipeline` package, so that is the directory to find.
6
7use std::env;
8use std::path::PathBuf;
9use std::process::Command;
10
11/// The `xil_pipeline` package directory — `os.path.dirname(__file__)` of the
12/// package that holds `chatterbox_turbo_worker.py` and its siblings.
13///
14/// Tried in order: `$XIL_CODEROOT/src/xil_pipeline` (a source checkout), then
15/// the interpreter beside `$XIL_PY_BIN` (a pip install, as in CI), then
16/// `python3` on `PATH`.
17pub fn python_package_dir() -> Option<PathBuf> {
18    static DIR: std::sync::OnceLock<Option<PathBuf>> = std::sync::OnceLock::new();
19    DIR.get_or_init(|| {
20        if let Some(root) = env::var_os("XIL_CODEROOT") {
21            let p = PathBuf::from(root).join("src").join("xil_pipeline");
22            if p.join("__init__.py").is_file() {
23                return Some(p);
24            }
25        }
26        let beside_py_bin = env::var_os("XIL_PY_BIN")
27            .map(PathBuf::from)
28            .and_then(|p| p.parent().map(|d| d.join("python")));
29        beside_py_bin
30            .into_iter()
31            .chain([PathBuf::from("python3")])
32            .find_map(|python| ask(&python))
33    })
34    .clone()
35}
36
37fn ask(python: &std::path::Path) -> Option<PathBuf> {
38    let out = Command::new(python)
39        .args([
40            "-c",
41            "import os, xil_pipeline; print(os.path.dirname(xil_pipeline.__file__))",
42        ])
43        .output()
44        .ok()?;
45    let dir = String::from_utf8_lossy(&out.stdout).trim().to_string();
46    (out.status.success() && !dir.is_empty()).then(|| PathBuf::from(dir))
47}
48
49/// The `xil status --toolchain` report: this build, and what the workers
50/// will run under.
51pub fn describe() -> String {
52    let mut out = String::new();
53    match python_package_dir() {
54        Some(d) => out.push_str(&format!("worker scripts: {}\n", d.display())),
55        None => out.push_str(
56            "worker scripts: NOT FOUND (set XIL_CODEROOT to the xil-pipeline checkout)\n",
57        ),
58    }
59    let package = python_package_dir();
60    for venv in ["venv-chatterbox", "venv-whisper", "venv-mmaudio"] {
61        let python = xil_core::workspace::resolve_venv_python(venv, None, package.as_deref());
62        let missing = format!(
63            "not found (run: xil setup {})",
64            venv.trim_start_matches("venv-")
65        );
66        out.push_str(&format!(
67            "{venv}: {}\n",
68            python.as_deref().unwrap_or(missing.as_str())
69        ));
70    }
71    out
72}