Skip to content
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
3 changes: 2 additions & 1 deletion crates/uv-distribution/src/metadata/dependency_groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,15 @@ impl SourcedDependencyGroups {
SourceStrategy::Enabled => MemberDiscovery::default(),
SourceStrategy::Disabled => MemberDiscovery::None,
},
..DiscoveryOptions::default()
};

// The subsequent API takes an absolute path to the dir the pyproject is in
let empty = PathBuf::new();
let absolute_pyproject_path =
std::path::absolute(pyproject_path).map_err(WorkspaceError::Normalize)?;
let project_dir = absolute_pyproject_path.parent().unwrap_or(&empty);
let project = VirtualProject::discover_defaulted(project_dir, &discovery, cache).await?;
let project = VirtualProject::discover(project_dir, &discovery, cache).await?;

// Collect the dependency groups.
let dependency_groups =
Expand Down
1 change: 1 addition & 0 deletions crates/uv-distribution/src/metadata/requires_dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ impl RequiresDist {
SourceStrategy::Enabled => MemberDiscovery::default(),
SourceStrategy::Disabled => MemberDiscovery::None,
},
..DiscoveryOptions::default()
};
let Some(project_workspace) =
ProjectWorkspace::from_maybe_project_root(install_path, &discovery, cache).await?
Expand Down
5 changes: 3 additions & 2 deletions crates/uv-workspace/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub use workspace::{
DiscoveryOptions, Editability, MemberDiscovery, ProjectWorkspace, RequiresPythonSources,
VirtualProject, Workspace, WorkspaceCache, WorkspaceError, WorkspaceMember,
DiscoveryOptions, Editability, MemberDiscovery, ProjectDiscovery, ProjectWorkspace,
RequiresPythonSources, VirtualProject, Workspace, WorkspaceCache, WorkspaceError,
WorkspaceMember,
};

pub mod dependency_groups;
Expand Down
75 changes: 44 additions & 31 deletions crates/uv-workspace/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,51 @@ pub enum MemberDiscovery {
Ignore(BTreeSet<PathBuf>),
}

/// Whether a "project" must be defined via a `[project]` table.
#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
pub enum ProjectDiscovery {
/// The `[project]` table is optional; when missing, the target is treated as virtual.
#[default]
Optional,
/// A `[project]` table must be defined, unless `[tool.uv.workspace]` is present indicating a
/// legacy non-project workspace root.
///
/// If neither is defined, discovery will fail.
Legacy,
/// A `[project]` table must be defined.
///
/// If not defined, discovery will fail.
Required,
}

impl ProjectDiscovery {
/// Whether a `[project]` table is required.
pub fn allows_implicit_workspace(&self) -> bool {
match self {
Self::Optional => true,
Self::Legacy => false,
Self::Required => false,
}
}

/// Whether a legacy workspace root is allowed.
pub fn allows_legacy_workspace(&self) -> bool {
match self {
Self::Optional => true,
Self::Legacy => true,
Self::Required => false,
}
}
}

#[derive(Debug, Default, Clone, Hash, PartialEq, Eq)]
pub struct DiscoveryOptions {
/// The path to stop discovery at.
pub stop_discovery_at: Option<PathBuf>,
/// The strategy to use when discovering workspace members.
pub members: MemberDiscovery,
/// The strategy to use when discovering the project.
pub project: ProjectDiscovery,
}

pub type RequiresPythonSources = BTreeMap<(PackageName, Option<GroupName>), VersionSpecifiers>;
Expand Down Expand Up @@ -1561,13 +1600,13 @@ fn is_included_in_workspace(

/// A project that can be discovered.
///
/// The project could be a package within a workspace, a real workspace root, or a (legacy)
/// non-project workspace root, which can define its own dev dependencies.
/// The project could be a package within a workspace, a real workspace root, or a non-project
/// workspace root, which can define its own dev dependencies.
#[derive(Debug, Clone)]
pub enum VirtualProject {
/// A project (which could be a workspace root or member).
Project(ProjectWorkspace),
/// A (legacy) non-project workspace root.
/// A non-project workspace root.
NonProject(Workspace),
}

Expand All @@ -1583,33 +1622,6 @@ impl VirtualProject {
path: &Path,
options: &DiscoveryOptions,
cache: &WorkspaceCache,
) -> Result<Self, WorkspaceError> {
Self::discover_impl(path, options, cache, false).await
}

/// Equivalent to [`VirtualProject::discover`] but consider it acceptable for
/// both `[project]` and `[tool.uv.workspace]` to be missing.
///
/// If they are, we act as if an empty `[tool.uv.workspace]` was found.
pub async fn discover_defaulted(
path: &Path,
options: &DiscoveryOptions,
cache: &WorkspaceCache,
) -> Result<Self, WorkspaceError> {
Self::discover_impl(path, options, cache, true).await
}

/// Find the current project or virtual workspace root, given the current directory.
///
/// Similar to calling [`ProjectWorkspace::discover`] with a fallback to [`Workspace::discover`],
/// but avoids rereading the `pyproject.toml` (and relying on error-handling as control flow).
///
/// This method requires an absolute path and panics otherwise.
async fn discover_impl(
path: &Path,
options: &DiscoveryOptions,
cache: &WorkspaceCache,
default_missing_workspace: bool,
) -> Result<Self, WorkspaceError> {
assert!(
path.is_absolute(),
Expand Down Expand Up @@ -1656,6 +1668,7 @@ impl VirtualProject {
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.workspace.as_ref())
.filter(|_| options.project.allows_legacy_workspace())
{
// Otherwise, if it contains a `tool.uv.workspace` table, it's a non-project workspace
// root.
Expand All @@ -1674,7 +1687,7 @@ impl VirtualProject {
.await?;

Ok(Self::NonProject(workspace))
} else if default_missing_workspace {
} else if options.project.allows_implicit_workspace() {
// Otherwise it's a pyproject.toml that maybe contains dependency-groups
// that we want to treat like a project/workspace to handle those uniformly
let project_path = std::path::absolute(project_root)
Expand Down
10 changes: 8 additions & 2 deletions crates/uv/src/commands/project/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,10 @@ async fn find_target(
VirtualProject::Project(
Workspace::discover(
project_dir,
&DiscoveryOptions::default(),
&DiscoveryOptions {
project: uv_workspace::ProjectDiscovery::Required,
..DiscoveryOptions::default()
},
&WorkspaceCache::default(),
)
.await
Expand All @@ -369,7 +372,10 @@ async fn find_target(
} else {
VirtualProject::discover(
project_dir,
&DiscoveryOptions::default(),
&DiscoveryOptions {
project: uv_workspace::ProjectDiscovery::Required,
..DiscoveryOptions::default()
},
&WorkspaceCache::default(),
)
.await
Expand Down
Loading
Loading