mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-30 11:37:24 +00:00
Create virtualenv if it doesn't exist in project API (#3499)
## Summary This doesn't yet respect `--python` or the `requires-python` in the project itself. Closes #3449.
This commit is contained in:
parent
5f8c3b7e45
commit
835ebe60c6
9 changed files with 83 additions and 21 deletions
|
|
@ -18,12 +18,24 @@ pub struct PythonEnvironment {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PythonEnvironment {
|
impl PythonEnvironment {
|
||||||
/// Create a [`PythonEnvironment`] for an existing virtual environment.
|
/// Create a [`PythonEnvironment`] for an existing virtual environment, detected from the
|
||||||
|
/// environment variables and filesystem.
|
||||||
pub fn from_virtualenv(cache: &Cache) -> Result<Self, Error> {
|
pub fn from_virtualenv(cache: &Cache) -> Result<Self, Error> {
|
||||||
let Some(venv) = detect_virtualenv()? else {
|
let Some(venv) = detect_virtualenv()? else {
|
||||||
return Err(Error::VenvNotFound);
|
return Err(Error::VenvNotFound);
|
||||||
};
|
};
|
||||||
let venv = fs_err::canonicalize(venv)?;
|
Self::from_root(&venv, cache)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a [`PythonEnvironment`] from the virtual environment at the given root.
|
||||||
|
pub fn from_root(root: &Path, cache: &Cache) -> Result<Self, Error> {
|
||||||
|
let venv = match fs_err::canonicalize(root) {
|
||||||
|
Ok(venv) => venv,
|
||||||
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
|
||||||
|
return Err(Error::VenvDoesNotExist(root.to_path_buf()));
|
||||||
|
}
|
||||||
|
Err(err) => return Err(err.into()),
|
||||||
|
};
|
||||||
let executable = virtualenv_python_executable(&venv);
|
let executable = virtualenv_python_executable(&venv);
|
||||||
let interpreter = Interpreter::query(&executable, cache)?;
|
let interpreter = Interpreter::query(&executable, cache)?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -736,7 +736,7 @@ mod windows {
|
||||||
));
|
));
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
format_err(result),
|
format_err(result),
|
||||||
@"Failed to locate Python interpreter at `C:\\does\\not\\exists\\python3.12`"
|
@"Failed to locate Python interpreter at: `C:\\does\\not\\exists\\python3.12`"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -793,6 +793,6 @@ mod tests {
|
||||||
"/does/not/exists/python3.12".to_string(),
|
"/does/not/exists/python3.12".to_string(),
|
||||||
));
|
));
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
format_err(result), @"Failed to locate Python interpreter at `/does/not/exists/python3.12`");
|
format_err(result), @"Failed to locate Python interpreter at: `/does/not/exists/python3.12`");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,8 @@ use std::process::ExitStatus;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use uv_fs::Simplified;
|
||||||
|
|
||||||
pub use crate::environment::PythonEnvironment;
|
pub use crate::environment::PythonEnvironment;
|
||||||
pub use crate::find_python::{find_best_python, find_default_python, find_requested_python};
|
pub use crate::find_python::{find_best_python, find_default_python, find_requested_python};
|
||||||
pub use crate::interpreter::Interpreter;
|
pub use crate::interpreter::Interpreter;
|
||||||
|
|
@ -35,13 +37,15 @@ mod virtualenv;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Expected `{0}` to be a virtualenv, but `pyvenv.cfg` is missing")]
|
#[error("Expected `{}` to be a virtualenv, but `pyvenv.cfg` is missing", _0.user_display())]
|
||||||
MissingPyVenvCfg(PathBuf),
|
MissingPyVenvCfg(PathBuf),
|
||||||
#[error("No versions of Python could be found. Is Python installed?")]
|
#[error("No versions of Python could be found. Is Python installed?")]
|
||||||
PythonNotFound,
|
PythonNotFound,
|
||||||
#[error("Failed to locate a virtualenv or Conda environment (checked: `VIRTUAL_ENV`, `CONDA_PREFIX`, and `.venv`). Run `uv venv` to create a virtualenv.")]
|
#[error("Failed to locate a virtualenv or Conda environment (checked: `VIRTUAL_ENV`, `CONDA_PREFIX`, and `.venv`). Run `uv venv` to create a virtualenv.")]
|
||||||
VenvNotFound,
|
VenvNotFound,
|
||||||
#[error("Failed to locate Python interpreter at `{0}`")]
|
#[error("Virtualenv does not exist at: `{}`", _0.user_display())]
|
||||||
|
VenvDoesNotExist(PathBuf),
|
||||||
|
#[error("Failed to locate Python interpreter at: `{0}`")]
|
||||||
RequestedPythonNotFound(String),
|
RequestedPythonNotFound(String),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Io(#[from] io::Error),
|
Io(#[from] io::Error),
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ mod tests {
|
||||||
));
|
));
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
format_err(result),
|
format_err(result),
|
||||||
@"Failed to locate Python interpreter at `C:\\does\\not\\exists\\python3.12`"
|
@"Failed to locate Python interpreter at: `C:\\does\\not\\exists\\python3.12`"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ use uv_cache::Cache;
|
||||||
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
|
use uv_client::{BaseClientBuilder, RegistryClientBuilder};
|
||||||
use uv_configuration::{ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy};
|
use uv_configuration::{ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
use uv_interpreter::PythonEnvironment;
|
|
||||||
use uv_requirements::{ExtrasSpecification, RequirementsSpecification};
|
use uv_requirements::{ExtrasSpecification, RequirementsSpecification};
|
||||||
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder};
|
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder};
|
||||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||||
|
|
@ -29,9 +28,6 @@ pub(crate) async fn lock(
|
||||||
warn_user!("`uv lock` is experimental and may change without warning.");
|
warn_user!("`uv lock` is experimental and may change without warning.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(charlie): If the environment doesn't exist, create it.
|
|
||||||
let venv = PythonEnvironment::from_virtualenv(cache)?;
|
|
||||||
|
|
||||||
// Find the project requirements.
|
// Find the project requirements.
|
||||||
let Some(project) = Project::find(std::env::current_dir()?)? else {
|
let Some(project) = Project::find(std::env::current_dir()?)? else {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
|
|
@ -39,6 +35,9 @@ pub(crate) async fn lock(
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Discover or create the virtual environment.
|
||||||
|
let venv = project::init(&project, cache, printer)?;
|
||||||
|
|
||||||
// TODO(zanieb): Support client configuration
|
// TODO(zanieb): Support client configuration
|
||||||
let client_builder = BaseClientBuilder::default();
|
let client_builder = BaseClientBuilder::default();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,9 @@ use uv_cache::Cache;
|
||||||
use uv_client::RegistryClient;
|
use uv_client::RegistryClient;
|
||||||
use uv_configuration::{Constraints, NoBinary, Overrides, Reinstall};
|
use uv_configuration::{Constraints, NoBinary, Overrides, Reinstall};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
|
use uv_fs::Simplified;
|
||||||
use uv_installer::{Downloader, Plan, Planner, SitePackages};
|
use uv_installer::{Downloader, Plan, Planner, SitePackages};
|
||||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment};
|
||||||
use uv_requirements::{
|
use uv_requirements::{
|
||||||
ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSpecification,
|
ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSpecification,
|
||||||
SourceTreeResolver,
|
SourceTreeResolver,
|
||||||
|
|
@ -25,6 +26,7 @@ use uv_resolver::{
|
||||||
};
|
};
|
||||||
use uv_types::{EmptyInstalledPackages, HashStrategy, InFlight};
|
use uv_types::{EmptyInstalledPackages, HashStrategy, InFlight};
|
||||||
|
|
||||||
|
use crate::commands::project::discovery::Project;
|
||||||
use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter};
|
use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter};
|
||||||
use crate::commands::{elapsed, ChangeEvent, ChangeEventKind};
|
use crate::commands::{elapsed, ChangeEvent, ChangeEventKind};
|
||||||
use crate::printer::Printer;
|
use crate::printer::Printer;
|
||||||
|
|
@ -45,6 +47,12 @@ pub(crate) enum Error {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Platform(#[from] platform_tags::PlatformError),
|
Platform(#[from] platform_tags::PlatformError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Interpreter(#[from] uv_interpreter::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Virtualenv(#[from] uv_virtualenv::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Hash(#[from] uv_types::HashStrategyError),
|
Hash(#[from] uv_types::HashStrategyError),
|
||||||
|
|
||||||
|
|
@ -61,6 +69,47 @@ pub(crate) enum Error {
|
||||||
Anyhow(#[from] anyhow::Error),
|
Anyhow(#[from] anyhow::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialize a virtual environment for the current project.
|
||||||
|
pub(crate) fn init(
|
||||||
|
project: &Project,
|
||||||
|
cache: &Cache,
|
||||||
|
printer: Printer,
|
||||||
|
) -> Result<PythonEnvironment, Error> {
|
||||||
|
let venv = project.root().join(".venv");
|
||||||
|
|
||||||
|
// Discover or create the virtual environment.
|
||||||
|
// TODO(charlie): If the environment isn't compatible with `--python`, recreate it.
|
||||||
|
match PythonEnvironment::from_root(&venv, cache) {
|
||||||
|
Ok(venv) => Ok(venv),
|
||||||
|
Err(uv_interpreter::Error::VenvDoesNotExist(_)) => {
|
||||||
|
// TODO(charlie): Respect `--python`; if unset, respect `Requires-Python`.
|
||||||
|
let interpreter = find_default_python(cache)?;
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"Using Python {} interpreter at: {}",
|
||||||
|
interpreter.python_version(),
|
||||||
|
interpreter.sys_executable().user_display().cyan()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
writeln!(
|
||||||
|
printer.stderr(),
|
||||||
|
"Creating virtualenv at: {}",
|
||||||
|
venv.user_display().cyan()
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(uv_virtualenv::create_venv(
|
||||||
|
&venv,
|
||||||
|
interpreter,
|
||||||
|
uv_virtualenv::Prompt::None,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolve a set of requirements, similar to running `pip compile`.
|
/// Resolve a set of requirements, similar to running `pip compile`.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) async fn resolve(
|
pub(crate) async fn resolve(
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ pub(crate) async fn run(
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let venv = PythonEnvironment::from_virtualenv(cache)?;
|
let venv = project::init(&project, cache, printer)?;
|
||||||
|
|
||||||
// Install the project requirements.
|
// Install the project requirements.
|
||||||
Some(update_environment(venv, &project.requirements(), preview, cache, printer).await?)
|
Some(update_environment(venv, &project.requirements(), preview, cache, printer).await?)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ use uv_client::RegistryClientBuilder;
|
||||||
use uv_configuration::{ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy};
|
use uv_configuration::{ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy};
|
||||||
use uv_dispatch::BuildDispatch;
|
use uv_dispatch::BuildDispatch;
|
||||||
use uv_installer::SitePackages;
|
use uv_installer::SitePackages;
|
||||||
use uv_interpreter::PythonEnvironment;
|
|
||||||
use uv_resolver::{FlatIndex, InMemoryIndex, Lock};
|
use uv_resolver::{FlatIndex, InMemoryIndex, Lock};
|
||||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||||
use uv_warnings::warn_user;
|
use uv_warnings::warn_user;
|
||||||
|
|
@ -27,11 +26,6 @@ pub(crate) async fn sync(
|
||||||
warn_user!("`uv sync` is experimental and may change without warning.");
|
warn_user!("`uv sync` is experimental and may change without warning.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(charlie): If the environment doesn't exist, create it.
|
|
||||||
let venv = PythonEnvironment::from_virtualenv(cache)?;
|
|
||||||
let markers = venv.interpreter().markers();
|
|
||||||
let tags = venv.interpreter().tags()?;
|
|
||||||
|
|
||||||
// Find the project requirements.
|
// Find the project requirements.
|
||||||
let Some(project) = Project::find(std::env::current_dir()?)? else {
|
let Some(project) = Project::find(std::env::current_dir()?)? else {
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
|
|
@ -39,6 +33,11 @@ pub(crate) async fn sync(
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Discover or create the virtual environment.
|
||||||
|
let venv = project::init(&project, cache, printer)?;
|
||||||
|
let markers = venv.interpreter().markers();
|
||||||
|
let tags = venv.interpreter().tags()?;
|
||||||
|
|
||||||
// Read the lockfile.
|
// Read the lockfile.
|
||||||
let resolution = {
|
let resolution = {
|
||||||
let encoded = fs_err::tokio::read_to_string(project.root().join("uv.lock")).await?;
|
let encoded = fs_err::tokio::read_to_string(project.root().join("uv.lock")).await?;
|
||||||
|
|
|
||||||
|
|
@ -108,8 +108,7 @@ fn missing_venv() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
error: failed to canonicalize path `[VENV]/`
|
error: Virtualenv does not exist at: `[VENV]/`
|
||||||
Caused by: No such file or directory (os error 2)
|
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
assert!(predicates::path::missing().eval(&context.venv));
|
assert!(predicates::path::missing().eval(&context.venv));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue