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