Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discover uv run projects hierarchically #3494

Merged
merged 1 commit into from
May 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion crates/uv-requirements/src/sources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub enum RequirementsSource {
SetupPy(PathBuf),
/// Dependencies were provided via a `setup.cfg` file (e.g., `pip-compile setup.cfg`).
SetupCfg(PathBuf),
/// Dependencies were provided via a path to a source tree (e.g., `pip install .`).
SourceTree(PathBuf),
}

impl RequirementsSource {
Expand Down Expand Up @@ -122,6 +124,12 @@ impl RequirementsSource {
Self::Package(name)
}

/// Parse a [`RequirementsSource`] from a user-provided string, assumed to be a path to a source
/// tree.
pub fn from_source_tree(path: PathBuf) -> Self {
Self::SourceTree(path)
}

/// Returns `true` if the source allows extras to be specified.
pub fn allows_extras(&self) -> bool {
matches!(
Expand All @@ -139,7 +147,8 @@ impl std::fmt::Display for RequirementsSource {
Self::RequirementsTxt(path)
| Self::PyprojectToml(path)
| Self::SetupPy(path)
| Self::SetupCfg(path) => {
| Self::SetupCfg(path)
| Self::SourceTree(path) => {
write!(f, "{}", path.simplified_display())
}
}
Expand Down
24 changes: 24 additions & 0 deletions crates/uv-requirements/src/specification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use distribution_types::{
FlatIndexLocation, IndexUrl, Requirement, RequirementSource, UnresolvedRequirement,
UnresolvedRequirementSpecification,
};
use pep508_rs::{UnnamedRequirement, VerbatimUrl};
use requirements_txt::{
EditableRequirement, FindLink, RequirementEntry, RequirementsTxt, RequirementsTxtRequirement,
};
Expand Down Expand Up @@ -163,6 +164,29 @@ impl RequirementsSpecification {
no_binary: NoBinary::default(),
no_build: NoBuild::default(),
},
RequirementsSource::SourceTree(path) => Self {
project: None,
requirements: vec![UnresolvedRequirementSpecification {
requirement: UnresolvedRequirement::Unnamed(UnnamedRequirement {
url: VerbatimUrl::from_path(path),
extras: vec![],
marker: None,
origin: None,
}),
hashes: vec![],
}],
constraints: vec![],
overrides: vec![],
editables: vec![],
source_trees: vec![],
extras: FxHashSet::default(),
index_url: None,
extra_index_urls: vec![],
no_index: false,
find_links: vec![],
no_binary: NoBinary::default(),
no_build: NoBuild::default(),
},
})
}

Expand Down
40 changes: 40 additions & 0 deletions crates/uv/src/commands/project/discovery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::path::{Path, PathBuf};

use tracing::debug;
use uv_fs::Simplified;

use uv_requirements::RequirementsSource;

#[derive(Debug, Clone)]
pub(crate) struct Project {
/// The path to the `pyproject.toml` file.
path: PathBuf,
}

impl Project {
/// Find the current project.
pub(crate) fn find(path: impl AsRef<Path>) -> Option<Self> {
for ancestor in path.as_ref().ancestors() {
let pyproject_path = ancestor.join("pyproject.toml");
if pyproject_path.exists() {
debug!(
"Loading requirements from: {}",
pyproject_path.user_display()
);
return Some(Self {
path: pyproject_path,
});
}
}

None
}

/// Return the requirements for the project.
pub(crate) fn requirements(&self) -> Vec<RequirementsSource> {
vec![
RequirementsSource::from_requirements_file(self.path.clone()),
RequirementsSource::from_source_tree(self.path.parent().unwrap().to_path_buf()),
]
}
}
7 changes: 4 additions & 3 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder};
use uv_types::{BuildIsolation, HashStrategy, InFlight};
use uv_warnings::warn_user;

