Avoid filtering preferences by --reinstall (#3929)

## Summary

In general, it's not quite right to filter preferences by `--reinstall`
-- we still want to respect existing versions, we just don't want to
respect _installed_ versions. But now that the installed versions and
preferences are decoupled, we can remove this (`--reinstall` is enforced
on the installed versions via the `Exclusions` struct that we pass to
the resolver).

While I was here, I also cleaned up the lockfile preference code to
better match the structure for `requirements.txt`.
This commit is contained in:
Charlie Marsh 2024-05-30 16:19:08 -04:00 committed by GitHub
parent 438b5c61d0
commit a14fe2f6c7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 70 additions and 38 deletions

1
Cargo.lock generated
View file

@ -4838,6 +4838,7 @@ dependencies = [
name = "uv-requirements" name = "uv-requirements"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anstream",
"anyhow", "anyhow",
"cache-key", "cache-key",
"configparser", "configparser",

View file

@ -27,6 +27,7 @@ uv-resolver = { workspace = true, features = ["clap"] }
uv-types = { workspace = true } uv-types = { workspace = true }
uv-warnings = { workspace = true } uv-warnings = { workspace = true }
anstream = { workspace = true }
anyhow = { workspace = true } anyhow = { workspace = true }
configparser = { workspace = true } configparser = { workspace = true }
console = { workspace = true } console = { workspace = true }

View file

@ -2,21 +2,26 @@ use std::path::Path;
use anyhow::Result; use anyhow::Result;
use anstream::eprint;
use requirements_txt::RequirementsTxt; use requirements_txt::RequirementsTxt;
use uv_client::{BaseClientBuilder, Connectivity}; use uv_client::{BaseClientBuilder, Connectivity};
use uv_configuration::Upgrade; use uv_configuration::Upgrade;
use uv_resolver::{Preference, PreferenceError}; use uv_resolver::{Lock, Preference, PreferenceError};
/// Load the preferred requirements from an existing lockfile, applying the upgrade strategy. use crate::ProjectWorkspace;
pub async fn read_lockfile(
/// Load the preferred requirements from an existing `requirements.txt`, applying the upgrade strategy.
pub async fn read_requirements_txt(
output_file: Option<&Path>, output_file: Option<&Path>,
upgrade: Upgrade, upgrade: &Upgrade,
) -> Result<Vec<Preference>> { ) -> Result<Vec<Preference>> {
// As an optimization, skip reading the lockfile is we're upgrading all packages anyway. // As an optimization, skip reading the lockfile is we're upgrading all packages anyway.
let Some(output_file) = output_file if upgrade.is_all() {
.filter(|_| !upgrade.is_all()) return Ok(Vec::new());
.filter(|output_file| output_file.exists()) }
else {
// If the lockfile doesn't exist, don't respect any pinned versions.
let Some(output_file) = output_file.filter(|path| path.exists()) else {
return Ok(Vec::new()); return Ok(Vec::new());
}; };
@ -27,6 +32,8 @@ pub async fn read_lockfile(
&BaseClientBuilder::new().connectivity(Connectivity::Offline), &BaseClientBuilder::new().connectivity(Connectivity::Offline),
) )
.await?; .await?;
// Map each entry in the lockfile to a preference.
let preferences = requirements_txt let preferences = requirements_txt
.requirements .requirements
.into_iter() .into_iter()
@ -47,3 +54,50 @@ pub async fn read_lockfile(
.collect(), .collect(),
}) })
} }
/// Load the preferred requirements from an existing lockfile, applying the upgrade strategy.
pub async fn read_lockfile(
project: &ProjectWorkspace,
upgrade: &Upgrade,
) -> Result<Vec<Preference>> {
// As an optimization, skip reading the lockfile is we're upgrading all packages anyway.
if upgrade.is_all() {
return Ok(Vec::new());
}
// If an existing lockfile exists, build up a set of preferences.
let lockfile = project.workspace().root().join("uv.lock");
let lock = match fs_err::tokio::read_to_string(&lockfile).await {
Ok(encoded) => match toml::from_str::<Lock>(&encoded) {
Ok(lock) => lock,
Err(err) => {
eprint!("Failed to parse lockfile; ignoring locked requirements: {err}");
return Ok(Vec::new());
}
},
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Ok(Vec::new());
}
Err(err) => return Err(err.into()),
};
// Map each entry in the lockfile to a preference.
let preferences: Vec<Preference> = lock
.distributions()
.iter()
.map(Preference::from_lock)
.collect();
// Apply the upgrade strategy to the requirements.
Ok(match upgrade {
// Respect all pinned versions from the existing lockfile.
Upgrade::None => preferences,
// Ignore all pinned versions from the existing lockfile.
Upgrade::All => vec![],
// Ignore pinned versions for the specified packages.
Upgrade::Packages(packages) => preferences
.into_iter()
.filter(|preference| !packages.contains(preference.name()))
.collect(),
})
}

