Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid serializing if lockfile does not change #4945

Merged
merged 2 commits into from
Jul 9, 2024
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
19 changes: 13 additions & 6 deletions crates/uv-requirements/src/upgrade.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ pub async fn read_requirements_txt(
})
}

/// Load the preferred requirements from an existing lockfile, applying the upgrade strategy.
pub async fn read_lockfile(workspace: &Workspace, upgrade: &Upgrade) -> Result<LockedRequirements> {
/// Load the lockfile from the workspace.
///
/// Returns `Ok(None)` if the lockfile does not exist, is invalid, or is not required for the given upgrade strategy.
pub async fn read_lockfile(workspace: &Workspace, upgrade: &Upgrade) -> Result<Option<Lock>> {
// As an optimization, skip reading the lockfile is we're upgrading all packages anyway.
if upgrade.is_all() {
return Ok(LockedRequirements::default());
return Ok(None);
}

// If an existing lockfile exists, build up a set of preferences.
Expand All @@ -77,15 +79,20 @@ pub async fn read_lockfile(workspace: &Workspace, upgrade: &Upgrade) -> Result<L
Ok(lock) => lock,
Err(err) => {
eprint!("Failed to parse lockfile; ignoring locked requirements: {err}");
return Ok(LockedRequirements::default());
return Ok(None);
}
},
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Ok(LockedRequirements::default());
return Ok(None);
}
Err(err) => return Err(err.into()),
};

Ok(Some(lock))
}

/// Load the preferred requirements from an existing lockfile, applying the upgrade strategy.
pub fn read_lock_requirements(lock: &Lock, upgrade: &Upgrade) -> LockedRequirements {
let mut preferences = Vec::new();
let mut git = Vec::new();

Expand All @@ -108,5 +115,5 @@ pub async fn read_lockfile(workspace: &Workspace, upgrade: &Upgrade) -> Result<L
}
}

Ok(LockedRequirements { preferences, git })
LockedRequirements { preferences, git }
}
15 changes: 9 additions & 6 deletions crates/uv-resolver/src/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use crate::{RequiresPython, ResolutionGraph};
/// The current version of the lock file format.
const VERSION: u32 = 1;

#[derive(Clone, Debug, serde::Deserialize)]
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
#[serde(try_from = "LockWire")]
pub struct Lock {
version: u32,
Expand Down Expand Up @@ -514,7 +514,7 @@ impl TryFrom<LockWire> for Lock {
}
}

#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Distribution {
pub(crate) id: DistributionId,
sdist: Option<SourceDist>,
Expand Down Expand Up @@ -1321,12 +1321,15 @@ enum SourceWire {
subdirectory: Option<String>,
},
Path {
#[serde(deserialize_with = "deserialize_path_with_dot")]
path: PathBuf,
},
Directory {
#[serde(deserialize_with = "deserialize_path_with_dot")]
directory: PathBuf,
},
Editable {
#[serde(deserialize_with = "deserialize_path_with_dot")]
editable: PathBuf,
},
}
Expand Down Expand Up @@ -1430,7 +1433,7 @@ enum GitSourceKind {
}

/// Inspired by: <https://discuss.python.org/t/lock-files-again-but-this-time-w-sdists/46593>
#[derive(Clone, Debug, serde::Deserialize)]
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
struct SourceDistMetadata {
/// A hash of the source distribution.
hash: Hash,
Expand All @@ -1444,7 +1447,7 @@ struct SourceDistMetadata {
/// locked against was found. The location does not need to exist in the
/// future, so this should be treated as only a hint to where to look
/// and/or recording where the source dist file originally came from.
#[derive(Clone, Debug, serde::Deserialize)]
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
#[serde(untagged)]
enum SourceDist {
Url {
Expand Down Expand Up @@ -1695,7 +1698,7 @@ fn locked_git_url(git_dist: &GitSourceDist) -> Url {
}

/// Inspired by: <https://discuss.python.org/t/lock-files-again-but-this-time-w-sdists/46593>
#[derive(Clone, Debug, serde::Deserialize)]
#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)]
#[serde(try_from = "WheelWire")]
struct Wheel {
/// A URL or file path (via `file://`) where the wheel that was locked
Expand Down Expand Up @@ -2047,7 +2050,7 @@ impl From<Dependency> for DependencyWire {
///
/// A hash is encoded as a single TOML string in the format
/// `{algorithm}:{digest}`.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, PartialEq, Eq)]
struct Hash(HashDigest);

impl From<HashDigest> for Hash {
Expand Down
15 changes: 12 additions & 3 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use uv_dispatch::BuildDispatch;
use uv_distribution::{Workspace, DEV_DEPENDENCIES};
use uv_git::ResolvedRepositoryReference;
use uv_python::{Interpreter, PythonFetch, PythonPreference, PythonRequest};
use uv_requirements::upgrade::{read_lockfile, LockedRequirements};
use uv_requirements::upgrade::{read_lock_requirements, read_lockfile, LockedRequirements};
use uv_resolver::{FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython};
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::{warn_user, warn_user_once};
Expand Down Expand Up @@ -175,7 +175,11 @@ pub(super) async fn do_lock(
};

// If an existing lockfile exists, build up a set of preferences.
let LockedRequirements { preferences, git } = read_lockfile(workspace, upgrade).await?;
let existing_lock = read_lockfile(workspace, upgrade).await?;
let LockedRequirements { preferences, git } = existing_lock
.as_ref()
.map(|lock| read_lock_requirements(lock, upgrade))
.unwrap_or_default();

// Populate the Git resolver.
for ResolvedRepositoryReference { reference, sha } in git {
Expand Down Expand Up @@ -234,8 +238,13 @@ pub(super) async fn do_lock(
// Notify the user of any resolution diagnostics.
pip::operations::diagnose_resolution(resolution.diagnostics(), printer)?;

// Write the lockfile to disk.
// Avoid serializing and writing to disk if the lock hasn't changed.
let lock = Lock::from_resolution_graph(&resolution)?;
if existing_lock.is_some_and(|existing_lock| existing_lock == lock) {
return Ok(lock);
}

// Write the lockfile to disk.
let encoded = lock.to_toml()?;
fs_err::tokio::write(workspace.install_path().join("uv.lock"), encoded.as_bytes()).await?;

Expand Down
Loading