py_executer_lib/
path.rs

1use crate::{get_python_exec_path, warning_println};
2use anyhow::anyhow;
3use std::path::PathBuf;
4use std::process::{Command, Stdio};
5
6/// Finds the native Python executable path.
7///
8/// If `uv_path` is empty, it uses `which` or `where` command to find the native Python executable.
9/// If the command is successful, it returns the path of the Python executable.
10/// If the command is not successful, it returns an empty string.
11///
12/// If `uv_path` is not empty, it returns an empty string.
13///
14/// # Platform-specific
15///
16/// On Unix-like systems, it uses `which` command.
17///
18/// On Windows, it uses `where` command.
19pub fn get_python_native_path(uv_path: &String) -> String {
20    if uv_path.is_empty() {
21        #[cfg(not(target_os = "windows"))]
22        let find_executable = "which";
23
24        // For Windows
25        #[cfg(target_os = "windows")]
26        let find_executable = "where";
27
28        let output = Command::new(find_executable)
29            .arg("python3")
30            .output()
31            .unwrap();
32        if output.status.success() {
33            String::from_utf8(output.stdout)
34                .unwrap_or("".to_string())
35                .trim()
36                .to_string()
37        } else {
38            "".to_string()
39        }
40    } else {
41        "".to_string()
42    }
43}
44
45/// Validates a venv path.
46///
47/// # Errors
48///
49/// The function returns an `Err` if the venv path does not exist or if the Python executable
50/// under the venv path does not exist.
51fn validate_venv(venv_path: PathBuf) -> anyhow::Result<PathBuf> {
52    if !venv_path.exists() {
53        Err(anyhow!("{} not exists", venv_path.display().to_string()))
54    } else {
55        let python_exec_paths = get_python_exec_path(&venv_path);
56        if !python_exec_paths.exists() {
57            Err(anyhow!(
58                "Python executable {} not exists",
59                python_exec_paths.display().to_string()
60            ))
61        } else {
62            Ok(venv_path)
63        }
64    }
65}
66
67/// Finds a virtual environment path.
68///
69/// # Errors
70///
71/// The function returns an `Err` if the provided venv path does not exist or if the Python executable
72/// under the venv path does not exist.
73///
74/// # Platform-specific
75///
76/// On Unix-like systems, it uses `which` command.
77///
78/// On Windows, it uses `where` command.
79///
80/// # Arguments
81///
82/// * `venv`: The venv path provided by the user.
83/// * `runtime_path`: The runtime path of the current directory.
84/// * `uv_path`: The path of the uv executable.
85/// * `python_native_path`: The path of the native Python executable.
86/// * `quiet`: If `true`, suppresses warnings and errors.
87/// * `clean`: If `true`, will clean the created uv-managed .venv and config files after execution.
88/// * `files_to_clean`: A vector of paths to clean.
89///
90/// # Returns
91///
92/// The path of the found virtual environment.
93pub fn get_venv_path(
94    venv: PathBuf,
95    runtime_path: PathBuf,
96    uv_path: String,
97    python_native_path: String,
98    quiet: bool,
99    clean: bool,
100    files_to_clean: &mut Vec<PathBuf>,
101) -> PathBuf {
102    match validate_venv(venv) {
103        Ok(venv) => venv,
104        Err(e) => {
105            if !quiet {
106                warning_println!(
107                    "Failed to validate  provided venv: {}, looking for a possible one under current directory",
108                    e
109                );
110            }
111            let possible_venv_dir_names = ["venv", ".venv"];
112            possible_venv_dir_names
113                .iter()
114                .map(|name| runtime_path.join(name))
115                .find(|path| path.exists())
116                .unwrap_or_else(|| {
117                    prepare_venv(
118                        quiet,
119                        &runtime_path,
120                        &uv_path,
121                        &python_native_path,
122                        clean,
123                        files_to_clean,
124                    )
125                })
126        }
127    }
128}
129
130fn prepare_venv(
131    quiet: bool,
132    runtime_path: &PathBuf,
133    uv_path: &String,
134    python_native_path: &String,
135    clean: bool,
136    files_to_clean: &mut Vec<PathBuf>,
137) -> PathBuf {
138    if !quiet {
139        warning_println!("No venv found in current directory, will generate one");
140    }
141    let new_venv_path = runtime_path.join(".venv");
142    let _ = Command::new(if uv_path.is_empty() {
143        &python_native_path
144    } else {
145        &uv_path
146    })
147    .args(["venv", &new_venv_path.to_str().unwrap()])
148    .stdout(if quiet {
149        Stdio::null()
150    } else {
151        Stdio::inherit()
152    })
153    .stderr(if quiet {
154        Stdio::null()
155    } else {
156        Stdio::inherit()
157    })
158    .output()
159    .unwrap();
160    if clean {
161        files_to_clean.push(new_venv_path.clone());
162    }
163    new_venv_path
164}