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