diff --git a/crates/distribution-types/src/resolution.rs b/crates/distribution-types/src/resolution.rs index b7b76a98aae4..faf3ef59787a 100644 --- a/crates/distribution-types/src/resolution.rs +++ b/crates/distribution-types/src/resolution.rs @@ -1,8 +1,12 @@ use rustc_hash::FxHashMap; +use pep508_rs::VerbatimUrl; use uv_normalize::PackageName; -use crate::{BuiltDist, Dist, Name, Requirement, RequirementSource, ResolvedDist, SourceDist}; +use crate::{ + BuiltDist, DirectorySourceDist, Dist, InstalledDirectUrlDist, InstalledDist, LocalEditable, + Name, Requirement, RequirementSource, ResolvedDist, SourceDist, +}; /// A set of packages pinned at specific versions. #[derive(Debug, Default, Clone)] @@ -68,6 +72,35 @@ impl Resolution { requirements.sort_unstable_by(|a, b| a.name.cmp(&b.name)); requirements } + + /// Return an iterator over the [`LocalEditable`] entities in this resolution. + pub fn editables(&self) -> impl Iterator + '_ { + self.0.values().filter_map(|dist| match dist { + ResolvedDist::Installable(Dist::Source(SourceDist::Directory( + DirectorySourceDist { + path, + url, + editable: true, + .. + }, + ))) => Some(LocalEditable { + url: url.clone(), + path: path.clone(), + extras: vec![], + }), + ResolvedDist::Installed(InstalledDist::Url(InstalledDirectUrlDist { + path, + url, + editable: true, + .. + })) => Some(LocalEditable { + url: VerbatimUrl::from_url(url.clone()), + path: path.clone(), + extras: vec![], + }), + _ => None, + }) + } } impl From<&ResolvedDist> for Requirement { diff --git a/crates/uv-configuration/src/package_options.rs b/crates/uv-configuration/src/package_options.rs index 17cc90520aca..5a87d2432cbf 100644 --- a/crates/uv-configuration/src/package_options.rs +++ b/crates/uv-configuration/src/package_options.rs @@ -3,9 +3,10 @@ use pep508_rs::PackageName; use rustc_hash::FxHashSet; /// Whether to reinstall packages. -#[derive(Debug, Clone)] +#[derive(Debug, Default, Clone)] pub enum Reinstall { /// Don't reinstall any packages; respect the existing installation. + #[default] None, /// Reinstall all packages in the plan. diff --git a/crates/uv-resolver/src/lock.rs b/crates/uv-resolver/src/lock.rs index 3a1791160931..d97b25bf03b7 100644 --- a/crates/uv-resolver/src/lock.rs +++ b/crates/uv-resolver/src/lock.rs @@ -277,6 +277,9 @@ impl Distribution { SourceKind::Directory => { unreachable!("Wheels cannot come from directory sources") } + SourceKind::Editable => { + unreachable!("Wheels cannot come from editable sources") + } }; } @@ -301,6 +304,16 @@ impl Distribution { let source_dist = distribution_types::SourceDist::Directory(dir_dist); Dist::Source(source_dist) } + SourceKind::Editable => { + let dir_dist = DirectorySourceDist { + name: self.id.name.clone(), + url: VerbatimUrl::from_url(self.id.source.url.clone()), + path: self.id.source.url.to_file_path().unwrap(), + editable: true, + }; + let source_dist = distribution_types::SourceDist::Directory(dir_dist); + Dist::Source(source_dist) + } SourceKind::Git(git) => { // Reconstruct the `GitUrl` from the `GitSource`. let git_url = uv_git::GitUrl::new( @@ -513,7 +526,11 @@ impl Source { fn from_directory_source_dist(directory_dist: &DirectorySourceDist) -> Source { Source { - kind: SourceKind::Directory, + kind: if directory_dist.editable { + SourceKind::Editable + } else { + SourceKind::Directory + }, url: directory_dist.url.to_url(), } } @@ -583,6 +600,10 @@ impl std::str::FromStr for Source { kind: SourceKind::Directory, url, }), + "editable" => Ok(Source { + kind: SourceKind::Editable, + url, + }), name => Err(SourceParseError::unrecognized_source_name(s, name)), } } @@ -624,6 +645,7 @@ pub(crate) enum SourceKind { Direct(DirectSource), Path, Directory, + Editable, } impl SourceKind { @@ -634,6 +656,7 @@ impl SourceKind { SourceKind::Direct(_) => "direct", SourceKind::Path => "path", SourceKind::Directory => "directory", + SourceKind::Editable => "editable", } } @@ -644,7 +667,7 @@ impl SourceKind { fn requires_hash(&self) -> bool { match *self { SourceKind::Registry | SourceKind::Direct(_) | SourceKind::Path => true, - SourceKind::Git(_) | SourceKind::Directory => false, + SourceKind::Git(_) | SourceKind::Directory | SourceKind::Editable => false, } } } diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index ac8457c196d5..c7b825187165 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -5,7 +5,7 @@ use install_wheel_rs::linker::LinkMode; use uv_cache::Cache; use uv_client::RegistryClientBuilder; use uv_configuration::{ - Concurrency, ConfigSettings, NoBinary, NoBuild, PreviewMode, SetupPyStrategy, + Concurrency, ConfigSettings, NoBinary, NoBuild, PreviewMode, Reinstall, SetupPyStrategy, }; use uv_dispatch::BuildDispatch; use uv_installer::SitePackages; @@ -65,6 +65,7 @@ pub(crate) async fn sync( let no_build = NoBuild::default(); let setup_py = SetupPyStrategy::default(); let concurrency = Concurrency::default(); + let reinstall = Reinstall::default(); // Create a build dispatch. let build_dispatch = BuildDispatch::new( @@ -84,8 +85,23 @@ pub(crate) async fn sync( concurrency, ); - // TODO(konsti): Read editables from lockfile. - let editables = ResolvedEditables::default(); + let site_packages = SitePackages::from_executable(&venv)?; + + // Build any editables. + let editables = ResolvedEditables::resolve( + resolution.editables(), + &site_packages, + &reinstall, + &hasher, + venv.interpreter(), + tags, + cache, + &client, + &build_dispatch, + concurrency, + printer, + ) + .await?; let site_packages = SitePackages::from_executable(&venv)?;