1use std::env::consts::EXE_SUFFIX;
4use std::io;
5use std::io::{BufWriter, Write};
6use std::path::Path;
7
8use console::Term;
9use fs_err::File;
10use itertools::Itertools;
11use owo_colors::OwoColorize;
12use tracing::{debug, trace};
13
14use uv_fs::{CWD, Simplified, cachedir};
15use uv_preview::Preview;
16use uv_pypi_types::Scheme;
17use uv_python::managed::{PythonMinorVersionLink, create_link_to_executable};
18use uv_python::{Interpreter, VirtualEnvironment};
19use uv_shell::escape_posix_for_single_quotes;
20use uv_version::version;
21use uv_warnings::warn_user_once;
22
23use crate::{Error, Prompt};
24
25const ACTIVATE_TEMPLATES: &[(&str, &str)] = &[
27 ("activate", include_str!("activator/activate")),
28 ("activate.csh", include_str!("activator/activate.csh")),
29 ("activate.fish", include_str!("activator/activate.fish")),
30 ("activate.nu", include_str!("activator/activate.nu")),
31 ("activate.ps1", include_str!("activator/activate.ps1")),
32 ("activate.bat", include_str!("activator/activate.bat")),
33 ("deactivate.bat", include_str!("activator/deactivate.bat")),
34 ("pydoc.bat", include_str!("activator/pydoc.bat")),
35 (
36 "activate_this.py",
37 include_str!("activator/activate_this.py"),
38 ),
39];
40const VIRTUALENV_PATCH: &str = include_str!("_virtualenv.py");
41
42fn write_cfg(f: &mut impl Write, data: &[(String, String)]) -> io::Result<()> {
44 for (key, value) in data {
45 writeln!(f, "{key} = {value}")?;
46 }
47 Ok(())
48}
49
50#[allow(clippy::fn_params_excessive_bools)]
52pub(crate) fn create(
53 location: &Path,
54 interpreter: &Interpreter,
55 prompt: Prompt,
56 system_site_packages: bool,
57 on_existing: OnExisting,
58 relocatable: bool,
59 seed: bool,
60 upgradeable: bool,
61 preview: Preview,
62) -> Result<VirtualEnvironment, Error> {
63 let base_python = if cfg!(unix) && interpreter.is_standalone() {
69 interpreter.find_base_python()?
70 } else {
71 interpreter.to_base_python()?
72 };
73
74 debug!(
75 "Using base executable for virtual environment: {}",
76 base_python.display()
77 );
78
79 let prompt = match prompt {
83 Prompt::CurrentDirectoryName => CWD
84 .file_name()
85 .map(|name| name.to_string_lossy().to_string()),
86 Prompt::Static(value) => Some(value),
87 Prompt::None => None,
88 };
89 let absolute = std::path::absolute(location)?;
90
91 match location.metadata() {
93 Ok(metadata) if metadata.is_file() => {
94 return Err(Error::Io(io::Error::new(
95 io::ErrorKind::AlreadyExists,
96 format!("File exists at `{}`", location.user_display()),
97 )));
98 }
99 Ok(metadata)
100 if metadata.is_dir()
101 && location
102 .read_dir()
103 .is_ok_and(|mut dir| dir.next().is_none()) =>
104 {
105 trace!(
107 "Using empty directory at `{}` for virtual environment",
108 location.user_display()
109 );
110 }
111 Ok(metadata) if metadata.is_dir() => {
112 let is_virtualenv = uv_fs::is_virtualenv_base(location);
113 let name = if is_virtualenv {
114 "virtual environment"
115 } else {
116 "directory"
117 };
118 let hint = format!(
119 "Use the `{}` flag or set `{}` to replace the existing {name}",
120 "--clear".green(),
121 "UV_VENV_CLEAR=1".green()
122 );
123 let err = Err(Error::Io(io::Error::new(
126 io::ErrorKind::AlreadyExists,
127 format!(
128 "A {name} already exists at: {}\n\n{}{} {hint}",
129 location.user_display(),
130 "hint".bold().cyan(),
131 ":".bold(),
132 ),
133 )));
134 match on_existing {
135 OnExisting::Allow => {
136 debug!("Allowing existing {name} due to `--allow-existing`");
137 }
138 OnExisting::Remove(reason) => {
139 debug!("Removing existing {name} ({reason})");
140 let location = location
144 .canonicalize()
145 .unwrap_or_else(|_| location.to_path_buf());
146 remove_virtualenv(&location)?;
147 fs_err::create_dir_all(&location)?;
148 }
149 OnExisting::Fail => return err,
150 OnExisting::Prompt if !is_virtualenv => return err,
152 OnExisting::Prompt => {
153 match confirm_clear(location, name)? {
154 Some(true) => {
155 debug!("Removing existing {name} due to confirmation");
156 let location = location
160 .canonicalize()
161 .unwrap_or_else(|_| location.to_path_buf());
162 remove_virtualenv(&location)?;
163 fs_err::create_dir_all(&location)?;
164 }
165 Some(false) => return err,
166 None => {
168 warn_user_once!(
169 "A {name} already exists at `{}`. In the future, uv will require `{}` to replace it",
170 location.user_display(),
171 "--clear".green(),
172 );
173 }
174 }
175 }
176 }
177 }
178 Ok(_) => {
179 return Err(Error::Io(io::Error::new(
181 io::ErrorKind::AlreadyExists,
182 format!("Object already exists at `{}`", location.user_display()),
183 )));
184 }
185 Err(err) if err.kind() == io::ErrorKind::NotFound => {
186 fs_err::create_dir_all(location)?;
187 }
188 Err(err) => return Err(Error::Io(err)),
189 }
190
191 let location = absolute;
193
194 let bin_name = if cfg!(unix) {
195 "bin"
196 } else if cfg!(windows) {
197 "Scripts"
198 } else {
199 unimplemented!("Only Windows and Unix are supported")
200 };
201 let scripts = location.join(&interpreter.virtualenv().scripts);
202
203 cachedir::ensure_tag(&location)?;
205
206 fs_err::write(location.join(".gitignore"), "*")?;
208
209 let mut using_minor_version_link = false;
210 let executable_target = if upgradeable && interpreter.is_standalone() {
211 if let Some(minor_version_link) = PythonMinorVersionLink::from_executable(
212 base_python.as_path(),
213 &interpreter.key(),
214 preview,
215 ) {
216 if !minor_version_link.exists() {
217 base_python.clone()
218 } else {
219 let debug_symlink_term = if cfg!(windows) {
220 "junction"
221 } else {
222 "symlink directory"
223 };
224 debug!(
225 "Using {} {} instead of base Python path: {}",
226 debug_symlink_term,
227 &minor_version_link.symlink_directory.display(),
228 &base_python.display()
229 );
230 using_minor_version_link = true;
231 minor_version_link.symlink_executable.clone()
232 }
233 } else {
234 base_python.clone()
235 }
236 } else {
237 base_python.clone()
238 };
239
240 let python_home = executable_target
245 .parent()
246 .ok_or_else(|| {
247 io::Error::new(
248 io::ErrorKind::NotFound,
249 "The Python interpreter needs to have a parent directory",
250 )
251 })?
252 .to_path_buf();
253 let python_home = python_home.as_path();
254
255 fs_err::create_dir_all(&scripts)?;
257 let executable = scripts.join(format!("python{EXE_SUFFIX}"));
258
259 #[cfg(unix)]
260 {
261 uv_fs::replace_symlink(&executable_target, &executable)?;
262 uv_fs::replace_symlink(
263 "python",
264 scripts.join(format!("python{}", interpreter.python_major())),
265 )?;
266 uv_fs::replace_symlink(
267 "python",
268 scripts.join(format!(
269 "python{}.{}",
270 interpreter.python_major(),
271 interpreter.python_minor(),
272 )),
273 )?;
274 if interpreter.gil_disabled() {
275 uv_fs::replace_symlink(
276 "python",
277 scripts.join(format!(
278 "python{}.{}t",
279 interpreter.python_major(),
280 interpreter.python_minor(),
281 )),
282 )?;
283 }
284
285 if interpreter.markers().implementation_name() == "pypy" {
286 uv_fs::replace_symlink(
287 "python",
288 scripts.join(format!("pypy{}", interpreter.python_major())),
289 )?;
290 uv_fs::replace_symlink("python", scripts.join("pypy"))?;
291 }
292
293 if interpreter.markers().implementation_name() == "graalpy" {
294 uv_fs::replace_symlink("python", scripts.join("graalpy"))?;
295 }
296 }
297
298 if cfg!(windows) {
302 if using_minor_version_link {
303 let target = scripts.join(WindowsExecutable::Python.exe(interpreter));
304 create_link_to_executable(target.as_path(), &executable_target)
305 .map_err(Error::Python)?;
306 let targetw = scripts.join(WindowsExecutable::Pythonw.exe(interpreter));
307 create_link_to_executable(targetw.as_path(), &executable_target)
308 .map_err(Error::Python)?;
309 if interpreter.gil_disabled() {
310 let targett = scripts.join(WindowsExecutable::PythonMajorMinort.exe(interpreter));
311 create_link_to_executable(targett.as_path(), &executable_target)
312 .map_err(Error::Python)?;
313 let targetwt = scripts.join(WindowsExecutable::PythonwMajorMinort.exe(interpreter));
314 create_link_to_executable(targetwt.as_path(), &executable_target)
315 .map_err(Error::Python)?;
316 }
317 } else {
318 copy_launcher_windows(
320 WindowsExecutable::Python,
321 interpreter,
322 &base_python,
323 &scripts,
324 python_home,
325 )?;
326
327 match interpreter.implementation_name() {
328 "graalpy" => {
329 copy_launcher_windows(
331 WindowsExecutable::GraalPy,
332 interpreter,
333 &base_python,
334 &scripts,
335 python_home,
336 )?;
337 copy_launcher_windows(
338 WindowsExecutable::PythonMajor,
339 interpreter,
340 &base_python,
341 &scripts,
342 python_home,
343 )?;
344 }
345 "pypy" => {
346 copy_launcher_windows(
348 WindowsExecutable::PythonMajor,
349 interpreter,
350 &base_python,
351 &scripts,
352 python_home,
353 )?;
354 copy_launcher_windows(
355 WindowsExecutable::PythonMajorMinor,
356 interpreter,
357 &base_python,
358 &scripts,
359 python_home,
360 )?;
361 copy_launcher_windows(
362 WindowsExecutable::Pythonw,
363 interpreter,
364 &base_python,
365 &scripts,
366 python_home,
367 )?;
368 copy_launcher_windows(
369 WindowsExecutable::PyPy,
370 interpreter,
371 &base_python,
372 &scripts,
373 python_home,
374 )?;
375 copy_launcher_windows(
376 WindowsExecutable::PyPyMajor,
377 interpreter,
378 &base_python,
379 &scripts,
380 python_home,
381 )?;
382 copy_launcher_windows(
383 WindowsExecutable::PyPyMajorMinor,
384 interpreter,
385 &base_python,
386 &scripts,
387 python_home,
388 )?;
389 copy_launcher_windows(
390 WindowsExecutable::PyPyw,
391 interpreter,
392 &base_python,
393 &scripts,
394 python_home,
395 )?;
396 copy_launcher_windows(
397 WindowsExecutable::PyPyMajorMinorw,
398 interpreter,
399 &base_python,
400 &scripts,
401 python_home,
402 )?;
403 }
404 _ => {
405 copy_launcher_windows(
407 WindowsExecutable::Pythonw,
408 interpreter,
409 &base_python,
410 &scripts,
411 python_home,
412 )?;
413
414 if interpreter.gil_disabled() {
416 copy_launcher_windows(
417 WindowsExecutable::PythonMajorMinort,
418 interpreter,
419 &base_python,
420 &scripts,
421 python_home,
422 )?;
423 copy_launcher_windows(
424 WindowsExecutable::PythonwMajorMinort,
425 interpreter,
426 &base_python,
427 &scripts,
428 python_home,
429 )?;
430 }
431 }
432 }
433 }
434 }
435
436 #[cfg(not(any(unix, windows)))]
437 {
438 compile_error!("Only Windows and Unix are supported")
439 }
440
441 for (name, template) in ACTIVATE_TEMPLATES {
443 let path_sep = if cfg!(windows) { ";" } else { ":" };
444
445 let relative_site_packages = [
446 interpreter.virtualenv().purelib.as_path(),
447 interpreter.virtualenv().platlib.as_path(),
448 ]
449 .iter()
450 .dedup()
451 .map(|path| {
452 pathdiff::diff_paths(path, &interpreter.virtualenv().scripts)
453 .expect("Failed to calculate relative path to site-packages")
454 })
455 .map(|path| path.simplified().to_str().unwrap().replace('\\', "\\\\"))
456 .join(path_sep);
457
458 let virtual_env_dir = match (relocatable, name.to_owned()) {
459 (true, "activate") => {
460 r#"'"$(dirname -- "$(dirname -- "$(realpath -- "$SCRIPT_PATH")")")"'"#.to_string()
461 }
462 (true, "activate.bat") => r"%~dp0..".to_string(),
463 (true, "activate.fish") => {
464 r#"'"$(dirname -- "$(cd "$(dirname -- "$(status -f)")"; and pwd)")"'"#.to_string()
465 }
466 _ => escape_posix_for_single_quotes(location.simplified().to_str().unwrap()),
470 };
471
472 let activator = template
473 .replace("{{ VIRTUAL_ENV_DIR }}", &virtual_env_dir)
474 .replace("{{ BIN_NAME }}", bin_name)
475 .replace(
476 "{{ VIRTUAL_PROMPT }}",
477 prompt.as_deref().unwrap_or_default(),
478 )
479 .replace("{{ PATH_SEP }}", path_sep)
480 .replace("{{ RELATIVE_SITE_PACKAGES }}", &relative_site_packages);
481 fs_err::write(scripts.join(name), activator)?;
482 }
483
484 let mut pyvenv_cfg_data: Vec<(String, String)> = vec![
485 (
486 "home".to_string(),
487 python_home.simplified_display().to_string(),
488 ),
489 (
490 "implementation".to_string(),
491 interpreter
492 .markers()
493 .platform_python_implementation()
494 .to_string(),
495 ),
496 ("uv".to_string(), version().to_string()),
497 (
498 "version_info".to_string(),
499 interpreter.markers().python_full_version().string.clone(),
500 ),
501 (
502 "include-system-site-packages".to_string(),
503 if system_site_packages {
504 "true".to_string()
505 } else {
506 "false".to_string()
507 },
508 ),
509 ];
510
511 if relocatable {
512 pyvenv_cfg_data.push(("relocatable".to_string(), "true".to_string()));
513 }
514
515 if seed {
516 pyvenv_cfg_data.push(("seed".to_string(), "true".to_string()));
517 }
518
519 if let Some(prompt) = prompt {
520 pyvenv_cfg_data.push(("prompt".to_string(), prompt));
521 }
522
523 if cfg!(windows) && interpreter.markers().implementation_name() == "graalpy" {
524 pyvenv_cfg_data.push((
525 "venvlauncher_command".to_string(),
526 python_home
527 .join("graalpy.exe")
528 .simplified_display()
529 .to_string(),
530 ));
531 }
532
533 let mut pyvenv_cfg = BufWriter::new(File::create(location.join("pyvenv.cfg"))?);
534 write_cfg(&mut pyvenv_cfg, &pyvenv_cfg_data)?;
535 drop(pyvenv_cfg);
536
537 let site_packages = location.join(&interpreter.virtualenv().purelib);
539 fs_err::create_dir_all(&site_packages)?;
540
541 #[cfg(unix)]
544 if interpreter.pointer_size().is_64()
545 && interpreter.markers().os_name() == "posix"
546 && interpreter.markers().sys_platform() != "darwin"
547 {
548 match fs_err::os::unix::fs::symlink("lib", location.join("lib64")) {
549 Ok(()) => {}
550 Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {}
551 Err(err) => {
552 return Err(err.into());
553 }
554 }
555 }
556
557 fs_err::write(site_packages.join("_virtualenv.py"), VIRTUALENV_PATCH)?;
559 fs_err::write(site_packages.join("_virtualenv.pth"), "import _virtualenv")?;
560
561 Ok(VirtualEnvironment {
562 scheme: Scheme {
563 purelib: location.join(&interpreter.virtualenv().purelib),
564 platlib: location.join(&interpreter.virtualenv().platlib),
565 scripts: location.join(&interpreter.virtualenv().scripts),
566 data: location.join(&interpreter.virtualenv().data),
567 include: location.join(&interpreter.virtualenv().include),
568 },
569 root: location,
570 executable,
571 base_executable: base_python,
572 })
573}
574
575fn confirm_clear(location: &Path, name: &'static str) -> Result<Option<bool>, io::Error> {
579 let term = Term::stderr();
580 if term.is_term() {
581 let prompt = format!(
582 "A {name} already exists at `{}`. Do you want to replace it?",
583 location.user_display(),
584 );
585 let hint = format!(
586 "Use the `{}` flag or set `{}` to skip this prompt",
587 "--clear".green(),
588 "UV_VENV_CLEAR=1".green()
589 );
590 Ok(Some(uv_console::confirm_with_hint(
591 &prompt, &hint, &term, true,
592 )?))
593 } else {
594 Ok(None)
595 }
596}
597
598pub fn remove_virtualenv(location: &Path) -> Result<(), Error> {
600 #[cfg(windows)]
603 if let Ok(itself) = std::env::current_exe() {
604 let target = std::path::absolute(location)?;
605 if itself.starts_with(&target) {
606 debug!("Detected self-delete of executable: {}", itself.display());
607 self_replace::self_delete_outside_path(location)?;
608 }
609 }
610
611 for entry in fs_err::read_dir(location)? {
614 let entry = entry?;
615 let path = entry.path();
616 if path == location.join("pyvenv.cfg") {
617 continue;
618 }
619 if path.is_dir() {
620 fs_err::remove_dir_all(&path)?;
621 } else {
622 fs_err::remove_file(&path)?;
623 }
624 }
625
626 match fs_err::remove_file(location.join("pyvenv.cfg")) {
627 Ok(()) => {}
628 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
629 Err(err) => return Err(err.into()),
630 }
631
632 match fs_err::remove_dir_all(location) {
634 Ok(()) => {}
635 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
636 Err(err) if err.kind() == io::ErrorKind::ResourceBusy => {
639 debug!(
640 "Skipping removal of `{}` directory due to {err}",
641 location.display(),
642 );
643 }
644 Err(err) => return Err(err.into()),
645 }
646
647 Ok(())
648}
649
650#[derive(Debug, Copy, Clone, Eq, PartialEq)]
651pub enum RemovalReason {
652 UserRequest,
654 TemporaryEnvironment,
657 ManagedEnvironment,
660}
661
662impl std::fmt::Display for RemovalReason {
663 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
664 match self {
665 Self::UserRequest => f.write_str("requested with `--clear`"),
666 Self::ManagedEnvironment => f.write_str("environment is managed by uv"),
667 Self::TemporaryEnvironment => f.write_str("environment is temporary"),
668 }
669 }
670}
671
672#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
673pub enum OnExisting {
674 #[default]
678 Prompt,
679 Fail,
681 Allow,
684 Remove(RemovalReason),
686}
687
688impl OnExisting {
689 pub fn from_args(allow_existing: bool, clear: bool, no_clear: bool) -> Self {
690 if allow_existing {
691 Self::Allow
692 } else if clear {
693 Self::Remove(RemovalReason::UserRequest)
694 } else if no_clear {
695 Self::Fail
696 } else {
697 Self::Prompt
698 }
699 }
700}
701
702#[derive(Debug, Copy, Clone)]
703enum WindowsExecutable {
704 Python,
706 PythonMajor,
708 PythonMajorMinor,
710 PythonMajorMinort,
712 Pythonw,
714 PythonwMajorMinort,
716 PyPy,
718 PyPyMajor,
720 PyPyMajorMinor,
722 PyPyw,
724 PyPyMajorMinorw,
726 GraalPy,
728}
729
730impl WindowsExecutable {
731 fn exe(self, interpreter: &Interpreter) -> String {
733 match self {
734 Self::Python => String::from("python.exe"),
735 Self::PythonMajor => {
736 format!("python{}.exe", interpreter.python_major())
737 }
738 Self::PythonMajorMinor => {
739 format!(
740 "python{}.{}.exe",
741 interpreter.python_major(),
742 interpreter.python_minor()
743 )
744 }
745 Self::PythonMajorMinort => {
746 format!(
747 "python{}.{}t.exe",
748 interpreter.python_major(),
749 interpreter.python_minor()
750 )
751 }
752 Self::Pythonw => String::from("pythonw.exe"),
753 Self::PythonwMajorMinort => {
754 format!(
755 "pythonw{}.{}t.exe",
756 interpreter.python_major(),
757 interpreter.python_minor()
758 )
759 }
760 Self::PyPy => String::from("pypy.exe"),
761 Self::PyPyMajor => {
762 format!("pypy{}.exe", interpreter.python_major())
763 }
764 Self::PyPyMajorMinor => {
765 format!(
766 "pypy{}.{}.exe",
767 interpreter.python_major(),
768 interpreter.python_minor()
769 )
770 }
771 Self::PyPyw => String::from("pypyw.exe"),
772 Self::PyPyMajorMinorw => {
773 format!(
774 "pypy{}.{}w.exe",
775 interpreter.python_major(),
776 interpreter.python_minor()
777 )
778 }
779 Self::GraalPy => String::from("graalpy.exe"),
780 }
781 }
782
783 fn launcher(self, interpreter: &Interpreter) -> &'static str {
785 match self {
786 Self::Python | Self::PythonMajor | Self::PythonMajorMinor
787 if interpreter.gil_disabled() =>
788 {
789 "venvlaunchert.exe"
790 }
791 Self::Python | Self::PythonMajor | Self::PythonMajorMinor => "venvlauncher.exe",
792 Self::Pythonw if interpreter.gil_disabled() => "venvwlaunchert.exe",
793 Self::Pythonw => "venvwlauncher.exe",
794 Self::PythonMajorMinort => "venvlaunchert.exe",
795 Self::PythonwMajorMinort => "venvwlaunchert.exe",
796 Self::PyPy | Self::PyPyMajor | Self::PyPyMajorMinor => "venvlauncher.exe",
799 Self::PyPyw | Self::PyPyMajorMinorw => "venvwlauncher.exe",
800 Self::GraalPy => "venvlauncher.exe",
801 }
802 }
803}
804
805fn copy_launcher_windows(
811 executable: WindowsExecutable,
812 interpreter: &Interpreter,
813 base_python: &Path,
814 scripts: &Path,
815 python_home: &Path,
816) -> Result<(), Error> {
817 let shim = interpreter
819 .stdlib()
820 .join("venv")
821 .join("scripts")
822 .join("nt")
823 .join(executable.exe(interpreter));
824 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
825 Ok(_) => return Ok(()),
826 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
827 Err(err) => {
828 return Err(err.into());
829 }
830 }
831
832 let shim = interpreter
836 .stdlib()
837 .join("venv")
838 .join("scripts")
839 .join("nt")
840 .join(executable.launcher(interpreter));
841 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
842 Ok(_) => return Ok(()),
843 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
844 Err(err) => {
845 return Err(err.into());
846 }
847 }
848
849 let shim = base_python.with_file_name(executable.launcher(interpreter));
852 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
853 Ok(_) => return Ok(()),
854 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
855 Err(err) => {
856 return Err(err.into());
857 }
858 }
859
860 match fs_err::copy(
864 base_python.with_file_name(executable.exe(interpreter)),
865 scripts.join(executable.exe(interpreter)),
866 ) {
867 Ok(_) => {
868 for directory in [
871 python_home,
872 interpreter.sys_base_prefix().join("DLLs").as_path(),
873 ] {
874 let entries = match fs_err::read_dir(directory) {
875 Ok(read_dir) => read_dir,
876 Err(err) if err.kind() == io::ErrorKind::NotFound => {
877 continue;
878 }
879 Err(err) => {
880 return Err(err.into());
881 }
882 };
883 for entry in entries {
884 let entry = entry?;
885 let path = entry.path();
886 if path.extension().is_some_and(|ext| {
887 ext.eq_ignore_ascii_case("dll") || ext.eq_ignore_ascii_case("pyd")
888 }) {
889 if let Some(file_name) = path.file_name() {
890 fs_err::copy(&path, scripts.join(file_name))?;
891 }
892 }
893 }
894 }
895
896 match fs_err::read_dir(python_home) {
898 Ok(entries) => {
899 for entry in entries {
900 let entry = entry?;
901 let path = entry.path();
902 if path
903 .extension()
904 .is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
905 {
906 if let Some(file_name) = path.file_name() {
907 fs_err::copy(&path, scripts.join(file_name))?;
908 }
909 }
910 }
911 }
912 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
913 Err(err) => {
914 return Err(err.into());
915 }
916 }
917
918 return Ok(());
919 }
920 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
921 Err(err) => {
922 return Err(err.into());
923 }
924 }
925
926 Err(Error::NotFound(base_python.user_display().to_string()))
927}