r2x_python/
utils.rs

1//! Utility constants and functions for platform-specific Python venv path handling
2//!
3//! This module provides compile-time constants for directories and files that differ
4//! between Windows and Unix-like systems in Python virtual environments.
5
6use super::errors::BridgeError;
7use std::fs;
8use std::path::PathBuf;
9
10/// The name of the library directory in a Python venv (e.g., "Lib" on Windows, "lib" on Unix)
11#[cfg(windows)]
12const PYTHON_LIB_DIR: &str = "Lib";
13#[cfg(unix)]
14const PYTHON_LIB_DIR: &str = "lib";
15
16/// The name of the binaries/scripts directory in a Python venv (e.g., "Scripts" on Windows, "bin" on Unix)
17#[cfg(windows)]
18const PYTHON_BIN_DIR: &str = "Scripts";
19#[cfg(unix)]
20const PYTHON_BIN_DIR: &str = "bin";
21
22/// Candidate executable names in a venv
23#[cfg(unix)]
24const PYTHON_EXE_CANDIDATES: &[&str] = &["python3", "python"];
25#[cfg(windows)]
26const PYTHON_EXE_CANDIDATES: &[&str] = &["python.exe", "python3.exe", "python3.12.exe"];
27
28// Site Packages differences.
29//
30// MacOS
31// .venv/lib/python {version}/site-packages
32//
33// Windows
34// .venv/Lib/site-packages
35
36pub fn resolve_site_package_path(venv_path: &PathBuf) -> Result<PathBuf, BridgeError> {
37    // Verify the venv_path exists and is a directory.
38    if !venv_path.is_dir() {
39        return Err(BridgeError::VenvNotFound(venv_path.to_path_buf()));
40    }
41
42    #[cfg(windows)]
43    {
44        let site_packages = venv_path.join(PYTHON_LIB_DIR).join("site-packages");
45
46        // verify site_package_path exists
47        if !site_packages.is_dir() {
48            return Err(BridgeError::Initialization(format!(
49                "unable to locate package directory: {}",
50                site_packages.display()
51            )));
52        }
53        Ok(site_packages)
54    }
55
56    #[cfg(not(windows))]
57    {
58        let lib_dir = venv_path.join(PYTHON_LIB_DIR);
59
60        if !lib_dir.is_dir() {
61            return Err(BridgeError::Initialization(format!(
62                "unable to locate lib directory: {}",
63                lib_dir.display()
64            )));
65        }
66
67        let python_version_dir = fs::read_dir(&lib_dir)
68            .map_err(|e| {
69                BridgeError::Initialization(format!("Failed to read lib directory: {}", e))
70            })?
71            .filter_map(|e| e.ok())
72            .find(|e| e.file_name().to_string_lossy().starts_with("python"))
73            .ok_or_else(|| {
74                BridgeError::Initialization("No python3.X directory found in venv/lib".to_string())
75            })?;
76
77        let site_packages = python_version_dir.path().join("site-packages");
78
79        if !site_packages.is_dir() {
80            return Err(BridgeError::Initialization(format!(
81                "unable to locate package directory: {}",
82                site_packages.display()
83            )));
84        }
85
86        Ok(site_packages)
87    }
88}
89
90pub fn resolve_python_path(venv_path: &PathBuf) -> Result<PathBuf, BridgeError> {
91    // validate venv path is a valid directory
92    if !venv_path.is_dir() {
93        return Err(BridgeError::VenvNotFound(venv_path.to_path_buf()));
94    }
95
96    let bin_dir = venv_path.join(PYTHON_BIN_DIR);
97    if !bin_dir.is_dir() {
98        return Err(BridgeError::Initialization(format!(
99            "Python bin directory missing: {}",
100            bin_dir.display()
101        )));
102    }
103
104    for exe in PYTHON_EXE_CANDIDATES {
105        let candidate = bin_dir.join(exe);
106        if candidate.is_file() {
107            return Ok(candidate);
108        }
109    }
110
111    if let Ok(entries) = fs::read_dir(&bin_dir) {
112        if let Some(candidate) = entries.filter_map(|e| e.ok()).map(|e| e.path()).find(|p| {
113            p.file_name()
114                .and_then(|n| n.to_str())
115                .map(|name| name.contains("python"))
116                .unwrap_or(false)
117                && p.is_file()
118        }) {
119            return Ok(candidate);
120        }
121    }
122
123    Err(BridgeError::Initialization(format!(
124        "Path to python binary is not valid in {}",
125        venv_path.display()
126    )))
127}