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

View file

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

View file

@ -2,21 +2,26 @@ use std::path::Path;
use anyhow::Result;
use anstream::eprint;
use requirements_txt::RequirementsTxt;
use uv_client::{BaseClientBuilder, Connectivity};
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.
pub async fn read_lockfile(
use crate::ProjectWorkspace;
/// Load the preferred requirements from an existing `requirements.txt`, applying the upgrade strategy.
pub async fn read_requirements_txt(
output_file: Option<&Path>,
upgrade: Upgrade,
upgrade: &Upgrade,
) -> Result<Vec<Preference>> {
// As an optimization, skip reading the lockfile is we're upgrading all packages anyway.
let Some(output_file) = output_file
.filter(|_| !upgrade.is_all())
.filter(|output_file| output_file.exists())
else {
if upgrade.is_all() {
return Ok(Vec::new());
}
// 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());
};
@ -27,6 +32,8 @@ pub async fn read_lockfile(
&BaseClientBuilder::new().connectivity(Connectivity::Offline),
)
.await?;
// Map each entry in the lockfile to a preference.
let preferences = requirements_txt
.requirements
.into_iter()
@ -47,3 +54,50 @@ pub async fn read_lockfile(
.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(),
})
}