View file

@ -34,8 +34,8 @@ use uv_interpreter::{
use uv_interpreter::{PythonVersion, SourceSelector}; use uv_interpreter::{PythonVersion, SourceSelector};
use uv_normalize::{ExtraName, PackageName}; use uv_normalize::{ExtraName, PackageName};
use uv_requirements::{ use uv_requirements::{
upgrade::read_lockfile, LookaheadResolver, NamedRequirementsResolver, RequirementsSource, upgrade::read_requirements_txt, LookaheadResolver, NamedRequirementsResolver,
RequirementsSpecification, SourceTreeResolver, RequirementsSource, RequirementsSpecification, SourceTreeResolver,
}; };
use uv_resolver::{ use uv_resolver::{
AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex, AnnotationStyle, DependencyMode, DisplayResolutionGraph, ExcludeNewer, Exclusions, FlatIndex,
@ -288,7 +288,7 @@ pub(crate) async fn pip_compile(
.build(); .build();
// Read the lockfile, if present. // Read the lockfile, if present.
let preferences = read_lockfile(output_file, upgrade).await?; let preferences = read_requirements_txt(output_file, &upgrade).await?;
// Resolve the flat indexes from `--find-links`. // Resolve the flat indexes from `--find-links`.
let flat_index = { let flat_index = {

View file

@ -197,12 +197,6 @@ pub(crate) async fn resolve<InstalledPackages: InstalledPackagesProvider>(
// TODO(zanieb): Consider consuming these instead of cloning // TODO(zanieb): Consider consuming these instead of cloning
let exclusions = Exclusions::new(reinstall.clone(), upgrade.clone()); let exclusions = Exclusions::new(reinstall.clone(), upgrade.clone());
// Filter out any excluded distributions from the preferences.
let preferences = preferences
.into_iter()
.filter(|dist| !exclusions.contains(dist.name()))
.collect::<Vec<_>>();
// Create a manifest of the requirements. // Create a manifest of the requirements.
let manifest = Manifest::new( let manifest = Manifest::new(
requirements, requirements,

View file

@ -11,8 +11,9 @@ use uv_configuration::{
}; };
use uv_dispatch::BuildDispatch; use uv_dispatch::BuildDispatch;
use uv_interpreter::PythonEnvironment; use uv_interpreter::PythonEnvironment;
use uv_requirements::upgrade::read_lockfile;
use uv_requirements::ProjectWorkspace; use uv_requirements::ProjectWorkspace;
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder, Preference}; use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight}; use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
use uv_warnings::warn_user; use uv_warnings::warn_user;
@ -105,26 +106,7 @@ pub(super) async fn do_lock(
let options = OptionsBuilder::new().exclude_newer(exclude_newer).build(); let options = OptionsBuilder::new().exclude_newer(exclude_newer).build();
// If an existing lockfile exists, build up a set of preferences. // If an existing lockfile exists, build up a set of preferences.
let lockfile = project.workspace().root().join("uv.lock"); let preferences = read_lockfile(project, &upgrade).await?;
let lock = match fs_err::tokio::read_to_string(&lockfile).await {
Ok(encoded) => match toml::from_str::<Lock>(&encoded) {
Ok(lock) => Some(lock),
Err(err) => {
eprint!("Failed to parse lockfile; ignoring locked requirements: {err}");
None
}
},
Err(err) if err.kind() == std::io::ErrorKind::NotFound => None,
Err(err) => return Err(err.into()),
};
let preferences: Vec<Preference> = lock
.map(|lock| {
lock.distributions()
.iter()
.map(Preference::from_lock)
.collect()
})
.unwrap_or_default();
// Create a build dispatch. // Create a build dispatch.
let build_dispatch = BuildDispatch::new( let build_dispatch = BuildDispatch::new(