diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index bed35cbee13ef..c8efeb8c13ab8 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -2395,6 +2395,12 @@ pub struct RunArgs { #[arg(long, overrides_with("dev"))] pub no_dev: bool, + /// Omit non-development dependencies. + /// + /// The project itself will also be omitted. + #[arg(long, conflicts_with("no_dev"))] + pub only_dev: bool, + /// The command to run. /// /// If the path to a Python script (i.e., ending in `.py`), it will be @@ -2541,6 +2547,12 @@ pub struct SyncArgs { #[arg(long, overrides_with("dev"))] pub no_dev: bool, + /// Omit non-development dependencies. + /// + /// The project itself will also be omitted. + #[arg(long, conflicts_with("no_dev"))] + pub only_dev: bool, + /// Do not remove extraneous packages present in the environment. /// /// When enabled, uv will make the minimum necessary changes to satisfy the requirements. @@ -2977,6 +2989,12 @@ pub struct ExportArgs { #[arg(long, overrides_with("dev"))] pub no_dev: bool, + /// Omit non-development dependencies. + /// + /// The project itself will also be omitted. + #[arg(long, conflicts_with("no_dev"))] + pub only_dev: bool, + /// Include hashes for all dependencies. #[arg(long, overrides_with("no_hashes"), hide = true)] pub hashes: bool, diff --git a/crates/uv-configuration/src/dev.rs b/crates/uv-configuration/src/dev.rs new file mode 100644 index 0000000000000..ab11c55fadcad --- /dev/null +++ b/crates/uv-configuration/src/dev.rs @@ -0,0 +1,53 @@ +use either::Either; +use uv_normalize::GroupName; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum DevMode { + /// Include development dependencies. + #[default] + Include, + /// Exclude development dependencies. + Exclude, + /// Only include development dependencies, excluding all other dependencies. + Only, +} + +impl DevMode { + /// Determine the [`DevMode`] policy from the command-line arguments. + pub fn from_args(dev: bool, no_dev: bool, only_dev: bool) -> Self { + if only_dev { + Self::Only + } else if no_dev { + Self::Exclude + } else if dev { + Self::Include + } else { + Self::default() + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum DevSpecification<'group> { + /// Include dev dependencies from the specified group. + Include(&'group [GroupName]), + /// Do not include dev dependencies. + Exclude, + /// Include dev dependencies from the specified group, and exclude all non-dev dependencies. + Only(&'group [GroupName]), +} + +impl<'group> DevSpecification<'group> { + /// Returns an [`Iterator`] over the group names to include. + pub fn iter(&self) -> impl Iterator { + match self { + Self::Exclude => Either::Left(std::iter::empty()), + Self::Include(groups) | Self::Only(groups) => Either::Right(groups.iter()), + } + } + + /// Returns `true` if the specification allows for production dependencies. + pub fn prod(&self) -> bool { + matches!(self, Self::Exclude | Self::Include(_)) + } +} diff --git a/crates/uv-configuration/src/lib.rs b/crates/uv-configuration/src/lib.rs index fad24007e19f4..6ad8cdc685289 100644 --- a/crates/uv-configuration/src/lib.rs +++ b/crates/uv-configuration/src/lib.rs @@ -3,6 +3,7 @@ pub use build_options::*; pub use concurrency::*; pub use config_settings::*; pub use constraints::*; +pub use dev::*; pub use export_format::*; pub use extras::*; pub use hash::*; @@ -20,6 +21,7 @@ mod build_options; mod concurrency; mod config_settings; mod constraints; +mod dev; mod export_format; mod extras; mod hash; diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index f0e615c5e012d..7c510d719c9ad 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -30,7 +30,7 @@ use pypi_types::{ redact_git_credentials, HashDigest, ParsedArchiveUrl, ParsedGitUrl, Requirement, RequirementSource, ResolverMarkerEnvironment, }; -use uv_configuration::{BuildOptions, ExtrasSpecification, InstallOptions}; +use uv_configuration::{BuildOptions, DevSpecification, ExtrasSpecification, InstallOptions}; use uv_distribution::DistributionDatabase; use uv_fs::{relative_to, PortablePath, PortablePathBuf}; use uv_git::{GitReference, GitSha, RepositoryReference, ResolvedRepositoryReference}; @@ -540,7 +540,7 @@ impl Lock { marker_env: &ResolverMarkerEnvironment, tags: &Tags, extras: &ExtrasSpecification, - dev: &[GroupName], + dev: DevSpecification<'_>, build_options: &BuildOptions, install_options: &InstallOptions, ) -> Result { @@ -558,26 +558,28 @@ impl Lock { name: root_name.clone(), })?; - // Add the base package. - queue.push_back((root, None)); + if dev.prod() { + // Add the base package. + queue.push_back((root, None)); - // Add any extras. - match extras { - ExtrasSpecification::None => {} - ExtrasSpecification::All => { - for extra in root.optional_dependencies.keys() { - queue.push_back((root, Some(extra))); + // Add any extras. + match extras { + ExtrasSpecification::None => {} + ExtrasSpecification::All => { + for extra in root.optional_dependencies.keys() { + queue.push_back((root, Some(extra))); + } } - } - ExtrasSpecification::Some(extras) => { - for extra in extras { - queue.push_back((root, Some(extra))); + ExtrasSpecification::Some(extras) => { + for extra in extras { + queue.push_back((root, Some(extra))); + } } } } // Add any dev dependencies. - for group in dev { + for group in dev.iter() { for dep in root.dev_dependencies.get(group).into_iter().flatten() { if dep.complexified_marker.evaluate(marker_env, &[]) { let dep_dist = self.find_by_id(&dep.package_id); @@ -596,7 +598,7 @@ impl Lock { // Add any dependency groups that are exclusive to the workspace root (e.g., dev // dependencies in (legacy) non-project workspace roots). - for group in dev { + for group in dev.iter() { for dependency in project.group(group) { if dependency.marker.evaluate(marker_env, &[]) { let root_name = &dependency.name; diff --git a/crates/uv-resolver/src/lock/requirements_txt.rs b/crates/uv-resolver/src/lock/requirements_txt.rs index a1dc759834768..8f4f82d09e260 100644 --- a/crates/uv-resolver/src/lock/requirements_txt.rs +++ b/crates/uv-resolver/src/lock/requirements_txt.rs @@ -7,16 +7,16 @@ use std::path::{Component, Path, PathBuf}; use either::Either; use petgraph::visit::IntoNodeReferences; use petgraph::{Directed, Graph}; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet}; use url::Url; use distribution_filename::{DistExtension, SourceDistExtension}; use pep508_rs::MarkerTree; use pypi_types::{ParsedArchiveUrl, ParsedGitUrl}; -use uv_configuration::{ExtrasSpecification, InstallOptions}; +use uv_configuration::{DevSpecification, ExtrasSpecification, InstallOptions}; use uv_fs::Simplified; use uv_git::GitReference; -use uv_normalize::{ExtraName, GroupName, PackageName}; +use uv_normalize::{ExtraName, PackageName}; use crate::graph_ops::marker_reachability; use crate::lock::{Package, PackageId, Source}; @@ -42,15 +42,16 @@ impl<'lock> RequirementsTxtExport<'lock> { lock: &'lock Lock, root_name: &PackageName, extras: &ExtrasSpecification, - dev: &[GroupName], + dev: DevSpecification<'_>, hashes: bool, install_options: &'lock InstallOptions, ) -> Result { let size_guess = lock.packages.len(); let mut petgraph = LockGraph::with_capacity(size_guess, size_guess); + let mut inverse = FxHashMap::with_capacity_and_hasher(size_guess, FxBuildHasher); let mut queue: VecDeque<(&Package, Option<&ExtraName>)> = VecDeque::new(); - let mut inverse = FxHashMap::default(); + let mut seen = FxHashSet::default(); // Add the workspace package to the queue. let root = lock @@ -58,30 +59,51 @@ impl<'lock> RequirementsTxtExport<'lock> { .expect("found too many packages matching root") .expect("could not find root"); - // Add the base package. - queue.push_back((root, None)); + if dev.prod() { + // Add the base package. + queue.push_back((root, None)); - // Add any extras. - match extras { - ExtrasSpecification::None => {} - ExtrasSpecification::All => { - for extra in root.optional_dependencies.keys() { - queue.push_back((root, Some(extra))); + // Add any extras. + match extras { + ExtrasSpecification::None => {} + ExtrasSpecification::All => { + for extra in root.optional_dependencies.keys() { + queue.push_back((root, Some(extra))); + } } - } - ExtrasSpecification::Some(extras) => { - for extra in extras { - queue.push_back((root, Some(extra))); + ExtrasSpecification::Some(extras) => { + for extra in extras { + queue.push_back((root, Some(extra))); + } } } + + // Add the root package to the graph. + inverse.insert(&root.id, petgraph.add_node(root)); } - // Add the root package to the graph. - inverse.insert(&root.id, petgraph.add_node(root)); + // Add any dev dependencies. + for group in dev.iter() { + for dep in root.dev_dependencies.get(group).into_iter().flatten() { + let dep_dist = lock.find_by_id(&dep.package_id); - // Create all the relevant nodes. - let mut seen = FxHashSet::default(); + // Add the dependency to the graph. + if let Entry::Vacant(entry) = inverse.entry(&dep.package_id) { + entry.insert(petgraph.add_node(dep_dist)); + } + if seen.insert((&dep.package_id, None)) { + queue.push_back((dep_dist, None)); + } + for extra in &dep.extra { + if seen.insert((&dep.package_id, Some(extra))) { + queue.push_back((dep_dist, Some(extra))); + } + } + } + } + + // Create all the relevant nodes. while let Some((package, extra)) = queue.pop_front() { let index = inverse[&package.id]; @@ -94,11 +116,7 @@ impl<'lock> RequirementsTxtExport<'lock> { .flatten(), ) } else { - Either::Right(package.dependencies.iter().chain( - dev.iter().flat_map(|group| { - package.dev_dependencies.get(group).into_iter().flatten() - }), - )) + Either::Right(package.dependencies.iter()) }; for dep in deps { diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 57f54c5f5e244..a8afea36ac057 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -16,7 +16,7 @@ use uv_auth::{store_credentials_from_url, Credentials}; use uv_cache::Cache; use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - Concurrency, Constraints, ExtrasSpecification, InstallOptions, SourceStrategy, + Concurrency, Constraints, DevMode, ExtrasSpecification, InstallOptions, SourceStrategy, }; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; @@ -729,17 +729,17 @@ async fn lock_and_sync( let (extras, dev) = match dependency_type { DependencyType::Production => { let extras = ExtrasSpecification::None; - let dev = false; + let dev = DevMode::Exclude; (extras, dev) } DependencyType::Dev => { let extras = ExtrasSpecification::None; - let dev = true; + let dev = DevMode::Include; (extras, dev) } DependencyType::Optional(ref group_name) => { let extras = ExtrasSpecification::Some(vec![group_name.clone()]); - let dev = false; + let dev = DevMode::Exclude; (extras, dev) } }; diff --git a/crates/uv/src/commands/project/export.rs b/crates/uv/src/commands/project/export.rs index dbf60af823f0b..bcb32bdf1d8b5 100644 --- a/crates/uv/src/commands/project/export.rs +++ b/crates/uv/src/commands/project/export.rs @@ -7,7 +7,9 @@ use std::path::PathBuf; use uv_cache::Cache; use uv_client::Connectivity; -use uv_configuration::{Concurrency, ExportFormat, ExtrasSpecification, InstallOptions}; +use uv_configuration::{ + Concurrency, DevMode, DevSpecification, ExportFormat, ExtrasSpecification, InstallOptions, +}; use uv_fs::CWD; use uv_normalize::{PackageName, DEV_DEPENDENCIES}; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; @@ -30,7 +32,7 @@ pub(crate) async fn export( install_options: InstallOptions, output_file: Option, extras: ExtrasSpecification, - dev: bool, + dev: DevMode, locked: bool, frozen: bool, python: Option, @@ -111,10 +113,10 @@ pub(crate) async fn export( }; // Include development dependencies, if requested. - let dev = if dev { - vec![DEV_DEPENDENCIES.clone()] - } else { - vec![] + let dev = match dev { + DevMode::Include => DevSpecification::Include(std::slice::from_ref(&DEV_DEPENDENCIES)), + DevMode::Exclude => DevSpecification::Exclude, + DevMode::Only => DevSpecification::Only(std::slice::from_ref(&DEV_DEPENDENCIES)), }; // Write the resolved dependencies to the output channel. @@ -127,7 +129,7 @@ pub(crate) async fn export( &lock, project.project_name(), &extras, - &dev, + dev, hashes, &install_options, )?; diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index e7644354652f0..5dee4c70c1cbd 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -6,7 +6,7 @@ use owo_colors::OwoColorize; use pep508_rs::PackageName; use uv_cache::Cache; use uv_client::Connectivity; -use uv_configuration::{Concurrency, ExtrasSpecification, InstallOptions}; +use uv_configuration::{Concurrency, DevMode, ExtrasSpecification, InstallOptions}; use uv_fs::{Simplified, CWD}; use uv_python::{PythonDownloads, PythonPreference, PythonRequest}; use uv_scripts::Pep723Script; @@ -189,7 +189,7 @@ pub(crate) async fn remove( // Perform a full sync, because we don't know what exactly is affected by the removal. // TODO(ibraheem): Should we accept CLI overrides for this? Should we even sync here? let extras = ExtrasSpecification::All; - let dev = true; + let dev = DevMode::Include; let install_options = InstallOptions::default(); // Initialize any shared state. diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 110c2a618fac6..6f783f0aa84f2 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -14,7 +14,7 @@ use tracing::{debug, warn}; use uv_cache::Cache; use uv_cli::ExternalCommand; use uv_client::{BaseClientBuilder, Connectivity}; -use uv_configuration::{Concurrency, ExtrasSpecification, InstallOptions, SourceStrategy}; +use uv_configuration::{Concurrency, DevMode, ExtrasSpecification, InstallOptions, SourceStrategy}; use uv_distribution::LoweredRequirement; use uv_fs::{PythonExt, Simplified, CWD}; use uv_installer::{SatisfiesResult, SitePackages}; @@ -57,7 +57,7 @@ pub(crate) async fn run( no_project: bool, no_config: bool, extras: ExtrasSpecification, - dev: bool, + dev: DevMode, python: Option, settings: ResolverInstallerSettings, python_preference: PythonPreference, @@ -268,9 +268,12 @@ pub(crate) async fn run( if !extras.is_empty() { warn_user!("Extras are not supported for Python scripts with inline metadata"); } - if !dev { + if matches!(dev, DevMode::Exclude) { warn_user!("`--no-dev` is not supported for Python scripts with inline metadata"); } + if matches!(dev, DevMode::Only) { + warn_user!("`--only-dev` is not supported for Python scripts with inline metadata"); + } if package.is_some() { warn_user!( "`--package` is a no-op for Python scripts with inline metadata, which always run in isolation" @@ -342,9 +345,12 @@ pub(crate) async fn run( if !extras.is_empty() { warn_user!("Extras have no effect when used alongside `--no-project`"); } - if !dev { + if matches!(dev, DevMode::Exclude) { warn_user!("`--no-dev` has no effect when used alongside `--no-project`"); } + if matches!(dev, DevMode::Only) { + warn_user!("`--only-dev` has no effect when used alongside `--no-project`"); + } if locked { warn_user!("`--locked` has no effect when used alongside `--no-project`"); } @@ -359,9 +365,12 @@ pub(crate) async fn run( if !extras.is_empty() { warn_user!("Extras have no effect when used outside of a project"); } - if !dev { + if matches!(dev, DevMode::Exclude) { warn_user!("`--no-dev` has no effect when used outside of a project"); } + if matches!(dev, DevMode::Only) { + warn_user!("`--only-dev` has no effect when used outside of a project"); + } if locked { warn_user!("`--locked` has no effect when used outside of a project"); } diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index ce2449494d197..4a533915ef2cd 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -7,7 +7,8 @@ use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ - Concurrency, Constraints, ExtrasSpecification, HashCheckingMode, InstallOptions, + Concurrency, Constraints, DevMode, DevSpecification, ExtrasSpecification, HashCheckingMode, + InstallOptions, }; use uv_dispatch::BuildDispatch; use uv_fs::CWD; @@ -34,7 +35,7 @@ pub(crate) async fn sync( frozen: bool, package: Option, extras: ExtrasSpecification, - dev: bool, + dev: DevMode, install_options: InstallOptions, modifications: Modifications, python: Option, @@ -155,7 +156,7 @@ pub(super) async fn do_sync( venv: &PythonEnvironment, lock: &Lock, extras: &ExtrasSpecification, - dev: bool, + dev: DevMode, install_options: InstallOptions, modifications: Modifications, settings: InstallerSettingsRef<'_>, @@ -218,10 +219,10 @@ pub(super) async fn do_sync( } // Include development dependencies, if requested. - let dev = if dev { - vec![DEV_DEPENDENCIES.clone()] - } else { - vec![] + let dev = match dev { + DevMode::Include => DevSpecification::Include(std::slice::from_ref(&DEV_DEPENDENCIES)), + DevMode::Exclude => DevSpecification::Exclude, + DevMode::Only => DevSpecification::Only(std::slice::from_ref(&DEV_DEPENDENCIES)), }; // Determine the tags to use for resolution. @@ -233,7 +234,7 @@ pub(super) async fn do_sync( &markers, tags, extras, - &dev, + dev, build_options, &install_options, )?; diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index 1bf7a398d0502..a2b7ca8563c34 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -22,9 +22,9 @@ use uv_cli::{ }; use uv_client::Connectivity; use uv_configuration::{ - BuildOptions, Concurrency, ConfigSettings, ExportFormat, ExtrasSpecification, HashCheckingMode, - IndexStrategy, InstallOptions, KeyringProviderType, NoBinary, NoBuild, PreviewMode, Reinstall, - SourceStrategy, TargetTriple, TrustedHost, Upgrade, + BuildOptions, Concurrency, ConfigSettings, DevMode, ExportFormat, ExtrasSpecification, + HashCheckingMode, IndexStrategy, InstallOptions, KeyringProviderType, NoBinary, NoBuild, + PreviewMode, Reinstall, SourceStrategy, TargetTriple, TrustedHost, Upgrade, }; use uv_normalize::PackageName; use uv_python::{Prefix, PythonDownloads, PythonPreference, PythonVersion, Target}; @@ -209,7 +209,7 @@ pub(crate) struct RunSettings { pub(crate) locked: bool, pub(crate) frozen: bool, pub(crate) extras: ExtrasSpecification, - pub(crate) dev: bool, + pub(crate) dev: DevMode, pub(crate) with: Vec, pub(crate) with_editable: Vec, pub(crate) with_requirements: Vec, @@ -233,6 +233,7 @@ impl RunSettings { no_all_extras, dev, no_dev, + only_dev, command: _, with, with_editable, @@ -257,7 +258,7 @@ impl RunSettings { flag(all_extras, no_all_extras).unwrap_or_default(), extra.unwrap_or_default(), ), - dev: flag(dev, no_dev).unwrap_or(true), + dev: DevMode::from_args(dev, no_dev, only_dev), with, with_editable, with_requirements: with_requirements @@ -659,7 +660,7 @@ pub(crate) struct SyncSettings { pub(crate) locked: bool, pub(crate) frozen: bool, pub(crate) extras: ExtrasSpecification, - pub(crate) dev: bool, + pub(crate) dev: DevMode, pub(crate) install_options: InstallOptions, pub(crate) modifications: Modifications, pub(crate) package: Option, @@ -678,6 +679,7 @@ impl SyncSettings { no_all_extras, dev, no_dev, + only_dev, inexact, exact, no_install_project, @@ -704,7 +706,7 @@ impl SyncSettings { flag(all_extras, no_all_extras).unwrap_or_default(), extra.unwrap_or_default(), ), - dev: flag(dev, no_dev).unwrap_or(true), + dev: DevMode::from_args(dev, no_dev, only_dev), install_options: InstallOptions::new( no_install_project, no_install_workspace, @@ -958,7 +960,7 @@ pub(crate) struct ExportSettings { pub(crate) format: ExportFormat, pub(crate) package: Option, pub(crate) extras: ExtrasSpecification, - pub(crate) dev: bool, + pub(crate) dev: DevMode, pub(crate) hashes: bool, pub(crate) install_options: InstallOptions, pub(crate) output_file: Option, @@ -981,6 +983,7 @@ impl ExportSettings { no_all_extras, dev, no_dev, + only_dev, hashes, no_hashes, output_file, @@ -1002,7 +1005,7 @@ impl ExportSettings { flag(all_extras, no_all_extras).unwrap_or_default(), extra.unwrap_or_default(), ), - dev: flag(dev, no_dev).unwrap_or(true), + dev: DevMode::from_args(dev, no_dev, only_dev), hashes: flag(hashes, no_hashes).unwrap_or(true), install_options: InstallOptions::new( no_emit_project, diff --git a/crates/uv/tests/export.rs b/crates/uv/tests/export.rs index aca409c99ef46..6d3ea51356c4a 100644 --- a/crates/uv/tests/export.rs +++ b/crates/uv/tests/export.rs @@ -611,6 +611,25 @@ fn dev() -> Result<()> { Resolved 5 packages in [TIME] "###); + uv_snapshot!(context.filters(), context.export().arg("--only-dev"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated via `uv export`. + anyio==4.3.0 \ + --hash=sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6 \ + --hash=sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8 + idna==3.6 \ + --hash=sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca \ + --hash=sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f + sniffio==1.3.1 \ + --hash=sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc \ + --hash=sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2 + + ----- stderr ----- + Resolved 5 packages in [TIME] + "###); + Ok(()) } diff --git a/crates/uv/tests/sync.rs b/crates/uv/tests/sync.rs index 57bcc1fcabb52..b8fe07f0eb3b2 100644 --- a/crates/uv/tests/sync.rs +++ b/crates/uv/tests/sync.rs @@ -945,6 +945,78 @@ fn sync_environment() -> Result<()> { Ok(()) } +#[test] +fn sync_dev() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["typing-extensions"] + + [tool.uv] + dev-dependencies = ["anyio"] + + [build-system] + requires = ["setuptools>=42"] + build-backend = "setuptools.build_meta" + "#, + )?; + + context.lock().assert().success(); + + uv_snapshot!(context.filters(), context.sync().arg("--only-dev"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "###); + + uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 2 packages in [TIME] + Uninstalled 3 packages in [TIME] + Installed 2 packages in [TIME] + - anyio==4.3.0 + - idna==3.6 + + project==0.1.0 (from file://[TEMP_DIR]/) + - sniffio==1.3.1 + + typing-extensions==4.10.0 + "###); + + uv_snapshot!(context.filters(), context.sync(), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 5 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "###); + + Ok(()) +} + /// Regression test for . /// /// Previously, we would read metadata statically from pyproject.toml and write that to `uv.lock`. In diff --git a/docs/reference/cli.md b/docs/reference/cli.md index a14a93eff1d5c..dde79738bdb9e 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -273,6 +273,10 @@ uv run [OPTIONS]

When disabled, uv will only use locally cached data and locally available files.

+
--only-dev

Omit non-development dependencies.

+ +

The project itself will also be omitted.

+
--package package

Run the command in a specific package in the workspace.

If the workspace member does not exist, uv will exit with an error.

@@ -1335,6 +1339,10 @@ uv sync [OPTIONS]

When disabled, uv will only use locally cached data and locally available files.

+
--only-dev

Omit non-development dependencies.

+ +

The project itself will also be omitted.

+
--package package

Sync for a specific package in the workspace.

The workspace’s environment (.venv) is updated to reflect the subset of dependencies declared by the specified workspace member package.

@@ -1889,6 +1897,10 @@ uv export [OPTIONS]

When disabled, uv will only use locally cached data and locally available files.

+
--only-dev

Omit non-development dependencies.

+ +

The project itself will also be omitted.

+
--output-file, -o output-file

Write the exported requirements to the given file

--package package

Export the dependencies for a specific package in the workspace.