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