mirror of https://github.com/astral-sh/uv
Refactors
This commit is contained in:
parent
70f3ec15c6
commit
31d031c319
|
|
@ -75,9 +75,7 @@ impl<'lock> Installable<'lock> for InstallTarget<'lock> {
|
|||
match self {
|
||||
Self::Project { name, .. } => Box::new(std::iter::once(*name)),
|
||||
Self::Projects { names, .. } => Box::new(names.iter()),
|
||||
Self::NonProjectWorkspace { lock, .. } => {
|
||||
Box::new(lock.members().iter())
|
||||
}
|
||||
Self::NonProjectWorkspace { lock, .. } => Box::new(lock.members().iter()),
|
||||
Self::Workspace { lock, .. } => {
|
||||
// Identify the workspace members.
|
||||
//
|
||||
|
|
@ -297,9 +295,9 @@ impl<'lock> InstallTarget<'lock> {
|
|||
Err(ProjectError::MissingExtraProject(extra.clone()))
|
||||
}
|
||||
Self::Projects { .. } => {
|
||||
Err(ProjectError::MissingExtraWorkspace(extra.clone()))
|
||||
Err(ProjectError::MissingExtraProjects(extra.clone()))
|
||||
}
|
||||
_ => Err(ProjectError::MissingExtraWorkspace(extra.clone())),
|
||||
_ => Err(ProjectError::MissingExtraProjects(extra.clone())),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -355,7 +353,7 @@ impl<'lock> InstallTarget<'lock> {
|
|||
|
||||
for group in groups.explicit_names() {
|
||||
if !known_groups.contains(group) {
|
||||
return Err(ProjectError::MissingGroupWorkspace(group.clone()));
|
||||
return Err(ProjectError::MissingGroupProjects(group.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -376,8 +374,12 @@ impl<'lock> InstallTarget<'lock> {
|
|||
for group in groups.explicit_names() {
|
||||
if !known_groups.contains(group) {
|
||||
return match self {
|
||||
Self::Project { .. } => Err(ProjectError::MissingGroupProject(group.clone())),
|
||||
Self::Projects { .. } => Err(ProjectError::MissingGroupWorkspace(group.clone())),
|
||||
Self::Project { .. } => {
|
||||
Err(ProjectError::MissingGroupProject(group.clone()))
|
||||
}
|
||||
Self::Projects { .. } => {
|
||||
Err(ProjectError::MissingGroupProjects(group.clone()))
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
|
@ -402,108 +404,27 @@ impl<'lock> InstallTarget<'lock> {
|
|||
groups: &DependencyGroupsWithDefaults,
|
||||
) -> BTreeSet<&PackageName> {
|
||||
match self {
|
||||
Self::Project { name, lock, .. } => {
|
||||
Self::Project { lock, .. } | Self::Projects { lock, .. } => {
|
||||
let roots = self.roots().collect::<FxHashSet<_>>();
|
||||
|
||||
// Collect the packages by name for efficient lookup.
|
||||
let packages = lock
|
||||
.packages()
|
||||
.iter()
|
||||
.map(|p| (p.name(), p))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
// We'll include the project itself
|
||||
let mut required_members = BTreeSet::new();
|
||||
required_members.insert(*name);
|
||||
|
||||
// Find all workspace member dependencies recursively
|
||||
let mut queue: VecDeque<(&PackageName, Option<&ExtraName>)> = VecDeque::new();
|
||||
let mut seen: FxHashSet<(&PackageName, Option<&ExtraName>)> = FxHashSet::default();
|
||||
|
||||
let Some(root_package) = packages.get(name) else {
|
||||
return required_members;
|
||||
};
|
||||
|
||||
if groups.prod() {
|
||||
// Add the root package
|
||||
queue.push_back((name, None));
|
||||
seen.insert((name, None));
|
||||
|
||||
// Add explicitly activated extras for the root package
|
||||
for extra in extras.extra_names(root_package.optional_dependencies().keys()) {
|
||||
if seen.insert((name, Some(extra))) {
|
||||
queue.push_back((name, Some(extra)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add activated dependency groups for the root package
|
||||
for (group_name, dependencies) in root_package.resolved_dependency_groups() {
|
||||
if !groups.contains(group_name) {
|
||||
continue;
|
||||
}
|
||||
for dependency in dependencies {
|
||||
let name = dependency.package_name();
|
||||
queue.push_back((name, None));
|
||||
for extra in dependency.extra() {
|
||||
queue.push_back((name, Some(extra)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some((pkg_name, extra)) = queue.pop_front() {
|
||||
if lock.members().contains(pkg_name) {
|
||||
required_members.insert(pkg_name);
|
||||
}
|
||||
|
||||
let Some(package) = packages.get(pkg_name) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(dependencies) = extra
|
||||
.map(|extra_name| {
|
||||
package
|
||||
.optional_dependencies()
|
||||
.get(extra_name)
|
||||
.map(Vec::as_slice)
|
||||
})
|
||||
.unwrap_or(Some(package.dependencies()))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for dependency in dependencies {
|
||||
let name = dependency.package_name();
|
||||
if seen.insert((name, None)) {
|
||||
queue.push_back((name, None));
|
||||
}
|
||||
for extra in dependency.extra() {
|
||||
if seen.insert((name, Some(extra))) {
|
||||
queue.push_back((name, Some(extra)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
required_members
|
||||
}
|
||||
Self::Projects { names, lock, .. } => {
|
||||
// Collect the packages by name for efficient lookup.
|
||||
let packages = lock
|
||||
.packages()
|
||||
.iter()
|
||||
.map(|p| (p.name(), p))
|
||||
.map(|package| (package.name(), package))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
// We'll include all specified projects
|
||||
let mut required_members = BTreeSet::new();
|
||||
for name in names.iter() {
|
||||
required_members.insert(name);
|
||||
for name in &roots {
|
||||
required_members.insert(*name);
|
||||
}
|
||||
|
||||
// Find all workspace member dependencies recursively for all specified packages
|
||||
let mut queue: VecDeque<(&PackageName, Option<&ExtraName>)> = VecDeque::new();
|
||||
let mut seen: FxHashSet<(&PackageName, Option<&ExtraName>)> = FxHashSet::default();
|
||||
|
||||
for name in names.iter() {
|
||||
for name in roots {
|
||||
let Some(root_package) = packages.get(name) else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -515,7 +436,8 @@ impl<'lock> InstallTarget<'lock> {
|
|||
}
|
||||
|
||||
// Add explicitly activated extras for the root package
|
||||
for extra in extras.extra_names(root_package.optional_dependencies().keys()) {
|
||||
for extra in extras.extra_names(root_package.optional_dependencies().keys())
|
||||
{
|
||||
if seen.insert((name, Some(extra))) {
|
||||
queue.push_back((name, Some(extra)));
|
||||
}
|
||||
|
|
@ -541,12 +463,12 @@ impl<'lock> InstallTarget<'lock> {
|
|||
}
|
||||
}
|
||||
|
||||
while let Some((pkg_name, extra)) = queue.pop_front() {
|
||||
if lock.members().contains(pkg_name) {
|
||||
required_members.insert(pkg_name);
|
||||
while let Some((package_name, extra)) = queue.pop_front() {
|
||||
if lock.members().contains(package_name) {
|
||||
required_members.insert(package_name);
|
||||
}
|
||||
|
||||
let Some(package) = packages.get(pkg_name) else {
|
||||
let Some(package) = packages.get(package_name) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ pub(crate) enum ProjectError {
|
|||
MissingGroupProject(GroupName),
|
||||
|
||||
#[error("Group `{0}` is not defined in any project's `dependency-groups` table")]
|
||||
MissingGroupWorkspace(GroupName),
|
||||
MissingGroupProjects(GroupName),
|
||||
|
||||
#[error("PEP 723 scripts do not support dependency groups, but group `{0}` was specified")]
|
||||
MissingGroupScript(GroupName),
|
||||
|
|
@ -175,7 +175,7 @@ pub(crate) enum ProjectError {
|
|||
MissingExtraProject(ExtraName),
|
||||
|
||||
#[error("Extra `{0}` is not defined in any project's `optional-dependencies` table")]
|
||||
MissingExtraWorkspace(ExtraName),
|
||||
MissingExtraProjects(ExtraName),
|
||||
|
||||
#[error("PEP 723 scripts do not support optional dependencies, but extra `{0}` was specified")]
|
||||
MissingExtraScript(ExtraName),
|
||||
|
|
|
|||
|
|
@ -109,40 +109,28 @@ pub(crate) async fn sync(
|
|||
&workspace_cache,
|
||||
)
|
||||
.await?
|
||||
} else if !package.is_empty() {
|
||||
// If a single package is specified, use it as the current project
|
||||
if package.len() == 1 {
|
||||
} else if let [name] = package.as_slice() {
|
||||
VirtualProject::Project(
|
||||
Workspace::discover(
|
||||
project_dir,
|
||||
&DiscoveryOptions::default(),
|
||||
&workspace_cache,
|
||||
)
|
||||
Workspace::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache)
|
||||
.await?
|
||||
.with_current_project(package[0].clone())
|
||||
.with_context(|| format!("Package `{}` not found in workspace", package[0]))?,
|
||||
.with_current_project(name.clone())
|
||||
.with_context(|| format!("Package `{name}` not found in workspace"))?,
|
||||
)
|
||||
} else {
|
||||
// Multiple packages specified - discover the workspace and validate all packages exist
|
||||
let workspace = Workspace::discover(
|
||||
let project = VirtualProject::discover(
|
||||
project_dir,
|
||||
&DiscoveryOptions::default(),
|
||||
&workspace_cache,
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Validate that all specified packages exist in the workspace
|
||||
for pkg in &package {
|
||||
if !workspace.packages().contains_key(pkg) {
|
||||
anyhow::bail!("Package `{pkg}` not found in workspace");
|
||||
for name in &package {
|
||||
if !project.workspace().packages().contains_key(name) {
|
||||
return Err(anyhow::anyhow!("Package `{name}` not found in workspace",));
|
||||
}
|
||||
}
|
||||
|
||||
VirtualProject::NonProject(workspace)
|
||||
}
|
||||
} else {
|
||||
VirtualProject::discover(project_dir, &DiscoveryOptions::default(), &workspace_cache)
|
||||
.await?
|
||||
project
|
||||
};
|
||||
|
||||
// TODO(lucab): improve warning content
|
||||
|
|
@ -493,49 +481,45 @@ fn identify_installation_target<'a>(
|
|||
workspace: project.workspace(),
|
||||
lock,
|
||||
}
|
||||
} else if !package.is_empty() {
|
||||
if package.len() == 1 {
|
||||
InstallTarget::Project {
|
||||
workspace: project.workspace(),
|
||||
name: &package[0],
|
||||
lock,
|
||||
}
|
||||
} else {
|
||||
InstallTarget::Projects {
|
||||
workspace: project.workspace(),
|
||||
names: package,
|
||||
lock,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// By default, install the root package.
|
||||
InstallTarget::Project {
|
||||
match package {
|
||||
// By default, install the root project.
|
||||
[] => InstallTarget::Project {
|
||||
workspace: project.workspace(),
|
||||
name: project.project_name(),
|
||||
lock,
|
||||
},
|
||||
[name] => InstallTarget::Project {
|
||||
workspace: project.workspace(),
|
||||
name,
|
||||
lock,
|
||||
},
|
||||
names => InstallTarget::Projects {
|
||||
workspace: project.workspace(),
|
||||
names,
|
||||
lock,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
VirtualProject::NonProject(workspace) => {
|
||||
if all_packages {
|
||||
InstallTarget::NonProjectWorkspace { workspace, lock }
|
||||
} else if !package.is_empty() {
|
||||
if package.len() == 1 {
|
||||
InstallTarget::Project {
|
||||
workspace,
|
||||
name: &package[0],
|
||||
lock,
|
||||
}
|
||||
} else {
|
||||
InstallTarget::Projects {
|
||||
workspace,
|
||||
names: package,
|
||||
lock,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match package {
|
||||
// By default, install the entire workspace.
|
||||
InstallTarget::NonProjectWorkspace { workspace, lock }
|
||||
[] => InstallTarget::NonProjectWorkspace { workspace, lock },
|
||||
[name] => InstallTarget::Project {
|
||||
workspace,
|
||||
name,
|
||||
lock,
|
||||
},
|
||||
names => InstallTarget::Projects {
|
||||
workspace,
|
||||
names,
|
||||
lock,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,126 +280,98 @@ fn multiple_packages() -> Result<()> {
|
|||
name = "root"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["child-a", "child-b", "child-c"]
|
||||
dependencies = ["foo", "bar", "baz"]
|
||||
|
||||
[tool.uv.sources]
|
||||
child-a = { workspace = true }
|
||||
child-b = { workspace = true }
|
||||
child-c = { workspace = true }
|
||||
foo = { workspace = true }
|
||||
bar = { workspace = true }
|
||||
baz = { workspace = true }
|
||||
|
||||
[tool.uv.workspace]
|
||||
members = ["packages/*"]
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let src = context.temp_dir.child("src").child("root");
|
||||
src.create_dir_all()?;
|
||||
src.child("__init__.py").touch()?;
|
||||
|
||||
// Create child-a with requests dependency
|
||||
let child_a = context.temp_dir.child("packages").child("child-a");
|
||||
fs_err::create_dir_all(&child_a)?;
|
||||
child_a.child("pyproject.toml").write_str(
|
||||
context
|
||||
.temp_dir
|
||||
.child("packages")
|
||||
.child("foo")
|
||||
.child("pyproject.toml")
|
||||
.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child-a"
|
||||
name = "foo"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["requests"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
dependencies = ["anyio"]
|
||||
"#,
|
||||
)?;
|
||||
let src = child_a.child("src").child("child_a");
|
||||
src.create_dir_all()?;
|
||||
src.child("__init__.py").touch()?;
|
||||
|
||||
// Create child-b with httpx dependency
|
||||
let child_b = context.temp_dir.child("packages").child("child-b");
|
||||
fs_err::create_dir_all(&child_b)?;
|
||||
child_b.child("pyproject.toml").write_str(
|
||||
context
|
||||
.temp_dir
|
||||
.child("packages")
|
||||
.child("bar")
|
||||
.child("pyproject.toml")
|
||||
.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child-b"
|
||||
name = "bar"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["httpx"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
dependencies = ["typing-extensions"]
|
||||
"#,
|
||||
)?;
|
||||
let src = child_b.child("src").child("child_b");
|
||||
src.create_dir_all()?;
|
||||
src.child("__init__.py").touch()?;
|
||||
|
||||
// Create child-c with pytest dependency
|
||||
let child_c = context.temp_dir.child("packages").child("child-c");
|
||||
fs_err::create_dir_all(&child_c)?;
|
||||
child_c.child("pyproject.toml").write_str(
|
||||
context
|
||||
.temp_dir
|
||||
.child("packages")
|
||||
.child("baz")
|
||||
.child("pyproject.toml")
|
||||
.write_str(
|
||||
r#"
|
||||
[project]
|
||||
name = "child-c"
|
||||
name = "baz"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.12"
|
||||
dependencies = ["pytest"]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
dependencies = ["iniconfig"]
|
||||
"#,
|
||||
)?;
|
||||
let src = child_c.child("src").child("child_c");
|
||||
src.create_dir_all()?;
|
||||
src.child("__init__.py").touch()?;
|
||||
|
||||
// Sync only child-a and child-b
|
||||
// Sync `foo` and `bar`.
|
||||
uv_snapshot!(context.filters(), context.sync()
|
||||
.arg("--package").arg("child-a")
|
||||
.arg("--package").arg("child-b"), @r"
|
||||
.arg("--package").arg("foo")
|
||||
.arg("--package").arg("bar"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 19 packages in [TIME]
|
||||
Prepared 12 packages in [TIME]
|
||||
Installed 12 packages in [TIME]
|
||||
Resolved 9 packages in [TIME]
|
||||
Prepared 6 packages in [TIME]
|
||||
Installed 6 packages in [TIME]
|
||||
+ anyio==4.3.0
|
||||
+ certifi==2024.2.2
|
||||
+ charset-normalizer==3.3.2
|
||||
+ child-a==0.1.0 (from file://[TEMP_DIR]/packages/child-a)
|
||||
+ child-b==0.1.0 (from file://[TEMP_DIR]/packages/child-b)
|
||||
+ h11==0.14.0
|
||||
+ httpcore==1.0.4
|
||||
+ httpx==0.27.0
|
||||
+ bar==0.1.0 (from file://[TEMP_DIR]/packages/bar)
|
||||
+ foo==0.1.0 (from file://[TEMP_DIR]/packages/foo)
|
||||
+ idna==3.6
|
||||
+ requests==2.31.0
|
||||
+ sniffio==1.3.1
|
||||
+ urllib3==2.2.1
|
||||
+ typing-extensions==4.10.0
|
||||
");
|
||||
|
||||
// Now sync all three packages
|
||||
// Sync `foo`, `bar`, and `baz`.
|
||||
uv_snapshot!(context.filters(), context.sync()
|
||||
.arg("--package").arg("child-a")
|
||||
.arg("--package").arg("child-b")
|
||||
.arg("--package").arg("child-c"), @r"
|
||||
.arg("--package").arg("foo")
|
||||
.arg("--package").arg("bar")
|
||||
.arg("--package").arg("baz"), @r"
|
||||
success: true
|
||||
exit_code: 0
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
Resolved 19 packages in [TIME]
|
||||
Prepared 5 packages in [TIME]
|
||||
Installed 5 packages in [TIME]
|
||||
+ child-c==0.1.0 (from file://[TEMP_DIR]/packages/child-c)
|
||||
Resolved 9 packages in [TIME]
|
||||
Prepared 2 packages in [TIME]
|
||||
Installed 2 packages in [TIME]
|
||||
+ baz==0.1.0 (from file://[TEMP_DIR]/packages/baz)
|
||||
+ iniconfig==2.0.0
|
||||
+ packaging==24.0
|
||||
+ pluggy==1.4.0
|
||||
+ pytest==8.1.1
|
||||
");
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -1513,9 +1513,9 @@ uv sync [OPTIONS]
|
|||
<ul>
|
||||
<li><code>text</code>: Display the result in a human-readable format</li>
|
||||
<li><code>json</code>: Display the result in JSON format</li>
|
||||
</ul></dd><dt id="uv-sync--package"><a href="#uv-sync--package"><code>--package</code></a> <i>package</i></dt><dd><p>Sync for a specific package in the workspace.</p>
|
||||
<p>The workspace's environment (<code>.venv</code>) is updated to reflect the subset of dependencies declared by the specified workspace member package.</p>
|
||||
<p>If the workspace member does not exist, uv will exit with an error.</p>
|
||||
</ul></dd><dt id="uv-sync--package"><a href="#uv-sync--package"><code>--package</code></a> <i>package</i></dt><dd><p>Sync for specific packages in the workspace.</p>
|
||||
<p>The workspace's environment (<code>.venv</code>) is updated to reflect the subset of dependencies declared by the specified workspace member packages.</p>
|
||||
<p>If any workspace member does not exist, uv will exit with an error.</p>
|
||||
</dd><dt id="uv-sync--prerelease"><a href="#uv-sync--prerelease"><code>--prerelease</code></a> <i>prerelease</i></dt><dd><p>The strategy to use when considering pre-release versions.</p>
|
||||
<p>By default, uv will accept pre-releases for packages that <em>only</em> publish pre-releases, along with first-party requirements that contain an explicit pre-release marker in the declared specifiers (<code>if-necessary-or-explicit</code>).</p>
|
||||
<p>May also be set with the <code>UV_PRERELEASE</code> environment variable.</p><p>Possible values:</p>
|
||||
|
|
|
|||
Loading…
Reference in New Issue