mirror of
https://github.com/astral-sh/uv.git
synced 2025-10-30 19:48:11 +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 {
|
||||
/// 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> {
|
||||
let Some(venv) = detect_virtualenv()? else {
|
||||
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 interpreter = Interpreter::query(&executable, cache)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -736,7 +736,7 @@ mod windows {
|
|||
));
|
||||
assert_snapshot!(
|
||||
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(),
|
||||
));
|
||||
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 uv_fs::Simplified;
|
||||
|
||||
pub use crate::environment::PythonEnvironment;
|
||||
pub use crate::find_python::{find_best_python, find_default_python, find_requested_python};
|
||||
pub use crate::interpreter::Interpreter;
|
||||
|
|
@ -35,13 +37,15 @@ mod virtualenv;
|
|||
|
||||
#[derive(Debug, 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),
|
||||
#[error("No versions of Python could be found. Is Python installed?")]
|
||||
PythonNotFound,
|
||||
#[error("Failed to locate a virtualenv or Conda environment (checked: `VIRTUAL_ENV`, `CONDA_PREFIX`, and `.venv`). Run `uv venv` to create a virtualenv.")]
|
||||
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),
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ mod tests {
|
|||
));
|
||||
assert_snapshot!(
|
||||
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_configuration::{ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_requirements::{ExtrasSpecification, RequirementsSpecification};
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder};
|
||||
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.");
|
||||
}
|
||||
|
||||
// TODO(charlie): If the environment doesn't exist, create it.
|
||||
let venv = PythonEnvironment::from_virtualenv(cache)?;
|
||||
|
||||
// Find the project requirements.
|
||||
let Some(project) = Project::find(std::env::current_dir()?)? else {
|
||||
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
|
||||
let client_builder = BaseClientBuilder::default();
|
||||
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ use uv_cache::Cache;
|
|||
use uv_client::RegistryClient;
|
||||
use uv_configuration::{Constraints, NoBinary, Overrides, Reinstall};
|
||||
use uv_dispatch::BuildDispatch;
|
||||
use uv_fs::Simplified;
|
||||
use uv_installer::{Downloader, Plan, Planner, SitePackages};
|
||||
use uv_interpreter::{Interpreter, PythonEnvironment};
|
||||
use uv_interpreter::{find_default_python, Interpreter, PythonEnvironment};
|
||||
use uv_requirements::{
|
||||
ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSpecification,
|
||||
SourceTreeResolver,
|
||||
|
|
@ -25,6 +26,7 @@ use uv_resolver::{
|
|||
};
|
||||
use uv_types::{EmptyInstalledPackages, HashStrategy, InFlight};
|
||||
|
||||
use crate::commands::project::discovery::Project;
|
||||
use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverReporter};
|
||||
use crate::commands::{elapsed, ChangeEvent, ChangeEventKind};
|
||||
use crate::printer::Printer;
|
||||
|
|
@ -45,6 +47,12 @@ pub(crate) enum Error {
|
|||
#[error(transparent)]
|
||||
Platform(#[from] platform_tags::PlatformError),
|
||||
|
||||
#[error(transparent)]
|
||||
Interpreter(#[from] uv_interpreter::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Virtualenv(#[from] uv_virtualenv::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Hash(#[from] uv_types::HashStrategyError),
|
||||
|
||||
|
|
@ -61,6 +69,47 @@ pub(crate) enum 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`.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
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.
|
||||
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_dispatch::BuildDispatch;
|
||||
use uv_installer::SitePackages;
|
||||
use uv_interpreter::PythonEnvironment;
|
||||
use uv_resolver::{FlatIndex, InMemoryIndex, Lock};
|
||||
use uv_types::{BuildIsolation, HashStrategy, InFlight};
|
||||
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.");
|
||||
}
|
||||
|
||||
// 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.
|
||||
let Some(project) = Project::find(std::env::current_dir()?)? else {
|
||||
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.
|
||||
let resolution = {
|
||||
let encoded = fs_err::tokio::read_to_string(project.root().join("uv.lock")).await?;
|
||||
|
|
|
|||
|
|
@ -108,8 +108,7 @@ fn missing_venv() -> Result<()> {
|
|||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: failed to canonicalize path `[VENV]/`
|
||||
Caused by: No such file or directory (os error 2)
|
||||
error: Virtualenv does not exist at: `[VENV]/`
|
||||
"###);
|
||||
|
||||
assert!(predicates::path::missing().eval(&context.venv));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue