Skip to main content

dora_core/
lib.rs

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