mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-20 03:49:54 +00:00
Build and install workspace members that are dependencies by default (#14663)
Regardless of the presence of a build system, as in https://github.com/astral-sh/uv/pull/14413 --------- Co-authored-by: John Mumm <jtfmumm@gmail.com>
This commit is contained in:
parent
0077f2357f
commit
cd40a34522
15 changed files with 791 additions and 67 deletions
|
|
@ -306,7 +306,10 @@ impl LoweredRequirement {
|
||||||
},
|
},
|
||||||
url,
|
url,
|
||||||
}
|
}
|
||||||
} else if member.pyproject_toml().is_package() {
|
} else if member
|
||||||
|
.pyproject_toml()
|
||||||
|
.is_package(!workspace.is_required_member(&requirement.name))
|
||||||
|
{
|
||||||
RequirementSource::Directory {
|
RequirementSource::Directory {
|
||||||
install_path: install_path.into_boxed_path(),
|
install_path: install_path.into_boxed_path(),
|
||||||
url,
|
url,
|
||||||
|
|
@ -736,7 +739,8 @@ fn path_source(
|
||||||
fs_err::read_to_string(&pyproject_path)
|
fs_err::read_to_string(&pyproject_path)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|contents| PyProjectToml::from_string(contents).ok())
|
.and_then(|contents| PyProjectToml::from_string(contents).ok())
|
||||||
.and_then(|pyproject_toml| pyproject_toml.tool_uv_package())
|
// We don't require a build system for path dependencies
|
||||||
|
.map(|pyproject_toml| pyproject_toml.is_package(false))
|
||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -771,7 +771,7 @@ mod tests {
|
||||||
/// A reference list can be generated with:
|
/// A reference list can be generated with:
|
||||||
/// ```text
|
/// ```text
|
||||||
/// $ python -c "from packaging import tags; [print(tag) for tag in tags.platform_tags()]"`
|
/// $ python -c "from packaging import tags; [print(tag) for tag in tags.platform_tags()]"`
|
||||||
/// ````
|
/// ```
|
||||||
#[test]
|
#[test]
|
||||||
fn test_platform_tags_manylinux() {
|
fn test_platform_tags_manylinux() {
|
||||||
let tags = compatible_tags(&Platform::new(
|
let tags = compatible_tags(&Platform::new(
|
||||||
|
|
|
||||||
|
|
@ -1255,6 +1255,7 @@ impl Lock {
|
||||||
root: &Path,
|
root: &Path,
|
||||||
packages: &BTreeMap<PackageName, WorkspaceMember>,
|
packages: &BTreeMap<PackageName, WorkspaceMember>,
|
||||||
members: &[PackageName],
|
members: &[PackageName],
|
||||||
|
required_members: &BTreeSet<PackageName>,
|
||||||
requirements: &[Requirement],
|
requirements: &[Requirement],
|
||||||
constraints: &[Requirement],
|
constraints: &[Requirement],
|
||||||
overrides: &[Requirement],
|
overrides: &[Requirement],
|
||||||
|
|
@ -1282,7 +1283,10 @@ impl Lock {
|
||||||
// Validate that the member sources have not changed (e.g., that they've switched from
|
// Validate that the member sources have not changed (e.g., that they've switched from
|
||||||
// virtual to non-virtual or vice versa).
|
// virtual to non-virtual or vice versa).
|
||||||
for (name, member) in packages {
|
for (name, member) in packages {
|
||||||
let expected = !member.pyproject_toml().is_package();
|
// We don't require a build system, if the workspace member is a dependency
|
||||||
|
let expected = !member
|
||||||
|
.pyproject_toml()
|
||||||
|
.is_package(!required_members.contains(name));
|
||||||
let actual = self
|
let actual = self
|
||||||
.find_by_name(name)
|
.find_by_name(name)
|
||||||
.ok()
|
.ok()
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ pub struct PyProjectToml {
|
||||||
|
|
||||||
/// Used to determine whether a `build-system` section is present.
|
/// Used to determine whether a `build-system` section is present.
|
||||||
#[serde(default, skip_serializing)]
|
#[serde(default, skip_serializing)]
|
||||||
build_system: Option<serde::de::IgnoredAny>,
|
pub build_system: Option<serde::de::IgnoredAny>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PyProjectToml {
|
impl PyProjectToml {
|
||||||
|
|
@ -81,18 +81,18 @@ impl PyProjectToml {
|
||||||
|
|
||||||
/// Returns `true` if the project should be considered a Python package, as opposed to a
|
/// Returns `true` if the project should be considered a Python package, as opposed to a
|
||||||
/// non-package ("virtual") project.
|
/// non-package ("virtual") project.
|
||||||
pub fn is_package(&self) -> bool {
|
pub fn is_package(&self, require_build_system: bool) -> bool {
|
||||||
// If `tool.uv.package` is set, defer to that explicit setting.
|
// If `tool.uv.package` is set, defer to that explicit setting.
|
||||||
if let Some(is_package) = self.tool_uv_package() {
|
if let Some(is_package) = self.tool_uv_package() {
|
||||||
return is_package;
|
return is_package;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, a project is assumed to be a package if `build-system` is present.
|
// Otherwise, a project is assumed to be a package if `build-system` is present.
|
||||||
self.build_system.is_some()
|
self.build_system.is_some() || !require_build_system
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the value of `tool.uv.package` if set.
|
/// Returns the value of `tool.uv.package` if set.
|
||||||
pub fn tool_uv_package(&self) -> Option<bool> {
|
fn tool_uv_package(&self) -> Option<bool> {
|
||||||
self.tool
|
self.tool
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|tool| tool.uv.as_ref())
|
.and_then(|tool| tool.uv.as_ref())
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use uv_warnings::warn_user_once;
|
||||||
|
|
||||||
use crate::dependency_groups::{DependencyGroupError, FlatDependencyGroup, FlatDependencyGroups};
|
use crate::dependency_groups::{DependencyGroupError, FlatDependencyGroup, FlatDependencyGroups};
|
||||||
use crate::pyproject::{
|
use crate::pyproject::{
|
||||||
Project, PyProjectToml, PyprojectTomlError, Sources, ToolUvSources, ToolUvWorkspace,
|
Project, PyProjectToml, PyprojectTomlError, Source, Sources, ToolUvSources, ToolUvWorkspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
type WorkspaceMembers = Arc<BTreeMap<PackageName, WorkspaceMember>>;
|
type WorkspaceMembers = Arc<BTreeMap<PackageName, WorkspaceMember>>;
|
||||||
|
|
@ -109,6 +109,8 @@ pub struct Workspace {
|
||||||
install_path: PathBuf,
|
install_path: PathBuf,
|
||||||
/// The members of the workspace.
|
/// The members of the workspace.
|
||||||
packages: WorkspaceMembers,
|
packages: WorkspaceMembers,
|
||||||
|
/// The workspace members that are required by other members.
|
||||||
|
required_members: BTreeSet<PackageName>,
|
||||||
/// The sources table from the workspace `pyproject.toml`.
|
/// The sources table from the workspace `pyproject.toml`.
|
||||||
///
|
///
|
||||||
/// This table is overridden by the project sources.
|
/// This table is overridden by the project sources.
|
||||||
|
|
@ -260,6 +262,7 @@ impl Workspace {
|
||||||
pyproject_toml: PyProjectToml,
|
pyproject_toml: PyProjectToml,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let mut packages = self.packages;
|
let mut packages = self.packages;
|
||||||
|
|
||||||
let member = Arc::make_mut(&mut packages).get_mut(package_name)?;
|
let member = Arc::make_mut(&mut packages).get_mut(package_name)?;
|
||||||
|
|
||||||
if member.root == self.install_path {
|
if member.root == self.install_path {
|
||||||
|
|
@ -279,17 +282,33 @@ impl Workspace {
|
||||||
// Set the `pyproject.toml` for the member.
|
// Set the `pyproject.toml` for the member.
|
||||||
member.pyproject_toml = pyproject_toml;
|
member.pyproject_toml = pyproject_toml;
|
||||||
|
|
||||||
|
// Recompute required_members with the updated data
|
||||||
|
let required_members = Self::collect_required_members(
|
||||||
|
&packages,
|
||||||
|
&workspace_sources,
|
||||||
|
&workspace_pyproject_toml,
|
||||||
|
);
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
pyproject_toml: workspace_pyproject_toml,
|
pyproject_toml: workspace_pyproject_toml,
|
||||||
sources: workspace_sources,
|
sources: workspace_sources,
|
||||||
packages,
|
packages,
|
||||||
|
required_members,
|
||||||
..self
|
..self
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Set the `pyproject.toml` for the member.
|
// Set the `pyproject.toml` for the member.
|
||||||
member.pyproject_toml = pyproject_toml;
|
member.pyproject_toml = pyproject_toml;
|
||||||
|
|
||||||
Some(Self { packages, ..self })
|
// Recompute required_members with the updated member data
|
||||||
|
let required_members =
|
||||||
|
Self::collect_required_members(&packages, &self.sources, &self.pyproject_toml);
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
packages,
|
||||||
|
required_members,
|
||||||
|
..self
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -303,7 +322,7 @@ impl Workspace {
|
||||||
|
|
||||||
/// Returns the set of all workspace members.
|
/// Returns the set of all workspace members.
|
||||||
pub fn members_requirements(&self) -> impl Iterator<Item = Requirement> + '_ {
|
pub fn members_requirements(&self) -> impl Iterator<Item = Requirement> + '_ {
|
||||||
self.packages.values().filter_map(|member| {
|
self.packages.iter().filter_map(|(name, member)| {
|
||||||
let url = VerbatimUrl::from_absolute_path(&member.root)
|
let url = VerbatimUrl::from_absolute_path(&member.root)
|
||||||
.expect("path is valid URL")
|
.expect("path is valid URL")
|
||||||
.with_given(member.root.to_string_lossy());
|
.with_given(member.root.to_string_lossy());
|
||||||
|
|
@ -312,7 +331,10 @@ impl Workspace {
|
||||||
extras: Box::new([]),
|
extras: Box::new([]),
|
||||||
groups: Box::new([]),
|
groups: Box::new([]),
|
||||||
marker: MarkerTree::TRUE,
|
marker: MarkerTree::TRUE,
|
||||||
source: if member.pyproject_toml.is_package() {
|
source: if member
|
||||||
|
.pyproject_toml()
|
||||||
|
.is_package(!self.is_required_member(name))
|
||||||
|
{
|
||||||
RequirementSource::Directory {
|
RequirementSource::Directory {
|
||||||
install_path: member.root.clone().into_boxed_path(),
|
install_path: member.root.clone().into_boxed_path(),
|
||||||
editable: Some(true),
|
editable: Some(true),
|
||||||
|
|
@ -332,9 +354,65 @@ impl Workspace {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The workspace members that are required my another member of the workspace.
|
||||||
|
pub fn required_members(&self) -> &BTreeSet<PackageName> {
|
||||||
|
&self.required_members
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the workspace members that are required by another member of the workspace.
|
||||||
|
///
|
||||||
|
/// N.B. this checks if a workspace member is required by inspecting `tool.uv.source` entries,
|
||||||
|
/// but does not actually check if the source is _used_, which could result in false positives
|
||||||
|
/// but is easier to compute.
|
||||||
|
fn collect_required_members(
|
||||||
|
packages: &BTreeMap<PackageName, WorkspaceMember>,
|
||||||
|
sources: &BTreeMap<PackageName, Sources>,
|
||||||
|
pyproject_toml: &PyProjectToml,
|
||||||
|
) -> BTreeSet<PackageName> {
|
||||||
|
sources
|
||||||
|
.iter()
|
||||||
|
.filter(|(name, _)| {
|
||||||
|
pyproject_toml
|
||||||
|
.project
|
||||||
|
.as_ref()
|
||||||
|
.is_none_or(|project| project.name != **name)
|
||||||
|
})
|
||||||
|
.chain(
|
||||||
|
packages
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(name, member)| {
|
||||||
|
member
|
||||||
|
.pyproject_toml
|
||||||
|
.tool
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|tool| tool.uv.as_ref())
|
||||||
|
.and_then(|uv| uv.sources.as_ref())
|
||||||
|
.map(ToolUvSources::inner)
|
||||||
|
.map(move |sources| {
|
||||||
|
sources
|
||||||
|
.iter()
|
||||||
|
.filter(move |(source_name, _)| name != *source_name)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
)
|
||||||
|
.filter_map(|(package, sources)| {
|
||||||
|
sources
|
||||||
|
.iter()
|
||||||
|
.any(|source| matches!(source, Source::Workspace { .. }))
|
||||||
|
.then_some(package.clone())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a given workspace member is required by another member.
|
||||||
|
pub fn is_required_member(&self, name: &PackageName) -> bool {
|
||||||
|
self.required_members().contains(name)
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the set of all workspace member dependency groups.
|
/// Returns the set of all workspace member dependency groups.
|
||||||
pub fn group_requirements(&self) -> impl Iterator<Item = Requirement> + '_ {
|
pub fn group_requirements(&self) -> impl Iterator<Item = Requirement> + '_ {
|
||||||
self.packages.values().filter_map(|member| {
|
self.packages.iter().filter_map(|(name, member)| {
|
||||||
let url = VerbatimUrl::from_absolute_path(&member.root)
|
let url = VerbatimUrl::from_absolute_path(&member.root)
|
||||||
.expect("path is valid URL")
|
.expect("path is valid URL")
|
||||||
.with_given(member.root.to_string_lossy());
|
.with_given(member.root.to_string_lossy());
|
||||||
|
|
@ -368,7 +446,10 @@ impl Workspace {
|
||||||
extras: Box::new([]),
|
extras: Box::new([]),
|
||||||
groups: groups.into_boxed_slice(),
|
groups: groups.into_boxed_slice(),
|
||||||
marker: MarkerTree::TRUE,
|
marker: MarkerTree::TRUE,
|
||||||
source: if member.pyproject_toml.is_package() {
|
source: if member
|
||||||
|
.pyproject_toml()
|
||||||
|
.is_package(!self.is_required_member(name))
|
||||||
|
{
|
||||||
RequirementSource::Directory {
|
RequirementSource::Directory {
|
||||||
install_path: member.root.clone().into_boxed_path(),
|
install_path: member.root.clone().into_boxed_path(),
|
||||||
editable: Some(true),
|
editable: Some(true),
|
||||||
|
|
@ -746,9 +827,16 @@ impl Workspace {
|
||||||
.and_then(|uv| uv.index)
|
.and_then(|uv| uv.index)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let required_members = Self::collect_required_members(
|
||||||
|
&workspace_members,
|
||||||
|
&workspace_sources,
|
||||||
|
&workspace_pyproject_toml,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(Workspace {
|
Ok(Workspace {
|
||||||
install_path: workspace_root,
|
install_path: workspace_root,
|
||||||
packages: workspace_members,
|
packages: workspace_members,
|
||||||
|
required_members,
|
||||||
sources: workspace_sources,
|
sources: workspace_sources,
|
||||||
indexes: workspace_indexes,
|
indexes: workspace_indexes,
|
||||||
pyproject_toml: workspace_pyproject_toml,
|
pyproject_toml: workspace_pyproject_toml,
|
||||||
|
|
@ -1232,15 +1320,23 @@ impl ProjectWorkspace {
|
||||||
project.name.clone(),
|
project.name.clone(),
|
||||||
current_project,
|
current_project,
|
||||||
)]));
|
)]));
|
||||||
|
let workspace_sources = BTreeMap::default();
|
||||||
|
let required_members = Workspace::collect_required_members(
|
||||||
|
¤t_project_as_members,
|
||||||
|
&workspace_sources,
|
||||||
|
project_pyproject_toml,
|
||||||
|
);
|
||||||
|
|
||||||
return Ok(Self {
|
return Ok(Self {
|
||||||
project_root: project_path.clone(),
|
project_root: project_path.clone(),
|
||||||
project_name: project.name.clone(),
|
project_name: project.name.clone(),
|
||||||
workspace: Workspace {
|
workspace: Workspace {
|
||||||
install_path: project_path.clone(),
|
install_path: project_path.clone(),
|
||||||
packages: current_project_as_members,
|
packages: current_project_as_members,
|
||||||
|
required_members,
|
||||||
// There may be package sources, but we don't need to duplicate them into the
|
// There may be package sources, but we don't need to duplicate them into the
|
||||||
// workspace sources.
|
// workspace sources.
|
||||||
sources: BTreeMap::default(),
|
sources: workspace_sources,
|
||||||
indexes: Vec::default(),
|
indexes: Vec::default(),
|
||||||
pyproject_toml: project_pyproject_toml.clone(),
|
pyproject_toml: project_pyproject_toml.clone(),
|
||||||
},
|
},
|
||||||
|
|
@ -1692,6 +1788,7 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [],
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"pyproject_toml": {
|
"pyproject_toml": {
|
||||||
|
|
@ -1745,6 +1842,7 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [],
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"pyproject_toml": {
|
"pyproject_toml": {
|
||||||
|
|
@ -1825,6 +1923,10 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [
|
||||||
|
"bird-feeder",
|
||||||
|
"seeds"
|
||||||
|
],
|
||||||
"sources": {
|
"sources": {
|
||||||
"bird-feeder": [
|
"bird-feeder": [
|
||||||
{
|
{
|
||||||
|
|
@ -1946,6 +2048,10 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [
|
||||||
|
"bird-feeder",
|
||||||
|
"seeds"
|
||||||
|
],
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"pyproject_toml": {
|
"pyproject_toml": {
|
||||||
|
|
@ -2013,6 +2119,7 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [],
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"pyproject_toml": {
|
"pyproject_toml": {
|
||||||
|
|
@ -2147,6 +2254,7 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [],
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"pyproject_toml": {
|
"pyproject_toml": {
|
||||||
|
|
@ -2254,6 +2362,7 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [],
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"pyproject_toml": {
|
"pyproject_toml": {
|
||||||
|
|
@ -2375,6 +2484,7 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [],
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"pyproject_toml": {
|
"pyproject_toml": {
|
||||||
|
|
@ -2470,6 +2580,7 @@ mod tests {
|
||||||
"pyproject_toml": "[PYPROJECT_TOML]"
|
"pyproject_toml": "[PYPROJECT_TOML]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"required_members": [],
|
||||||
"sources": {},
|
"sources": {},
|
||||||
"indexes": [],
|
"indexes": [],
|
||||||
"pyproject_toml": {
|
"pyproject_toml": {
|
||||||
|
|
|
||||||
|
|
@ -263,7 +263,7 @@ async fn build_impl(
|
||||||
.get(package)
|
.get(package)
|
||||||
.ok_or_else(|| anyhow::anyhow!("Package `{package}` not found in workspace"))?;
|
.ok_or_else(|| anyhow::anyhow!("Package `{package}` not found in workspace"))?;
|
||||||
|
|
||||||
if !package.pyproject_toml().is_package() {
|
if !package.pyproject_toml().is_package(true) {
|
||||||
let name = &package.project().name;
|
let name = &package.project().name;
|
||||||
let pyproject_toml = package.root().join("pyproject.toml");
|
let pyproject_toml = package.root().join("pyproject.toml");
|
||||||
return Err(anyhow::anyhow!(
|
return Err(anyhow::anyhow!(
|
||||||
|
|
@ -300,7 +300,7 @@ async fn build_impl(
|
||||||
let packages: Vec<_> = workspace
|
let packages: Vec<_> = workspace
|
||||||
.packages()
|
.packages()
|
||||||
.values()
|
.values()
|
||||||
.filter(|package| package.pyproject_toml().is_package())
|
.filter(|package| package.pyproject_toml().is_package(true))
|
||||||
.map(|package| AnnotatedSource {
|
.map(|package| AnnotatedSource {
|
||||||
source: Source::Directory(Cow::Borrowed(package.root())),
|
source: Source::Directory(Cow::Borrowed(package.root())),
|
||||||
package: Some(package.project().name.clone()),
|
package: Some(package.project().name.clone()),
|
||||||
|
|
|
||||||
|
|
@ -444,6 +444,7 @@ async fn do_lock(
|
||||||
// Collect the requirements, etc.
|
// Collect the requirements, etc.
|
||||||
let members = target.members();
|
let members = target.members();
|
||||||
let packages = target.packages();
|
let packages = target.packages();
|
||||||
|
let required_members = target.required_members();
|
||||||
let requirements = target.requirements();
|
let requirements = target.requirements();
|
||||||
let overrides = target.overrides();
|
let overrides = target.overrides();
|
||||||
let constraints = target.constraints();
|
let constraints = target.constraints();
|
||||||
|
|
@ -693,6 +694,7 @@ async fn do_lock(
|
||||||
target.install_path(),
|
target.install_path(),
|
||||||
packages,
|
packages,
|
||||||
&members,
|
&members,
|
||||||
|
required_members,
|
||||||
&requirements,
|
&requirements,
|
||||||
&dependency_groups,
|
&dependency_groups,
|
||||||
&constraints,
|
&constraints,
|
||||||
|
|
@ -906,6 +908,7 @@ impl ValidatedLock {
|
||||||
install_path: &Path,
|
install_path: &Path,
|
||||||
packages: &BTreeMap<PackageName, WorkspaceMember>,
|
packages: &BTreeMap<PackageName, WorkspaceMember>,
|
||||||
members: &[PackageName],
|
members: &[PackageName],
|
||||||
|
required_members: &BTreeSet<PackageName>,
|
||||||
requirements: &[Requirement],
|
requirements: &[Requirement],
|
||||||
dependency_groups: &BTreeMap<GroupName, Vec<Requirement>>,
|
dependency_groups: &BTreeMap<GroupName, Vec<Requirement>>,
|
||||||
constraints: &[Requirement],
|
constraints: &[Requirement],
|
||||||
|
|
@ -1117,6 +1120,7 @@ impl ValidatedLock {
|
||||||
install_path,
|
install_path,
|
||||||
packages,
|
packages,
|
||||||
members,
|
members,
|
||||||
|
required_members,
|
||||||
requirements,
|
requirements,
|
||||||
constraints,
|
constraints,
|
||||||
overrides,
|
overrides,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::collections::BTreeMap;
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
|
|
@ -154,6 +154,18 @@ impl<'lock> LockTarget<'lock> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return the set of required workspace members, i.e., those that are required by other
|
||||||
|
/// members.
|
||||||
|
pub(crate) fn required_members(self) -> &'lock BTreeSet<PackageName> {
|
||||||
|
match self {
|
||||||
|
Self::Workspace(workspace) => workspace.required_members(),
|
||||||
|
Self::Script(_) => {
|
||||||
|
static EMPTY: BTreeSet<PackageName> = BTreeSet::new();
|
||||||
|
&EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the set of supported environments for the [`LockTarget`].
|
/// Returns the set of supported environments for the [`LockTarget`].
|
||||||
pub(crate) fn environments(self) -> Option<&'lock SupportedEnvironments> {
|
pub(crate) fn environments(self) -> Option<&'lock SupportedEnvironments> {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ pub(crate) async fn sync(
|
||||||
// TODO(lucab): improve warning content
|
// TODO(lucab): improve warning content
|
||||||
// <https://github.com/astral-sh/uv/issues/7428>
|
// <https://github.com/astral-sh/uv/issues/7428>
|
||||||
if project.workspace().pyproject_toml().has_scripts()
|
if project.workspace().pyproject_toml().has_scripts()
|
||||||
&& !project.workspace().pyproject_toml().is_package()
|
&& !project.workspace().pyproject_toml().is_package(true)
|
||||||
{
|
{
|
||||||
warn_user!(
|
warn_user!(
|
||||||
"Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system`"
|
"Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system`"
|
||||||
|
|
|
||||||
|
|
@ -10362,7 +10362,7 @@ fn add_self() -> Result<()> {
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
}, {
|
}, {
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
pyproject_toml, @r###"
|
pyproject_toml, @r#"
|
||||||
[project]
|
[project]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -10377,7 +10377,7 @@ fn add_self() -> Result<()> {
|
||||||
|
|
||||||
[tool.uv.sources]
|
[tool.uv.sources]
|
||||||
anyio = { workspace = true }
|
anyio = { workspace = true }
|
||||||
"###
|
"#
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -10398,7 +10398,7 @@ fn add_self() -> Result<()> {
|
||||||
filters => context.filters(),
|
filters => context.filters(),
|
||||||
}, {
|
}, {
|
||||||
assert_snapshot!(
|
assert_snapshot!(
|
||||||
pyproject_toml, @r###"
|
pyproject_toml, @r#"
|
||||||
[project]
|
[project]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
@ -10418,7 +10418,7 @@ fn add_self() -> Result<()> {
|
||||||
dev = [
|
dev = [
|
||||||
"anyio[types]",
|
"anyio[types]",
|
||||||
]
|
]
|
||||||
"###
|
"#
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -13173,7 +13173,9 @@ fn add_path_with_existing_workspace() -> Result<()> {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Added `dep` to workspace members
|
Added `dep` to workspace members
|
||||||
Resolved 3 packages in [TIME]
|
Resolved 3 packages in [TIME]
|
||||||
Audited in [TIME]
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ dep==0.1.0 (from file://[TEMP_DIR]/dep)
|
||||||
");
|
");
|
||||||
|
|
||||||
let pyproject_toml = context.read("pyproject.toml");
|
let pyproject_toml = context.read("pyproject.toml");
|
||||||
|
|
@ -13250,7 +13252,9 @@ fn add_path_with_workspace() -> Result<()> {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Added `dep` to workspace members
|
Added `dep` to workspace members
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Audited in [TIME]
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ dep==0.1.0 (from file://[TEMP_DIR]/dep)
|
||||||
");
|
");
|
||||||
|
|
||||||
let pyproject_toml = context.read("pyproject.toml");
|
let pyproject_toml = context.read("pyproject.toml");
|
||||||
|
|
@ -13316,7 +13320,9 @@ fn add_path_within_workspace_defaults_to_workspace() -> Result<()> {
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Added `dep` to workspace members
|
Added `dep` to workspace members
|
||||||
Resolved 2 packages in [TIME]
|
Resolved 2 packages in [TIME]
|
||||||
Audited in [TIME]
|
Prepared 1 package in [TIME]
|
||||||
|
Installed 1 package in [TIME]
|
||||||
|
+ dep==0.1.0 (from file://[TEMP_DIR]/dep)
|
||||||
");
|
");
|
||||||
|
|
||||||
let pyproject_toml = context.read("pyproject.toml");
|
let pyproject_toml = context.read("pyproject.toml");
|
||||||
|
|
|
||||||
|
|
@ -12064,10 +12064,6 @@ fn lock_remove_member() -> Result<()> {
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = ["leaf"]
|
dependencies = ["leaf"]
|
||||||
|
|
||||||
[build-system]
|
|
||||||
requires = ["setuptools>=42"]
|
|
||||||
build-backend = "setuptools.build_meta"
|
|
||||||
|
|
||||||
[tool.uv.workspace]
|
[tool.uv.workspace]
|
||||||
members = ["leaf"]
|
members = ["leaf"]
|
||||||
|
|
||||||
|
|
@ -12130,7 +12126,7 @@ fn lock_remove_member() -> Result<()> {
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leaf"
|
name = "leaf"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "leaf" }
|
source = { editable = "leaf" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
]
|
]
|
||||||
|
|
@ -12141,13 +12137,13 @@ fn lock_remove_member() -> Result<()> {
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "project"
|
name = "project"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { editable = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "leaf" },
|
{ name = "leaf" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata]
|
[package.metadata]
|
||||||
requires-dist = [{ name = "leaf", virtual = "leaf" }]
|
requires-dist = [{ name = "leaf", editable = "leaf" }]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sniffio"
|
name = "sniffio"
|
||||||
|
|
@ -12162,16 +12158,124 @@ fn lock_remove_member() -> Result<()> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Re-run with `--locked`.
|
// Re-run with `--locked`.
|
||||||
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
|
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// Remove the member.
|
// Remove the member as a dependency (retain it as a workspace member)
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[tool.uv.workspace]
|
||||||
|
members = ["leaf"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
leaf = { workspace = true }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Re-run with `--locked`. This should fail.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 5 packages in [TIME]
|
||||||
|
The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.
|
||||||
|
");
|
||||||
|
|
||||||
|
// Re-run without `--locked`.
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 5 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",
|
||||||
|
"project",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "sniffio" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leaf"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "leaf" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "anyio", specifier = ">3" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||||
|
]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove the member entirely
|
||||||
pyproject_toml.write_str(
|
pyproject_toml.write_str(
|
||||||
r#"
|
r#"
|
||||||
[project]
|
[project]
|
||||||
|
|
@ -12238,7 +12342,7 @@ fn lock_remove_member() -> Result<()> {
|
||||||
/// This test would fail if we didn't write the list of workspace members to the lockfile, since
|
/// This test would fail if we didn't write the list of workspace members to the lockfile, since
|
||||||
/// we wouldn't be able to determine that a new member was added.
|
/// we wouldn't be able to determine that a new member was added.
|
||||||
#[test]
|
#[test]
|
||||||
fn lock_add_member() -> Result<()> {
|
fn lock_add_member_with_build_system() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
// Create a workspace, but don't add the member.
|
// Create a workspace, but don't add the member.
|
||||||
|
|
@ -12449,6 +12553,339 @@ fn lock_add_member() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lock_add_member_without_build_system() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
// Create a workspace, but don't add the member.
|
||||||
|
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 = []
|
||||||
|
|
||||||
|
[tool.uv.workspace]
|
||||||
|
members = []
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package 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"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-run with `--locked`.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 1 package in [TIME]
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Create a workspace member.
|
||||||
|
let leaf = context.temp_dir.child("leaf");
|
||||||
|
leaf.child("pyproject.toml").write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "leaf"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["anyio>3"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Add the member to the workspace, but not as a dependency of the root.
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[tool.uv.workspace]
|
||||||
|
members = ["leaf"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Re-run with `--locked`. This should fail.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 5 packages in [TIME]
|
||||||
|
The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.
|
||||||
|
");
|
||||||
|
|
||||||
|
// Re-run with `--offline`. This should also fail, during the resolve phase.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--offline").arg("--no-cache"), @r###"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
× No solution found when resolving dependencies:
|
||||||
|
╰─▶ Because anyio was not found in the cache and leaf depends on anyio>3, we can conclude that leaf's requirements are unsatisfiable.
|
||||||
|
And because your workspace requires leaf, we can conclude that your workspace's requirements are unsatisfiable.
|
||||||
|
|
||||||
|
hint: Packages were unavailable because the network was disabled. When the network is disabled, registry packages may only be read from the cache.
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Re-run without `--locked`.
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 5 packages in [TIME]
|
||||||
|
Added anyio v4.3.0
|
||||||
|
Added idna v3.6
|
||||||
|
Added leaf v0.1.0
|
||||||
|
Added sniffio v1.3.1
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// Re-run with `--locked`.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r###"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 5 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",
|
||||||
|
"project",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "sniffio" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leaf"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "leaf" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "anyio", specifier = ">3" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||||
|
]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the member to the workspace, as a dependency of the root.
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["leaf"]
|
||||||
|
|
||||||
|
[tool.uv.workspace]
|
||||||
|
members = ["leaf"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
leaf = { workspace = true }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Re-run with `--locked`. This should fail.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r"
|
||||||
|
success: false
|
||||||
|
exit_code: 1
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 5 packages in [TIME]
|
||||||
|
The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`.
|
||||||
|
");
|
||||||
|
|
||||||
|
// Re-run without `--locked`.
|
||||||
|
uv_snapshot!(context.filters(), context.lock(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 5 packages in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
// Re-run with `--locked`.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 5 packages in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
let lock = context.read("uv.lock");
|
||||||
|
|
||||||
|
// It should change from a virtual to an editable source
|
||||||
|
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",
|
||||||
|
"project",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyio"
|
||||||
|
version = "4.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "sniffio" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "leaf"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "leaf" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "anyio" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "anyio", specifier = ">3" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "leaf" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "leaf", editable = "leaf" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sniffio"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
|
||||||
|
]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Lock a `pyproject.toml`, then add a dependency that's already included in the resolution.
|
/// Lock a `pyproject.toml`, then add a dependency that's already included in the resolution.
|
||||||
/// In theory, we shouldn't need to re-resolve, but based on our current strategy, we don't accept
|
/// In theory, we shouldn't need to re-resolve, but based on our current strategy, we don't accept
|
||||||
/// the existing lockfile.
|
/// the existing lockfile.
|
||||||
|
|
|
||||||
|
|
@ -1094,18 +1094,19 @@ fn extra_unconditional() -> Result<()> {
|
||||||
"###);
|
"###);
|
||||||
// This is fine because we are only enabling one
|
// This is fine because we are only enabling one
|
||||||
// extra, and thus, there is no conflict.
|
// extra, and thus, there is no conflict.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Prepared 3 packages in [TIME]
|
Prepared 4 packages in [TIME]
|
||||||
Installed 3 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.1.0
|
+ anyio==4.1.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
+ proxy1==0.1.0 (from file://[TEMP_DIR]/proxy1)
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// And same thing for the other extra.
|
// And same thing for the other extra.
|
||||||
root_pyproject_toml.write_str(
|
root_pyproject_toml.write_str(
|
||||||
|
|
@ -1215,18 +1216,19 @@ fn extra_unconditional_non_conflicting() -> Result<()> {
|
||||||
// `uv sync` wasn't correctly propagating extras in a way
|
// `uv sync` wasn't correctly propagating extras in a way
|
||||||
// that would satisfy the conflict markers that got added
|
// that would satisfy the conflict markers that got added
|
||||||
// to the `proxy1[extra1]` dependency.
|
// to the `proxy1[extra1]` dependency.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Prepared 3 packages in [TIME]
|
Prepared 4 packages in [TIME]
|
||||||
Installed 3 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.1.0
|
+ anyio==4.1.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
+ proxy1==0.1.0 (from file://[TEMP_DIR]/proxy1)
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -1301,16 +1303,17 @@ fn extra_unconditional_in_optional() -> Result<()> {
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
// This should install `sortedcontainers==2.3.0`.
|
// This should install `sortedcontainers==2.3.0`.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra=x1"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra=x1"), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Prepared 1 package in [TIME]
|
Prepared 2 packages in [TIME]
|
||||||
Installed 1 package in [TIME]
|
Installed 2 packages in [TIME]
|
||||||
|
+ proxy1==0.1.0 (from file://[TEMP_DIR]/proxy1)
|
||||||
+ sortedcontainers==2.3.0
|
+ sortedcontainers==2.3.0
|
||||||
"###);
|
");
|
||||||
|
|
||||||
// This should install `sortedcontainers==2.4.0`.
|
// This should install `sortedcontainers==2.4.0`.
|
||||||
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra=x2"), @r###"
|
uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra=x2"), @r###"
|
||||||
|
|
@ -4460,19 +4463,20 @@ conflicts = [
|
||||||
error: Extra `x2` is not defined in the project's `optional-dependencies` table
|
error: Extra `x2` is not defined in the project's `optional-dependencies` table
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
uv_snapshot!(context.filters(), context.sync(), @r###"
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
success: true
|
success: true
|
||||||
exit_code: 0
|
exit_code: 0
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 7 packages in [TIME]
|
Resolved 7 packages in [TIME]
|
||||||
Prepared 3 packages in [TIME]
|
Prepared 4 packages in [TIME]
|
||||||
Installed 3 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
|
+ proxy1==0.1.0 (from file://[TEMP_DIR]/proxy1)
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
"###);
|
");
|
||||||
|
|
||||||
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();
|
||||||
insta::with_settings!({
|
insta::with_settings!({
|
||||||
|
|
@ -4558,14 +4562,14 @@ conflicts = [
|
||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "anyio", specifier = ">=4" },
|
{ name = "anyio", specifier = ">=4" },
|
||||||
{ name = "idna", marker = "extra == 'x1'", specifier = "==3.6" },
|
{ name = "idna", marker = "extra == 'x1'", specifier = "==3.6" },
|
||||||
{ name = "proxy1", virtual = "proxy1" },
|
{ name = "proxy1", editable = "proxy1" },
|
||||||
]
|
]
|
||||||
provides-extras = ["x1"]
|
provides-extras = ["x1"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proxy1"
|
name = "proxy1"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = { virtual = "proxy1" }
|
source = { editable = "proxy1" }
|
||||||
|
|
||||||
[package.optional-dependencies]
|
[package.optional-dependencies]
|
||||||
x2 = [
|
x2 = [
|
||||||
|
|
|
||||||
|
|
@ -15772,18 +15772,18 @@ fn project_and_group_workspace_inherit() -> Result<()> {
|
||||||
----- stdout -----
|
----- stdout -----
|
||||||
# This file was autogenerated by uv via the following command:
|
# This file was autogenerated by uv via the following command:
|
||||||
# uv pip compile --cache-dir [CACHE_DIR] --group packages/mysubproject/pyproject.toml:foo
|
# uv pip compile --cache-dir [CACHE_DIR] --group packages/mysubproject/pyproject.toml:foo
|
||||||
|
-e file://[TEMP_DIR]/packages/pytest
|
||||||
|
# via mysubproject (packages/mysubproject/pyproject.toml:foo)
|
||||||
|
-e file://[TEMP_DIR]/packages/sniffio
|
||||||
|
# via
|
||||||
|
# mysubproject (packages/mysubproject/pyproject.toml:foo)
|
||||||
|
# anyio
|
||||||
anyio==4.3.0
|
anyio==4.3.0
|
||||||
# via mysubproject (packages/mysubproject/pyproject.toml:foo)
|
# via mysubproject (packages/mysubproject/pyproject.toml:foo)
|
||||||
idna==3.6
|
idna==3.6
|
||||||
# via anyio
|
# via anyio
|
||||||
iniconfig==2.0.0
|
iniconfig==2.0.0
|
||||||
# via mysubproject (packages/mysubproject/pyproject.toml:foo)
|
# via mysubproject (packages/mysubproject/pyproject.toml:foo)
|
||||||
pytest @ file://[TEMP_DIR]/packages/pytest
|
|
||||||
# via mysubproject (packages/mysubproject/pyproject.toml:foo)
|
|
||||||
sniffio @ file://[TEMP_DIR]/packages/sniffio
|
|
||||||
# via
|
|
||||||
# mysubproject (packages/mysubproject/pyproject.toml:foo)
|
|
||||||
# anyio
|
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
|
|
|
||||||
|
|
@ -3565,6 +3565,101 @@ fn sync_ignore_extras_check_when_no_provides_extras() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sync_workspace_members_with_transitive_dependencies() -> Result<()> {
|
||||||
|
let context = TestContext::new("3.12");
|
||||||
|
|
||||||
|
let pyproject_toml = context.temp_dir.child("pyproject.toml");
|
||||||
|
pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[tool.uv.workspace]
|
||||||
|
members = [
|
||||||
|
"packages/*",
|
||||||
|
]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let packages = context.temp_dir.child("packages");
|
||||||
|
packages.create_dir_all()?;
|
||||||
|
|
||||||
|
// Create three workspace members with transitive dependency from
|
||||||
|
// pkg-c -> pkg-b -> pkg-a
|
||||||
|
let pkg_a = packages.child("pkg-a");
|
||||||
|
pkg_a.create_dir_all()?;
|
||||||
|
let pkg_a_pyproject_toml = pkg_a.child("pyproject.toml");
|
||||||
|
pkg_a_pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "pkg-a"
|
||||||
|
version = "0.0.1"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["anyio"]
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let pkg_b = packages.child("pkg-b");
|
||||||
|
pkg_b.create_dir_all()?;
|
||||||
|
let pkg_b_pyproject_toml = pkg_b.child("pyproject.toml");
|
||||||
|
pkg_b_pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "pkg-b"
|
||||||
|
version = "0.0.1"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["pkg-a"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
pkg-a = { workspace = true }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let pkg_c = packages.child("pkg-c");
|
||||||
|
pkg_c.create_dir_all()?;
|
||||||
|
let pkg_c_pyproject_toml = pkg_c.child("pyproject.toml");
|
||||||
|
pkg_c_pyproject_toml.write_str(
|
||||||
|
r#"
|
||||||
|
[project]
|
||||||
|
name = "pkg-c"
|
||||||
|
version = "0.0.1"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = ["pkg-b"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
pkg-b = { workspace = true }
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Syncing should build the two transitive dependencies pkg-a and pkg-b,
|
||||||
|
// but not pkg-c, which is not a dependency.
|
||||||
|
uv_snapshot!(context.filters(), context.sync(), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
Prepared 5 packages in [TIME]
|
||||||
|
Installed 5 packages in [TIME]
|
||||||
|
+ anyio==4.3.0
|
||||||
|
+ idna==3.6
|
||||||
|
+ pkg-a==0.0.1 (from file://[TEMP_DIR]/packages/pkg-a)
|
||||||
|
+ pkg-b==0.0.1 (from file://[TEMP_DIR]/packages/pkg-b)
|
||||||
|
+ sniffio==1.3.1
|
||||||
|
");
|
||||||
|
|
||||||
|
// The lockfile should be valid.
|
||||||
|
uv_snapshot!(context.filters(), context.lock().arg("--check"), @r"
|
||||||
|
success: true
|
||||||
|
exit_code: 0
|
||||||
|
----- stdout -----
|
||||||
|
|
||||||
|
----- stderr -----
|
||||||
|
Resolved 6 packages in [TIME]
|
||||||
|
");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn sync_non_existent_extra_workspace_member() -> Result<()> {
|
fn sync_non_existent_extra_workspace_member() -> Result<()> {
|
||||||
let context = TestContext::new("3.12");
|
let context = TestContext::new("3.12");
|
||||||
|
|
@ -3626,9 +3721,10 @@ fn sync_non_existent_extra_workspace_member() -> Result<()> {
|
||||||
|
|
||||||
----- stderr -----
|
----- stderr -----
|
||||||
Resolved 5 packages in [TIME]
|
Resolved 5 packages in [TIME]
|
||||||
Prepared 3 packages in [TIME]
|
Prepared 4 packages in [TIME]
|
||||||
Installed 3 packages in [TIME]
|
Installed 4 packages in [TIME]
|
||||||
+ anyio==4.3.0
|
+ anyio==4.3.0
|
||||||
|
+ child==0.1.0 (from file://[TEMP_DIR]/child)
|
||||||
+ idna==3.6
|
+ idna==3.6
|
||||||
+ sniffio==1.3.1
|
+ sniffio==1.3.1
|
||||||
");
|
");
|
||||||
|
|
|
||||||
|
|
@ -808,9 +808,9 @@ $ uv add --no-editable ./path/foo
|
||||||
uv allows dependencies to be "virtual", in which the dependency itself is not installed as a
|
uv allows dependencies to be "virtual", in which the dependency itself is not installed as a
|
||||||
[package](./config.md#project-packaging), but its dependencies are.
|
[package](./config.md#project-packaging), but its dependencies are.
|
||||||
|
|
||||||
By default, only workspace members without build systems declared are virtual.
|
By default, dependencies are never virtual.
|
||||||
|
|
||||||
A dependency with a [`path` source](#path) is not virtual unless it explicitly sets
|
A dependency with a [`path` source](#path) can be virtual if it explicitly sets
|
||||||
[`tool.uv.package = false`](../../reference/settings.md#package). Unlike working _in_ the dependent
|
[`tool.uv.package = false`](../../reference/settings.md#package). Unlike working _in_ the dependent
|
||||||
project with uv, the package will be built even if a [build system](./config.md#build-systems) is
|
project with uv, the package will be built even if a [build system](./config.md#build-systems) is
|
||||||
not declared.
|
not declared.
|
||||||
|
|
@ -825,8 +825,8 @@ dependencies = ["bar"]
|
||||||
bar = { path = "../projects/bar", package = false }
|
bar = { path = "../projects/bar", package = false }
|
||||||
```
|
```
|
||||||
|
|
||||||
Similarly, if a dependency sets `tool.uv.package = false`, it can be overridden by declaring
|
If a dependency sets `tool.uv.package = false`, it can be overridden by declaring `package = true`
|
||||||
`package = true` on the source:
|
on the source:
|
||||||
|
|
||||||
```toml title="pyproject.toml"
|
```toml title="pyproject.toml"
|
||||||
[project]
|
[project]
|
||||||
|
|
@ -836,6 +836,52 @@ dependencies = ["bar"]
|
||||||
bar = { path = "../projects/bar", package = true }
|
bar = { path = "../projects/bar", package = true }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Similarly, a dependency with a [`workspace` source](#workspace-member) can be virtual if it
|
||||||
|
explicitly sets [`tool.uv.package = false`](../../reference/settings.md#package). The workspace
|
||||||
|
member will be built even if a [build system](./config.md#build-systems) is not declared.
|
||||||
|
|
||||||
|
Workspace members that are _not_ dependencies can be virtual by default, e.g., if the parent
|
||||||
|
`pyproject.toml` is:
|
||||||
|
|
||||||
|
```toml title="pyproject.toml"
|
||||||
|
[project]
|
||||||
|
name = "parent"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = []
|
||||||
|
|
||||||
|
[tool.uv.workspace]
|
||||||
|
members = ["child"]
|
||||||
|
```
|
||||||
|
|
||||||
|
And the child `pyproject.toml` excluded a build system:
|
||||||
|
|
||||||
|
```toml title="pyproject.toml"
|
||||||
|
[project]
|
||||||
|
name = "child"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = ["anyio"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the `child` workspace member would not be installed, but the transitive dependency `anyio`
|
||||||
|
would be.
|
||||||
|
|
||||||
|
In contrast, if the parent declared a dependency on `child`:
|
||||||
|
|
||||||
|
```toml title="pyproject.toml"
|
||||||
|
[project]
|
||||||
|
name = "parent"
|
||||||
|
version = "1.0.0"
|
||||||
|
dependencies = ["child"]
|
||||||
|
|
||||||
|
[tool.uv.sources]
|
||||||
|
child = { workspace = true }
|
||||||
|
|
||||||
|
[tool.uv.workspace]
|
||||||
|
members = ["child"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Then `child` would be built and installed.
|
||||||
|
|
||||||
## Dependency specifiers
|
## Dependency specifiers
|
||||||
|
|
||||||
uv uses standard
|
uv uses standard
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue