|
1 | | -use std::collections::BTreeSet; |
| 1 | +use std::collections::{BTreeMap, BTreeSet}; |
2 | 2 | use std::fmt::Write; |
3 | 3 | use std::path::{Path, PathBuf}; |
4 | 4 | use std::sync::Arc; |
@@ -155,6 +155,9 @@ pub(crate) enum ProjectError { |
155 | 155 | #[error("Environment markers `{0}` don't overlap with Python requirement `{1}`")] |
156 | 156 | DisjointEnvironment(MarkerTreeContents, VersionSpecifiers), |
157 | 157 |
|
| 158 | + #[error("The workspace contains conflicting Python requirements:\n{}", _0.iter().map(|(name, specifiers)| format!("- `{name}`: `{specifiers}`")).join("\n"))] |
| 159 | + DisjointRequiresPython(BTreeMap<PackageName, VersionSpecifiers>), |
| 160 | + |
158 | 161 | #[error("Environment marker is empty")] |
159 | 162 | EmptyEnvironment, |
160 | 163 |
|
@@ -317,14 +320,27 @@ impl std::error::Error for ConflictError {} |
317 | 320 | /// |
318 | 321 | /// For a [`Workspace`] with multiple packages, the `Requires-Python` bound is the union of the |
319 | 322 | /// `Requires-Python` bounds of all the packages. |
320 | | -pub(crate) fn find_requires_python(workspace: &Workspace) -> Option<RequiresPython> { |
321 | | - RequiresPython::intersection(workspace.packages().values().filter_map(|member| { |
322 | | - member |
323 | | - .pyproject_toml() |
324 | | - .project |
325 | | - .as_ref() |
326 | | - .and_then(|project| project.requires_python.as_ref()) |
327 | | - })) |
| 323 | +#[allow(clippy::result_large_err)] |
| 324 | +pub(crate) fn find_requires_python( |
| 325 | + workspace: &Workspace, |
| 326 | +) -> Result<Option<RequiresPython>, ProjectError> { |
| 327 | + // If there are no `Requires-Python` specifiers in the workspace, return `None`. |
| 328 | + if workspace.requires_python().next().is_none() { |
| 329 | + return Ok(None); |
| 330 | + } |
| 331 | + match RequiresPython::intersection( |
| 332 | + workspace |
| 333 | + .requires_python() |
| 334 | + .map(|(.., specifiers)| specifiers), |
| 335 | + ) { |
| 336 | + Some(requires_python) => Ok(Some(requires_python)), |
| 337 | + None => Err(ProjectError::DisjointRequiresPython( |
| 338 | + workspace |
| 339 | + .requires_python() |
| 340 | + .map(|(name, specifiers)| (name.clone(), specifiers.clone())) |
| 341 | + .collect(), |
| 342 | + )), |
| 343 | + } |
328 | 344 | } |
329 | 345 |
|
330 | 346 | /// Returns an error if the [`Interpreter`] does not satisfy the [`Workspace`] `requires-python`. |
@@ -732,7 +748,7 @@ impl WorkspacePython { |
732 | 748 | project_dir: &Path, |
733 | 749 | no_config: bool, |
734 | 750 | ) -> Result<Self, ProjectError> { |
735 | | - let requires_python = workspace.and_then(find_requires_python); |
| 751 | + let requires_python = workspace.map(find_requires_python).transpose()?.flatten(); |
736 | 752 |
|
737 | 753 | let workspace_root = workspace.map(Workspace::install_path); |
738 | 754 |
|
|
0 commit comments