Remove special-casing for editable requirements (#3869)

## Summary

There are a few behavior changes in here:

- We now enforce `--require-hashes` for editables, like pip. So if you
use `--require-hashes` with an editable requirement, we'll reject it. I
could change this if it seems off.
- We now treat source tree requirements, editable or not (e.g., both `-e
./black` and `./black`) as if `--refresh` is always enabled. This
doesn't mean that we _always_ rebuild them; but if you pass
`--reinstall`, then yes, we always rebuild them. I think this is an
improvement and is close to how editables work today.

Closes #3844.

Closes #2695.
This commit is contained in:
Charlie Marsh 2024-05-28 11:49:34 -04:00 committed by GitHub
parent 063a0a4384
commit 1fc6a59707
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
64 changed files with 583 additions and 1813 deletions

View file

@ -37,14 +37,13 @@ use uv_types::{BuildContext, HashStrategy, InstalledPackagesProvider};
use crate::candidate_selector::{CandidateDist, CandidateSelector};
use crate::dependency_provider::UvDependencyProvider;
use crate::editables::Editables;
use crate::error::ResolveError;
use crate::manifest::Manifest;
use crate::pins::FilePins;
use crate::preferences::Preferences;
use crate::pubgrub::{
PubGrubDependencies, PubGrubDistribution, PubGrubPackage, PubGrubPackageInner,
PubGrubPriorities, PubGrubPython, PubGrubRequirement, PubGrubSpecifier,
PubGrubPriorities, PubGrubPython, PubGrubSpecifier,
};
use crate::python_requirement::PythonRequirement;
use crate::resolution::ResolutionGraph;
@ -85,7 +84,6 @@ struct ResolverState<InstalledPackages: InstalledPackagesProvider> {
overrides: Overrides,
preferences: Preferences,
exclusions: Exclusions,
editables: Editables,
urls: Urls,
locals: Locals,
dependency_mode: DependencyMode,
@ -192,7 +190,6 @@ impl<Provider: ResolverProvider, InstalledPackages: InstalledPackagesProvider>
overrides: manifest.overrides,
preferences: Preferences::from_iter(manifest.preferences, markers),
exclusions: manifest.exclusions,
editables: Editables::from_requirements(manifest.editables),
hasher: hasher.clone(),
markers: markers.cloned(),
python_requirement: python_requirement.clone(),
@ -343,7 +340,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
self.index.distributions(),
&state.pubgrub,
&self.preferences,
self.editables.clone(),
);
};
state.next = highest_priority_pkg;
@ -560,11 +556,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
return Err(ResolveError::UnhashedPackage(name.clone()));
}
// If the package is an editable, we don't need to fetch metadata.
if self.editables.contains(name) {
return Ok(());
}
// Emit a request to fetch the metadata for this distribution.
let dist = Dist::from_url(name.clone(), url.clone())?;
if self.index.distributions().register(dist.version_id()) {
@ -649,31 +640,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
url.verbatim
);
// If the dist is an editable, return the version from the editable metadata.
if let Some(editable) = self.editables.get(name) {
let version = &editable.metadata.version;
// The version is incompatible with the requirement.
if !range.contains(version) {
return Ok(None);
}
// The version is incompatible due to its Python requirement.
if let Some(requires_python) = editable.metadata.requires_python.as_ref() {
let target = self.python_requirement.target();
if !requires_python.contains(target) {
return Ok(Some(ResolverVersion::Unavailable(
version.clone(),
UnavailableVersion::IncompatibleDist(IncompatibleDist::Source(
IncompatibleSource::RequiresPython(requires_python.clone()),
)),
)));
}
}
return Ok(Some(ResolverVersion::Available(version.clone())));
}
let dist = PubGrubDistribution::from_url(name, url);
let response = self
.index
@ -853,7 +819,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
self.markers.as_ref(),
);
let mut dependencies = match dependencies {
let dependencies = match dependencies {
Ok(dependencies) => dependencies,
Err(err) => {
return Ok(Dependencies::Unavailable(
@ -872,54 +838,6 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
self.visit_package(package, request_sink)?;
}
// Add a dependency on each editable.
for editable in self.editables.iter() {
let package = PubGrubPackage::from_package(
editable.metadata.name.clone(),
None,
None,
&self.urls,
);
let version = Range::singleton(editable.metadata.version.clone());
// Update the package priorities.
priorities.insert(&package, &version);
// Add the editable as a direct dependency.
dependencies.push(package, version);
// Add a dependency on each extra.
for extra in &editable.built.extras {
dependencies.push(
PubGrubPackage::from_package(
editable.metadata.name.clone(),
Some(extra.clone()),
None,
&self.urls,
),
Range::singleton(editable.metadata.version.clone()),
);
}
// Add any constraints.
for constraint in self
.constraints
.get(&editable.metadata.name)
.into_iter()
.flatten()
{
if constraint.evaluate_markers(self.markers.as_ref(), &[]) {
let PubGrubRequirement { package, version } =
PubGrubRequirement::from_constraint(
constraint,
&self.urls,
&self.locals,
)?;
dependencies.push(package, version);
}
}
}
Ok(Dependencies::Available(dependencies.into()))
}
@ -935,57 +853,22 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
if self.dependency_mode.is_direct() {
// If an extra is provided, wait for the metadata to be available, since it's
// still required for generating the lock file.
if !self.editables.contains(name) {
// Determine the distribution to lookup.
let dist = match url {
Some(url) => PubGrubDistribution::from_url(name, url),
None => PubGrubDistribution::from_registry(name, version),
};
let version_id = dist.version_id();
// Wait for the metadata to be available.
self.index
.distributions()
.wait_blocking(&version_id)
.ok_or(ResolveError::Unregistered)?;
}
let dist = match url {
Some(url) => PubGrubDistribution::from_url(name, url),
None => PubGrubDistribution::from_registry(name, version),
};
let version_id = dist.version_id();
// Wait for the metadata to be available.
self.index
.distributions()
.wait_blocking(&version_id)
.ok_or(ResolveError::Unregistered)?;
return Ok(Dependencies::Available(Vec::default()));
}
// Determine if the distribution is editable.
if let Some(editable) = self.editables.get(name) {
let requirements: Vec<_> = editable
.metadata
.requires_dist
.iter()
.cloned()
.map(Requirement::from)
.collect();
let dependencies = PubGrubDependencies::from_requirements(
&requirements,
&self.constraints,
&self.overrides,
Some(name),
extra.as_ref(),
&self.urls,
&self.locals,
self.markers.as_ref(),
)?;
for (dep_package, dep_version) in dependencies.iter() {
debug!("Adding transitive dependency for {package}=={version}: {dep_package}{dep_version}");
// Update the package priorities.
priorities.insert(dep_package, dep_version);
// Emit a request to fetch the metadata for this package.
self.visit_package(dep_package, request_sink)?;
}
return Ok(Dependencies::Available(dependencies.into()));
}
// Determine the distribution to lookup.
let dist = match url {
Some(url) => PubGrubDistribution::from_url(name, url),
@ -1232,6 +1115,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
ResolveError::FetchAndBuild(Box::new(source_dist), err)
}
})?;
Ok(Some(Response::Dist { dist, metadata }))
}
@ -1320,6 +1204,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
ResolveError::FetchAndBuild(Box::new(source_dist), err)
}
})?;
Response::Dist { dist, metadata }
}
ResolvedDist::Installed(dist) => {