Skip to main content

dora_core/
lib.rs

1use eyre::{Context, bail, eyre};
2use std::{
3    env::consts::{DLL_PREFIX, DLL_SUFFIX},
4    path::Path,
5};
6
7pub use dora_message::{config, uhlc};
8
9#[cfg(feature = "build")]
10pub mod build;
11pub mod descriptor;
12#[cfg(feature = "type-inference")]
13pub mod inference;
14pub mod manifest;
15pub mod topics;
16pub mod types;
17
18/// Adjusts a shared library path by adding the platform-specific prefix and suffix.
19///
20/// Takes a base path (without platform-specific prefix and extension) and returns
21/// a path with the appropriate shared library naming conventions using
22/// [`DLL_PREFIX`] and [`DLL_SUFFIX`].
23///
24/// # Errors
25///
26/// Returns an error if the path has no file name, contains invalid UTF-8,
27/// already starts with `lib`, or already has an extension.
28///
29/// # Example
30///
31/// ```
32/// use std::path::Path;
33/// use dora_core::adjust_shared_library_path;
34///
35/// let adjusted = adjust_shared_library_path(Path::new("mylib")).unwrap();
36/// let expected = format!("{}mylib{}", std::env::consts::DLL_PREFIX, std::env::consts::DLL_SUFFIX);
37/// assert_eq!(adjusted.file_name().unwrap().to_str().unwrap(), expected);
38/// ```
39pub fn adjust_shared_library_path(path: &Path) -> Result<std::path::PathBuf, eyre::ErrReport> {
40    let file_name = path
41        .file_name()
42        .ok_or_else(|| eyre!("shared library path has no file name"))?
43        .to_str()
44        .ok_or_else(|| eyre!("shared library file name is not valid UTF8"))?;
45    if file_name.starts_with("lib") {
46        bail!("Shared library file name must not start with `lib`, prefix is added automatically");
47    }
48    if path.extension().is_some() {
49        bail!("Shared library file name must have no extension, it is added automatically");
50    }
51
52    let library_filename = format!("{DLL_PREFIX}{file_name}{DLL_SUFFIX}");
53
54    let path = path.with_file_name(library_filename);
55    Ok(path)
56}
57
58// Search for python binary.
59// 1. If `uv` is available, use `uv python find` to get the Python path
60// 2. Otherwise, try `python` and check it's not Python 2
61// 3. Fall back to `python3` if `python` is Python 2
62pub fn get_python_path() -> Result<std::path::PathBuf, eyre::ErrReport> {
63    // First, try using uv if available
64    if let Ok(uv_path) = get_uv_path() {
65        let output = std::process::Command::new(&uv_path)
66            .args(["python", "find"])
67            .output();
68
69        if let Ok(output) = output
70            && output.status.success()
71        {
72            let python_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
73            if !python_path.is_empty() {
74                let path = std::path::PathBuf::from(&python_path);
75                if path.exists() {
76                    return Ok(path);
77                }
78            }
79        }
80    }
81
82    // Fall back to finding python directly
83    if let Ok(python) = which::which("python") {
84        // Check if it's Python 2
85        if !is_python2(&python) {
86            return Ok(python);
87        }
88    }
89
90    // Fall back to python3
91    which::which("python3").context(
92        "failed to find a valid Python 3 installation. Make sure that python3 is available.",
93    )
94}
95
96fn is_python2(python_path: &std::path::Path) -> bool {
97    let output = std::process::Command::new(python_path)
98        .args(["--version"])
99        .output();
100
101    match output {
102        Ok(output) => {
103            // Python 2 prints version to stderr, Python 3 to stdout
104            let version = if output.stdout.is_empty() {
105                String::from_utf8_lossy(&output.stderr)
106            } else {
107                String::from_utf8_lossy(&output.stdout)
108            };
109            version.starts_with("Python 2")
110        }
111        Err(_) => false,
112    }
113}
114
115// Search for uv binary.
116pub fn get_uv_path() -> Result<std::path::PathBuf, eyre::ErrReport> {
117    which::which("uv")
118        .context("failed to find `uv`. Make sure to install it using: https://docs.astral.sh/uv/getting-started/installation/")
119}