Support conflicting editable settings across groups

This commit is contained in:
Charlie Marsh 2025-06-21 22:07:48 -04:00
parent e104eea34a
commit 6d32ab671f
23 changed files with 538 additions and 216 deletions

View file

@ -124,7 +124,10 @@ impl SourceUrl<'_> {
pub fn is_editable(&self) -> bool {
matches!(
self,
Self::Directory(DirectorySourceUrl { editable: true, .. })
Self::Directory(DirectorySourceUrl {
editable: Some(true),
..
})
)
}
@ -210,7 +213,7 @@ impl<'a> From<&'a PathSourceDist> for PathSourceUrl<'a> {
pub struct DirectorySourceUrl<'a> {
pub url: &'a DisplaySafeUrl,
pub install_path: Cow<'a, Path>,
pub editable: bool,
pub editable: Option<bool>,
}
impl std::fmt::Display for DirectorySourceUrl<'_> {

View file

@ -343,9 +343,9 @@ pub struct DirectorySourceDist {
/// The absolute path to the distribution which we use for installing.
pub install_path: Box<Path>,
/// Whether the package should be installed in editable mode.
pub editable: bool,
pub editable: Option<bool>,
/// Whether the package should be built and installed.
pub r#virtual: bool,
pub r#virtual: Option<bool>,
/// The URL as it was provided by the user.
pub url: VerbatimUrl,
}
@ -452,8 +452,8 @@ impl Dist {
name: PackageName,
url: VerbatimUrl,
install_path: &Path,
editable: bool,
r#virtual: bool,
editable: Option<bool>,
r#virtual: Option<bool>,
) -> Result<Dist, Error> {
// Convert to an absolute path.
let install_path = path::absolute(install_path)?;
@ -655,7 +655,7 @@ impl SourceDist {
/// Returns `true` if the distribution is editable.
pub fn is_editable(&self) -> bool {
match self {
Self::Directory(DirectorySourceDist { editable, .. }) => *editable,
Self::Directory(DirectorySourceDist { editable, .. }) => editable.unwrap_or(false),
_ => false,
}
}
@ -663,7 +663,7 @@ impl SourceDist {
/// Returns `true` if the distribution is virtual.
pub fn is_virtual(&self) -> bool {
match self {
Self::Directory(DirectorySourceDist { r#virtual, .. }) => *r#virtual,
Self::Directory(DirectorySourceDist { r#virtual, .. }) => r#virtual.unwrap_or(false),
_ => false,
}
}

View file

@ -429,9 +429,9 @@ pub enum RequirementSource {
/// The absolute path to the distribution which we use for installing.
install_path: Box<Path>,
/// For a source tree (a directory), whether to install as an editable.
editable: bool,
editable: Option<bool>,
/// For a source tree (a directory), whether the project should be built and installed.
r#virtual: bool,
r#virtual: Option<bool>,
/// The PEP 508 style URL in the format
/// `file:///<path>#subdirectory=<subdirectory>`.
url: VerbatimUrl,
@ -545,7 +545,13 @@ impl RequirementSource {
/// Returns `true` if the source is editable.
pub fn is_editable(&self) -> bool {
matches!(self, Self::Directory { editable: true, .. })
matches!(
self,
Self::Directory {
editable: Some(true),
..
}
)
}
/// Returns `true` if the source is empty.
@ -792,11 +798,11 @@ impl From<RequirementSource> for RequirementSourceWire {
r#virtual,
url: _,
} => {
if editable {
if editable.unwrap_or(false) {
Self::Editable {
editable: PortablePathBuf::from(install_path),
}
} else if r#virtual {
} else if r#virtual.unwrap_or(false) {
Self::Virtual {
r#virtual: PortablePathBuf::from(install_path),
}
@ -908,8 +914,8 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
))?;
Ok(Self::Directory {
install_path: directory,
editable: false,
r#virtual: false,
editable: Some(false),
r#virtual: Some(false),
url,
})
}
@ -920,8 +926,8 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
))?;
Ok(Self::Directory {
install_path: editable,
editable: true,
r#virtual: false,
editable: Some(true),
r#virtual: Some(false),
url,
})
}
@ -932,8 +938,8 @@ impl TryFrom<RequirementSourceWire> for RequirementSource {
))?;
Ok(Self::Directory {
install_path: r#virtual,
editable: false,
r#virtual: true,
editable: Some(false),
r#virtual: Some(true),
url,
})
}
@ -980,8 +986,8 @@ mod tests {
marker: MarkerTree::TRUE,
source: RequirementSource::Directory {
install_path: PathBuf::from(path).into_boxed_path(),
editable: false,
r#virtual: false,
editable: Some(false),
r#virtual: Some(false),
url: VerbatimUrl::from_absolute_path(path).unwrap(),
},
origin: None,

View file

@ -119,7 +119,7 @@ impl<'a> BuiltWheelIndex<'a> {
) -> Result<Option<CachedWheel>, Error> {
let cache_shard = self.cache.shard(
CacheBucket::SourceDistributions,
if source_dist.editable {
if source_dist.editable.unwrap_or(false) {
WheelCache::Editable(&source_dist.url).root()
} else {
WheelCache::Path(&source_dist.url).root()

View file

@ -310,15 +310,15 @@ impl LoweredRequirement {
RequirementSource::Directory {
install_path: install_path.into_boxed_path(),
url,
editable: true,
r#virtual: false,
editable: Some(true),
r#virtual: Some(false),
}
} else {
RequirementSource::Directory {
install_path: install_path.into_boxed_path(),
url,
editable: false,
r#virtual: true,
editable: Some(false),
r#virtual: Some(true),
}
};
(source, marker)
@ -724,8 +724,8 @@ fn path_source(
Ok(RequirementSource::Directory {
install_path: install_path.into_boxed_path(),
url,
editable: true,
r#virtual: false,
editable,
r#virtual: Some(false),
})
} else {
// Determine whether the project is a package or virtual.
@ -738,12 +738,14 @@ fn path_source(
.unwrap_or(true)
});
// If the project is not a package, treat it as a virtual dependency.
let r#virtual = !is_package;
Ok(RequirementSource::Directory {
install_path: install_path.into_boxed_path(),
url,
editable: false,
// If a project is not a package, treat it as a virtual dependency.
r#virtual: !is_package,
editable: Some(false),
r#virtual: Some(r#virtual),
})
}
} else {

View file

@ -1070,7 +1070,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let cache_shard = self.build_context.cache().shard(
CacheBucket::SourceDistributions,
if resource.editable {
if resource.editable.unwrap_or(false) {
WheelCache::Editable(resource.url).root()
} else {
WheelCache::Path(resource.url).root()
@ -1183,7 +1183,7 @@ impl<'a, T: BuildContext> SourceDistributionBuilder<'a, T> {
let cache_shard = self.build_context.cache().shard(
CacheBucket::SourceDistributions,
if resource.editable {
if resource.editable.unwrap_or(false) {
WheelCache::Editable(resource.url).root()
} else {
WheelCache::Path(resource.url).root()

View file

@ -241,7 +241,7 @@ impl RequirementSatisfaction {
return Self::Mismatch;
};
if *requested_editable != installed_editable.unwrap_or_default() {
if requested_editable != installed_editable {
trace!(
"Editable mismatch: {:?} vs. {:?}",
*requested_editable,

View file

@ -86,8 +86,8 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
ParsedUrl::Directory(ParsedDirectoryUrl {
url,
install_path,
editable: false,
r#virtual: false,
editable: None,
r#virtual: None,
})
} else {
ParsedUrl::Path(ParsedPathUrl {
@ -118,8 +118,8 @@ impl UnnamedRequirementUrl for VerbatimParsedUrl {
ParsedUrl::Directory(ParsedDirectoryUrl {
url,
install_path,
editable: false,
r#virtual: false,
editable: None,
r#virtual: None,
})
} else {
ParsedUrl::Path(ParsedPathUrl {
@ -187,7 +187,10 @@ impl ParsedUrl {
pub fn is_editable(&self) -> bool {
matches!(
self,
Self::Directory(ParsedDirectoryUrl { editable: true, .. })
Self::Directory(ParsedDirectoryUrl {
editable: Some(true),
..
})
)
}
}
@ -226,16 +229,18 @@ pub struct ParsedDirectoryUrl {
pub url: DisplaySafeUrl,
/// The absolute path to the distribution which we use for installing.
pub install_path: Box<Path>,
pub editable: bool,
pub r#virtual: bool,
/// Whether the project at the given URL should be installed in editable mode.
pub editable: Option<bool>,
/// Whether the project at the given URL should be treated as a virtual package.
pub r#virtual: Option<bool>,
}
impl ParsedDirectoryUrl {
/// Construct a [`ParsedDirectoryUrl`] from a path requirement source.
pub fn from_source(
install_path: Box<Path>,
editable: bool,
r#virtual: bool,
editable: Option<bool>,
r#virtual: Option<bool>,
url: DisplaySafeUrl,
) -> Self {
Self {
@ -399,8 +404,8 @@ impl TryFrom<DisplaySafeUrl> for ParsedUrl {
Ok(Self::Directory(ParsedDirectoryUrl {
url,
install_path: path.into_boxed_path(),
editable: false,
r#virtual: false,
editable: None,
r#virtual: None,
}))
} else {
Ok(Self::Path(ParsedPathUrl {
@ -445,7 +450,7 @@ impl From<&ParsedDirectoryUrl> for DirectUrl {
Self::LocalDirectory {
url: value.url.to_string(),
dir_info: DirInfo {
editable: value.editable.then_some(true),
editable: value.editable,
},
subdirectory: None,
}

View file

@ -2064,8 +2064,10 @@ mod test {
fragment: None,
},
install_path: "/foo/bar",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {

View file

@ -90,7 +90,7 @@ impl RequirementsTxtRequirement {
version_or_url: Some(uv_pep508::VersionOrUrl::Url(VerbatimParsedUrl {
verbatim: url.verbatim,
parsed_url: ParsedUrl::Directory(ParsedDirectoryUrl {
editable: true,
editable: Some(true),
..parsed_url
}),
})),
@ -115,7 +115,7 @@ impl RequirementsTxtRequirement {
url: VerbatimParsedUrl {
verbatim: requirement.url.verbatim,
parsed_url: ParsedUrl::Directory(ParsedDirectoryUrl {
editable: true,
editable: Some(true),
..parsed_url
}),
},

View file

@ -22,8 +22,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -72,8 +72,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -126,8 +126,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "/scripts/packages/black_editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -176,8 +176,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -226,8 +226,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -276,8 +276,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {

View file

@ -24,8 +24,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -81,8 +83,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -138,8 +142,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -195,8 +201,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -252,8 +260,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -302,8 +312,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable[d",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -352,8 +364,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -402,8 +416,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {

View file

@ -22,8 +22,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -72,8 +72,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -126,8 +126,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black_editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -176,8 +176,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -226,8 +226,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -276,8 +276,8 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/scripts/packages/black editable",
editable: false,
virtual: false,
editable: None,
virtual: None,
},
),
verbatim: VerbatimUrl {

View file

@ -24,8 +24,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -81,8 +83,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -138,8 +142,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -195,8 +201,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -252,8 +260,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -302,8 +312,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable[d",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -352,8 +364,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {
@ -402,8 +416,10 @@ RequirementsTxt {
fragment: None,
},
install_path: "<REQUIREMENTS_DIR>/editable",
editable: true,
virtual: false,
editable: Some(
true,
),
virtual: None,
},
),
verbatim: VerbatimUrl {

View file

@ -154,7 +154,7 @@ impl<'a, Context: BuildContext> SourceTreeResolver<'a, Context> {
let source = SourceUrl::Directory(DirectorySourceUrl {
url: &url,
install_path: Cow::Borrowed(source_tree),
editable: false,
editable: None,
});
// Determine the hash policy. Since we don't have a package name, we perform a

View file

@ -500,7 +500,7 @@ impl<'lock> PylockToml {
.unwrap_or_else(|_| dist.install_path.clone());
package.directory = Some(PylockTomlDirectory {
path: PortablePathBuf::from(path),
editable: if dist.editable { Some(true) } else { None },
editable: dist.editable,
subdirectory: None,
});
}
@ -737,7 +737,7 @@ impl<'lock> PylockToml {
),
editable: match editable {
EditableMode::NonEditable => None,
EditableMode::Editable => Some(sdist.editable),
EditableMode::Editable => sdist.editable,
},
subdirectory: None,
}),
@ -1394,8 +1394,8 @@ impl PylockTomlDirectory {
Ok(DirectorySourceDist {
name: name.clone(),
install_path: path.into_boxed_path(),
editable: self.editable.unwrap_or(false),
r#virtual: false,
editable: self.editable,
r#virtual: Some(false),
url,
})
}

View file

@ -2402,8 +2402,8 @@ impl Package {
name: self.id.name.clone(),
url: verbatim_url(&install_path, &self.id)?,
install_path: install_path.into_boxed_path(),
editable: false,
r#virtual: false,
editable: Some(false),
r#virtual: Some(false),
};
uv_distribution_types::SourceDist::Directory(dir_dist)
}
@ -2413,8 +2413,8 @@ impl Package {
name: self.id.name.clone(),
url: verbatim_url(&install_path, &self.id)?,
install_path: install_path.into_boxed_path(),
editable: true,
r#virtual: false,
editable: Some(true),
r#virtual: Some(false),
};
uv_distribution_types::SourceDist::Directory(dir_dist)
}
@ -2424,8 +2424,8 @@ impl Package {
name: self.id.name.clone(),
url: verbatim_url(&install_path, &self.id)?,
install_path: install_path.into_boxed_path(),
editable: false,
r#virtual: true,
editable: Some(false),
r#virtual: Some(true),
};
uv_distribution_types::SourceDist::Directory(dir_dist)
}
@ -3256,9 +3256,9 @@ impl Source {
let path = relative_to(&directory_dist.install_path, root)
.or_else(|_| std::path::absolute(&directory_dist.install_path))
.map_err(LockErrorKind::DistributionRelativePath)?;
if directory_dist.editable {
if directory_dist.editable.unwrap_or(false) {
Ok(Source::Editable(path.into_boxed_path()))
} else if directory_dist.r#virtual {
} else if directory_dist.r#virtual.unwrap_or(false) {
Ok(Source::Virtual(path.into_boxed_path()))
} else {
Ok(Source::Directory(path.into_boxed_path()))
@ -4806,8 +4806,8 @@ fn normalize_requirement(
marker: requires_python.simplify_markers(requirement.marker),
source: RequirementSource::Directory {
install_path,
editable,
r#virtual,
editable: Some(editable.unwrap_or(false)),
r#virtual: Some(r#virtual.unwrap_or(false)),
url,
},
origin: None,

View file

@ -619,6 +619,7 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
&state.python_requirement,
&state.pubgrub,
)?;
match forked_deps {
ForkedDependencies::Unavailable(reason) => {
// Then here, if we get a reason that we consider unrecoverable, we should

View file

@ -63,9 +63,9 @@ impl Urls {
verbatim: _,
} = package_url
{
if !*editable {
if editable.is_none() {
debug!("Allowing an editable variant of {}", &package_url.verbatim);
*editable = true;
*editable = Some(true);
}
}
}
@ -201,8 +201,9 @@ fn same_resource(a: &ParsedUrl, b: &ParsedUrl, git: &GitResolver) -> bool {
|| is_same_file(&a.install_path, &b.install_path).unwrap_or(false)
}
(ParsedUrl::Directory(a), ParsedUrl::Directory(b)) => {
a.install_path == b.install_path
|| is_same_file(&a.install_path, &b.install_path).unwrap_or(false)
(a.install_path == b.install_path
|| is_same_file(&a.install_path, &b.install_path).unwrap_or(false))
&& a.editable.is_none_or(|a| b.editable.is_none_or(|b| a == b))
}
_ => false,
}

View file

@ -315,15 +315,15 @@ impl Workspace {
source: if member.pyproject_toml.is_package() {
RequirementSource::Directory {
install_path: member.root.clone().into_boxed_path(),
editable: true,
r#virtual: false,
editable: Some(true),
r#virtual: Some(false),
url,
}
} else {
RequirementSource::Directory {
install_path: member.root.clone().into_boxed_path(),
editable: false,
r#virtual: true,
editable: Some(false),
r#virtual: Some(true),
url,
}
},
@ -371,15 +371,15 @@ impl Workspace {
source: if member.pyproject_toml.is_package() {
RequirementSource::Directory {
install_path: member.root.clone().into_boxed_path(),
editable: true,
r#virtual: false,
editable: Some(true),
r#virtual: Some(false),
url,
}
} else {
RequirementSource::Directory {
install_path: member.root.clone().into_boxed_path(),
editable: false,
r#virtual: true,
editable: Some(false),
r#virtual: Some(true),
url,
}
},

View file

@ -802,7 +802,7 @@ fn apply_no_virtual_project(resolution: Resolution) -> Resolution {
return true;
};
!dist.r#virtual
!dist.r#virtual.unwrap_or(false)
})
}
@ -820,8 +820,8 @@ fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolu
let Dist::Source(SourceDist::Directory(DirectorySourceDist {
name,
install_path,
editable: true,
r#virtual: false,
editable: Some(true),
r#virtual,
url,
})) = dist.as_ref()
else {
@ -832,8 +832,8 @@ fn apply_editable_mode(resolution: Resolution, editable: EditableMode) -> Resolu
dist: Arc::new(Dist::Source(SourceDist::Directory(DirectorySourceDist {
name: name.clone(),
install_path: install_path.clone(),
editable: false,
r#virtual: false,
editable: Some(false),
r#virtual: *r#virtual,
url: url.clone(),
}))),
version: version.clone(),

View file

@ -10944,7 +10944,7 @@ fn lock_sources_source_tree() -> Result<()> {
}
/// Lock a project in which a given dependency is requested from two different members, once as
/// editable, and once as non-editable.
/// editable, and once as non-editable. This should trigger a conflicting URL error.
#[test]
fn lock_editable() -> Result<()> {
let context = TestContext::new("3.12");
@ -11084,86 +11084,16 @@ fn lock_editable() -> Result<()> {
library = { path = "../../library", editable = true }
"#})?;
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
uv_snapshot!(context.filters(), context.lock(), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
"###);
let lock = context.read("uv.lock");
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r#"
version = 1
revision = 2
requires-python = ">=3.12"
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[manifest]
members = [
"leaf",
"workspace",
]
[[package]]
name = "leaf"
version = "0.1.0"
source = { editable = "packages/leaf" }
dependencies = [
{ name = "library" },
]
[package.metadata]
requires-dist = [{ name = "library", editable = "library" }]
[[package]]
name = "library"
version = "0.1.0"
source = { editable = "library" }
[[package]]
name = "workspace"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "library" },
]
[package.metadata]
requires-dist = [{ name = "library", directory = "library" }]
"#
);
});
// Re-run with `--locked`.
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
"###);
// Install from the lockfile.
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ library==0.1.0 (from file://[TEMP_DIR]/library)
"###);
error: Requirements contain conflicting URLs for package `library` in all marker environments:
- file://[TEMP_DIR]/library
- file://[TEMP_DIR]/library (editable)
");
Ok(())
}

View file

@ -9989,3 +9989,343 @@ fn sync_url_with_query_parameters() -> Result<()> {
Ok(())
}
/// See: <https://github.com/astral-sh/uv/issues/11648>
#[test]
#[cfg(not(windows))]
fn conflicting_editable() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[dependency-groups]
foo = [
"child",
]
bar = [
"child",
]
[tool.uv]
conflicts = [
[
{ group = "foo" },
{ group = "bar" },
],
]
[tool.uv.sources]
child = [
{ path = "./child", editable = true, group = "foo" },
{ path = "./child", editable = false, group = "bar" },
]
"#,
)?;
context
.temp_dir
.child("child")
.child("pyproject.toml")
.write_str(
r#"
[project]
name = "child"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"#,
)?;
context
.temp_dir
.child("child")
.child("src")
.child("child")
.child("__init__.py")
.touch()?;
uv_snapshot!(context.filters(), context.sync(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Audited in [TIME]
");
let lock = context.read("uv.lock");
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r#"
version = 1
revision = 2
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "bar" },
{ package = "project", group = "foo" },
]]
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "child"
version = "0.1.0"
source = { directory = "child" }
[[package]]
name = "child"
version = "0.1.0"
source = { editable = "child" }
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
[package.dev-dependencies]
bar = [
{ name = "child", version = "0.1.0", source = { directory = "child" } },
]
foo = [
{ name = "child", version = "0.1.0", source = { editable = "child" } },
]
[package.metadata]
[package.metadata.requires-dev]
bar = [{ name = "child", directory = "child" }]
foo = [{ name = "child", editable = "child" }]
"#
);
});
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ child==0.1.0 (from file://[TEMP_DIR]/child)
");
uv_snapshot!(context.filters(), context.pip_list().arg("--format").arg("json"), @r#"
success: true
exit_code: 0
----- stdout -----
[{"name":"child","version":"0.1.0","editable_project_location":"[TEMP_DIR]/child"}]
----- stderr -----
"#);
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ child==0.1.0 (from file://[TEMP_DIR]/child)
");
uv_snapshot!(context.filters(), context.pip_list().arg("--format").arg("json"), @r#"
success: true
exit_code: 0
----- stdout -----
[{"name":"child","version":"0.1.0"}]
----- stderr -----
"#);
Ok(())
}
/// See: <https://github.com/astral-sh/uv/issues/11648>
#[test]
#[cfg(not(windows))]
fn undeclared_editable() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[dependency-groups]
foo = [
"child",
]
bar = [
"child",
]
[tool.uv]
conflicts = [
[
{ group = "foo" },
{ group = "bar" },
],
]
[tool.uv.sources]
child = [
{ path = "./child", editable = true, group = "foo" },
{ path = "./child", group = "bar" },
]
"#,
)?;
context
.temp_dir
.child("child")
.child("pyproject.toml")
.write_str(
r#"
[project]
name = "child"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = []
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
"#,
)?;
context
.temp_dir
.child("child")
.child("src")
.child("child")
.child("__init__.py")
.touch()?;
uv_snapshot!(context.filters(), context.sync(), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Audited in [TIME]
");
let lock = context.read("uv.lock");
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r#"
version = 1
revision = 2
requires-python = ">=3.12"
conflicts = [[
{ package = "project", group = "bar" },
{ package = "project", group = "foo" },
]]
[options]
exclude-newer = "2024-03-25T00:00:00Z"
[[package]]
name = "child"
version = "0.1.0"
source = { directory = "child" }
[[package]]
name = "child"
version = "0.1.0"
source = { editable = "child" }
[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
[package.dev-dependencies]
bar = [
{ name = "child", version = "0.1.0", source = { directory = "child" } },
]
foo = [
{ name = "child", version = "0.1.0", source = { editable = "child" } },
]
[package.metadata]
[package.metadata.requires-dev]
bar = [{ name = "child", directory = "child" }]
foo = [{ name = "child", editable = "child" }]
"#
);
});
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 1 package in [TIME]
Installed 1 package in [TIME]
+ child==0.1.0 (from file://[TEMP_DIR]/child)
");
uv_snapshot!(context.filters(), context.pip_list().arg("--format").arg("json"), @r#"
success: true
exit_code: 0
----- stdout -----
[{"name":"child","version":"0.1.0","editable_project_location":"[TEMP_DIR]/child"}]
----- stderr -----
"#);
uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Prepared 1 package in [TIME]
Uninstalled 1 package in [TIME]
Installed 1 package in [TIME]
~ child==0.1.0 (from file://[TEMP_DIR]/child)
");
uv_snapshot!(context.filters(), context.pip_list().arg("--format").arg("json"), @r#"
success: true
exit_code: 0
----- stdout -----
[{"name":"child","version":"0.1.0"}]
----- stderr -----
"#);
Ok(())
}