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