Skip to content

Commit

Permalink
Un-cache editable requirements with dynamic metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Feb 28, 2024
1 parent 8214bfe commit 6b1e0c5
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 34 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions crates/uv-installer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ once-map = { path = "../once-map" }
pep440_rs = { path = "../pep440-rs" }
pep508_rs = { path = "../pep508-rs" }
platform-tags = { path = "../platform-tags" }
pypi-types = { path = "../pypi-types" }
requirements-txt = { path = "../requirements-txt" }
uv-build = { path = "../uv-build" }
uv-cache = { path = "../uv-cache" }
uv-client = { path = "../uv-client" }
uv-distribution = { path = "../uv-distribution" }
Expand All @@ -29,8 +32,6 @@ uv-git = { path = "../uv-git", features = ["vendored-openssl"] }
uv-interpreter = { path = "../uv-interpreter" }
uv-normalize = { path = "../uv-normalize" }
uv-traits = { path = "../uv-traits" }
pypi-types = { path = "../pypi-types" }
requirements-txt = { path = "../requirements-txt" }

anyhow = { workspace = true }
fs-err = { workspace = true }
Expand All @@ -40,5 +41,6 @@ rustc-hash = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }
32 changes: 32 additions & 0 deletions crates/uv-installer/src/editable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ use distribution_types::{
CachedDist, InstalledDist, InstalledMetadata, InstalledVersion, LocalEditable, Name,
};
use pypi_types::Metadata21;
use requirements_txt::EditableRequirement;
use uv_build::PyProjectToml;
use uv_cache::ArchiveTimestamp;
use uv_normalize::PackageName;

/// An editable distribution that has been built.
Expand All @@ -22,6 +25,35 @@ pub enum ResolvedEditable {
Built(BuiltEditable),
}

/// Returns `true` if the installed distribution is up-to-date with the [`EditableRequirement`].
pub fn not_modified(editable: &EditableRequirement, installed: &InstalledDist) -> bool {
let Ok(Some(installed_at)) = ArchiveTimestamp::from_path(installed.path().join("METADATA"))
else {
return false;
};
let Ok(Some(modified_at)) = ArchiveTimestamp::from_path(&editable.path) else {
return false;
};
installed_at > modified_at
}

/// Returns `true` if the [`EditableRequirement`] contains dynamic metadata.
pub fn is_dynamic(editable: &EditableRequirement) -> bool {
// If there's no `pyproject.toml`, we assume it's dynamic.
let Ok(contents) = fs_err::read_to_string(editable.path.join("pyproject.toml")) else {
return true;
};
let Ok(pyproject_toml) = toml::from_str::<PyProjectToml>(&contents) else {
return true;
};
// If `[project]` is not present, we assume it's dynamic.
let Some(project) = pyproject_toml.project else {
return true;
};
// `[project.dynamic]` must be present and non-empty.
project.dynamic.is_some_and(|dynamic| !dynamic.is_empty())
}

