Respect exclusions in uv init (#5318)

## Summary

Avoid adding to the workspace.

Closes https://github.com/astral-sh/uv/issues/5254.
This commit is contained in:
Charlie Marsh 2024-07-22 20:15:17 -04:00 committed by GitHub
parent 13dd8853d9
commit 2f768b8bb0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 84 additions and 15 deletions

View file

@ -330,6 +330,21 @@ impl Workspace {
&self.pyproject_toml
}
/// Returns `true` if the path is excluded by the workspace.
pub fn excludes(&self, project_path: &Path) -> Result<bool, WorkspaceError> {
if let Some(workspace) = self
.pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.workspace.as_ref())
{
is_excluded_from_workspace(project_path, &self.install_path, workspace)
} else {
Ok(false)
}
}
/// Collect the workspace member projects from the `members` and `excludes` entries.
async fn collect_members(
workspace_root: PathBuf,

View file

@ -115,22 +115,32 @@ pub(crate) async fn init(
}
if let Some(workspace) = workspace {
// Add the package to the workspace.
let mut pyproject = PyProjectTomlMut::from_toml(workspace.pyproject_toml())?;
pyproject.add_workspace(path.strip_prefix(workspace.install_path())?)?;
if workspace.excludes(&path)? {
// If the member is excluded by the workspace, ignore it.
writeln!(
printer.stderr(),
"Project `{}` is excluded by workspace `{}`",
name.cyan(),
workspace.install_path().simplified_display().cyan()
)?;
} else {
// Add the package to the workspace.
let mut pyproject = PyProjectTomlMut::from_toml(workspace.pyproject_toml())?;
pyproject.add_workspace(path.strip_prefix(workspace.install_path())?)?;
// Save the modified `pyproject.toml`.
fs_err::write(
workspace.install_path().join("pyproject.toml"),
pyproject.to_string(),
)?;
// Save the modified `pyproject.toml`.
fs_err::write(
workspace.install_path().join("pyproject.toml"),
pyproject.to_string(),
)?;
writeln!(
printer.stderr(),
"Adding `{}` as member of workspace `{}`",
name.cyan(),
workspace.install_path().simplified_display().cyan()
)?;
writeln!(
printer.stderr(),
"Adding `{}` as member of workspace `{}`",
name.cyan(),
workspace.install_path().simplified_display().cyan()
)?;
}
}
match explicit_path {

View file

@ -727,7 +727,7 @@ fn init_virtual_workspace() -> Result<()> {
/// Run `uv init` from within a workspace. The path is already included via `members`.
#[test]
fn init_matches_member() -> Result<()> {
fn init_matches_members() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
@ -766,3 +766,47 @@ fn init_matches_member() -> Result<()> {
Ok(())
}
/// Run `uv init` from within a workspace. The path is already included via `members`.
#[test]
fn init_matches_exclude() -> Result<()> {
let context = TestContext::new("3.12");
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc! {
r"
[tool.uv.workspace]
exclude = ['packages/foo']
members = ['packages/*']
",
})?;
let packages = context.temp_dir.join("packages");
fs_err::create_dir_all(packages)?;
uv_snapshot!(context.filters(), context.init().current_dir(context.temp_dir.join("packages")).arg("foo"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
warning: `uv init` is experimental and may change without warning
Project `foo` is excluded by workspace `[TEMP_DIR]/`
Initialized project `foo` at `[TEMP_DIR]/packages/foo`
"###);
let workspace = fs_err::read_to_string(context.temp_dir.join("pyproject.toml"))?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
workspace, @r###"
[tool.uv.workspace]
exclude = ['packages/foo']
members = ['packages/*']
"###
);
});
Ok(())
}