Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
20 changes: 14 additions & 6 deletions crates/uv-configuration/src/dependency_groups.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{borrow::Cow, sync::Arc};

use uv_normalize::{GroupName, DEV_DEPENDENCIES};
use uv_normalize::{DefaultGroups, GroupName, DEV_DEPENDENCIES};

/// Manager of all dependency-group decisions and settings history.
///
Expand Down Expand Up @@ -63,10 +63,18 @@ impl DependencyGroups {
} else {
// Merge all these lists, they're equivalent now
group.append(&mut only_group);
// Resolve default groups potentially also setting All
if default_groups {
group.append(&mut defaults);
match &mut defaults {
DefaultGroups::All => IncludeGroups::All,
DefaultGroups::List(defaults) => {
group.append(defaults);
IncludeGroups::Some(group)
}
}
} else {
IncludeGroups::Some(group)
}
IncludeGroups::Some(group)
};

Self(Arc::new(DependencyGroupsInner {
Expand Down Expand Up @@ -112,7 +120,7 @@ impl DependencyGroups {
all_groups,
no_default_groups,
// This is unknown at CLI-time, use `.with_defaults(...)` to apply this later!
defaults: Vec::new(),
defaults: DefaultGroups::default(),
})
}

Expand All @@ -135,7 +143,7 @@ impl DependencyGroups {
/// Apply defaults to a base [`DependencyGroups`].
///
/// This is appropriate in projects, where the `dev` group is synced by default.
pub fn with_defaults(&self, defaults: Vec<GroupName>) -> DependencyGroupsWithDefaults {
pub fn with_defaults(&self, defaults: DefaultGroups) -> DependencyGroupsWithDefaults {
// Explicitly clone the inner history and set the defaults, then remake the result.
let mut history = self.0.history.clone();
history.defaults = defaults;
Expand Down Expand Up @@ -220,7 +228,7 @@ pub struct DependencyGroupsHistory {
pub no_group: Vec<GroupName>,
pub all_groups: bool,
pub no_default_groups: bool,
pub defaults: Vec<GroupName>,
pub defaults: DefaultGroups,
}

impl DependencyGroupsHistory {
Expand Down
82 changes: 82 additions & 0 deletions crates/uv-normalize/src/group_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::LazyLock;

use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use uv_small_str::SmallString;
Expand Down Expand Up @@ -164,6 +165,87 @@ impl Display for PipGroupName {
}
}

/// Either the literal "all" or a list of groups
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DefaultGroups {
/// All groups are defaulted
All,
/// A list of groups
List(Vec<GroupName>),
}

/// Serialize a [`DefaultGroups`] struct into a list of marker strings.
impl serde::Serialize for DefaultGroups {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
DefaultGroups::All => serializer.serialize_str("all"),
DefaultGroups::List(groups) => {
let mut seq = serializer.serialize_seq(Some(groups.len()))?;
for group in groups {
seq.serialize_element(&group)?;
}
seq.end()
}
}
}
}

/// Deserialize a "all" or list of [`GroupName`] into a [`DefaultGroups`] enum.
impl<'de> serde::Deserialize<'de> for DefaultGroups {
fn deserialize<D>(deserializer: D) -> Result<DefaultGroups, D::Error>
where
D: serde::Deserializer<'de>,
{
struct StringOrVecVisitor;

impl<'de> serde::de::Visitor<'de> for StringOrVecVisitor {
type Value = DefaultGroups;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str(r#"the string "all" or a list of strings"#)
}

fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
if value != "all" {
return Err(serde::de::Error::custom(
r#"default-groups must be "all" or a ["list", "of", "groups"]"#,
));
}
Ok(DefaultGroups::All)
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut groups = Vec::new();

while let Some(elem) = seq.next_element::<GroupName>()? {
groups.push(elem);
}

Ok(DefaultGroups::List(groups))
}
}

deserializer.deserialize_any(StringOrVecVisitor)
}
}

impl Default for DefaultGroups {
/// Note this is an "empty" default unlike other contexts where `["dev"]` is the default
fn default() -> Self {
DefaultGroups::List(Vec::new())
}
}

/// The name of the global `dev-dependencies` group.
///
/// Internally, we model dependency groups as a generic concept; but externally, we only expose the
Expand Down
2 changes: 1 addition & 1 deletion crates/uv-normalize/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt::{Display, Formatter};

pub use dist_info_name::DistInfoName;
pub use extra_name::ExtraName;
pub use group_name::{GroupName, PipGroupName, DEV_DEPENDENCIES};
pub use group_name::{DefaultGroups, GroupName, PipGroupName, DEV_DEPENDENCIES};
pub use package_name::PackageName;

use uv_small_str::SmallString;
Expand Down
8 changes: 5 additions & 3 deletions crates/uv-workspace/src/pyproject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use uv_distribution_types::{Index, IndexName};
use uv_fs::{relative_to, PortablePathBuf};
use uv_git_types::GitReference;
use uv_macros::OptionsMetadata;
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_normalize::{DefaultGroups, ExtraName, GroupName, PackageName};
use uv_pep440::{Version, VersionSpecifiers};
use uv_pep508::MarkerTree;
use uv_pypi_types::{
Expand Down Expand Up @@ -343,14 +343,16 @@ pub struct ToolUv {
pub package: Option<bool>,

/// The list of `dependency-groups` to install by default.
///
/// Can aksi be the literal "all" to default enable all groups.
#[option(
default = r#"["dev"]"#,
value_type = "list[str]",
value_type = r#"str | list[str]"#,
example = r#"
default-groups = ["docs"]
"#
)]
pub default_groups: Option<Vec<GroupName>>,
pub default_groups: Option<DefaultGroups>,

/// The project's development dependencies.
///
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use uv_distribution_types::{
use uv_fs::Simplified;
use uv_git::GIT_STORE;
use uv_git_types::GitReference;
use uv_normalize::{PackageName, DEV_DEPENDENCIES};
use uv_normalize::{DefaultGroups, PackageName, DEV_DEPENDENCIES};
use uv_pep508::{ExtraName, MarkerTree, Requirement, UnnamedRequirement, VersionOrUrl};
use uv_pypi_types::{redact_credentials, ParsedUrl, RequirementSource, VerbatimParsedUrl};
use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
Expand Down Expand Up @@ -897,7 +897,7 @@ async fn lock_and_sync(
target,
venv,
&extras,
&dev.with_defaults(Vec::new()),
&dev.with_defaults(DefaultGroups::default()),
EditableMode::Editable,
InstallOptions::default(),
Modifications::Sufficient,
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use uv_configuration::{
Concurrency, DependencyGroups, EditableMode, ExportFormat, ExtrasSpecification, InstallOptions,
PreviewMode,
};
use uv_normalize::PackageName;
use uv_normalize::{DefaultGroups, PackageName};
use uv_python::{PythonDownloads, PythonPreference, PythonRequest};
use uv_resolver::RequirementsTxtExport;
use uv_scripts::{Pep723ItemRef, Pep723Script};
Expand Down Expand Up @@ -110,7 +110,7 @@ pub(crate) async fn export(
// Determine the default groups to include.
let defaults = match &target {
ExportTarget::Project(project) => default_dependency_groups(project.pyproject_toml())?,
ExportTarget::Script(_) => vec![],
ExportTarget::Script(_) => DefaultGroups::default(),
};
let dev = dev.with_defaults(defaults);

Expand Down
22 changes: 12 additions & 10 deletions crates/uv/src/commands/project/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use uv_distribution_types::{
use uv_fs::{LockedFile, Simplified, CWD};
use uv_git::ResolvedRepositoryReference;
use uv_installer::{SatisfiesResult, SitePackages};
use uv_normalize::{ExtraName, GroupName, PackageName, DEV_DEPENDENCIES};
use uv_normalize::{DefaultGroups, ExtraName, GroupName, PackageName, DEV_DEPENDENCIES};
use uv_pep440::{Version, VersionSpecifiers};
use uv_pep508::MarkerTreeContents;
use uv_pypi_types::{ConflictPackage, ConflictSet, Conflicts, Requirement};
Expand Down Expand Up @@ -2276,24 +2276,26 @@ pub(crate) async fn init_script_python_requirement(
#[allow(clippy::result_large_err)]
pub(crate) fn default_dependency_groups(
pyproject_toml: &PyProjectToml,
) -> Result<Vec<GroupName>, ProjectError> {
) -> Result<DefaultGroups, ProjectError> {
if let Some(defaults) = pyproject_toml
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref().and_then(|uv| uv.default_groups.as_ref()))
{
for group in defaults {
if !pyproject_toml
.dependency_groups
.as_ref()
.is_some_and(|groups| groups.contains_key(group))
{
return Err(ProjectError::MissingDefaultGroup(group.clone()));
if let DefaultGroups::List(defaults) = defaults {
for group in defaults {
if !pyproject_toml
.dependency_groups
.as_ref()
.is_some_and(|groups| groups.contains_key(group))
{
return Err(ProjectError::MissingDefaultGroup(group.clone()));
}
}
}
Ok(defaults.clone())
} else {
Ok(vec![DEV_DEPENDENCIES.clone()])
Ok(DefaultGroups::List(vec![DEV_DEPENDENCIES.clone()]))
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use uv_configuration::{
use uv_fs::which::is_executable;
use uv_fs::{PythonExt, Simplified};
use uv_installer::{SatisfiesResult, SitePackages};
use uv_normalize::PackageName;
use uv_normalize::{DefaultGroups, PackageName};
use uv_python::{
EnvironmentPreference, Interpreter, PyVenvConfiguration, PythonDownloads, PythonEnvironment,
PythonInstallation, PythonPreference, PythonRequest, PythonVersionFile,
Expand Down Expand Up @@ -284,7 +284,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
target,
&environment,
&extras,
&dev.with_defaults(Vec::new()),
&dev.with_defaults(DefaultGroups::default()),
editable,
install_options,
modifications,
Expand Down
4 changes: 2 additions & 2 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use uv_distribution_types::{
};
use uv_fs::Simplified;
use uv_installer::SitePackages;
use uv_normalize::PackageName;
use uv_normalize::{DefaultGroups, PackageName};
use uv_pep508::{MarkerTree, VersionOrUrl};
use uv_pypi_types::{ParsedArchiveUrl, ParsedGitUrl, ParsedUrl};
use uv_python::{PythonDownloads, PythonEnvironment, PythonPreference, PythonRequest};
Expand Down Expand Up @@ -117,7 +117,7 @@ pub(crate) async fn sync(
// Determine the default groups to include.
let defaults = match &target {
SyncTarget::Project(project) => default_dependency_groups(project.pyproject_toml())?,
SyncTarget::Script(..) => Vec::new(),
SyncTarget::Script(..) => DefaultGroups::default(),
};

// Discover or create the virtual environment.
Expand Down
3 changes: 2 additions & 1 deletion crates/uv/src/commands/project/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use uv_cache_info::Timestamp;
use uv_client::RegistryClientBuilder;
use uv_configuration::{Concurrency, DependencyGroups, PreviewMode, TargetTriple};
use uv_distribution_types::IndexCapabilities;
use uv_normalize::DefaultGroups;
use uv_pep508::PackageName;
use uv_python::{PythonDownloads, PythonPreference, PythonRequest, PythonVersion};
use uv_resolver::{PackageMap, TreeDisplay};
Expand Down Expand Up @@ -74,7 +75,7 @@ pub(crate) async fn tree(
// Determine the default groups to include.
let defaults = match target {
LockTarget::Workspace(workspace) => default_dependency_groups(workspace.pyproject_toml())?,
LockTarget::Script(_) => vec![],
LockTarget::Script(_) => DefaultGroups::default(),
};

let native_tls = network_settings.native_tls;
Expand Down
Loading
Loading