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 (true, "activate.nu") => r"(path self | path dirname | path dirname)".to_string(),
467 (false, "activate.nu") => {
468 format!(
469 "'{}'",
470 escape_posix_for_single_quotes(location.simplified().to_str().unwrap())
471 )
472 }
473 _ => escape_posix_for_single_quotes(location.simplified().to_str().unwrap()),
477 };
478
479 let activator = template
480 .replace("{{ VIRTUAL_ENV_DIR }}", &virtual_env_dir)
481 .replace("{{ BIN_NAME }}", bin_name)
482 .replace(
483 "{{ VIRTUAL_PROMPT }}",
484 prompt.as_deref().unwrap_or_default(),
485 )
486 .replace("{{ PATH_SEP }}", path_sep)
487 .replace("{{ RELATIVE_SITE_PACKAGES }}", &relative_site_packages);
488 fs_err::write(scripts.join(name), activator)?;
489 }
490
491 let mut pyvenv_cfg_data: Vec<(String, String)> = vec![
492 (
493 "home".to_string(),
494 python_home.simplified_display().to_string(),
495 ),
496 (
497 "implementation".to_string(),
498 interpreter
499 .markers()
500 .platform_python_implementation()
501 .to_string(),
502 ),
503 ("uv".to_string(), version().to_string()),
504 (
505 "version_info".to_string(),
506 interpreter.markers().python_full_version().string.clone(),
507 ),
508 (
509 "include-system-site-packages".to_string(),
510 if system_site_packages {
511 "true".to_string()
512 } else {
513 "false".to_string()
514 },
515 ),
516 ];
517
518 if relocatable {
519 pyvenv_cfg_data.push(("relocatable".to_string(), "true".to_string()));
520 }
521
522 if seed {
523 pyvenv_cfg_data.push(("seed".to_string(), "true".to_string()));
524 }
525
526 if let Some(prompt) = prompt {
527 pyvenv_cfg_data.push(("prompt".to_string(), prompt));
528 }
529
530 if cfg!(windows) && interpreter.markers().implementation_name() == "graalpy" {
531 pyvenv_cfg_data.push((
532 "venvlauncher_command".to_string(),
533 python_home
534 .join("graalpy.exe")
535 .simplified_display()
536 .to_string(),
537 ));
538 }
539
540 let mut pyvenv_cfg = BufWriter::new(File::create(location.join("pyvenv.cfg"))?);
541 write_cfg(&mut pyvenv_cfg, &pyvenv_cfg_data)?;
542 drop(pyvenv_cfg);
543
544 let site_packages = location.join(&interpreter.virtualenv().purelib);
546 fs_err::create_dir_all(&site_packages)?;
547
548 #[cfg(unix)]
551 if interpreter.pointer_size().is_64()
552 && interpreter.markers().os_name() == "posix"
553 && interpreter.markers().sys_platform() != "darwin"
554 {
555 match fs_err::os::unix::fs::symlink("lib", location.join("lib64")) {
556 Ok(()) => {}
557 Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {}
558 Err(err) => {
559 return Err(err.into());
560 }
561 }
562 }
563
564 fs_err::write(site_packages.join("_virtualenv.py"), VIRTUALENV_PATCH)?;
566 fs_err::write(site_packages.join("_virtualenv.pth"), "import _virtualenv")?;
567
568 Ok(VirtualEnvironment {
569 scheme: Scheme {
570 purelib: location.join(&interpreter.virtualenv().purelib),
571 platlib: location.join(&interpreter.virtualenv().platlib),
572 scripts: location.join(&interpreter.virtualenv().scripts),
573 data: location.join(&interpreter.virtualenv().data),
574 include: location.join(&interpreter.virtualenv().include),
575 },
576 root: location,
577 executable,
578 base_executable: base_python,
579 })
580}
581
582fn confirm_clear(location: &Path, name: &'static str) -> Result<Option<bool>, io::Error> {
586 let term = Term::stderr();
587 if term.is_term() {
588 let prompt = format!(
589 "A {name} already exists at `{}`. Do you want to replace it?",
590 location.user_display(),
591 );
592 let hint = format!(
593 "Use the `{}` flag or set `{}` to skip this prompt",
594 "--clear".green(),
595 "UV_VENV_CLEAR=1".green()
596 );
597 Ok(Some(uv_console::confirm_with_hint(
598 &prompt, &hint, &term, true,
599 )?))
600 } else {
601 Ok(None)
602 }
603}
604
605pub fn remove_virtualenv(location: &Path) -> Result<(), Error> {
607 #[cfg(windows)]
610 if let Ok(itself) = std::env::current_exe() {
611 let target = std::path::absolute(location)?;
612 if itself.starts_with(&target) {
613 debug!("Detected self-delete of executable: {}", itself.display());
614 self_replace::self_delete_outside_path(location)?;
615 }
616 }
617
618 for entry in fs_err::read_dir(location)? {
621 let entry = entry?;
622 let path = entry.path();
623 if path == location.join("pyvenv.cfg") {
624 continue;
625 }
626 if path.is_dir() {
627 fs_err::remove_dir_all(&path)?;
628 } else {
629 fs_err::remove_file(&path)?;
630 }
631 }
632
633 match fs_err::remove_file(location.join("pyvenv.cfg")) {
634 Ok(()) => {}
635 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
636 Err(err) => return Err(err.into()),
637 }
638
639 match fs_err::remove_dir_all(location) {
641 Ok(()) => {}
642 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
643 Err(err) if err.kind() == io::ErrorKind::ResourceBusy => {
646 debug!(
647 "Skipping removal of `{}` directory due to {err}",
648 location.display(),
649 );
650 }
651 Err(err) => return Err(err.into()),
652 }
653
654 Ok(())
655}
656
657#[derive(Debug, Copy, Clone, Eq, PartialEq)]
658pub enum RemovalReason {
659 UserRequest,
661 TemporaryEnvironment,
664 ManagedEnvironment,
667}
668
669impl std::fmt::Display for RemovalReason {
670 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
671 match self {
672 Self::UserRequest => f.write_str("requested with `--clear`"),
673 Self::ManagedEnvironment => f.write_str("environment is managed by uv"),
674 Self::TemporaryEnvironment => f.write_str("environment is temporary"),
675 }
676 }
677}
678
679#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
680pub enum OnExisting {
681 #[default]
685 Prompt,
686 Fail,
688 Allow,
691 Remove(RemovalReason),
693}
694
695impl OnExisting {
696 pub fn from_args(allow_existing: bool, clear: bool, no_clear: bool) -> Self {
697 if allow_existing {
698 Self::Allow
699 } else if clear {
700 Self::Remove(RemovalReason::UserRequest)
701 } else if no_clear {
702 Self::Fail
703 } else {
704 Self::Prompt
705 }
706 }
707}
708
709#[derive(Debug, Copy, Clone)]
710enum WindowsExecutable {
711 Python,
713 PythonMajor,
715 PythonMajorMinor,
717 PythonMajorMinort,
719 Pythonw,
721 PythonwMajorMinort,
723 PyPy,
725 PyPyMajor,
727 PyPyMajorMinor,
729 PyPyw,
731 PyPyMajorMinorw,
733 GraalPy,
735}
736
737impl WindowsExecutable {
738 fn exe(self, interpreter: &Interpreter) -> String {
740 match self {
741 Self::Python => String::from("python.exe"),
742 Self::PythonMajor => {
743 format!("python{}.exe", interpreter.python_major())
744 }
745 Self::PythonMajorMinor => {
746 format!(
747 "python{}.{}.exe",
748 interpreter.python_major(),
749 interpreter.python_minor()
750 )
751 }
752 Self::PythonMajorMinort => {
753 format!(
754 "python{}.{}t.exe",
755 interpreter.python_major(),
756 interpreter.python_minor()
757 )
758 }
759 Self::Pythonw => String::from("pythonw.exe"),
760 Self::PythonwMajorMinort => {
761 format!(
762 "pythonw{}.{}t.exe",
763 interpreter.python_major(),
764 interpreter.python_minor()
765 )
766 }
767 Self::PyPy => String::from("pypy.exe"),
768 Self::PyPyMajor => {
769 format!("pypy{}.exe", interpreter.python_major())
770 }
771 Self::PyPyMajorMinor => {
772 format!(
773 "pypy{}.{}.exe",
774 interpreter.python_major(),
775 interpreter.python_minor()
776 )
777 }
778 Self::PyPyw => String::from("pypyw.exe"),
779 Self::PyPyMajorMinorw => {
780 format!(
781 "pypy{}.{}w.exe",
782 interpreter.python_major(),
783 interpreter.python_minor()
784 )
785 }
786 Self::GraalPy => String::from("graalpy.exe"),
787 }
788 }
789
790 fn launcher(self, interpreter: &Interpreter) -> &'static str {
792 match self {
793 Self::Python | Self::PythonMajor | Self::PythonMajorMinor
794 if interpreter.gil_disabled() =>
795 {
796 "venvlaunchert.exe"
797 }
798 Self::Python | Self::PythonMajor | Self::PythonMajorMinor => "venvlauncher.exe",
799 Self::Pythonw if interpreter.gil_disabled() => "venvwlaunchert.exe",
800 Self::Pythonw => "venvwlauncher.exe",
801 Self::PythonMajorMinort => "venvlaunchert.exe",
802 Self::PythonwMajorMinort => "venvwlaunchert.exe",
803 Self::PyPy | Self::PyPyMajor | Self::PyPyMajorMinor => "venvlauncher.exe",
806 Self::PyPyw | Self::PyPyMajorMinorw => "venvwlauncher.exe",
807 Self::GraalPy => "venvlauncher.exe",
808 }
809 }
810}
811
812fn copy_launcher_windows(
818 executable: WindowsExecutable,
819 interpreter: &Interpreter,
820 base_python: &Path,
821 scripts: &Path,
822 python_home: &Path,
823) -> Result<(), Error> {
824 let shim = interpreter
826 .stdlib()
827 .join("venv")
828 .join("scripts")
829 .join("nt")
830 .join(executable.exe(interpreter));
831 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
832 Ok(_) => return Ok(()),
833 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
834 Err(err) => {
835 return Err(err.into());
836 }
837 }
838
839 let shim = interpreter
843 .stdlib()
844 .join("venv")
845 .join("scripts")
846 .join("nt")
847 .join(executable.launcher(interpreter));
848 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
849 Ok(_) => return Ok(()),
850 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
851 Err(err) => {
852 return Err(err.into());
853 }
854 }
855
856 let shim = base_python.with_file_name(executable.launcher(interpreter));
859 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
860 Ok(_) => return Ok(()),
861 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
862 Err(err) => {
863 return Err(err.into());
864 }
865 }
866
867 match fs_err::copy(
871 base_python.with_file_name(executable.exe(interpreter)),
872 scripts.join(executable.exe(interpreter)),
873 ) {
874 Ok(_) => {
875 for directory in [
878 python_home,
879 interpreter.sys_base_prefix().join("DLLs").as_path(),
880 ] {
881 let entries = match fs_err::read_dir(directory) {
882 Ok(read_dir) => read_dir,
883 Err(err) if err.kind() == io::ErrorKind::NotFound => {
884 continue;
885 }
886 Err(err) => {
887 return Err(err.into());
888 }
889 };
890 for entry in entries {
891 let entry = entry?;
892 let path = entry.path();
893 if path.extension().is_some_and(|ext| {
894 ext.eq_ignore_ascii_case("dll") || ext.eq_ignore_ascii_case("pyd")
895 }) {
896 if let Some(file_name) = path.file_name() {
897 fs_err::copy(&path, scripts.join(file_name))?;
898 }
899 }
900 }
901 }
902
903 match fs_err::read_dir(python_home) {
905 Ok(entries) => {
906 for entry in entries {
907 let entry = entry?;
908 let path = entry.path();
909 if path
910 .extension()
911 .is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
912 {
913 if let Some(file_name) = path.file_name() {
914 fs_err::copy(&path, scripts.join(file_name))?;
915 }
916 }
917 }
918 }
919 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
920 Err(err) => {
921 return Err(err.into());
922 }
923 }
924
925 return Ok(());
926 }
927 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
928 Err(err) => {
929 return Err(err.into());
930 }
931 }
932
933 Err(Error::NotFound(base_python.user_display().to_string()))
934}