Skip to content

Commit 85230b9

Browse files
committed
Ignore dynamic version in source dist
When encountering `dynamic = ["version"]` in the pyproject.toml of a source dist, we can ignore that and treat it as a statically known metadata distribution, since the filename tells us the version and that version must not change on build. This fixed locking PyGObject 3.50.0 from ` pygobject-3.50.0.tar.gz` (minimized): ```toml [project] name = "PyGObject" description = "Python bindings for GObject Introspection" requires-python = ">=3.9, <4.0" dependencies = [ "pycairo>=1.16" ] dynamic = ["version"] ``` Afterwards, `uv add --no-sync toga` passes on Ubuntu 24.04 without the pygobject build deps, when previously it needed `{ name = "pygobject", version = "3.50.0", requires-dist = [], requires-python = ">=3.9" }`.
1 parent 8126a5e commit 85230b9

File tree

3 files changed

+30
-13
lines changed

3 files changed

+30
-13
lines changed

crates/uv-distribution/src/source/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,7 +1916,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
19161916
subdirectory: Option<&Path>,
19171917
) -> Result<Option<ResolutionMetadata>, Error> {
19181918
// Attempt to read static metadata from the `pyproject.toml`.
1919-
match read_pyproject_toml(source_root, subdirectory).await {
1919+
match read_pyproject_toml(source_root, subdirectory, source.version()).await {
19201920
Ok(metadata) => {
19211921
debug!("Found static `pyproject.toml` for: {source}");
19221922

@@ -2339,6 +2339,7 @@ async fn read_pkg_info(
23392339
async fn read_pyproject_toml(
23402340
source_tree: &Path,
23412341
subdirectory: Option<&Path>,
2342+
sdist_version: Option<&Version>,
23422343
) -> Result<ResolutionMetadata, Error> {
23432344
// Read the `pyproject.toml` file.
23442345
let pyproject_toml = match subdirectory {
@@ -2354,8 +2355,8 @@ async fn read_pyproject_toml(
23542355
};
23552356

23562357
// Parse the metadata.
2357-
let metadata =
2358-
ResolutionMetadata::parse_pyproject_toml(&content).map_err(Error::PyprojectToml)?;
2358+
let metadata = ResolutionMetadata::parse_pyproject_toml(&content, sdist_version)
2359+
.map_err(Error::PyprojectToml)?;
23592360

23602361
Ok(metadata)
23612362
}

crates/uv-pypi-types/src/metadata/metadata_resolver.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,11 @@ impl ResolutionMetadata {
151151
})
152152
}
153153

154-
pub fn parse_pyproject_toml(toml: &str) -> Result<Self, MetadataError> {
155-
parse_pyproject_toml(toml)
154+
pub fn parse_pyproject_toml(
155+
toml: &str,
156+
sdist_version: Option<&Version>,
157+
) -> Result<Self, MetadataError> {
158+
parse_pyproject_toml(toml, sdist_version)
156159
}
157160
}
158161

crates/uv-pypi-types/src/metadata/pyproject_toml.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ use uv_pep440::{Version, VersionSpecifiers};
1212
use uv_pep508::Requirement;
1313

1414
/// Extract the metadata from a `pyproject.toml` file, as specified in PEP 621.
15-
pub(crate) fn parse_pyproject_toml(contents: &str) -> Result<ResolutionMetadata, MetadataError> {
15+
///
16+
/// If we're coming from a source distribution, we may already know the version (unlike for a source
17+
/// tree), so we can tolerate dynamic versions.
18+
pub(crate) fn parse_pyproject_toml(
19+
contents: &str,
20+
sdist_version: Option<&Version>,
21+
) -> Result<ResolutionMetadata, MetadataError> {
1622
let pyproject_toml = PyProjectToml::from_toml(contents)?;
1723

1824
let project = pyproject_toml
@@ -28,7 +34,11 @@ pub(crate) fn parse_pyproject_toml(contents: &str) -> Result<ResolutionMetadata,
2834
return Err(MetadataError::DynamicField("optional-dependencies"))
2935
}
3036
"requires-python" => return Err(MetadataError::DynamicField("requires-python")),
31-
"version" => return Err(MetadataError::DynamicField("version")),
37+
// When building from a source distribution, the version is known from the filename and
38+
// fixed by it, so we can pretend it's static.
39+
"version" if sdist_version.is_none() => {
40+
return Err(MetadataError::DynamicField("version"))
41+
}
3242
_ => (),
3343
}
3444
}
@@ -44,6 +54,9 @@ pub(crate) fn parse_pyproject_toml(contents: &str) -> Result<ResolutionMetadata,
4454
let name = project.name;
4555
let version = project
4656
.version
57+
// When building from a source distribution, the version is known from the filename and
58+
// fixed by it, so we can pretend it's static.
59+
.or_else(|| sdist_version.cloned())
4760
.ok_or(MetadataError::FieldNotFound("version"))?;
4861

4962
// Parse the Python version requirements.
@@ -238,23 +251,23 @@ mod tests {
238251
[project]
239252
name = "asdf"
240253
"#;
241-
let meta = parse_pyproject_toml(s);
254+
let meta = parse_pyproject_toml(s, None);
242255
assert!(matches!(meta, Err(MetadataError::FieldNotFound("version"))));
243256

244257
let s = r#"
245258
[project]
246259
name = "asdf"
247260
dynamic = ["version"]
248261
"#;
249-
let meta = parse_pyproject_toml(s);
262+
let meta = parse_pyproject_toml(s, None);
250263
assert!(matches!(meta, Err(MetadataError::DynamicField("version"))));
251264

252265
let s = r#"
253266
[project]
254267
name = "asdf"
255268
version = "1.0"
256269
"#;
257-
let meta = parse_pyproject_toml(s).unwrap();
270+
let meta = parse_pyproject_toml(s, None).unwrap();
258271
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
259272
assert_eq!(meta.version, Version::new([1, 0]));
260273
assert!(meta.requires_python.is_none());
@@ -267,7 +280,7 @@ mod tests {
267280
version = "1.0"
268281
requires-python = ">=3.6"
269282
"#;
270-
let meta = parse_pyproject_toml(s).unwrap();
283+
let meta = parse_pyproject_toml(s, None).unwrap();
271284
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
272285
assert_eq!(meta.version, Version::new([1, 0]));
273286
assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap()));
@@ -281,7 +294,7 @@ mod tests {
281294
requires-python = ">=3.6"
282295
dependencies = ["foo"]
283296
"#;
284-
let meta = parse_pyproject_toml(s).unwrap();
297+
let meta = parse_pyproject_toml(s, None).unwrap();
285298
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
286299
assert_eq!(meta.version, Version::new([1, 0]));
287300
assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap()));
@@ -298,7 +311,7 @@ mod tests {
298311
[project.optional-dependencies]
299312
dotenv = ["bar"]
300313
"#;
301-
let meta = parse_pyproject_toml(s).unwrap();
314+
let meta = parse_pyproject_toml(s, None).unwrap();
302315
assert_eq!(meta.name, PackageName::from_str("asdf").unwrap());
303316
assert_eq!(meta.version, Version::new([1, 0]));
304317
assert_eq!(meta.requires_python, Some(">=3.6".parse().unwrap()));

0 commit comments

Comments
 (0)