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
8 changes: 6 additions & 2 deletions crates/uv-resolver/src/lock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,8 @@ impl Package {
} else {
annotated_dist
.metadata
.as_ref()
.expect("metadata is present")
.requires_dist
.iter()
.cloned()
Expand All @@ -1471,6 +1473,8 @@ impl Package {
} else {
annotated_dist
.metadata
.as_ref()
.expect("metadata is present")
.dev_dependencies
.iter()
.map(|(group, requirements)| {
Expand Down Expand Up @@ -2168,8 +2172,8 @@ impl PackageId {
annotated_dist: &AnnotatedDist,
root: &Path,
) -> Result<PackageId, LockError> {
let name = annotated_dist.metadata.name.clone();
let version = annotated_dist.metadata.version.clone();
let name = annotated_dist.name.clone();
let version = annotated_dist.version.clone();
let source = Source::from_resolved_dist(&annotated_dist.dist, root)?;
Ok(Self {
name,
Expand Down
67 changes: 36 additions & 31 deletions crates/uv-resolver/src/resolution/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,29 +300,32 @@ impl ResolutionGraph {
git,
)?;

// Validate the extra.
if let Some(extra) = extra {
if !metadata.provides_extras.contains(extra) {
diagnostics.push(ResolutionDiagnostic::MissingExtra {
dist: dist.clone(),
extra: extra.clone(),
});
if let Some(metadata) = metadata.as_ref() {
// Validate the extra.
if let Some(extra) = extra {
if !metadata.provides_extras.contains(extra) {
diagnostics.push(ResolutionDiagnostic::MissingExtra {
dist: dist.clone(),
extra: extra.clone(),
});
}
}
}

// Validate the development dependency group.
if let Some(dev) = dev {
if !metadata.dev_dependencies.contains_key(dev) {
diagnostics.push(ResolutionDiagnostic::MissingDev {
dist: dist.clone(),
dev: dev.clone(),
});
// Validate the development dependency group.
if let Some(dev) = dev {
if !metadata.dev_dependencies.contains_key(dev) {
diagnostics.push(ResolutionDiagnostic::MissingDev {
dist: dist.clone(),
dev: dev.clone(),
});
}
}
}

// Add the distribution to the graph.
let index = petgraph.add_node(ResolutionGraphNode::Dist(AnnotatedDist {
dist,
name: name.clone(),
version: version.clone(),
extra: extra.clone(),
dev: dev.clone(),
Expand Down Expand Up @@ -351,7 +354,7 @@ impl ResolutionGraph {
preferences: &Preferences,
index: &InMemoryIndex,
git: &GitResolver,
) -> Result<(ResolvedDist, Vec<HashDigest>, Metadata), ResolveError> {
) -> Result<(ResolvedDist, Vec<HashDigest>, Option<Metadata>), ResolveError> {
Ok(if let Some(url) = url {
// Create the distribution.
let dist = Dist::from_url(name.clone(), url_to_precise(url.clone(), git))?;
Expand All @@ -365,17 +368,17 @@ impl ResolutionGraph {
// Extract the metadata.
let metadata = {
let response = index.distributions().get(&version_id).unwrap_or_else(|| {
panic!("Every package should have metadata: {version_id:?}")
panic!("Every URL distribution should have metadata: {version_id:?}")
});

let MetadataResponse::Found(archive) = &*response else {
panic!("Every package should have metadata: {version_id:?}")
panic!("Every URL distribution should have metadata: {version_id:?}")
};

archive.metadata.clone()
};

(dist.into(), hashes, metadata)
(dist.into(), hashes, Some(metadata))
} else {
let dist = pins
.get(name, version)
Expand Down Expand Up @@ -406,15 +409,13 @@ impl ResolutionGraph {

// Extract the metadata.
let metadata = {
let response = index.distributions().get(&version_id).unwrap_or_else(|| {
panic!("Every package should have metadata: {version_id:?}")
});

let MetadataResponse::Found(archive) = &*response else {
panic!("Every package should have metadata: {version_id:?}")
};

archive.metadata.clone()
index.distributions().get(&version_id).and_then(|response| {
if let MetadataResponse::Found(archive) = &*response {
Some(archive.metadata.clone())
} else {
None
}
})
};

(dist, hashes, metadata)
Expand Down Expand Up @@ -726,13 +727,17 @@ fn has_lower_bound(
return true;
}

let Some(metadata) = neighbor_dist.metadata.as_ref() else {
// We can't check for lower bounds if we lack metadata.
return true;
};

// Get all individual specifier for the current package and check if any has a lower
// bound.
for requirement in neighbor_dist
.metadata
for requirement in metadata
.requires_dist
.iter()
.chain(neighbor_dist.metadata.dev_dependencies.values().flatten())
.chain(metadata.dev_dependencies.values().flatten())
{
if requirement.name != *package_name {
continue;
Expand Down
3 changes: 2 additions & 1 deletion crates/uv-resolver/src/resolution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ mod requirements_txt;
#[derive(Debug, Clone)]
pub(crate) struct AnnotatedDist {
pub(crate) dist: ResolvedDist,
pub(crate) name: PackageName,
pub(crate) version: Version,
pub(crate) extra: Option<ExtraName>,
pub(crate) dev: Option<GroupName>,
pub(crate) hashes: Vec<HashDigest>,
pub(crate) metadata: Metadata,
pub(crate) metadata: Option<Metadata>,
}

impl AnnotatedDist {
Expand Down
47 changes: 25 additions & 22 deletions crates/uv-resolver/src/resolver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,12 +331,14 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}

// Pre-visit all candidate packages, to allow metadata to be fetched in parallel.
Self::pre_visit(
state.pubgrub.partial_solution.prioritized_packages(),
&self.urls,
&state.python_requirement,
&request_sink,
)?;
if self.dependency_mode.is_transitive() {
Self::pre_visit(
state.pubgrub.partial_solution.prioritized_packages(),
&self.urls,
&state.python_requirement,
&request_sink,
)?;
}

// Choose a package version.
let Some(highest_priority_pkg) = state
Expand Down Expand Up @@ -1109,17 +1111,19 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag

// Emit a request to fetch the metadata for this version.
if matches!(&**package, PubGrubPackageInner::Package { .. }) {
if self.index.distributions().register(candidate.version_id()) {
// Verify that the package is allowed under the hash-checking policy.
if !self
.hasher
.allows_package(candidate.name(), candidate.version())
{
return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
}
if self.dependency_mode.is_transitive() {
if self.index.distributions().register(candidate.version_id()) {
// Verify that the package is allowed under the hash-checking policy.
if !self
.hasher
.allows_package(candidate.name(), candidate.version())
{
return Err(ResolveError::UnhashedPackage(candidate.name().clone()));
}

let request = Request::from(dist.for_resolution());
request_sink.blocking_send(request)?;
let request = Request::from(dist.for_resolution());
request_sink.blocking_send(request)?;
}
}
}

Expand Down Expand Up @@ -1186,6 +1190,11 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
dev,
marker,
} => {
// If we're excluding transitive dependencies, short-circuit.
if self.dependency_mode.is_direct() {
return Ok(Dependencies::Unforkable(Vec::default()));
}

// Determine the distribution to lookup.
let dist = match url {
Some(url) => PubGrubDistribution::from_url(name, url),
Expand Down Expand Up @@ -1273,12 +1282,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
}
};

// If we're excluding transitive dependencies, short-circuit. (It's important that
// we fetched the metadata, though, since we need it to validate extras.)
if self.dependency_mode.is_direct() {
return Ok(Dependencies::Unforkable(Vec::default()));
}

let requirements = self.flatten_requirements(
&metadata.requires_dist,
&metadata.dev_dependencies,
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/tests/cache_clean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ fn clean_package_pypi() -> Result<()> {
----- stderr -----
DEBUG uv [VERSION] ([COMMIT] DATE)
DEBUG Removing dangling cache entry: [CACHE_DIR]/archive-v0/[ENTRY]
Removed 13 files for iniconfig ([SIZE])
Removed 12 files for iniconfig ([SIZE])
"###);

// Assert that the `.rkyv` file is removed for `iniconfig`.
Expand Down Expand Up @@ -158,7 +158,7 @@ fn clean_package_index() -> Result<()> {
----- stderr -----
DEBUG uv [VERSION] ([COMMIT] DATE)
DEBUG Removing dangling cache entry: [CACHE_DIR]/archive-v0/[ENTRY]
Removed 13 files for iniconfig ([SIZE])
Removed 12 files for iniconfig ([SIZE])
"###);

// Assert that the `.rkyv` file is removed for `iniconfig`.
Expand Down
22 changes: 7 additions & 15 deletions crates/uv/tests/cache_prune.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ fn prune_unzipped() -> Result<()> {

----- stderr -----
Pruning cache at: [CACHE_DIR]/
Removed 163 files ([SIZE])
Removed 162 files ([SIZE])
"###);

// Reinstalling the source distribution should not require re-downloading the source
Expand All @@ -226,28 +226,20 @@ fn prune_unzipped() -> Result<()> {
~ source-distribution==0.0.1
"###);

// But reinstalling the other package should require a download, since we pruned the wheel.
requirements_txt.write_str(indoc! { r"
iniconfig
" })?;
uv_snapshot!(context.filters(), context.pip_sync().env_remove("UV_EXCLUDE_NEWER").arg("requirements.txt").arg("--reinstall").arg("--offline"), @r###"
success: false
exit_code: 1
exit_code: 2
----- stdout -----

----- stderr -----
× No solution found when resolving dependencies:
╰─▶ Because only the following versions of iniconfig are available:
iniconfig<=0.1
iniconfig>=1.0.0
and all of:
iniconfig<=0.1
iniconfig>=1.0.0
need to be downloaded from a registry, we can conclude that iniconfig<1.0.0 cannot be used.
And because you require iniconfig, we can conclude that your requirements are unsatisfiable.

hint: Pre-releases are available for iniconfig in the requested range (e.g., 0.2.dev0), but pre-releases weren't enabled (try: `--prerelease=allow`)

hint: Packages were unavailable because the network was disabled. When the network is disabled, registry packages may only be read from the cache.
Resolved 1 package in [TIME]
error: Failed to prepare distributions
Caused by: Failed to fetch wheel: iniconfig==2.0.0
Caused by: Network connectivity is disabled, but the requested data wasn't found in the cache for: `https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl`
"###);

Ok(())
Expand Down
3 changes: 1 addition & 2 deletions crates/uv/tests/pip_compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6151,7 +6151,7 @@ fn no_deps_valid_extra() -> Result<()> {
Ok(())
}

/// Resolve a package with `--no-deps`, including an invalid extra.
/// Resolve a package with `--no-deps`, including an invalid extra. We don't warn here.
#[test]
fn no_deps_invalid_extra() -> Result<()> {
let context = TestContext::new("3.12");
Expand All @@ -6171,7 +6171,6 @@ fn no_deps_invalid_extra() -> Result<()> {

----- stderr -----
Resolved 1 package in [TIME]
warning: The package `flask==3.0.2` does not have an extra named `empty`
"###
);

Expand Down
12 changes: 9 additions & 3 deletions crates/uv/tests/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4783,7 +4783,9 @@ fn require_hashes_find_links_invalid_hash() -> Result<()> {
----- stdout -----

----- stderr -----
error: Failed to download and build `example-a-961b4c22==1.0.0`
Resolved 1 package in [TIME]
error: Failed to prepare distributions
Caused by: Failed to fetch wheel: example-a-961b4c22==1.0.0
Caused by: Hash mismatch for `example-a-961b4c22==1.0.0`

Expected:
Expand Down Expand Up @@ -4991,7 +4993,9 @@ fn require_hashes_registry_invalid_hash() -> Result<()> {
----- stdout -----

----- stderr -----
error: Failed to download and build `example-a-961b4c22==1.0.0`
Resolved 1 package in [TIME]
error: Failed to prepare distributions
Caused by: Failed to fetch wheel: example-a-961b4c22==1.0.0
Caused by: Hash mismatch for `example-a-961b4c22==1.0.0`

Expected:
Expand Down Expand Up @@ -5472,7 +5476,9 @@ fn incompatible_build_constraint() -> Result<()> {
----- stdout -----

----- stderr -----
error: Failed to download and build `requests==1.2.0`
Resolved 1 package in [TIME]
error: Failed to prepare distributions
Caused by: Failed to fetch wheel: requests==1.2.0
Caused by: Failed to install requirements from `setup.py` build (resolve)
Caused by: No solution found when resolving: setuptools>=40.8.0
Caused by: Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable.
Expand Down
Loading