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