Warn when missing minimal bounds when using tool.uv.sources (#3452)

When using `tool.uv.sources`, we warn that requirements have a bound,
i.e. at least a lower version constraint.

When using a library, the symbols you import were introduced in
different versions, creating an implicit lower bound. This warning makes
this explicit. This is crucial to prevent backtracking resolvers from
selecting an ancient versions that is not compatible (or worse, doesn't
build), and a performance optimization on top.

This feature is gated to `tool.uv.sources` (as it should have been to
begin with for #3263/#3443) to not unnecessarily break legacy workflows.
It is also helpful specifically when using a `tool.uv.sources` section
that contains constraints that are not published to pypi, e.g. for
workspace dependencies. We can adjust those later to e.g. not constrain
workspace dependencies with `publish = false`, but i think it's the
right setting to start with.
This commit is contained in:
konsti 2024-05-08 19:16:25 +02:00 committed by GitHub
parent 7c6632114b
commit 7c7c9e2189
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 118 additions and 5 deletions

View file

@ -27,6 +27,7 @@ use uv_configuration::PreviewMode;
use uv_fs::Simplified;
use uv_git::GitReference;
use uv_normalize::{ExtraName, PackageName};
use uv_warnings::warn_user_once;
use crate::ExtrasSpecification;
@ -280,6 +281,7 @@ impl Pep621Metadata {
let requirements = lower_requirements(
&project.dependencies.unwrap_or_default(),
&project.optional_dependencies.unwrap_or_default(),
&project.name,
project_dir,
&project_sources.unwrap_or_default(),
workspace_sources,
@ -318,6 +320,7 @@ impl Pep621Metadata {
pub(crate) fn lower_requirements(
dependencies: &[String],
optional_dependencies: &IndexMap<ExtraName, Vec<String>>,
project_name: &PackageName,
project_dir: &Path,
project_sources: &HashMap<PackageName, Source>,
workspace_sources: &HashMap<PackageName, Source>,
@ -331,6 +334,7 @@ pub(crate) fn lower_requirements(
let name = requirement.name.clone();
lower_requirement(
requirement,
project_name,
project_dir,
project_sources,
workspace_sources,
@ -350,6 +354,7 @@ pub(crate) fn lower_requirements(
let name = requirement.name.clone();
lower_requirement(
requirement,
project_name,
project_dir,
project_sources,
workspace_sources,
@ -371,6 +376,7 @@ pub(crate) fn lower_requirements(
/// Combine `project.dependencies` or `project.optional-dependencies` with `tool.uv.sources`.
pub(crate) fn lower_requirement(
requirement: pep508_rs::Requirement,
project_name: &PackageName,
project_dir: &Path,
project_sources: &HashMap<PackageName, Source>,
workspace_sources: &HashMap<PackageName, Source>,
@ -395,7 +401,15 @@ pub(crate) fn lower_requirement(
}
let Some(source) = source else {
let has_sources = !project_sources.is_empty() || !workspace_sources.is_empty();
// Support recursive editable inclusions.
if has_sources && requirement.version_or_url.is_none() && &requirement.name != project_name
{
warn_user_once!(
"Missing version constraint (e.g., a lower bound) for `{}`",
requirement.name
);
}
return Ok(Requirement::from_pep508(requirement)?);
};
@ -476,10 +490,16 @@ pub(crate) fn lower_requirement(
path_source(path, project_dir, editable)?
}
Source::Registry { index } => match requirement.version_or_url {
None => RequirementSource::Registry {
specifier: VersionSpecifiers::empty(),
index: Some(index),
},
None => {
warn_user_once!(
"Missing version constraint (e.g., a lower bound) for `{}`",
requirement.name
);
RequirementSource::Registry {
specifier: VersionSpecifiers::empty(),
index: Some(index),
}
}
Some(VersionOrUrl::VersionSpecifier(version)) => RequirementSource::Registry {
specifier: version,
index: Some(index),
@ -630,8 +650,8 @@ mod test {
use anyhow::Context;
use indoc::indoc;
use insta::assert_snapshot;
use uv_configuration::PreviewMode;
use uv_configuration::PreviewMode;
use uv_fs::Simplified;
use crate::{ExtrasSpecification, RequirementsSpecification};
@ -756,6 +776,20 @@ mod test {
"###);
}
#[test]
fn missing_constraint() {
let input = indoc! {r#"
[project]
name = "foo"
version = "0.0.0"
dependencies = [
"tqdm",
]
"#};
assert!(from_source(input, "pyproject.toml", &ExtrasSpecification::None).is_ok());
}
#[test]
fn invalid_syntax() {
let input = indoc! {r#"