Skip to main content

dora_core/
lib.rs

1use eyre::{Context, bail, eyre};
2use std::{
3    env::consts::{DLL_PREFIX, DLL_SUFFIX},
4    ffi::OsStr,
5    path::Path,
6};
7
8pub use dora_message::{config, uhlc};
9
10#[cfg(feature = "build")]
11pub mod build;
12pub mod descriptor;
13pub mod metadata;
14pub mod topics;
15
16pub fn adjust_shared_library_path(path: &Path) -> Result<std::path::PathBuf, eyre::ErrReport> {
17    let file_name = path
18        .file_name()
19        .ok_or_else(|| eyre!("shared library path has no file name"))?
20        .to_str()
21        .ok_or_else(|| eyre!("shared library file name is not valid UTF8"))?;
22    if file_name.starts_with("lib") {
23        bail!("Shared library file name must not start with `lib`, prefix is added automatically");
24    }
25    if path.extension().is_some() {
26        bail!("Shared library file name must have no extension, it is added automatically");
27    }
28
29    let library_filename = format!("{DLL_PREFIX}{file_name}{DLL_SUFFIX}");
30
31    let path = path.with_file_name(library_filename);
32    Ok(path)
33}
34
35// Search for python binary.
36// 1. If `uv` is available, use `uv python find` to get the Python path
37// 2. Otherwise, try `python` and check it's not Python 2
38// 3. Fall back to `python3` if `python` is Python 2
39pub fn get_python_path() -> Result<std::path::PathBuf, eyre::ErrReport> {
40    // First, try using uv if available
41    if let Ok(uv_path) = get_uv_path() {
42        let output = std::process::Command::new(&uv_path)
43            .args(["python", "find"])
44            .output();
45
46        if let Ok(output) = output {
47            if output.status.success() {
48                let python_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
49                if !python_path.is_empty() {
50                    let path = std::path::PathBuf::from(&python_path);
51                    if path.exists() {
52                        return Ok(path);
53                    }
54                }
55            }
56        }
57    }
58
59    // Fall back to finding python directly
60    if let Ok(python) = which::which("python") {
61        // Check if it's Python 2
62        if !is_python2(&python) {
63            return Ok(python);
64        }
65    }
66
67    // Fall back to python3
68    which::which("python3").context(
69        "failed to find a valid Python 3 installation. Make sure that python3 is available.",
70    )
71}
72
73fn is_python2(python_path: &std::path::Path) -> bool {
74    let output = std::process::Command::new(python_path)
75        .args(["--version"])
76        .output();
77
78    match output {
79        Ok(output) => {
80            // Python 2 prints version to stderr, Python 3 to stdout
81            let version = if output.stdout.is_empty() {
82                String::from_utf8_lossy(&output.stderr)
83            } else {
84                String::from_utf8_lossy(&output.stdout)
85            };
86            version.starts_with("Python 2")
87        }
88        Err(_) => false,
89    }
90}
91
92// Search for pip binary.
93// First search for `pip3` as for ubuntu <20, `pip` can resolves to `python2,7 -m pip`
94// Then search for `pip`, this will resolve for windows to python3 -m pip.
95pub fn get_pip_path() -> Result<std::path::PathBuf, eyre::ErrReport> {
96    let python = match which::which("pip3") {
97        Ok(python) => python,
98        Err(_) => which::which("pip")
99            .context("failed to find `pip3` or `pip`. Make sure that python is available.")?,
100    };
101    Ok(python)
102}
103
104// Search for uv binary.
105pub fn get_uv_path() -> Result<std::path::PathBuf, eyre::ErrReport> {
106    which::which("uv")
107        .context("failed to find `uv`. Make sure to install it using: https://docs.astral.sh/uv/getting-started/installation/")
108}
109
110// Helper function to run a program
111pub async fn run<S>(program: S, args: &[&str], pwd: Option<&Path>) -> eyre::Result<()>
112where
113    S: AsRef<OsStr>,
114{
115    let mut run = tokio::process::Command::new(program);
116    run.args(args);
117
118    if let Some(pwd) = pwd {
119        run.current_dir(pwd);
120    }
121    if !run.status().await?.success() {
122        eyre::bail!("failed to run {args:?}");
123    };
124    Ok(())
125}