Skip to content

Commit b729fe7

Browse files
Default to --workspace when adding subdirectories (#14529)
## Summary If `--workspace` is provided, we add all paths as workspace members. If `--no-workspace` is provided, we add all paths as direct path dependencies. If neither is provided, then we add any paths that are under the workspace root as workspace members, and the rest as direct path dependencies. Closes #14524.
1 parent 421e2c7 commit b729fe7

5 files changed

Lines changed: 521 additions & 41 deletions

File tree

crates/uv-cli/src/lib.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3713,10 +3713,19 @@ pub struct AddArgs {
37133713

37143714
/// Add the dependency as a workspace member.
37153715
///
3716-
/// When used with a path dependency, the package will be added to the workspace's `members`
3717-
/// list in the root `pyproject.toml` file.
3718-
#[arg(long)]
3716+
/// By default, uv will add path dependencies that are within the workspace directory
3717+
/// as workspace members. When used with a path dependency, the package will be added
3718+
/// to the workspace's `members` list in the root `pyproject.toml` file.
3719+
#[arg(long, overrides_with = "no_workspace")]
37193720
pub workspace: bool,
3721+
3722+
/// Don't add the dependency as a workspace member.
3723+
///
3724+
/// By default, when adding a dependency that's a local path and is within the workspace
3725+
/// directory, uv will add it as a workspace member; pass `--no-workspace` to add the package
3726+
/// as direct path dependency instead.
3727+
#[arg(long, overrides_with = "workspace")]
3728+
pub no_workspace: bool,
37203729
}
37213730

37223731
#[derive(Args)]

crates/uv/src/commands/project/add.rs

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ pub(crate) async fn add(
8383
extras_of_dependency: Vec<ExtraName>,
8484
package: Option<PackageName>,
8585
python: Option<String>,
86-
workspace: bool,
86+
workspace: Option<bool>,
8787
install_mirrors: PythonInstallMirrors,
8888
settings: ResolverInstallerSettings,
8989
network_settings: NetworkSettings,
@@ -495,16 +495,41 @@ pub(crate) async fn add(
495495
// Track modification status, for reverts.
496496
let mut modified = false;
497497

498-
// If `--workspace` is provided, add any members to the `workspace` section of the
498+
// Determine whether to use workspace mode.
499+
let use_workspace = match workspace {
500+
Some(workspace) => workspace,
501+
None => {
502+
// Check if we're in a project (not a script), and if any requirements are path
503+
// dependencies within the workspace.
504+
if let AddTarget::Project(ref project, _) = target {
505+
let workspace_root = project.workspace().install_path();
506+
requirements.iter().any(|req| {
507+
if let RequirementSource::Directory { install_path, .. } = &req.source {
508+
let absolute_path = if install_path.is_absolute() {
509+
install_path.to_path_buf()
510+
} else {
511+
project.root().join(install_path)
512+
};
513+
absolute_path.starts_with(workspace_root)
514+
} else {
515+
false
516+
}
517+
})
518+
} else {
519+
false
520+
}
521+
}
522+
};
523+
524+
// If workspace mode is enabled, add any members to the `workspace` section of the
499525
// `pyproject.toml` file.
500-
if workspace {
526+
if use_workspace {
501527
let AddTarget::Project(project, python_target) = target else {
502528
unreachable!("`--workspace` and `--script` are conflicting options");
503529
};
504530

505-
let workspace = project.workspace();
506531
let mut toml = PyProjectTomlMut::from_toml(
507-
&workspace.pyproject_toml().raw,
532+
&project.workspace().pyproject_toml().raw,
508533
DependencyTarget::PyProjectToml,
509534
)?;
510535

@@ -517,21 +542,32 @@ pub(crate) async fn add(
517542
project.root().join(install_path)
518543
};
519544

520-
// Check if the path is not already included in the workspace.
521-
if !workspace.includes(&absolute_path)? {
522-
let relative_path = absolute_path
523-
.strip_prefix(workspace.install_path())
524-
.unwrap_or(&absolute_path);
525-
526-
toml.add_workspace(relative_path)?;
527-
modified |= true;
545+
// Either `--workspace` was provided explicitly, or it was omitted but the path is
546+
// within the workspace root.
547+
let use_workspace = workspace.unwrap_or_else(|| {
548+
absolute_path.starts_with(project.workspace().install_path())
549+
});
550+
if !use_workspace {
551+
continue;
552+
}
528553

529-
writeln!(
530-
printer.stderr(),
531-
"Added `{}` to workspace members",
532-
relative_path.user_display().cyan()
533-
)?;
554+
// If the project is already a member of the workspace, skip it.
555+
if project.workspace().includes(&absolute_path)? {
556+
continue;
534557
}
558+
559+
let relative_path = absolute_path
560+
.strip_prefix(project.workspace().install_path())
561+
.unwrap_or(&absolute_path);
562+
563+
toml.add_workspace(relative_path)?;
564+
modified |= true;
565+
566+
writeln!(
567+
printer.stderr(),
568+
"Added `{}` to workspace members",
569+
relative_path.user_display().cyan()
570+
)?;
535571
}
536572
}
537573

@@ -540,7 +576,7 @@ pub(crate) async fn add(
540576
target = if modified {
541577
let workspace_content = toml.to_string();
542578
fs_err::write(
543-
workspace.install_path().join("pyproject.toml"),
579+
project.workspace().install_path().join("pyproject.toml"),
544580
&workspace_content,
545581
)?;
546582

@@ -745,13 +781,13 @@ fn edits(
745781
.and_then(|tool| tool.uv.as_ref())
746782
.and_then(|uv| uv.sources.as_ref())
747783
.map(ToolUvSources::inner);
748-
let workspace = project
784+
let is_workspace_member = project
749785
.workspace()
750786
.packages()
751787
.contains_key(&requirement.name);
752788
resolve_requirement(
753789
requirement,
754-
workspace,
790+
is_workspace_member,
755791
editable,
756792
index.cloned(),
757793
rev.map(ToString::to_string),

crates/uv/src/settings.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,7 +1329,7 @@ pub(crate) struct AddSettings {
13291329
pub(crate) package: Option<PackageName>,
13301330
pub(crate) script: Option<PathBuf>,
13311331
pub(crate) python: Option<String>,
1332-
pub(crate) workspace: bool,
1332+
pub(crate) workspace: Option<bool>,
13331333
pub(crate) install_mirrors: PythonInstallMirrors,
13341334
pub(crate) refresh: Refresh,
13351335
pub(crate) indexes: Vec<Index>,
@@ -1368,6 +1368,7 @@ impl AddSettings {
13681368
script,
13691369
python,
13701370
workspace,
1371+
no_workspace,
13711372
} = args;
13721373

13731374
let dependency_type = if let Some(extra) = optional {
@@ -1468,7 +1469,7 @@ impl AddSettings {
14681469
package,
14691470
script,
14701471
python: python.and_then(Maybe::into_option),
1471-
workspace,
1472+
workspace: flag(workspace, no_workspace, "workspace"),
14721473
editable: flag(editable, no_editable, "editable"),
14731474
extras: extra.unwrap_or_default(),
14741475
refresh: Refresh::from(refresh),

0 commit comments

Comments
 (0)