Skip to content

Commit

Permalink
Discover projects hierarchically
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed May 9, 2024
1 parent ad01a76 commit 6d3a5f8
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 38 deletions.
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

0 comments on commit 6d3a5f8

Please sign in to comment.