Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions crates/uv-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3713,10 +3713,19 @@ pub struct AddArgs {

/// Add the dependency as a workspace member.
///
/// When used with a path dependency, the package will be added to the workspace's `members`
/// list in the root `pyproject.toml` file.
#[arg(long)]
/// By default, uv will add path dependencies that are within the workspace directory
/// as workspace members. When used with a path dependency, the package will be added
/// to the workspace's `members` list in the root `pyproject.toml` file.
#[arg(long, overrides_with = "no_workspace")]
pub workspace: bool,

/// Don't add the dependency as a workspace member.
///
/// By default, when adding a dependency that's a local path and is within the workspace
/// directory, uv will add it as a workspace member; pass `--no-workspace` to add the package
/// as direct path dependency instead.
#[arg(long, overrides_with = "workspace")]
pub no_workspace: bool,
}

#[derive(Args)]
Expand Down
78 changes: 57 additions & 21 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ pub(crate) async fn add(
extras_of_dependency: Vec<ExtraName>,
package: Option<PackageName>,
python: Option<String>,
workspace: bool,
workspace: Option<bool>,
install_mirrors: PythonInstallMirrors,
settings: ResolverInstallerSettings,
network_settings: NetworkSettings,
Expand Down Expand Up @@ -495,16 +495,41 @@ pub(crate) async fn add(
// Track modification status, for reverts.
let mut modified = false;

// If `--workspace` is provided, add any members to the `workspace` section of the
// Determine whether to use workspace mode.
let use_workspace = match workspace {
Some(workspace) => workspace,
None => {
// Check if we're in a project (not a script), and if any requirements are path
// dependencies within the workspace.
if let AddTarget::Project(ref project, _) = target {
let workspace_root = project.workspace().install_path();
requirements.iter().any(|req| {
if let RequirementSource::Directory { install_path, .. } = &req.source {
let absolute_path = if install_path.is_absolute() {
install_path.to_path_buf()
} else {
project.root().join(install_path)
};
absolute_path.starts_with(workspace_root)
} else {
false
}
})
} else {
false
}
}
};

// If workspace mode is enabled, add any members to the `workspace` section of the
// `pyproject.toml` file.
if workspace {
if use_workspace {
let AddTarget::Project(project, python_target) = target else {
unreachable!("`--workspace` and `--script` are conflicting options");
};

let workspace = project.workspace();
let mut toml = PyProjectTomlMut::from_toml(
&workspace.pyproject_toml().raw,
&project.workspace().pyproject_toml().raw,
DependencyTarget::PyProjectToml,
)?;

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

// Check if the path is not already included in the workspace.
if !workspace.includes(&absolute_path)? {
let relative_path = absolute_path
.strip_prefix(workspace.install_path())
.unwrap_or(&absolute_path);

toml.add_workspace(relative_path)?;
modified |= true;
// Either `--workspace` was provided explicitly, or it was omitted but the path is
// within the workspace root.
let use_workspace = workspace.unwrap_or_else(|| {
absolute_path.starts_with(project.workspace().install_path())
});
if !use_workspace {
continue;
}

writeln!(
printer.stderr(),
"Added `{}` to workspace members",
relative_path.user_display().cyan()
)?;
// If the project is already a member of the workspace, skip it.
if project.workspace().includes(&absolute_path)? {
continue;
}

let relative_path = absolute_path
.strip_prefix(project.workspace().install_path())
.unwrap_or(&absolute_path);

toml.add_workspace(relative_path)?;
modified |= true;

writeln!(
printer.stderr(),
"Added `{}` to workspace members",
relative_path.user_display().cyan()
)?;
}
}

Expand All @@ -540,7 +576,7 @@ pub(crate) async fn add(
target = if modified {
let workspace_content = toml.to_string();
fs_err::write(
workspace.install_path().join("pyproject.toml"),
project.workspace().install_path().join("pyproject.toml"),
&workspace_content,
)?;

Expand Down Expand Up @@ -745,13 +781,13 @@ fn edits(
.and_then(|tool| tool.uv.as_ref())
.and_then(|uv| uv.sources.as_ref())
.map(ToolUvSources::inner);
let workspace = project
let is_workspace_member = project
.workspace()
.packages()
.contains_key(&requirement.name);
resolve_requirement(
requirement,
workspace,
is_workspace_member,
editable,
index.cloned(),
rev.map(ToString::to_string),
Expand Down
5 changes: 3 additions & 2 deletions crates/uv/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1329,7 +1329,7 @@ pub(crate) struct AddSettings {
pub(crate) package: Option<PackageName>,
pub(crate) script: Option<PathBuf>,
pub(crate) python: Option<String>,
pub(crate) workspace: bool,
pub(crate) workspace: Option<bool>,
pub(crate) install_mirrors: PythonInstallMirrors,
pub(crate) refresh: Refresh,
pub(crate) indexes: Vec<Index>,
Expand Down Expand Up @@ -1368,6 +1368,7 @@ impl AddSettings {
script,
python,
workspace,
no_workspace,
} = args;

let dependency_type = if let Some(extra) = optional {
Expand Down Expand Up @@ -1468,7 +1469,7 @@ impl AddSettings {
package,
script,
python: python.and_then(Maybe::into_option),
workspace,
workspace: flag(workspace, no_workspace, "workspace"),
editable: flag(editable, no_editable, "editable"),
extras: extra.unwrap_or_default(),
refresh: Refresh::from(refresh),
Expand Down
Loading
Loading