Skip to content

Commit

Permalink
Use lockfile versions as resolution preferences (#3921)
Browse files Browse the repository at this point in the history
## Summary

Ensures that we avoid upgrading packages unless `--upgrade` or similar
is passed.

For now, the resolver only respects these for registry distributions.

Closes #3918.
  • Loading branch information
charliermarsh authored May 30, 2024
1 parent 502e042 commit 1445669
Show file tree
Hide file tree
Showing 8 changed files with 333 additions and 13 deletions.
7 changes: 6 additions & 1 deletion crates/uv-resolver/src/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ impl Lock {
Lock::try_from(wire)
}

/// Returns the [`Distribution`] entries in this lock.
pub fn distributions(&self) -> &[Distribution] {
&self.distributions
}

pub fn to_resolution(
&self,
marker_env: &MarkerEnvironment,
Expand Down Expand Up @@ -202,7 +207,7 @@ impl TryFrom<LockWire> for Lock {
}

#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
pub(crate) struct Distribution {
pub struct Distribution {
#[serde(flatten)]
pub(crate) id: DistributionId,
#[serde(default)]
Expand Down
10 changes: 10 additions & 0 deletions crates/uv-resolver/src/preferences.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ impl Preference {
}
}

/// Create a [`Preference`] from a locked distribution.
pub fn from_lock(dist: &crate::lock::Distribution) -> Self {
Self {
name: dist.id.name.clone(),
version: dist.id.version.clone(),
marker: None,
hashes: Vec::new(),
}
}

/// Return the [`PackageName`] of the package for this [`Preference`].
pub fn name(&self) -> &PackageName {
&self.name
Expand Down
72 changes: 70 additions & 2 deletions crates/uv/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,8 +372,6 @@ pub(crate) struct PipCompileArgs {
#[arg(long, env = "UV_CUSTOM_COMPILE_COMMAND")]
pub(crate) custom_compile_command: Option<String>,

/// Run offline, i.e., without accessing the network.
/// Refresh all cached data.
#[arg(long, conflicts_with("offline"), overrides_with("no_refresh"))]
pub(crate) refresh: bool,
Expand Down Expand Up @@ -1786,6 +1784,33 @@ pub(crate) struct RunArgs {
#[arg(long)]
pub(crate) with: Vec<String>,

/// Refresh all cached data.
#[arg(long, conflicts_with("offline"), overrides_with("no_refresh"))]
pub(crate) refresh: bool,

#[arg(
long,
conflicts_with("offline"),
overrides_with("refresh"),
hide = true
)]
pub(crate) no_refresh: bool,

/// Refresh cached data for a specific package.
#[arg(long)]
pub(crate) refresh_package: Vec<PackageName>,

/// Allow package upgrades, ignoring pinned versions in the existing lockfile.
#[arg(long, short = 'U', overrides_with("no_upgrade"))]
pub(crate) upgrade: bool,

#[arg(long, overrides_with("upgrade"), hide = true)]
pub(crate) no_upgrade: bool,

/// Allow upgrades for a specific package, ignoring pinned versions in the existing lockfile.
#[arg(long, short = 'P')]
pub(crate) upgrade_package: Vec<PackageName>,

/// The Python interpreter to use to build the run environment.
///
/// By default, `uv` uses the virtual environment in the current working directory or any parent
Expand Down Expand Up @@ -1822,6 +1847,22 @@ pub(crate) struct SyncArgs {
#[arg(long, overrides_with("all_extras"), hide = true)]
pub(crate) no_all_extras: bool,

/// Refresh all cached data.
#[arg(long, conflicts_with("offline"), overrides_with("no_refresh"))]
pub(crate) refresh: bool,

#[arg(
long,
conflicts_with("offline"),
overrides_with("refresh"),
hide = true
)]
pub(crate) no_refresh: bool,

/// Refresh cached data for a specific package.
#[arg(long)]
pub(crate) refresh_package: Vec<PackageName>,

/// The Python interpreter to use to build the run environment.
///
/// By default, `uv` uses the virtual environment in the current working directory or any parent
Expand All @@ -1840,6 +1881,33 @@ pub(crate) struct SyncArgs {
#[derive(Args)]
#[allow(clippy::struct_excessive_bools)]
pub(crate) struct LockArgs {
/// Refresh all cached data.
#[arg(long, conflicts_with("offline"), overrides_with("no_refresh"))]
pub(crate) refresh: bool,

#[arg(
long,
conflicts_with("offline"),
overrides_with("refresh"),
hide = true
)]
pub(crate) no_refresh: bool,

/// Refresh cached data for a specific package.
#[arg(long)]
pub(crate) refresh_package: Vec<PackageName>,

/// Allow package upgrades, ignoring pinned versions in the existing lockfile.
#[arg(long, short = 'U', overrides_with("no_upgrade"))]
pub(crate) upgrade: bool,

#[arg(long, overrides_with("upgrade"), hide = true)]
pub(crate) no_upgrade: bool,

/// Allow upgrades for a specific package, ignoring pinned versions in the existing lockfile.
#[arg(long, short = 'P')]
pub(crate) upgrade_package: Vec<PackageName>,

/// The Python interpreter to use to build the run environment.
///
/// By default, `uv` uses the virtual environment in the current working directory or any parent
Expand Down
30 changes: 26 additions & 4 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use uv_configuration::{
use uv_dispatch::BuildDispatch;
use uv_interpreter::PythonEnvironment;
use uv_requirements::ProjectWorkspace;
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder};
use uv_resolver::{ExcludeNewer, FlatIndex, InMemoryIndex, Lock, OptionsBuilder, Preference};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy, InFlight};
use uv_warnings::warn_user;

Expand All @@ -23,6 +23,7 @@ use crate::printer::Printer;
/// Resolve the project requirements into a lockfile.
#[allow(clippy::too_many_arguments)]
pub(crate) async fn lock(
upgrade: Upgrade,
exclude_newer: Option<ExcludeNewer>,
preview: PreviewMode,
cache: &Cache,
Expand All @@ -39,7 +40,7 @@ pub(crate) async fn lock(
let venv = project::init_environment(&project, preview, cache, printer)?;

// Perform the lock operation.
match do_lock(&project, &venv, exclude_newer, cache, printer).await {
match do_lock(&project, &venv, upgrade, exclude_newer, cache, printer).await {
Ok(_) => Ok(ExitStatus::Success),
Err(ProjectError::Operation(pip::operations::Error::Resolve(
uv_resolver::ResolveError::NoSolution(err),
Expand All @@ -57,6 +58,7 @@ pub(crate) async fn lock(
pub(super) async fn do_lock(
project: &ProjectWorkspace,
venv: &PythonEnvironment,
upgrade: Upgrade,
exclude_newer: Option<ExcludeNewer>,
cache: &Cache,
printer: Printer,
Expand Down Expand Up @@ -96,14 +98,34 @@ pub(super) async fn do_lock(
let link_mode = LinkMode::default();
let no_binary = NoBinary::default();
let no_build = NoBuild::default();
let preferences = Vec::default();
let reinstall = Reinstall::default();
let setup_py = SetupPyStrategy::default();
let upgrade = Upgrade::default();

let hasher = HashStrategy::Generate;
let options = OptionsBuilder::new().exclude_newer(exclude_newer).build();

// 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) => 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.
let build_dispatch = BuildDispatch::new(
&client,
Expand Down
6 changes: 4 additions & 2 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tracing::debug;

use uv_cache::Cache;
use uv_client::Connectivity;
use uv_configuration::{ExtrasSpecification, PreviewMode};
use uv_configuration::{ExtrasSpecification, PreviewMode, Upgrade};
use uv_interpreter::{PythonEnvironment, SystemPython};
use uv_requirements::{ProjectWorkspace, RequirementsSource};
use uv_resolver::ExcludeNewer;
Expand All @@ -26,6 +26,7 @@ pub(crate) async fn run(
mut args: Vec<OsString>,
requirements: Vec<RequirementsSource>,
python: Option<String>,
upgrade: Upgrade,
exclude_newer: Option<ExcludeNewer>,
isolated: bool,
preview: PreviewMode,
Expand All @@ -47,7 +48,8 @@ pub(crate) async fn run(
let venv = project::init_environment(&project, preview, cache, printer)?;

// Lock and sync the environment.
let lock = project::lock::do_lock(&project, &venv, exclude_newer, cache, printer).await?;
let lock =
project::lock::do_lock(&project, &venv, upgrade, exclude_newer, cache, printer).await?;
project::sync::do_sync(&project, &venv, &lock, extras, cache, printer).await?;

Some(venv)
Expand Down
16 changes: 12 additions & 4 deletions crates/uv/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ async fn run() -> Result<ExitStatus> {
let args = settings::RunSettings::resolve(args, workspace);

// Initialize the cache.
let cache = cache.init()?;
let cache = cache.init()?.with_refresh(args.refresh);

let requirements = args
.with
Expand Down Expand Up @@ -578,6 +578,7 @@ async fn run() -> Result<ExitStatus> {
args.args,
requirements,
args.python,
args.upgrade,
args.exclude_newer,
globals.isolated,
globals.preview,
Expand All @@ -592,7 +593,7 @@ async fn run() -> Result<ExitStatus> {
let args = settings::SyncSettings::resolve(args, workspace);

// Initialize the cache.
let cache = cache.init()?;
let cache = cache.init()?.with_refresh(args.refresh);

commands::sync(args.extras, globals.preview, &cache, printer).await
}
Expand All @@ -601,9 +602,16 @@ async fn run() -> Result<ExitStatus> {
let args = settings::LockSettings::resolve(args, workspace);

// Initialize the cache.
let cache = cache.init()?;
let cache = cache.init()?.with_refresh(args.refresh);

commands::lock(args.exclude_newer, globals.preview, &cache, printer).await
commands::lock(
args.upgrade,
args.exclude_newer,
globals.preview,
&cache,
printer,
)
.await
}
#[cfg(feature = "self-update")]
Commands::Self_(SelfNamespace {
Expand Down
25 changes: 25 additions & 0 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ pub(crate) struct RunSettings {
pub(crate) args: Vec<OsString>,
pub(crate) with: Vec<String>,
pub(crate) python: Option<String>,
pub(crate) refresh: Refresh,
pub(crate) upgrade: Upgrade,
pub(crate) exclude_newer: Option<ExcludeNewer>,
}

Expand All @@ -116,11 +118,19 @@ impl RunSettings {
target,
args,
with,
refresh,
no_refresh,
refresh_package,
upgrade,
no_upgrade,
upgrade_package,
python,
exclude_newer,
} = args;

Self {
refresh: Refresh::from_args(flag(refresh, no_refresh), refresh_package),
upgrade: Upgrade::from_args(flag(upgrade, no_upgrade), upgrade_package),
extras: ExtrasSpecification::from_args(
flag(all_extras, no_all_extras).unwrap_or_default(),
extra.unwrap_or_default(),
Expand All @@ -138,6 +148,7 @@ impl RunSettings {
#[allow(clippy::struct_excessive_bools, dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct SyncSettings {
pub(crate) refresh: Refresh,
pub(crate) extras: ExtrasSpecification,
pub(crate) python: Option<String>,
}
Expand All @@ -150,10 +161,14 @@ impl SyncSettings {
extra,
all_extras,
no_all_extras,
refresh,
no_refresh,
refresh_package,
python,
} = args;

Self {
refresh: Refresh::from_args(flag(refresh, no_refresh), refresh_package),
extras: ExtrasSpecification::from_args(
flag(all_extras, no_all_extras).unwrap_or_default(),
extra.unwrap_or_default(),
Expand All @@ -167,6 +182,8 @@ impl SyncSettings {
#[allow(clippy::struct_excessive_bools, dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct LockSettings {
pub(crate) refresh: Refresh,
pub(crate) upgrade: Upgrade,
pub(crate) exclude_newer: Option<ExcludeNewer>,
pub(crate) python: Option<String>,
}
Expand All @@ -176,11 +193,19 @@ impl LockSettings {
#[allow(clippy::needless_pass_by_value)]
pub(crate) fn resolve(args: LockArgs, _workspace: Option<Workspace>) -> Self {
let LockArgs {
refresh,
no_refresh,
refresh_package,
upgrade,
no_upgrade,
upgrade_package,
exclude_newer,
python,
} = args;

Self {
refresh: Refresh::from_args(flag(refresh, no_refresh), refresh_package),
upgrade: Upgrade::from_args(flag(upgrade, no_upgrade), upgrade_package),
exclude_newer,
python,
}
Expand Down
Loading

0 comments on commit 1445669

Please sign in to comment.