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