Skip to content

Commit

Permalink
Create virtualenv if it doesn't exist in project API
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed May 10, 2024
1 parent d68b740 commit 4b26545
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 14 deletions.
16 changes: 14 additions & 2 deletions crates/uv-interpreter/src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::VenvNotFound);
}
Err(err) => return Err(err.into()),
};
let executable = virtualenv_python_executable(&venv);
let interpreter = Interpreter::query(&executable, cache)?;

Expand Down
7 changes: 3 additions & 4 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -29,16 +28,16 @@ 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!(
"Unable to find `pyproject.toml` for project."
));
};

// Discover or create the virtual environment.
let venv = project::init(&project, cache, printer)?;

// TODO(zanieb): Support client configuration
let client_builder = BaseClientBuilder::default();

Expand Down
49 changes: 48 additions & 1 deletion crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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),

Expand All @@ -61,6 +69,45 @@ 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.
match PythonEnvironment::from_root(&venv, cache) {
Ok(venv) => Ok(venv),
Err(uv_interpreter::Error::VenvNotFound) => {
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(
Expand Down
2 changes: 1 addition & 1 deletion crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
Expand Down
11 changes: 5 additions & 6 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,18 +26,18 @@ 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!(
"Unable to find `pyproject.toml` for project."
));
};

// 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?;
Expand Down

0 comments on commit 4b26545

Please sign in to comment.