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
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::Warn => {
153 warn_user_once!(
154 "The `--clear` option will remove the existing directory at `{}` \
155 even though it is not a virtual environment. \
156 This will become an error in a future release. \
157 Use `--force` to suppress this warning, or \
158 `--preview-features venv-safe-clear` to error on this now.",
159 location.user_display()
160 );
161 }
162 ClearNonVirtualenv::Error => {
163 return Err(Error::ClearNonVirtualenv {
164 path: location.to_path_buf(),
165 });
166 }
167 }
168 }
169 debug!("Removing existing {name} ({reason})");
170 uv_fs::clear_virtualenv(location)?;
171 }
172 OnExisting::Fail => return err,
173 OnExisting::Prompt if !is_virtualenv => return err,
175 OnExisting::Prompt => {
176 match confirm_clear(location, name)? {
177 Some(true) => {
178 debug!("Removing existing {name} due to confirmation");
179 uv_fs::clear_virtualenv(location)?;
180 }
181 Some(false) => return err,
182 None => {
184 return Err(Error::Exists {
185 name,
186 path: location.to_path_buf(),
187 });
188 }
189 }
190 }
191 }
192 }
193 Ok(_) => {
194 return Err(Error::Io(io::Error::new(
196 io::ErrorKind::AlreadyExists,
197 format!("Object already exists at `{}`", location.user_display()),
198 )));
199 }
200 Err(err) if err.kind() == io::ErrorKind::NotFound => {
201 fs_err::create_dir_all(location)?;
202 }
203 Err(err) => return Err(Error::Io(err)),
204 }
205
206 let location = absolute;
208
209 let bin_name = if cfg!(unix) {
210 "bin"
211 } else if cfg!(windows) {
212 "Scripts"
213 } else {
214 unimplemented!("Only Windows and Unix are supported")
215 };
216 let scripts = location.join(&interpreter.virtualenv().scripts);
217
218 cachedir::ensure_tag(&location)?;
220
221 fs_err::write(location.join(".gitignore"), "*")?;
223
224 let mut using_minor_version_link = false;
225 let executable_target = if upgradeable {
226 if let Some(minor_version_link) =
227 ManagedPythonInstallation::try_from_interpreter(interpreter)
228 .and_then(|installation| PythonMinorVersionLink::from_installation(&installation))
229 {
230 if !minor_version_link.exists() {
231 base_python.clone()
232 } else {
233 let debug_symlink_term = if cfg!(windows) {
234 "junction"
235 } else {
236 "symlink directory"
237 };
238 debug!(
239 "Using {} {} instead of base Python path: {}",
240 debug_symlink_term,
241 &minor_version_link.symlink_directory.display(),
242 &base_python.display()
243 );
244 using_minor_version_link = true;
245 minor_version_link.symlink_executable.clone()
246 }
247 } else {
248 base_python.clone()
249 }
250 } else {
251 base_python.clone()
252 };
253
254 let python_home = executable_target
259 .parent()
260 .ok_or_else(|| {
261 io::Error::new(
262 io::ErrorKind::NotFound,
263 "The Python interpreter needs to have a parent directory",
264 )
265 })?
266 .to_path_buf();
267 let python_home = python_home.as_path();
268
269 fs_err::create_dir_all(&scripts)?;
271 let executable = scripts.join(format!("python{EXE_SUFFIX}"));
272
273 #[cfg(unix)]
274 {
275 uv_fs::replace_symlink(&executable_target, &executable)?;
276 uv_fs::replace_symlink(
277 "python",
278 scripts.join(format!("python{}", interpreter.python_major())),
279 )?;
280 uv_fs::replace_symlink(
281 "python",
282 scripts.join(format!(
283 "python{}.{}",
284 interpreter.python_major(),
285 interpreter.python_minor(),
286 )),
287 )?;
288 if interpreter.gil_disabled() {
289 uv_fs::replace_symlink(
290 "python",
291 scripts.join(format!(
292 "python{}.{}t",
293 interpreter.python_major(),
294 interpreter.python_minor(),
295 )),
296 )?;
297 }
298
299 if interpreter.markers().implementation_name() == "pypy" {
300 uv_fs::replace_symlink(
301 "python",
302 scripts.join(format!("pypy{}", interpreter.python_major())),
303 )?;
304 uv_fs::replace_symlink("python", scripts.join("pypy"))?;
305 }
306
307 if interpreter.markers().implementation_name() == "graalpy" {
308 uv_fs::replace_symlink("python", scripts.join("graalpy"))?;
309 }
310 }
311
312 if cfg!(windows) {
316 if using_minor_version_link {
317 let target = scripts.join(WindowsExecutable::Python.exe(interpreter));
318 replace_link_to_executable(target.as_path(), &executable_target)
319 .map_err(Error::Python)?;
320 let targetw = scripts.join(WindowsExecutable::Pythonw.exe(interpreter));
321 replace_link_to_executable(targetw.as_path(), &executable_target)
322 .map_err(Error::Python)?;
323 if interpreter.gil_disabled() {
324 let targett = scripts.join(WindowsExecutable::PythonMajorMinort.exe(interpreter));
325 replace_link_to_executable(targett.as_path(), &executable_target)
326 .map_err(Error::Python)?;
327 let targetwt = scripts.join(WindowsExecutable::PythonwMajorMinort.exe(interpreter));
328 replace_link_to_executable(targetwt.as_path(), &executable_target)
329 .map_err(Error::Python)?;
330 }
331 } else if matches!(
332 interpreter.platform().os(),
333 Os::Pyodide { .. } | Os::PyEmscripten { .. }
334 ) {
335 let target = scripts.join(WindowsExecutable::Python.exe(interpreter));
338 replace_link_to_executable(target.as_path(), &executable_target)
339 .map_err(Error::Python)?;
340 } else {
341 copy_launcher_windows(
343 WindowsExecutable::Python,
344 interpreter,
345 &base_python,
346 &scripts,
347 python_home,
348 )?;
349
350 match interpreter.implementation_name() {
351 "graalpy" => {
352 copy_launcher_windows(
354 WindowsExecutable::GraalPy,
355 interpreter,
356 &base_python,
357 &scripts,
358 python_home,
359 )?;
360 copy_launcher_windows(
361 WindowsExecutable::PythonMajor,
362 interpreter,
363 &base_python,
364 &scripts,
365 python_home,
366 )?;
367 }
368 "pypy" => {
369 copy_launcher_windows(
371 WindowsExecutable::PythonMajor,
372 interpreter,
373 &base_python,
374 &scripts,
375 python_home,
376 )?;
377 copy_launcher_windows(
378 WindowsExecutable::PythonMajorMinor,
379 interpreter,
380 &base_python,
381 &scripts,
382 python_home,
383 )?;
384 copy_launcher_windows(
385 WindowsExecutable::Pythonw,
386 interpreter,
387 &base_python,
388 &scripts,
389 python_home,
390 )?;
391 copy_launcher_windows(
392 WindowsExecutable::PyPy,
393 interpreter,
394 &base_python,
395 &scripts,
396 python_home,
397 )?;
398 copy_launcher_windows(
399 WindowsExecutable::PyPyMajor,
400 interpreter,
401 &base_python,
402 &scripts,
403 python_home,
404 )?;
405 copy_launcher_windows(
406 WindowsExecutable::PyPyMajorMinor,
407 interpreter,
408 &base_python,
409 &scripts,
410 python_home,
411 )?;
412 copy_launcher_windows(
413 WindowsExecutable::PyPyw,
414 interpreter,
415 &base_python,
416 &scripts,
417 python_home,
418 )?;
419 copy_launcher_windows(
420 WindowsExecutable::PyPyMajorMinorw,
421 interpreter,
422 &base_python,
423 &scripts,
424 python_home,
425 )?;
426 }
427 _ => {
428 copy_launcher_windows(
430 WindowsExecutable::Pythonw,
431 interpreter,
432 &base_python,
433 &scripts,
434 python_home,
435 )?;
436
437 if interpreter.gil_disabled() {
439 copy_launcher_windows(
440 WindowsExecutable::PythonMajorMinort,
441 interpreter,
442 &base_python,
443 &scripts,
444 python_home,
445 )?;
446 copy_launcher_windows(
447 WindowsExecutable::PythonwMajorMinort,
448 interpreter,
449 &base_python,
450 &scripts,
451 python_home,
452 )?;
453 }
454 }
455 }
456 }
457 }
458
459 #[cfg(not(any(unix, windows)))]
460 {
461 compile_error!("Only Windows and Unix are supported")
462 }
463
464 for (name, template) in ACTIVATE_TEMPLATES {
466 if relocatable && *name == "activate.csh" {
470 continue;
471 }
472
473 let path_sep = if cfg!(windows) { ";" } else { ":" };
474
475 let relative_site_packages = [
476 interpreter.virtualenv().purelib.as_path(),
477 interpreter.virtualenv().platlib.as_path(),
478 ]
479 .iter()
480 .dedup()
481 .map(|path| {
482 pathdiff::diff_paths(path, &interpreter.virtualenv().scripts)
483 .expect("Failed to calculate relative path to site-packages")
484 })
485 .map(|path| path.simplified().to_str().unwrap().replace('\\', "\\\\"))
486 .join(path_sep);
487
488 let location_string = location
489 .simplified()
490 .to_str()
491 .ok_or_else(|| Error::NonUtf8Path {
492 path: location.clone(),
493 })?;
494 let virtual_env_dir = match (relocatable, name.to_owned()) {
495 (true, "activate") => Cow::Borrowed(
496 r#"'"$(dirname -- "$(dirname -- "$(realpath -- "$SCRIPT_PATH")")")"'"#,
497 ),
498 (true, "activate.bat") => Cow::Borrowed(r"%~dp0.."),
499 (true, "activate.fish") => {
500 Cow::Borrowed(r"'(dirname -- (dirname -- (realpath -- (status -f))))'")
501 }
502 (true, "activate.nu") => Cow::Borrowed(r"(path self | path dirname | path dirname)"),
503 (false, "activate.nu") => Cow::Owned(format!(
504 "'{}'",
505 escape_posix_for_single_quotes(location_string)
506 )),
507 _ => escape_posix_for_single_quotes(location_string),
509 };
510
511 let activator = template
512 .replace("{{ VIRTUAL_ENV_DIR }}", &virtual_env_dir)
513 .replace("{{ BIN_NAME }}", bin_name)
514 .replace(
515 "{{ VIRTUAL_PROMPT }}",
516 prompt.as_deref().unwrap_or_default(),
517 )
518 .replace("{{ PATH_SEP }}", path_sep)
519 .replace("{{ RELATIVE_SITE_PACKAGES }}", &relative_site_packages);
520 fs_err::write(scripts.join(name), activator)?;
521 }
522
523 let mut pyvenv_cfg_data: Vec<(String, String)> = vec![
524 (
525 "home".to_string(),
526 python_home.simplified_display().to_string(),
527 ),
528 (
529 "implementation".to_string(),
530 interpreter
531 .markers()
532 .platform_python_implementation()
533 .to_string(),
534 ),
535 ("uv".to_string(), version().to_string()),
536 (
537 "version_info".to_string(),
538 if using_minor_version_link {
539 interpreter.python_minor_version().to_string()
540 } else {
541 interpreter.markers().python_full_version().string.clone()
542 },
543 ),
544 (
545 "include-system-site-packages".to_string(),
546 if system_site_packages {
547 "true".to_string()
548 } else {
549 "false".to_string()
550 },
551 ),
552 ];
553
554 if relocatable {
555 pyvenv_cfg_data.push(("relocatable".to_string(), "true".to_string()));
556 }
557
558 match seed {
559 Seed::Enabled => pyvenv_cfg_data.push(("seed".to_string(), "true".to_string())),
560 Seed::Disabled => {}
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, Eq, PartialEq, Default)]
711pub enum Seed {
712 Enabled,
714 #[default]
716 Disabled,
717}
718
719impl Seed {
720 pub fn from_args(seed: bool) -> Self {
722 if seed { Self::Enabled } else { Self::Disabled }
723 }
724}
725
726#[derive(Debug, Copy, Clone)]
727enum WindowsExecutable {
728 Python,
730 PythonMajor,
732 PythonMajorMinor,
734 PythonMajorMinort,
736 Pythonw,
738 PythonwMajorMinort,
740 PyPy,
742 PyPyMajor,
744 PyPyMajorMinor,
746 PyPyw,
748 PyPyMajorMinorw,
750 GraalPy,
752}
753
754impl WindowsExecutable {
755 fn exe(self, interpreter: &Interpreter) -> Cow<'static, OsStr> {
757 match self {
758 Self::Python => Cow::Borrowed(OsStr::new("python.exe")),
759 Self::PythonMajor => Cow::Owned(OsString::from(format!(
760 "python{}.exe",
761 interpreter.python_major()
762 ))),
763 Self::PythonMajorMinor => Cow::Owned(OsString::from(format!(
764 "python{}.{}.exe",
765 interpreter.python_major(),
766 interpreter.python_minor()
767 ))),
768 Self::PythonMajorMinort => Cow::Owned(OsString::from(format!(
769 "python{}.{}t.exe",
770 interpreter.python_major(),
771 interpreter.python_minor()
772 ))),
773 Self::Pythonw => Cow::Borrowed(OsStr::new("pythonw.exe")),
774 Self::PythonwMajorMinort => Cow::Owned(OsString::from(format!(
775 "pythonw{}.{}t.exe",
776 interpreter.python_major(),
777 interpreter.python_minor()
778 ))),
779 Self::PyPy => Cow::Borrowed(OsStr::new("pypy.exe")),
780 Self::PyPyMajor => Cow::Owned(OsString::from(format!(
781 "pypy{}.exe",
782 interpreter.python_major()
783 ))),
784 Self::PyPyMajorMinor => Cow::Owned(OsString::from(format!(
785 "pypy{}.{}.exe",
786 interpreter.python_major(),
787 interpreter.python_minor()
788 ))),
789 Self::PyPyw => Cow::Borrowed(OsStr::new("pypyw.exe")),
790 Self::PyPyMajorMinorw => Cow::Owned(OsString::from(format!(
791 "pypy{}.{}w.exe",
792 interpreter.python_major(),
793 interpreter.python_minor()
794 ))),
795 Self::GraalPy => Cow::Borrowed(OsStr::new("graalpy.exe")),
796 }
797 }
798
799 fn launcher(self, interpreter: &Interpreter) -> &'static str {
801 match self {
802 Self::Python | Self::PythonMajor | Self::PythonMajorMinor
803 if interpreter.gil_disabled() =>
804 {
805 "venvlaunchert.exe"
806 }
807 Self::Python | Self::PythonMajor | Self::PythonMajorMinor => "venvlauncher.exe",
808 Self::Pythonw if interpreter.gil_disabled() => "venvwlaunchert.exe",
809 Self::Pythonw => "venvwlauncher.exe",
810 Self::PythonMajorMinort => "venvlaunchert.exe",
811 Self::PythonwMajorMinort => "venvwlaunchert.exe",
812 Self::PyPy | Self::PyPyMajor | Self::PyPyMajorMinor => "venvlauncher.exe",
815 Self::PyPyw | Self::PyPyMajorMinorw => "venvwlauncher.exe",
816 Self::GraalPy => "venvlauncher.exe",
817 }
818 }
819}
820
821fn copy_launcher_windows(
827 executable: WindowsExecutable,
828 interpreter: &Interpreter,
829 base_python: &Path,
830 scripts: &Path,
831 python_home: &Path,
832) -> Result<(), Error> {
833 let shim = interpreter
835 .stdlib()
836 .join("venv")
837 .join("scripts")
838 .join("nt")
839 .join(executable.exe(interpreter));
840 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
841 Ok(_) => return Ok(()),
842 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
843 Err(err) => {
844 return Err(err.into());
845 }
846 }
847
848 let shim = interpreter
852 .stdlib()
853 .join("venv")
854 .join("scripts")
855 .join("nt")
856 .join(executable.launcher(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 = base_python.with_file_name(executable.launcher(interpreter));
868 match fs_err::copy(shim, scripts.join(executable.exe(interpreter))) {
869 Ok(_) => return Ok(()),
870 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
871 Err(err) => {
872 return Err(err.into());
873 }
874 }
875
876 match fs_err::copy(
880 base_python.with_file_name(executable.exe(interpreter)),
881 scripts.join(executable.exe(interpreter)),
882 ) {
883 Ok(_) => {
884 for directory in [
887 python_home,
888 interpreter.sys_base_prefix().join("DLLs").as_path(),
889 ] {
890 let entries = match fs_err::read_dir(directory) {
891 Ok(read_dir) => read_dir,
892 Err(err) if err.kind() == io::ErrorKind::NotFound => {
893 continue;
894 }
895 Err(err) => {
896 return Err(err.into());
897 }
898 };
899 for entry in entries {
900 let entry = entry?;
901 let path = entry.path();
902 if path.extension().is_some_and(|ext| {
903 ext.eq_ignore_ascii_case("dll") || ext.eq_ignore_ascii_case("pyd")
904 }) {
905 if let Some(file_name) = path.file_name() {
906 fs_err::copy(&path, scripts.join(file_name))?;
907 }
908 }
909 }
910 }
911
912 match fs_err::read_dir(python_home) {
914 Ok(entries) => {
915 for entry in entries {
916 let entry = entry?;
917 let path = entry.path();
918 if path
919 .extension()
920 .is_some_and(|ext| ext.eq_ignore_ascii_case("zip"))
921 {
922 if let Some(file_name) = path.file_name() {
923 fs_err::copy(&path, scripts.join(file_name))?;
924 }
925 }
926 }
927 }
928 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
929 Err(err) => {
930 return Err(err.into());
931 }
932 }
933
934 return Ok(());
935 }
936 Err(err) if err.kind() == io::ErrorKind::NotFound => {}
937 Err(err) => {
938 return Err(err.into());
939 }
940 }
941
942 Err(Error::NotFound(base_python.user_display().to_string()))
943}