1use crate::warning_println;
2use anyhow::{anyhow, Result};
3use colored::*;
4use std::env;
5use std::path::PathBuf;
6use std::process::{Command, Stdio};
7
8pub fn get_uv_path() -> Result<String> {
9 #[cfg(not(target_os = "windows"))]
11 let find_executable = "which";
12
13 #[cfg(target_os = "windows")]
15 let find_executable = "where";
16
17 let output = Command::new(find_executable).arg("uv").output()?;
18 if output.status.success() {
19 let path = String::from_utf8(output.stdout)?.trim().to_string();
21 Ok(path)
22 } else {
23 eprintln!("Please run the following command to install uv:");
27 #[cfg(not(target_os = "windows"))]
28 eprintln!("wget -qO- https://astral.sh/uv/install.sh | sh");
29
30 #[cfg(target_os = "windows")]
32 eprintln!(
33 "powershell -ExecutionPolicy ByPass -c \"irm https://astral.sh/uv/install.ps1 | iex\""
34 );
35 Err(anyhow!("uv not installed"))
36 }
37}
38
39fn prepare_uv_project(project_path: &PathBuf, quiet: bool) -> Result<()> {
40 let uv_path = get_uv_path()?;
41 let output = Command::new(&uv_path)
42 .args(["init", "--bare", project_path.to_str().unwrap()])
43 .stdout(if quiet {
44 Stdio::null()
45 } else {
46 Stdio::inherit()
47 })
48 .stderr(Stdio::piped())
49 .output()?;
50
51 if output.status.success() {
52 Ok(())
53 } else {
54 if let Ok(stderr) = String::from_utf8(output.stderr) {
55 if stderr.contains("Project is already initialized") {
56 return Ok(());
57 }
58 eprintln!("{}", stderr);
59 }
60 Err(anyhow!("Failed to prepare uv project"))
61 }
62}
63
64pub fn prepare_venv(venv_path: &PathBuf, quiet: bool) -> Result<()> {
65 let uv_path = get_uv_path()?;
66 let output = Command::new(&uv_path)
67 .args(["venv", venv_path.to_str().unwrap()])
68 .stdout(if quiet {
69 Stdio::null()
70 } else {
71 Stdio::inherit()
72 })
73 .stderr(if quiet {
74 Stdio::null()
75 } else {
76 Stdio::inherit()
77 })
78 .output()?;
79
80 if output.status.success() {
81 Ok(())
82 } else {
83 Err(anyhow!("Failed to prepare venv"))
84 }
85}
86
87fn install_requirements(
88 venv_path: &PathBuf,
89 requirements_path: &PathBuf,
90 quiet: bool,
91) -> Result<()> {
92 if !requirements_path.exists() {
93 if !quiet {
94 warning_println!(
95 "{} not exists, skipping",
96 requirements_path.display().to_string().bold()
97 );
98 }
99 return Ok(());
100 }
101
102 let uv_path = get_uv_path()?;
103 let output = Command::new(&uv_path)
104 .args([
105 "add",
106 "--quiet",
107 "--directory",
108 venv_path.to_str().unwrap(),
109 "-r",
110 venv_path.join(requirements_path).to_str().unwrap(),
111 ])
112 .stdout(if quiet {
113 Stdio::null()
114 } else {
115 Stdio::inherit()
116 })
117 .stderr(if quiet {
118 Stdio::null()
119 } else {
120 Stdio::inherit()
121 })
122 .output()?;
123
124 if output.status.success() {
125 Ok(())
126 } else {
127 Err(anyhow!("Failed to install requirements"))
128 }
129}
130
131fn use_uv_venv(
132 quiet: bool,
133 clean: bool,
134 files_to_clean: &mut Vec<PathBuf>,
135) -> Result<(PathBuf, Vec<PathBuf>)> {
136 let current_dir = env::current_dir()?;
137
138 if clean {
139 let original_venv = current_dir.join(".venv");
140 if !original_venv.exists() {
141 files_to_clean.push(original_venv);
143 }
144
145 let pyproject_toml_path = current_dir.join("pyproject.toml");
146 if !pyproject_toml_path.exists() {
147 files_to_clean.push(pyproject_toml_path);
149 }
150
151 let uv_lock_path = current_dir.join("uv.lock");
152 if !uv_lock_path.exists() {
153 files_to_clean.push(uv_lock_path);
155 }
156 }
157
158 prepare_uv_project(¤t_dir, quiet)?;
159 let venv_path = current_dir.join(".venv");
160 if !venv_path.exists() {
161 prepare_venv(&venv_path, quiet)?;
162 }
163 install_requirements(&venv_path, ¤t_dir.join("requirements.txt"), quiet)?;
164
165 Ok((venv_path, files_to_clean.to_vec()))
166}
167
168pub fn venv(
169 venv_path_from_arg: &PathBuf,
170 quiet: bool,
171 clean: bool,
172) -> Result<(PathBuf, Vec<PathBuf>)> {
173 let current_dir = env::current_dir()?;
174 let mut files_to_clean: Vec<PathBuf> = Vec::new();
175
176 if !venv_path_from_arg.exists() {
177 if venv_path_from_arg.to_str().unwrap_or("") == ".venv" {
178 if !quiet {
181 warning_println!("No .venv found in current directory, trying venv");
182 }
183 let venv_path_alternate = current_dir.join("venv");
184 if venv_path_alternate.exists() {
185 if clean && !quiet {
186 warning_println!("Clean mode is not activated when using existing venv");
187 }
188 if current_dir.join("requirements.txt").exists() && !quiet {
189 warning_println!(
190 "You are about to use an existing venv, it is not possible for now to install requirements.txt on it"
191 );
192 }
193 return Ok((venv_path_alternate, files_to_clean));
194 }
195 }
196 if !quiet {
197 warning_println!("No venv found in current directory, trying uv");
198 }
199 let (venv_path, files_to_clean) = use_uv_venv(quiet, clean, &mut files_to_clean)?;
200 return Ok((venv_path, files_to_clean));
201 }
202
203 let venv_path_from_arg_absolute = match venv_path_from_arg.canonicalize() {
205 Ok(path) => path,
206 Err(err) => {
207 return Err(anyhow!(
208 "Can not get absolute path of {} , {}",
209 venv_path_from_arg.display().to_string().bold(),
210 err
211 ));
212 }
213 };
214
215 if clean && !quiet {
216 warning_println!("Clean mode is not activated when using existing venv");
217 }
218 if current_dir.join("requirements.txt").exists() {
219 if !quiet {
220 warning_println!(
221 "You are about to use an existing venv, it is not possible for now to install requirements.txt on it"
222 );
223 }
224 }
225 Ok((venv_path_from_arg_absolute, files_to_clean))
226}