impl Name for BuiltEditable {
fn name(&self) -> &PackageName {
&self.metadata.name
Expand Down
4 changes: 2 additions & 2 deletions crates/uv-installer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
pub use downloader::{Downloader, Reporter as DownloadReporter};
pub use editable::{BuiltEditable, ResolvedEditable};
pub use editable::{is_dynamic, not_modified, BuiltEditable, ResolvedEditable};
pub use installer::{Installer, Reporter as InstallReporter};
pub use plan::{Plan, Planner, Reinstall};
// TODO(zanieb): Just import this properly everywhere else
pub use site_packages::SitePackages;
pub use uninstall::uninstall;
pub use uv_traits::NoBinary;

mod downloader;
mod editable;
mod installer;
Expand Down
17 changes: 7 additions & 10 deletions crates/uv-installer/src/site_packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ use distribution_types::{InstalledDist, InstalledMetadata, InstalledVersion, Nam
use pep440_rs::{Version, VersionSpecifiers};
use pep508_rs::{Requirement, VerbatimUrl};
use requirements_txt::EditableRequirement;
use uv_cache::ArchiveTimestamp;
use uv_interpreter::Virtualenv;
use uv_normalize::PackageName;

use crate::{is_dynamic, not_modified};

/// An index over the packages installed in an environment.
///
/// Packages are indexed by both name and (for editable installs) URL.
Expand Down Expand Up @@ -275,16 +276,12 @@ impl<'a> SitePackages<'a> {
}
[distribution] => {
// Is the editable out-of-date?
let Ok(Some(installed_at)) =
ArchiveTimestamp::from_path(distribution.path().join("METADATA"))
else {
return Ok(false);
};
let Ok(Some(modified_at)) = ArchiveTimestamp::from_path(&requirement.path)
else {
if !not_modified(requirement, distribution) {
return Ok(false);
};
if modified_at > installed_at {
}

// Does the editable have dynamic metadata?
if is_dynamic(requirement) {
return Ok(false);
}

Expand Down
27 changes: 7 additions & 20 deletions crates/uv/src/commands/pip_sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,19 @@ use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::debug;

use distribution_types::{
IndexLocations, InstalledDist, InstalledMetadata, LocalDist, LocalEditable, Name,
};
use distribution_types::{IndexLocations, InstalledMetadata, LocalDist, LocalEditable, Name};
use install_wheel_rs::linker::LinkMode;
use platform_host::Platform;
use platform_tags::Tags;
use pypi_types::Yanked;
use requirements_txt::EditableRequirement;
use uv_cache::{ArchiveTimestamp, Cache};
use uv_cache::Cache;
use uv_client::{Connectivity, FlatIndex, FlatIndexClient, RegistryClient, RegistryClientBuilder};
use uv_dispatch::BuildDispatch;
use uv_fs::Normalized;
use uv_installer::{
Downloader, NoBinary, Plan, Planner, Reinstall, ResolvedEditable, SitePackages,
is_dynamic, not_modified, Downloader, NoBinary, Plan, Planner, Reinstall, ResolvedEditable,
SitePackages,
};
use uv_interpreter::Virtualenv;
use uv_resolver::InMemoryIndex;
Expand Down Expand Up @@ -393,18 +392,6 @@ async fn resolve_editables(
build_dispatch: &BuildDispatch<'_>,
mut printer: Printer,
) -> Result<ResolvedEditables> {
/// Returns `true` if the installed distribution is up-to-date.
fn not_modified(editable: &EditableRequirement, installed: &InstalledDist) -> bool {
let Ok(Some(installed_at)) = ArchiveTimestamp::from_path(installed.path().join("METADATA"))
else {
return false;
};
let Ok(Some(modified_at)) = ArchiveTimestamp::from_path(&editable.path) else {
return false;
};
installed_at > modified_at
}

// Partition the editables into those that are already installed, and those that must be built.
let mut installed = Vec::with_capacity(editables.len());
let mut uninstalled = Vec::with_capacity(editables.len());
Expand All @@ -415,7 +402,7 @@ async fn resolve_editables(
match existing.as_slice() {
[] => uninstalled.push(editable),
[dist] => {
if not_modified(&editable, dist) {
if not_modified(&editable, dist) && !is_dynamic(&editable) {
installed.push((*dist).clone());
} else {
uninstalled.push(editable);
Expand All @@ -433,10 +420,10 @@ async fn resolve_editables(
let existing = site_packages.get_editables(editable.raw());
match existing.as_slice() {
[] => uninstalled.push(editable),
[dist] if not_modified(&editable, dist) => {
[dist] => {
if packages.contains(dist.name()) {
uninstalled.push(editable);
} else if not_modified(&editable, dist) {
} else if not_modified(&editable, dist) && !is_dynamic(&editable) {
installed.push((*dist).clone());
} else {
uninstalled.push(editable);
Expand Down

0 comments on commit 6b1e0c5

Please sign in to comment.