1use std::borrow::Cow;
4use std::env::consts::EXE_SUFFIX;
5use std::ffi::{OsStr, OsString};
6use std::io;
7use std::io::{BufWriter, Write};
8use std::path::Path;
9
10use console::Term;
11use fs_err::File;
12use itertools::Itertools;
13use owo_colors::OwoColorize;
14
15use tracing::{debug, trace};
16
17use crate::{Error, Prompt};
18use uv_fs::{CWD, Simplified, cachedir};
19use uv_platform_tags::Os;
20use uv_pypi_types::Scheme;
21use uv_python::managed::{
22 ManagedPythonInstallation, PythonMinorVersionLink, replace_link_to_executable,
23};
24use uv_python::{Interpreter, VirtualEnvironment};
25use uv_shell::escape_posix_for_single_quotes;
26use uv_version::version;
27use uv_warnings::warn_user_once;
28
29const ACTIVATE_TEMPLATES: &[(&str, &str)] = &[
31 ("activate", include_str!("activator/activate")),
32 ("activate.csh", include_str!("activator/activate.csh")),
33 ("activate.fish", include_str!("activator/activate.fish")),
34 ("activate.nu", include_str!("activator/activate.nu")),
35 ("activate.ps1", include_str!("activator/activate.ps1")),
36 ("activate.bat", include_str!("activator/activate.bat")),
37 ("deactivate.bat", include_str!("activator/deactivate.bat")),
38 ("pydoc.bat", include_str!("activator/pydoc.bat")),
39 (
40 "activate_this.py",
41 include_str!("activator/activate_this.py"),
42 ),
43];
44const VIRTUALENV_PATCH: &str = include_str!("_virtualenv.py");
45
46fn write_cfg(f: &mut impl Write, data: &[(String, String)]) -> io::Result<()> {
48 for (key, value) in data {
49 writeln!(f, "{key} = {value}")?;
50 }
51 Ok(())
52}
53
54#[expect(clippy::fn_params_excessive_bools)]
56pub(crate) fn create(
57 location: &Path,
58 interpreter: &Interpreter,
59 prompt: Prompt,
60 system_site_packages: bool,
61 on_existing: OnExisting,
62 relocatable: bool,
63 seed: bool,
64 upgradeable: bool,
65) -> Result<VirtualEnvironment, Error> {
66 let base_python = if cfg!(unix) && interpreter.is_standalone() {
72 interpreter.find_base_python()?
73 } else {
74 interpreter.to_base_python()?
75 };
76
77 debug!(
78 "Using base executable for virtual environment: {}",
79 base_python.display()
80 );
81
82 let prompt = match prompt {
86 Prompt::CurrentDirectoryName => CWD
87 .file_name()
88 .map(|name| name.to_string_lossy().to_string()),
89 Prompt::Static(value) => Some(value),
90 Prompt::None => None,
91 };
92 let absolute = std::path::absolute(location)?;
93
94 match location.metadata() {
96 Ok(metadata) if metadata.is_file() => {
97 return Err(Error::Io(io::Error::new(
98 io::ErrorKind::AlreadyExists,
99 format!("File exists at `{}`", location.user_display()),
100 )));
101 }
102 Ok(metadata)
103 if metadata.is_dir()
104 && location
105 .read_dir()
106 .is_ok_and(|mut dir| dir.next().is_none()) =>
107 {
108 trace!(
110 "Using empty directory at `{}` for virtual environment",
111 location.user_display()
112 );
113 }
114 Ok(metadata) if metadata.is_dir() => {
115 let is_virtualenv = uv_fs::is_virtualenv_base(location);
116 let name = if is_virtualenv {
117 "virtual environment"
118 } else {
119 "directory"
120 };
121 let err = Err(Error::Exists {
124 name,
125 path: location.to_path_buf(),
126 });
127 match on_existing {
128 OnExisting::Allow => {
129 debug!("Allowing existing {name} due to `--allow-existing`");
130 }
131 OnExisting::Remove(reason) => {
132 if !is_virtualenv
133 && let RemovalReason::UserRequest(clear_non_virtualenv) = reason
134 {
135 match clear_non_virtualenv {
136 ClearNonVirtualenv::Allow => {}
137 ClearNonVirtualenv::Warn => {
138 warn_user_once!(
139 "The `--clear` option will remove the existing directory at `{}` \
140 even though it is not a virtual environment. \
141 This will become an error in a future release. \
142 Use `--force` to suppress this warning, or \
143 `--preview-features venv-safe-clear` to error on this now.",
144 location.user_display()
145 );
146 }
147 ClearNonVirtualenv::Error => {
148 return Err(Error::ClearNonVirtualenv {
149 path: location.to_path_buf(),
150 });
151 }
152 }
153 }
154 debug!("Removing existing {name} ({reason})");
155 uv_fs::clear_virtualenv(location)?;
156 }
157 OnExisting::Fail => return err,
158 OnExisting::Prompt if !is_virtualenv => return err,
160 OnExisting::Prompt => {
161 match confirm_clear(location, name)? {
162 Some(true) => {
163 debug!("Removing existing {name} due to confirmation");
164 uv_fs::clear_virtualenv(location)?;
165 }
166 Some(false) => return err,
167 None => {
169 return Err(Error::Exists {
170 name,
171 path: location.to_path_buf(),
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 {
211 if let Some(minor_version_link) =
212 ManagedPythonInstallation::try_from_interpreter(interpreter)
213 .and_then(|installation| PythonMinorVersionLink::from_installation(&installation))
214 {
215 if !minor_version_link.exists() {
216 base_python.clone()
217 } else {
218 let debug_symlink_term = if cfg!(windows) {
219 "junction"
220 } else {
221 "symlink directory"
222 };
223 debug!(
224 "Using {} {} instead of base Python path: {}",
225 debug_symlink_term,
226 &minor_version_link.symlink_directory.display(),
227 &base_python.display()
228 );
229 using_minor_version_link = true;
230 minor_version_link.symlink_executable.clone()
231 }
232 } else {
233 base_python.clone()
234 }
235 } else {
236 base_python.clone()
237 };
238
239 let python_home = executable_target
244 .parent()
245 .ok_or_else(|| {
246 io::Error::new(
247 io::ErrorKind::NotFound,
248 "The Python interpreter needs to have a parent directory",
249 )
250 })?
251 .to_path_buf();
252 let python_home = python_home.as_path();
253
254 fs_err::create_dir_all(&scripts)?;
256 let executable = scripts.join(format!("python{EXE_SUFFIX}"));
257
258 #[cfg(unix)]
259 {
260 uv_fs::replace_symlink(&executable_target, &executable)?;
261 uv_fs::replace_symlink(
262 "python",
263 scripts.join(format!("python{}", interpreter.python_major())),
264 )?;
265 uv_fs::replace_symlink(
266 "python",
267 scripts.join(format!(
268 "python{}.{}",
269 interpreter.python_major(),
270 interpreter.python_minor(),
271 )),
272 )?;
273 if interpreter.gil_disabled() {
274 uv_fs::replace_symlink(
275 "python",
276 scripts.join(format!(
277 "python{}.{}t",
278 interpreter.python_major(),
279 interpreter.python_minor(),
280 )),
281 )?;
282 }
283
284 if interpreter.markers().implementation_name() == "pypy" {
285 uv_fs::replace_symlink(
286 "python",
287 scripts.join(format!("pypy{}", interpreter.python_major())),
288 )?;
289 uv_fs::replace_symlink("python", scripts.join("pypy"))?;
290 }
291
292 if interpreter.markers().implementation_name() == "graalpy" {
293 uv_fs::replace_symlink("python", scripts.join("graalpy"))?;
294 }
295 }
296
297 if cfg!(windows) {
301 if using_minor_version_link {
302 let target = scripts.join(WindowsExecutable::Python.exe(interpreter));
303 replace_link_to_executable(target.as_path(), &executable_target)
304 .map_err(Error::Python)?;
305 let targetw = scripts.join(WindowsExecutable::Pythonw.exe(interpreter));
306 replace_link_to_executable(targetw.as_path(), &executable_target)
307 .map_err(Error::Python)?;
308 if interpreter.gil_disabled() {
309 let targett = scripts.join(WindowsExecutable::PythonMajorMinort.exe(interpreter));
310 replace_link_to_executable(targett.as_path(), &executable_target)
311 .map_err(Error::Python)?;
312 let targetwt = scripts.join(WindowsExecutable::PythonwMajorMinort.exe(interpreter));
313 replace_link_to_executable(targetwt.as_path(), &executable_target)
314 .map_err(Error::Python)?;
315 }
316 } else if matches!(
317 interpreter.platform().os(),
318 Os::Pyodide { .. } | Os::PyEmscripten { .. }
319 ) {
320 let target = scripts.join(WindowsExecutable::Python.exe(interpreter));
323 replace_link_to_executable(target.as_path(), &executable_target)
324 .map_err(Error::Python)?;
325 } else {
326 copy_launcher_windows(
328 WindowsExecutable::Python,
329 interpreter,
330 &base_python,
331 &scripts,
332 python_home,
333 )?;
334
335 match interpreter.implementation_name() {
336 "graalpy" => {
337 copy_launcher_windows(
339 WindowsExecutable::GraalPy,
340 interpreter,
341 &base_python,
342 &scripts,
343 python_home,
344 )?;
345 copy_launcher_windows(
346 WindowsExecutable::PythonMajor,
347 interpreter,
348 &base_python,
349 &scripts,
350 python_home,
351 )?;
352 }
353 "pypy" => {
354 copy_launcher_windows(
356 WindowsExecutable::PythonMajor,
357 interpreter,
358 &base_python,
359 &scripts,
360 python_home,
361 )?;
362 copy_launcher_windows(
363 WindowsExecutable::PythonMajorMinor,
364 interpreter,
365 &base_python,
366 &scripts,
367 python_home,
368 )?;
369 copy_launcher_windows(
370 WindowsExecutable::Pythonw,
371 interpreter,
372 &base_python,
373 &scripts,
374 python_home,
375 )?;
376 copy_launcher_windows(
377 WindowsExecutable::PyPy,
378 interpreter,
379 &base_python,
380 &scripts,
381 python_home,
382 )?;
383 copy_launcher_windows(
384 WindowsExecutable::PyPyMajor,
385 interpreter,
386 &base_python,
387 &scripts,
388 python_home,
389 )?;
390 copy_launcher_windows(
391 WindowsExecutable::PyPyMajorMinor,
392 interpreter,
393 &base_python,
394 &scripts,
395 python_home,
396 )?;
397 copy_launcher_windows(
398 WindowsExecutable::PyPyw,
399 interpreter,
400 &base_python,
401 &scripts,
402 python_home,
403 )?;
404 copy_launcher_windows(
405 WindowsExecutable::PyPyMajorMinorw,
406 interpreter,
407 &base_python,
408 &scripts,
409 python_home,
410 )?;
411 }
412 _ => {
413 copy_launcher_windows(
415 WindowsExecutable::Pythonw,
416 interpreter,
417 &base_python,
418 &scripts,
419 python_home,
420 )?;
421
422 if interpreter.gil_disabled() {
424 copy_launcher_windows(
425 WindowsExecutable::PythonMajorMinort,
426 interpreter,
427 &base_python,
428 &scripts,
429 python_home,
430 )?;
431 copy_launcher_windows(
432 WindowsExecutable::PythonwMajorMinort,
433 interpreter,
434 &base_python,
435 &scripts,
436 python_home,
437 )?;
438 }
439 }
440 }
441 }
442 }
443
444 #[cfg(not(any(unix, windows)))]
445 {
446 compile_error!("Only Windows and Unix are supported")
447 }
448
449 for (name, template) in ACTIVATE_TEMPLATES {
451 if relocatable && *name == "activate.csh" {
455 continue;
456 }
457
458 let path_sep = if cfg!(windows) { ";" } else { ":" };
459
460 let relative_site_packages = [
461 interpreter.virtualenv().purelib.as_path(),
462 interpreter.virtualenv().platlib.as_path(),
463 ]
464 .iter()
465 .dedup()
466 .map(|path| {
467 pathdiff::diff_paths(path, &interpreter.virtualenv().scripts)
468 .expect("Failed to calculate relative path to site-packages")
469 })
470 .map(|path| path.simplified().to_str().unwrap().replace('\\', "\\\\"))
471 .join(path_sep);
472
473 let virtual_env_dir = match (relocatable, name.to_owned()) {
474 (true, "activate") => Cow::Borrowed(
475 r#"'"$(dirname -- "$(dirname -- "$(realpath -- "$SCRIPT_PATH")")")"'"#,
476 ),
477 (true, "activate.bat") => Cow::Borrowed(r"%~dp0.."),
478 (true, "activate.fish") => {
479 Cow::Borrowed(r"'(dirname -- (dirname -- (realpath -- (status -f))))'")
480 }
481 (true, "activate.nu") => Cow::Borrowed(r"(path self | path dirname | path dirname)"),
482 (false, "activate.nu") => Cow::Owned(format!(
483 "'{}'",
484 escape_posix_for_single_quotes(location.simplified().to_str().unwrap())
485 )),
486 _ => escape_posix_for_single_quotes(location.simplified().to_str().unwrap()),
488 };
489
490 let activator = template
491 .replace("{{ VIRTUAL_ENV_DIR }}", &virtual_env_dir)
492 .replace("{{ BIN_NAME }}", bin_name)
493 .replace(
494 "{{ VIRTUAL_PROMPT }}",
495 prompt.as_deref().unwrap_or_default(),
496 )
497 .replace("{{ PATH_SEP }}", path_sep)
498 .replace("{{ RELATIVE_SITE_PACKAGES }}", &relative_site_packages);
499 fs_err::write(scripts.join(name), activator)?;
500 }
501
502 let mut pyvenv_cfg_data: Vec<(String, String)> = vec![
503 (
504 "home".to_string(),
505 python_home.simplified_display().to_string(),
506 ),
507 (
508 "implementation".to_string(),
509 interpreter
510 .markers()
511 .platform_python_implementation()
512 .to_string(),
513 ),
514 ("uv".to_string(), version().to_string()),
515 (
516 "version_info".to_string(),
517 if using_minor_version_link {
518 interpreter.python_minor_version().to_string()
519 } else {
520 interpreter.markers().python_full_version().string.clone()
521 },
522 ),
523 (
524 "include-system-site-packages".to_string(),
525 if system_site_packages {
526 "true".to_string()
527 } else {
528 "false".to_string()
529 },
530 ),
531 ];
532
533 if relocatable {
534 pyvenv_cfg_data.push(("relocatable".to_string(), "true".to_string()));
535 }
536
537 if seed {
538 pyvenv_cfg_data.push(("seed".to_string(), "true".to_string()));
539 }
540
541 if let Some(prompt) = prompt {
542 pyvenv_cfg_data.push(("prompt".to_string(), prompt));
543 }
544
545 if cfg!(windows) && interpreter.markers().implementation_name() == "graalpy" {
546 pyvenv_cfg_data.push((
547 "venvlauncher_command".to_string(),
548 python_home
549 .join("graalpy.exe")
550 .simplified_display()
551 .to_string(),
552 ));
553 }
554
555 let mut pyvenv_cfg = BufWriter::new(File::create(location.join("pyvenv.cfg"))?);
556 write_cfg(&mut pyvenv_cfg, &pyvenv_cfg_data)?;
557 drop(pyvenv_cfg);
558
559 let site_packages = location.join(&interpreter.virtualenv().purelib);
561 fs_err::create_dir_all(&site_packages)?;
562
563 #[cfg(unix)]
566 if interpreter.pointer_size().is_64()
567 && interpreter.markers().os_name() == "posix"
568 && interpreter.markers().sys_platform() != "darwin"
569 {
570 match fs_err::os::unix::fs::symlink("lib", location.join("lib64")) {
571 Ok(()) => {}
572 Err(err) if err.kind() == io::ErrorKind::AlreadyExists => {}
573 Err(err) => {
574 return Err(err.into());
575 }
576 }
577 }
578
579 fs_err::write(site_packages.join("_virtualenv.py"), VIRTUALENV_PATCH)?;
581 fs_err::write(site_packages.join("_virtualenv.pth"), "import _virtualenv")?;
582
583 Ok(VirtualEnvironment {
584 scheme: Scheme {
585 purelib: location.join(&interpreter.virtualenv().purelib),
586 platlib: location.join(&interpreter.virtualenv().platlib),
587 scripts: location.join(&interpreter.virtualenv().scripts),
588 data: location.join(&interpreter.virtualenv().data),
589 include: location.join(&interpreter.virtualenv().include),
590 },
591 root: location,
592 executable,
593 base_executable: base_python,
594 })
595}
596
597fn confirm_clear(location: &Path, name: &'static str) -> Result<Option<bool>, io::Error> {
601 let term = Term::stderr();
602 if term.is_term() {
603 let prompt = format!(
604 "A {name} already exists at `{}`. Do you want to replace it?",
605 location.user_display(),
606 );
607 let hint = format!(
608 "Use the `{}` flag or set `{}` to skip this prompt",
609 "--clear".green(),
610 "UV_VENV_CLEAR=1".green()
611 );
612 Ok(Some(uv_console::confirm_with_hint(
613 &prompt, &hint, &term, true,
614 )?))
615 } else {
616 Ok(None)
617 }
618}
619
620#[derive(Debug, Copy, Clone, Eq, PartialEq)]
621pub enum ClearNonVirtualenv {
622 Allow,
624 Warn,
626 Error,
628}
629
630#[derive(Debug, Copy, Clone, Eq, PartialEq)]
631pub enum RemovalReason {
632 UserRequest(ClearNonVirtualenv),
634 TemporaryEnvironment,
637 ManagedEnvironment,
640}
641
642impl std::fmt::Display for RemovalReason {
643 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
644 match self {
645 Self::UserRequest(_) => f.write_str("requested with `--clear`"),
646 Self::ManagedEnvironment => f.write_str("environment is managed by uv"),
647 Self::TemporaryEnvironment => f.write_str("environment is temporary"),
648 }
649 }
650}
651
652#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
653pub enum OnExisting {
654 #[default]
658 Prompt,
659 Fail,
661 Allow,
664 Remove(RemovalReason),
666}
667
668impl OnExisting {
669 pub fn from_args(
670 allow_existing: bool,
671 clear: bool,
672 no_clear: bool,
673 clear_non_virtualenv: ClearNonVirtualenv,
674 ) -> Self {
675 if allow_existing {
676 Self::Allow
677 } else if clear {
678 Self::Remove(RemovalReason::UserRequest(clear_non_virtualenv))
679 } else if no_clear {
680 Self::Fail
681 } else {
682 Self::Prompt
683 }
684 }
685}
686
687#[derive(Debug, Copy, Clone)]
688enum WindowsExecutable {
689 Python,
691 PythonMajor,
693 PythonMajorMinor,
695 PythonMajorMinort,
697 Pythonw,
699 PythonwMajorMinort,
701 PyPy,
703 PyPyMajor,
705 PyPyMajorMinor,
707 PyPyw,
709 PyPyMajorMinorw,
711 GraalPy,
713}
714
715impl WindowsExecutable {
716 fn exe(self, interpreter: &Interpreter) -> Cow<'static, OsStr> {
718 match self {
719 Self::Python => Cow::Borrowed(OsStr::new("python.exe")),
720 Self::PythonMajor => Cow::Owned(OsString::from(format!(
721 "python{}.exe",
722 interpreter.python_major()
723 ))),
724 Self::PythonMajorMinor => Cow::Owned(OsString::from(format!(
725 "python{}.{}.exe",
726 interpreter.python_major(),
727 interpreter.python_minor()
728 ))),
729 Self::PythonMajorMinort => Cow::Owned(OsString::from(format!(
730 "python{}.{}t.exe",
731 interpreter.python_major(),
732 interpreter.python_minor()
733 ))),
734 Self::Pythonw => Cow::Borrowed(OsStr::new("pythonw.exe")),
735 Self::PythonwMajorMinort => Cow::Owned(OsString::from(format!(
736 "pythonw{}.{}t.exe",
737 interpreter.python_major(),
738 interpreter.python_minor()
739 ))),
740 Self::PyPy => Cow::Borrowed(OsStr::new("pypy.exe")),
741 Self::PyPyMajor => Cow::Owned(OsString::from(format!(
742 "pypy{}.exe",
743 interpreter.python_major()
744 ))),
745 Self::PyPyMajorMinor => Cow::Owned(OsString::from(format!(
746 "pypy{}.{}.exe",
747 interpreter.python_major(),
748 interpreter.python_minor()
749 ))),
750 Self::PyPyw => Cow::Borrowed(OsStr::new("pypyw.exe")),
751 Self::PyPyMajorMinorw => Cow::Owned(OsString::from(format!(
752 "pypy{}.{}w.exe",
753 interpreter.python_major(),
754 interpreter.python_minor()
755 ))),
756 Self::GraalPy => Cow::Borrowed(OsStr::new("graalpy.exe")),
757 }
758 }
759
760 fn launcher(self, interpreter: &Interpreter) -> &'static str {
762 match self {
763 Self::Python | Self::PythonMajor | Self::PythonMajorMinor
764 if interpreter.gil_disabled() =>
765 {
766 "venvlaunchert.exe"
767 }
768 Self::Python | Self::PythonMajor | Self::PythonMajorMinor => "venvlauncher.exe",
769 Self::Pythonw if interpreter.gil_disabled() => "venvwlaunchert.exe",
770 Self::Pythonw => "venvwlauncher.exe",
771 Self::PythonMajorMinort => "venvlaunchert.exe",
772 Self::PythonwMajorMinort => "venvwlaunchert.exe",
773 Self::PyPy | Self::PyPyMajor | Self::PyPyMajorMinor => "venvlauncher.exe",
776 Self::PyPyw | Self::PyPyMajorMinorw => "venvwlauncher.exe",
777 Self::GraalPy => "venvlauncher.exe",
778 }
779 }
780}
781
782fn copy_launcher_windows(
788 executable: WindowsExecutable,
789 interpreter: &Interpreter,
790 base_python: &Path,
791 scripts: &Path,
792 python_home: &Path,
793) -> Result<(), Error> {
794 let shim = interpreter
796 .stdlib()
797 .join("venv")
798 .join("scripts")
799 .join("nt")
800 .join(executable.exe(interpreter));
801 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
802 Ok(_) => return Ok(()),
803 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
804 Err(err) => {
805 return Err(err.into());
806 }
807 }
808
809 let shim = interpreter
813 .stdlib()
814 .join("venv")
815 .join("scripts")
816 .join("nt")
817 .join(executable.launcher(interpreter));
818 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
819 Ok(_) => return Ok(()),
820 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
821 Err(err) => {
822 return Err(err.into());
823 }
824 }
825
826 let shim = base_python.with_file_name(executable.launcher(interpreter));
829 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
830 Ok(_) => return Ok(()),
831 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
832 Err(err) => {
833 return Err(err.into());
834 }
835 }
836
837 match fs_err::copy(
841 base_python.with_file_name(executable.exe(interpreter)),
842 scripts.join(executable.exe(interpreter)),
843 ) {
844 Ok(_) => {
845 for directory in [
848 python_home,
849 interpreter.sys_base_prefix().join("DLLs").as_path(),
850 ] {
851 let entries = match fs_err::read_dir(directory) {
852 Ok(read_dir) => read_dir,
853 Err(err) if err.kind() == io::ErrorKind::NotFound => {
854 continue;
855 }
856 Err(err) => {
857 return Err(err.into());
858 }
859 };
860 for entry in entries {
861 let entry = entry?;
862 let path = entry.path();
863 if path.extension().is_some_and(|ext| {
864 ext.eq_ignore_ascii_case("dll") || ext.eq_ignore_ascii_case("pyd")
865 }) {
866 if let Some(file_name) = path.file_name() {
867 fs_err::copy(&path, scripts.join(file_name))?;
868 }
869 }
870 }
871 }
872
873 match fs_err::read_dir(python_home) {
875 Ok(entries) => {
876 for entry in entries {
877 let entry = entry?;
878 let path = entry.path();
879 if path
880 .extension()
881 .is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
882 {
883 if let Some(file_name) = path.file_name() {
884 fs_err::copy(&path, scripts.join(file_name))?;
885 }
886 }
887 }
888 }
889 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
890 Err(err) => {
891 return Err(err.into());
892 }
893 }
894
895 return Ok(());
896 }
897 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
898 Err(err) => {
899 return Err(err.into());
900 }
901 }
902
903 Err(Error::NotFound(base_python.user_display().to_string()))
904}