use crate::commands::project::discovery::Project;
use crate::commands::project::Error;
use crate::commands::{project, ExitStatus};
use crate::printer::Printer;
Expand All @@ -32,9 +33,9 @@ pub(crate) async fn lock(
let venv = PythonEnvironment::from_virtualenv(cache)?;

// Find the project requirements.
let Some(requirements) = project::find_project()? else {
let Some(project) = Project::find(std::env::current_dir()?) else {
return Err(anyhow::anyhow!(
"Unable to find `pyproject.toml` for project project."
"Unable to find `pyproject.toml` for project."
));
};

Expand All @@ -45,7 +46,7 @@ pub(crate) async fn lock(
// TODO(zanieb): Consider allowing constraints and extras
// TODO(zanieb): Allow specifying extras somehow
let spec = RequirementsSpecification::from_sources(
&requirements,
&project.requirements(),
&[],
&[],
&ExtrasSpecification::None,
Expand Down
26 changes: 3 additions & 23 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::fmt::Write;
use anyhow::{Context, Result};
use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::debug;

use distribution_types::{IndexLocations, InstalledMetadata, LocalDist, Name, Resolution};
use install_wheel_rs::linker::LinkMode;
Expand All @@ -14,12 +13,11 @@ 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_requirements::{
ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSource,
RequirementsSpecification, SourceTreeResolver,
ExtrasSpecification, LookaheadResolver, NamedRequirementsResolver, RequirementsSpecification,
SourceTreeResolver,
};
use uv_resolver::{
Exclusions, FlatIndex, InMemoryIndex, Manifest, Options, PythonRequirement, ResolutionGraph,
Expand All @@ -31,6 +29,7 @@ use crate::commands::reporters::{DownloadReporter, InstallReporter, ResolverRepo
use crate::commands::{elapsed, ChangeEvent, ChangeEventKind};
use crate::printer::Printer;

mod discovery;
pub(crate) mod lock;
pub(crate) mod run;
pub(crate) mod sync;
Expand Down Expand Up @@ -62,25 +61,6 @@ pub(crate) enum Error {
Anyhow(#[from] anyhow::Error),
}

/// Find the requirements for the current workspace.
pub(crate) fn find_project() -> Result<Option<Vec<RequirementsSource>>> {
// TODO(zanieb): Add/use workspace logic to load requirements for a workspace
// We cannot use `Workspace::find` yet because it depends on a `[tool.uv]` section
let pyproject_path = std::env::current_dir()?.join("pyproject.toml");
if pyproject_path.exists() {
debug!(
"Loading requirements from {}",
pyproject_path.user_display()
);
return Ok(Some(vec![
RequirementsSource::from_requirements_file(pyproject_path),
RequirementsSource::from_package(".".to_string()),
]));
}

Ok(None)
}

/// Resolve a set of requirements, similar to running `pip compile`.
#[allow(clippy::too_many_arguments)]
pub(crate) async fn resolve(
Expand Down
22 changes: 11 additions & 11 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;

Expand All @@ -21,6 +20,7 @@ use uv_resolver::{FlatIndex, InMemoryIndex, OptionsBuilder};
use uv_types::{BuildIsolation, HashStrategy, InFlight};
use uv_warnings::warn_user;

use crate::commands::project::discovery::Project;
use crate::commands::{project, ExitStatus};
use crate::printer::Printer;

Expand Down Expand Up @@ -62,16 +62,16 @@ pub(crate) async fn run(
} else {
debug!("Syncing project environment.");

let Some(project_requirements) = project::find_project()? else {
let Some(project) = Project::find(std::env::current_dir()?) else {
return Err(anyhow::anyhow!(
"Unable to find `pyproject.toml` for project project."
"Unable to find `pyproject.toml` for project."
));
};

let venv = PythonEnvironment::from_virtualenv(cache)?;

// Install the project requirements.
Some(update_environment(venv, &project_requirements, preview, cache, printer).await?)
Some(update_environment(venv, &project.requirements(), preview, cache, printer).await?)
};

// If necessary, create an environment for the ephemeral requirements.
Expand All @@ -97,7 +97,7 @@ pub(crate) async fn run(

// Create a virtual environment
// TODO(zanieb): Move this path derivation elsewhere
let uv_state_path = env::current_dir()?.join(".uv");
let uv_state_path = std::env::current_dir()?.join(".uv");
fs_err::create_dir_all(&uv_state_path)?;
tmpdir = tempdir_in(uv_state_path)?;
let venv = uv_virtualenv::create_venv(
Expand All @@ -117,7 +117,7 @@ pub(crate) async fn run(
process.args(&args);

// Construct the `PATH` environment variable.
let new_path = env::join_paths(
let new_path = std::env::join_paths(
ephemeral_env
.as_ref()
.map(PythonEnvironment::scripts)
Expand All @@ -130,16 +130,16 @@ pub(crate) async fn run(
)
.map(PathBuf::from)
.chain(
env::var_os("PATH")
std::env::var_os("PATH")
.as_ref()
.iter()
.flat_map(env::split_paths),
.flat_map(std::env::split_paths),
),
)?;
process.env("PATH", new_path);

// Construct the `PYTHONPATH` environment variable.
let new_python_path = env::join_paths(
let new_python_path = std::env::join_paths(
ephemeral_env
.as_ref()
.map(PythonEnvironment::site_packages)
Expand All @@ -154,10 +154,10 @@ pub(crate) async fn run(
)
.map(PathBuf::from)
.chain(
env::var_os("PYTHONPATH")
std::env::var_os("PYTHONPATH")
.as_ref()
.iter()
.flat_map(env::split_paths),
.flat_map(std::env::split_paths),
),
)?;
process.env("PYTHONPATH", new_python_path);
Expand Down
Loading