Skip to main content

uv_virtualenv/
lib.rs

1use std::io;
2use std::path::{Path, PathBuf};
3
4use thiserror::Error;
5
6use uv_fs::Simplified;
7use uv_python::{Interpreter, PythonEnvironment};
8
9pub use virtualenv::{ClearNonVirtualenv, OnExisting, RemovalReason};
10
11mod virtualenv;
12
13#[derive(Debug, Error)]
14pub enum Error {
15    #[error(transparent)]
16    Io(#[from] io::Error),
17    #[error(
18        "Could not find a suitable Python executable for the virtual environment based on the interpreter: {0}"
19    )]
20    NotFound(String),
21    #[error(transparent)]
22    Python(#[from] uv_python::managed::Error),
23    #[error("A {name} already exists at: {}", path.user_display())]
24    Exists {
25        /// The type of environment (e.g., "virtual environment" or "directory").
26        name: &'static str,
27        /// The path to the existing environment.
28        path: PathBuf,
29    },
30    #[error("uv will not clear a directory that is not a virtual environment")]
31    ClearNonVirtualenv {
32        /// The non-virtual environment directory that would have been cleared.
33        path: PathBuf,
34    },
35}
36
37impl uv_errors::Hint for Error {
38    fn hints(&self) -> uv_errors::Hints<'_> {
39        match self {
40            Self::Exists { name, .. } => uv_errors::Hints::from(format!(
41                "Use the `--clear` flag or set `UV_VENV_CLEAR=1` to replace the existing {name}",
42            )),
43            Self::ClearNonVirtualenv { .. } => uv_errors::Hints::from(
44                "Use the `--force` flag to remove the existing directory anyway",
45            ),
46            _ => uv_errors::Hints::none(),
47        }
48    }
49}
50
51/// The value to use for the shell prompt when inside a virtual environment.
52#[derive(Debug)]
53pub enum Prompt {
54    /// Use the current directory name as the prompt.
55    CurrentDirectoryName,
56    /// Use the fixed string as the prompt.
57    Static(String),
58    /// Default to no prompt. The prompt is then set by the activator script
59    /// to the virtual environment's directory name.
60    None,
61}
62
63impl Prompt {
64    /// Determine the prompt value to be used from the command line arguments.
65    pub fn from_args(prompt: Option<String>) -> Self {
66        match prompt {
67            Some(prompt) if prompt == "." => Self::CurrentDirectoryName,
68            Some(prompt) => Self::Static(prompt),
69            None => Self::None,
70        }
71    }
72}
73
74/// Create a virtualenv.
75#[expect(clippy::fn_params_excessive_bools)]
76pub fn create_venv(
77    location: &Path,
78    interpreter: Interpreter,
79    prompt: Prompt,
80    system_site_packages: bool,
81    on_existing: OnExisting,
82    relocatable: bool,
83    seed: bool,
84    upgradeable: bool,
85) -> Result<PythonEnvironment, Error> {
86    // Create the virtualenv at the given location.
87    let virtualenv = virtualenv::create(
88        location,
89        &interpreter,
90        prompt,
91        system_site_packages,
92        on_existing,
93        relocatable,
94        seed,
95        upgradeable,
96    )?;
97
98    // Create the corresponding `PythonEnvironment`.
99    let interpreter = interpreter.with_virtualenv(virtualenv);
100    Ok(PythonEnvironment::from_interpreter(interpreter))
101}