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::path::PathBuf;
8
9#[cfg(unix)]
10use std::fs;
11
12/// The name of the library directory in a Python venv (e.g., "Lib" on Windows, "lib" on Unix)
13#[cfg(windows)]
14const PYTHON_LIB_DIR: &str = "Lib";
15#[cfg(unix)]
16const PYTHON_LIB_DIR: &str = "lib";
17
18/// The name of the binaries/scripts directory in a Python venv (e.g., "Scripts" on Windows, "bin" on Unix)
19#[cfg(windows)]
20const PYTHON_BIN_DIR: &str = "Scripts";
21#[cfg(unix)]
22const PYTHON_BIN_DIR: &str = "bin";
23
24/// The name of the Python executable in a venv (e.g., "python.exe" on Windows, "python" on Unix)
25#[cfg(windows)]
26const PYTHON_EXE: &str = "python.exe";
27#[cfg(unix)]
28const PYTHON_EXE: &str = "python";
29
30// Site Packages differences.
31//
32// MacOS
33// .venv/lib/python {version}/site-packages
34//
35// Windows
36// .venv/Lib/site-packages
37
38pub fn resolve_site_package_path(venv_path: &PathBuf) -> Result<PathBuf, BridgeError> {
39    // Verify the venv_path exists and is a directory.
40    if !venv_path.is_dir() {
41        return Err(BridgeError::VenvNotFound(venv_path.to_path_buf()));
42    }
43
44    #[cfg(windows)]
45    {
46        let site_packages = venv_path.join(PYTHON_LIB_DIR).join("site-packages");
47
48        // verify site_package_path exists
49        if !site_packages.is_dir() {
50            return Err(BridgeError::Initialization(format!(
51                "unable to locate package directory: {}",
52                site_packages.display()
53            )));
54        }
55        Ok(site_packages)
56    }
57
58    #[cfg(not(windows))]
59    {
60        let lib_dir = venv_path.join(PYTHON_LIB_DIR);
61
62        if !lib_dir.is_dir() {
63            return Err(BridgeError::Initialization(format!(
64                "unable to locate lib directory: {}",
65                lib_dir.display()
66            )));
67        }
68
69        let python_version_dir = fs::read_dir(&lib_dir)
70            .map_err(|e| {
71                BridgeError::Initialization(format!("Failed to read lib directory: {}", e))
72            })?
73            .filter_map(|e| e.ok())
74            .find(|e| e.file_name().to_string_lossy().starts_with("python"))
75            .ok_or_else(|| {
76                BridgeError::Initialization("No python3.X directory found in venv/lib".to_string())
77            })?;
78
79        let site_packages = python_version_dir.path().join("site-packages");
80
81        if !site_packages.is_dir() {
82            return Err(BridgeError::Initialization(format!(
83                "unable to locate package directory: {}",
84                site_packages.display()
85            )));
86        }
87
88        Ok(site_packages)
89    }
90}
91
92pub fn resolve_python_path(venv_path: &PathBuf) -> Result<PathBuf, BridgeError> {
93    // validate venv path is a valid directory
94    if !venv_path.is_dir() {
95        return Err(BridgeError::VenvNotFound(venv_path.to_path_buf()));
96    }
97
98    let python_path = venv_path.join(PYTHON_BIN_DIR).join(PYTHON_EXE);
99    // validate the interpreter path is valid
100    if !python_path.is_file() {
101        return Err(BridgeError::Initialization(format!(
102            "Path to python binary is not valid: {}",
103            python_path.display()
104        )));
105    }
106
107    return Ok(python_path);
108}