From 34fbc06ad6111f43a259993c51f12ac021cc2238 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 14 Jul 2025 10:53:39 -0400 Subject: [PATCH] Add experimental `uv sync --output-format json` (#13689) This is a continuation of the work in * #12405 I have: * moved to an architecture where the human output is derived from the json structs to centralize more of the printing state/logic * cleaned up some of the names/types * added tests * removed the restriction that this output is --dry-run only I have not yet added package info, which was TBD in their design. --------- Co-authored-by: x0rw Co-authored-by: Zanie Blue Co-authored-by: John Mumm --- crates/uv-cli/src/lib.rs | 13 + crates/uv-fs/src/path.rs | 6 + crates/uv/src/commands/project/mod.rs | 16 + crates/uv/src/commands/project/sync.rs | 615 ++++++++--- crates/uv/src/lib.rs | 1 + crates/uv/src/settings.rs | 7 +- crates/uv/tests/it/common/mod.rs | 2 +- crates/uv/tests/it/sync.rs | 1326 ++++++++++++++++-------- docs/reference/cli.md | 7 +- 9 files changed, 1389 insertions(+), 604 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 056447959..0f3652341 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -46,6 +46,15 @@ pub enum PythonListFormat { Json, } +#[derive(Debug, Default, Clone, Copy, clap::ValueEnum)] +pub enum SyncFormat { + /// Display the result in a human-readable format. + #[default] + Text, + /// Display the result in JSON format. + Json, +} + #[derive(Debug, Default, Clone, clap::ValueEnum)] pub enum ListFormat { /// Display the list of packages in a human-readable table. @@ -3207,6 +3216,10 @@ pub struct SyncArgs { #[arg(long, conflicts_with = "all_extras", value_parser = extra_name_with_clap_error)] pub extra: Option>, + /// Select the output format. + #[arg(long, value_enum, default_value_t = SyncFormat::default())] + pub output_format: SyncFormat, + /// Include all optional dependencies. /// /// When two or more extras are declared as conflicting in `tool.uv.conflicts`, using this flag diff --git a/crates/uv-fs/src/path.rs b/crates/uv-fs/src/path.rs index 40e579f8e..45d1da1c8 100644 --- a/crates/uv-fs/src/path.rs +++ b/crates/uv-fs/src/path.rs @@ -398,6 +398,12 @@ impl From> for PortablePathBuf { } } +impl<'a> From<&'a Path> for PortablePathBuf { + fn from(path: &'a Path) -> Self { + Box::::from(path).into() + } +} + #[cfg(feature = "serde")] impl serde::Serialize for PortablePathBuf { fn serialize(&self, serializer: S) -> Result diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 1a0274cac..774009f63 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -1408,6 +1408,14 @@ impl ProjectEnvironment { Self::WouldCreate(..) => Err(ProjectError::DroppedEnvironment), } } + + /// Return the path to the actual target, if this was a dry run environment. + pub(crate) fn dry_run_target(&self) -> Option<&Path> { + match self { + Self::WouldReplace(path, _, _) | Self::WouldCreate(path, _, _) => Some(path), + Self::Created(_) | Self::Existing(_) | Self::Replaced(_) => None, + } + } } impl std::ops::Deref for ProjectEnvironment { @@ -1588,6 +1596,14 @@ impl ScriptEnvironment { Self::WouldCreate(..) => Err(ProjectError::DroppedEnvironment), } } + + /// Return the path to the actual target, if this was a dry run environment. + pub(crate) fn dry_run_target(&self) -> Option<&Path> { + match self { + Self::WouldReplace(path, _, _) | Self::WouldCreate(path, _, _) => Some(path), + Self::Created(_) | Self::Existing(_) | Self::Replaced(_) => None, + } + } } impl std::ops::Deref for ScriptEnvironment { diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index a9a161527..94586004f 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -6,9 +6,10 @@ use std::sync::Arc; use anyhow::{Context, Result}; use itertools::Itertools; use owo_colors::OwoColorize; +use serde::Serialize; use tracing::warn; - use uv_cache::Cache; +use uv_cli::SyncFormat; use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder}; use uv_configuration::{ Concurrency, Constraints, DependencyGroups, DependencyGroupsWithDefaults, DryRun, EditableMode, @@ -19,7 +20,7 @@ use uv_dispatch::BuildDispatch; use uv_distribution_types::{ DirectorySourceDist, Dist, Index, Requirement, Resolution, ResolvedDist, SourceDist, }; -use uv_fs::Simplified; +use uv_fs::{PortablePathBuf, Simplified}; use uv_installer::SitePackages; use uv_normalize::{DefaultExtras, DefaultGroups, PackageName}; use uv_pep508::{MarkerTree, VersionOrUrl}; @@ -77,7 +78,14 @@ pub(crate) async fn sync( cache: &Cache, printer: Printer, preview: PreviewMode, + output_format: SyncFormat, ) -> Result { + if preview.is_enabled() && matches!(output_format, SyncFormat::Json) { + warn_user!( + "The `--output-format json` option is experimental and the schema may change without warning. Pass `--preview` to disable this warning." + ); + } + // Identify the target. let workspace_cache = WorkspaceCache::default(); let target = if let Some(script) = script { @@ -180,103 +188,16 @@ pub(crate) async fn sync( }) .ok(); - // Notify the user of any environment changes. - match &environment { - SyncEnvironment::Project(ProjectEnvironment::Existing(environment)) - if dry_run.enabled() => - { - writeln!( - printer.stderr(), - "{}", - format!( - "Discovered existing environment at: {}", - environment.root().user_display().bold() - ) - .dimmed() - )?; - } - SyncEnvironment::Project(ProjectEnvironment::WouldReplace(root, ..)) - if dry_run.enabled() => - { - writeln!( - printer.stderr(), - "{}", - format!( - "Would replace existing virtual environment at: {}", - root.user_display().bold() - ) - .dimmed() - )?; - } - SyncEnvironment::Project(ProjectEnvironment::WouldCreate(root, ..)) - if dry_run.enabled() => - { - writeln!( - printer.stderr(), - "{}", - format!( - "Would create virtual environment at: {}", - root.user_display().bold() - ) - .dimmed() - )?; - } - SyncEnvironment::Script(ScriptEnvironment::Existing(environment)) => { - if dry_run.enabled() { - writeln!( - printer.stderr(), - "{}", - format!( - "Discovered existing environment at: {}", - environment.root().user_display().bold() - ) - .dimmed() - )?; - } else { - writeln!( - printer.stderr(), - "Using script environment at: {}", - environment.root().user_display().cyan() - )?; - } - } - SyncEnvironment::Script(ScriptEnvironment::Replaced(environment)) if !dry_run.enabled() => { - writeln!( - printer.stderr(), - "Recreating script environment at: {}", - environment.root().user_display().cyan() - )?; - } - SyncEnvironment::Script(ScriptEnvironment::Created(environment)) if !dry_run.enabled() => { - writeln!( - printer.stderr(), - "Creating script environment at: {}", - environment.root().user_display().cyan() - )?; - } - SyncEnvironment::Script(ScriptEnvironment::WouldReplace(root, ..)) if dry_run.enabled() => { - writeln!( - printer.stderr(), - "{}", - format!( - "Would replace existing script environment at: {}", - root.user_display().bold() - ) - .dimmed() - )?; - } - SyncEnvironment::Script(ScriptEnvironment::WouldCreate(root, ..)) if dry_run.enabled() => { - writeln!( - printer.stderr(), - "{}", - format!( - "Would create script environment at: {}", - root.user_display().bold() - ) - .dimmed() - )?; - } - _ => {} + let sync_report = SyncReport { + dry_run: dry_run.enabled(), + environment: EnvironmentReport::from(&environment), + action: SyncAction::from(&environment), + target: TargetName::from(&target), + }; + + // Show the intermediate results if relevant + if let Some(message) = sync_report.format(output_format) { + writeln!(printer.stderr(), "{message}")?; } // Special-case: we're syncing a script that doesn't have an associated lockfile. In that case, @@ -340,7 +261,23 @@ pub(crate) async fn sync( ) .await { - Ok(..) => return Ok(ExitStatus::Success), + Ok(..) => { + // Generate a report for the script without a lockfile + let report = Report { + schema: SchemaReport::default(), + target: TargetName::from(&target), + project: None, + script: Some(ScriptReport::from(script)), + sync: sync_report, + lock: None, + dry_run: dry_run.enabled(), + }; + if let Some(output) = report.format(output_format) { + writeln!(printer.stdout(), "{output}")?; + } + return Ok(ExitStatus::Success); + } + // TODO(zanieb): We should respect `--output-format json` for the error case Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls( network_settings.native_tls, @@ -387,46 +324,7 @@ pub(crate) async fn sync( .execute(lock_target) .await { - Ok(result) => { - if dry_run.enabled() { - match result { - LockResult::Unchanged(..) => { - writeln!( - printer.stderr(), - "{}", - format!( - "Found up-to-date lockfile at: {}", - lock_target.lock_path().user_display().bold() - ) - .dimmed() - )?; - } - LockResult::Changed(None, ..) => { - writeln!( - printer.stderr(), - "{}", - format!( - "Would create lockfile at: {}", - lock_target.lock_path().user_display().bold() - ) - .dimmed() - )?; - } - LockResult::Changed(Some(..), ..) => { - writeln!( - printer.stderr(), - "{}", - format!( - "Would update lockfile at: {}", - lock_target.lock_path().user_display().bold() - ) - .dimmed() - )?; - } - } - } - Outcome::Success(result.into_lock()) - } + Ok(result) => Outcome::Success(result), Err(ProjectError::Operation(err)) => { return diagnostics::OperationDiagnostic::native_tls(network_settings.native_tls) .report(err) @@ -440,6 +338,25 @@ pub(crate) async fn sync( Err(err) => return Err(err.into()), }; + let lock_report = LockReport::from((&lock_target, &mode, &outcome)); + if let Some(message) = lock_report.format(output_format) { + writeln!(printer.stderr(), "{message}")?; + } + + let report = Report { + schema: SchemaReport::default(), + target: TargetName::from(&target), + project: target.project().map(ProjectReport::from), + script: target.script().map(ScriptReport::from), + sync: sync_report, + lock: Some(lock_report), + dry_run: dry_run.enabled(), + }; + + if let Some(output) = report.format(output_format) { + writeln!(printer.stdout(), "{output}")?; + } + // Identify the installation target. let sync_target = identify_installation_target(&target, outcome.lock(), all_packages, package.as_ref()); @@ -490,7 +407,7 @@ pub(crate) async fn sync( #[allow(clippy::large_enum_variant)] enum Outcome { /// The `lock` operation was successful. - Success(Lock), + Success(LockResult), /// The `lock` operation successfully resolved, but failed due to a mismatch (e.g., with `--locked`). LockMismatch(Box), } @@ -499,7 +416,7 @@ impl Outcome { /// Return the [`Lock`] associated with this outcome. fn lock(&self) -> &Lock { match self { - Self::Success(lock) => lock, + Self::Success(lock) => lock.lock(), Self::LockMismatch(lock) => lock, } } @@ -563,6 +480,22 @@ enum SyncTarget { Script(Pep723Script), } +impl SyncTarget { + fn project(&self) -> Option<&VirtualProject> { + match self { + Self::Project(project) => Some(project), + Self::Script(_) => None, + } + } + + fn script(&self) -> Option<&Pep723Script> { + match self { + Self::Project(_) => None, + Self::Script(script) => Some(script), + } + } +} + #[derive(Debug)] enum SyncEnvironment { /// A Python environment for a project. @@ -571,6 +504,15 @@ enum SyncEnvironment { Script(ScriptEnvironment), } +impl SyncEnvironment { + fn dry_run_target(&self) -> Option<&Path> { + match self { + Self::Project(env) => env.dry_run_target(), + Self::Script(env) => env.dry_run_target(), + } + } +} + impl Deref for SyncEnvironment { type Target = PythonEnvironment; @@ -892,3 +834,392 @@ fn store_credentials_from_target(target: InstallTarget<'_>) { } } } + +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +struct WorkspaceReport { + /// The workspace directory path. + path: PortablePathBuf, +} + +impl From<&Workspace> for WorkspaceReport { + fn from(workspace: &Workspace) -> Self { + Self { + path: workspace.install_path().as_path().into(), + } + } +} +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +struct ProjectReport { + // + path: PortablePathBuf, + workspace: WorkspaceReport, +} + +impl From<&VirtualProject> for ProjectReport { + fn from(project: &VirtualProject) -> Self { + Self { + path: project.root().into(), + workspace: WorkspaceReport::from(project.workspace()), + } + } +} + +impl From<&SyncTarget> for TargetName { + fn from(target: &SyncTarget) -> Self { + match target { + SyncTarget::Project(_) => TargetName::Project, + SyncTarget::Script(_) => TargetName::Script, + } + } +} + +#[derive(Serialize, Debug)] +struct ScriptReport { + /// The path to the script. + path: PortablePathBuf, +} + +impl From<&Pep723Script> for ScriptReport { + fn from(script: &Pep723Script) -> Self { + Self { + path: script.path.as_path().into(), + } + } +} + +#[derive(Serialize, Debug, Default)] +#[serde(rename_all = "snake_case")] +enum SchemaVersion { + /// An unstable, experimental schema. + #[default] + Preview, +} + +#[derive(Serialize, Debug, Default)] +struct SchemaReport { + /// The version of the schema. + version: SchemaVersion, +} + +/// A report of the uv sync operation +#[derive(Debug, Serialize)] +#[serde(rename_all = "snake_case")] +struct Report { + /// The schema of this report. + schema: SchemaReport, + /// The target of the sync operation, either a project or a script. + target: TargetName, + /// The report for a [`TargetName::Project`], if applicable. + #[serde(skip_serializing_if = "Option::is_none")] + project: Option, + /// The report for a [`TargetName::Script`], if applicable. + #[serde(skip_serializing_if = "Option::is_none")] + script: Option, + /// The report for the sync operation. + sync: SyncReport, + /// The report for the lock operation. + lock: Option, + /// Whether this is a dry run. + dry_run: bool, +} + +/// The kind of target +#[derive(Debug, Serialize, Clone, Copy)] +#[serde(rename_all = "snake_case")] +enum TargetName { + Project, + Script, +} + +impl std::fmt::Display for TargetName { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TargetName::Project => write!(f, "project"), + TargetName::Script => write!(f, "script"), + } + } +} + +/// Represents the action taken during a sync. +#[derive(Serialize, Debug)] +#[serde(rename_all = "snake_case")] +enum SyncAction { + /// The environment was checked and required no updates. + Check, + /// The environment was updated. + Update, + /// The environment was replaced. + Replace, + /// A new environment was created. + Create, +} + +impl From<&SyncEnvironment> for SyncAction { + fn from(env: &SyncEnvironment) -> Self { + match &env { + SyncEnvironment::Project(ProjectEnvironment::Existing(..)) => SyncAction::Check, + SyncEnvironment::Project(ProjectEnvironment::Created(..)) => SyncAction::Create, + SyncEnvironment::Project(ProjectEnvironment::WouldCreate(..)) => SyncAction::Create, + SyncEnvironment::Project(ProjectEnvironment::WouldReplace(..)) => SyncAction::Replace, + SyncEnvironment::Project(ProjectEnvironment::Replaced(..)) => SyncAction::Update, + SyncEnvironment::Script(ScriptEnvironment::Existing(..)) => SyncAction::Check, + SyncEnvironment::Script(ScriptEnvironment::Created(..)) => SyncAction::Create, + SyncEnvironment::Script(ScriptEnvironment::WouldCreate(..)) => SyncAction::Create, + SyncEnvironment::Script(ScriptEnvironment::WouldReplace(..)) => SyncAction::Replace, + SyncEnvironment::Script(ScriptEnvironment::Replaced(..)) => SyncAction::Update, + } + } +} + +impl SyncAction { + fn message(&self, target: TargetName, dry_run: bool) -> Option<&'static str> { + let message = if dry_run { + match self { + SyncAction::Check => "Would use", + SyncAction::Update => "Would update", + SyncAction::Replace => "Would replace", + SyncAction::Create => "Would create", + } + } else { + // For projects, we omit some of these messages when we're not in dry-run mode + let is_project = matches!(target, TargetName::Project); + match self { + SyncAction::Check | SyncAction::Update | SyncAction::Create if is_project => { + return None; + } + SyncAction::Check => "Using", + SyncAction::Update => "Updating", + SyncAction::Replace => "Replacing", + SyncAction::Create => "Creating", + } + }; + Some(message) + } +} + +/// Represents the action taken during a lock. +#[derive(Serialize, Debug)] +#[serde(rename_all = "snake_case")] +enum LockAction { + /// The lockfile was used without checking. + Use, + /// The lockfile was checked and required no updates. + Check, + /// The lockfile was updated. + Update, + /// A new lockfile was created. + Create, +} + +impl LockAction { + fn message(&self, dry_run: bool) -> Option<&'static str> { + let message = if dry_run { + match self { + LockAction::Use => return None, + LockAction::Check => "Found up-to-date", + LockAction::Update => "Would update", + LockAction::Create => "Would create", + } + } else { + return None; + }; + Some(message) + } +} + +#[derive(Serialize, Debug)] +struct PythonReport { + path: PortablePathBuf, + version: uv_pep508::StringVersion, + implementation: String, +} + +impl From<&uv_python::Interpreter> for PythonReport { + fn from(interpreter: &uv_python::Interpreter) -> Self { + Self { + path: interpreter.sys_executable().into(), + version: interpreter.python_full_version().clone(), + implementation: interpreter.implementation_name().to_string(), + } + } +} + +impl PythonReport { + /// Set the path for this Python report. + #[must_use] + fn with_path(mut self, path: PortablePathBuf) -> Self { + self.path = path; + self + } +} + +#[derive(Serialize, Debug)] +struct EnvironmentReport { + /// The path to the environment. + path: PortablePathBuf, + /// The Python interpreter for the environment. + python: PythonReport, +} + +impl From<&PythonEnvironment> for EnvironmentReport { + fn from(env: &PythonEnvironment) -> Self { + Self { + python: PythonReport::from(env.interpreter()), + path: env.root().into(), + } + } +} + +impl From<&SyncEnvironment> for EnvironmentReport { + fn from(env: &SyncEnvironment) -> Self { + let report = EnvironmentReport::from(&**env); + // Replace the path if necessary; we construct a temporary virtual environment during dry + // run invocations and want to report the path we _would_ use. + if let Some(path) = env.dry_run_target() { + report.with_path(path.into()) + } else { + report + } + } +} + +impl EnvironmentReport { + /// Set the path for this environment report. + #[must_use] + fn with_path(mut self, path: PortablePathBuf) -> Self { + let python_path = &self.python.path; + if let Ok(python_path) = python_path.as_ref().strip_prefix(self.path) { + let new_path = path.as_ref().to_path_buf().join(python_path); + self.python = self.python.with_path(new_path.as_path().into()); + } + self.path = path; + self + } +} + +/// The report for a sync operation. +#[derive(Serialize, Debug)] +struct SyncReport { + /// The environment. + environment: EnvironmentReport, + /// The action performed during the sync, e.g., what was done to the environment. + action: SyncAction, + + // We store these fields so the report can format itself self-contained, but the outer + // [`Report`] is intended to include these in user-facing output + #[serde(skip)] + dry_run: bool, + #[serde(skip)] + target: TargetName, +} + +impl SyncReport { + fn format(&self, output_format: SyncFormat) -> Option { + match output_format { + // This is an intermediate report, when using JSON, it's only rendered at the end + SyncFormat::Json => None, + SyncFormat::Text => self.to_human_readable_string(), + } + } + + fn to_human_readable_string(&self) -> Option { + let Self { + environment, + action, + dry_run, + target, + } = self; + + let action = action.message(*target, *dry_run)?; + + let message = format!( + "{action} {target} environment at: {path}", + path = environment.path.user_display().cyan(), + ); + if *dry_run { + return Some(message.dimmed().to_string()); + } + + Some(message) + } +} + +/// The report for a lock operation. +#[derive(Debug, Serialize)] +struct LockReport { + /// The path to the lockfile + path: PortablePathBuf, + /// Whether the lockfile was preserved, created, or updated. + action: LockAction, + + // We store this field so the report can format itself self-contained, but the outer + // [`Report`] is intended to include this in user-facing output + #[serde(skip)] + dry_run: bool, +} + +impl From<(&LockTarget<'_>, &LockMode<'_>, &Outcome)> for LockReport { + fn from((target, mode, outcome): (&LockTarget, &LockMode, &Outcome)) -> Self { + Self { + path: target.lock_path().deref().into(), + action: match outcome { + Outcome::Success(result) => { + match result { + LockResult::Unchanged(..) => match mode { + // When `--frozen` is used, we don't check the lockfile + LockMode::Frozen => LockAction::Use, + LockMode::DryRun(_) | LockMode::Locked(_) | LockMode::Write(_) => { + LockAction::Check + } + }, + LockResult::Changed(None, ..) => LockAction::Create, + LockResult::Changed(Some(_), ..) => LockAction::Update, + } + } + // TODO(zanieb): We don't have a way to report the outcome of the lock yet + Outcome::LockMismatch(_) => LockAction::Check, + }, + dry_run: matches!(mode, LockMode::DryRun(_)), + } + } +} + +impl LockReport { + fn format(&self, output_format: SyncFormat) -> Option { + match output_format { + SyncFormat::Json => None, + SyncFormat::Text => self.to_human_readable_string(), + } + } + + fn to_human_readable_string(&self) -> Option { + let Self { + path, + action, + dry_run, + } = self; + + let action = action.message(*dry_run)?; + + let message = format!( + "{action} lockfile at: {path}", + path = path.user_display().cyan(), + ); + if *dry_run { + return Some(message.dimmed().to_string()); + } + + Some(message) + } +} + +impl Report { + fn format(&self, output_format: SyncFormat) -> Option { + match output_format { + SyncFormat::Json => serde_json::to_string_pretty(self).ok(), + SyncFormat::Text => None, + } + } +} diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 2a163d32c..0b4d0bb82 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -1818,6 +1818,7 @@ async fn run_project( &cache, printer, globals.preview, + args.output_format, )) .await } diff --git a/crates/uv/src/settings.rs b/crates/uv/src/settings.rs index f89704d45..8a325d538 100644 --- a/crates/uv/src/settings.rs +++ b/crates/uv/src/settings.rs @@ -11,8 +11,8 @@ use uv_cli::{ PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs, PythonListFormat, PythonPinArgs, PythonUninstallArgs, PythonUpgradeArgs, RemoveArgs, RunArgs, - SyncArgs, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, - VenvArgs, VersionArgs, VersionBump, VersionFormat, + SyncArgs, SyncFormat, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs, + ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs, VersionBump, VersionFormat, }; use uv_cli::{ AuthorFrom, BuildArgs, ExportArgs, PublishArgs, PythonDirArgs, ResolverInstallerArgs, @@ -1154,6 +1154,7 @@ pub(crate) struct SyncSettings { pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) refresh: Refresh, pub(crate) settings: ResolverInstallerSettings, + pub(crate) output_format: SyncFormat, } impl SyncSettings { @@ -1194,6 +1195,7 @@ impl SyncSettings { python_platform, check, no_check, + output_format, } = args; let install_mirrors = filesystem .clone() @@ -1213,6 +1215,7 @@ impl SyncSettings { }; Self { + output_format, locked, frozen, dry_run, diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs index 90f436f6f..2dc72fa1d 100644 --- a/crates/uv/tests/it/common/mod.rs +++ b/crates/uv/tests/it/common/mod.rs @@ -210,7 +210,7 @@ impl TestContext { pub fn with_filtered_python_names(mut self) -> Self { if cfg!(windows) { self.filters - .push(("python.exe".to_string(), "python".to_string())); + .push((r"python\.exe".to_string(), "python".to_string())); } else { self.filters .push((r"python\d.\d\d".to_string(), "python".to_string())); diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index d4479296a..7063035f9 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -27,7 +27,7 @@ fn sync() -> Result<()> { )?; // Running `uv sync` should generate a lockfile. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -37,7 +37,7 @@ fn sync() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -60,14 +60,14 @@ fn locked() -> Result<()> { )?; // Running with `--locked` should error, if no lockfile is present. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. - "###); + "); // Lock the initial requirements. context.lock().assert().success(); @@ -86,7 +86,7 @@ fn locked() -> Result<()> { )?; // Running with `--locked` should error. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" success: false exit_code: 2 ----- stdout ----- @@ -94,7 +94,7 @@ fn locked() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. - "###); + "); let updated = context.read("uv.lock"); @@ -120,14 +120,14 @@ fn frozen() -> Result<()> { )?; // Running with `--frozen` should error, if no lockfile is present. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`. - "###); + "); context.lock().assert().success(); @@ -143,7 +143,7 @@ fn frozen() -> Result<()> { )?; // Running with `--frozen` should install the stale lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -154,7 +154,7 @@ fn frozen() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -172,7 +172,7 @@ fn empty() -> Result<()> { )?; // Running `uv sync` should generate an empty lockfile. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -181,12 +181,12 @@ fn empty() -> Result<()> { warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. Resolved in [TIME] Audited in [TIME] - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); // Running `uv sync` again should succeed. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -195,7 +195,7 @@ fn empty() -> Result<()> { warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. Resolved in [TIME] Audited in [TIME] - "###); + "); Ok(()) } @@ -252,7 +252,7 @@ fn package() -> Result<()> { let init = src.child("__init__.py"); init.touch()?; - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child"), @r" success: true exit_code: 0 ----- stdout ----- @@ -263,7 +263,239 @@ fn package() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 - "###); + "); + + Ok(()) +} + +/// Test json output +#[test] +fn sync_json() -> Result<()> { + let context = TestContext::new("3.12") + .with_filtered_python_names() + .with_filtered_virtualenv_bin(); + + 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 = ["iniconfig"] + "#, + )?; + + uv_snapshot!(context.filters(), context.sync() + .arg("--output-format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "check" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "create" + }, + "dry_run": false + } + + ----- stderr ----- + Resolved 2 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + iniconfig==2.0.0 + "#); + + assert!(context.temp_dir.child("uv.lock").exists()); + + uv_snapshot!(context.filters(), context.sync() + .arg("--frozen") + .arg("--output-format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "check" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "use" + }, + "dry_run": false + } + + ----- stderr ----- + Audited 1 package in [TIME] + "#); + + uv_snapshot!(context.filters(), context.sync() + .arg("--locked") + .arg("--output-format").arg("json"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "check" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "check" + }, + "dry_run": false + } + + ----- stderr ----- + Resolved 2 packages in [TIME] + Audited 1 package in [TIME] + "#); + + // Invalidate the lockfile by changing the requirements. + 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 = ["iniconfig<2"] + "#, + )?; + + uv_snapshot!(context.filters(), context.sync() + .arg("--locked") + .arg("--output-format").arg("json"), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + "); + + Ok(()) +} + +/// Test --dry json output +#[test] +fn sync_dry_json() -> Result<()> { + let context = TestContext::new_with_versions(&["3.12"]) + .with_filtered_python_names() + .with_filtered_virtualenv_bin(); + + 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 = ["iniconfig"] + "#, + )?; + + // Running `uv sync` should report intent to create the environment and lockfile + uv_snapshot!(context.filters(), context.sync() + .arg("--output-format").arg("json") + .arg("--dry-run"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "project", + "project": { + "path": "[TEMP_DIR]/", + "workspace": { + "path": "[TEMP_DIR]/" + } + }, + "sync": { + "environment": { + "path": "[VENV]/", + "python": { + "path": "[VENV]/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "create" + }, + "lock": { + "path": "[TEMP_DIR]/uv.lock", + "action": "create" + }, + "dry_run": true + } + + ----- stderr ----- + Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] + Resolved 2 packages in [TIME] + Would download 1 package + Would install 1 package + + iniconfig==2.0.0 + "#); Ok(()) } @@ -322,7 +554,7 @@ fn mixed_requires_python() -> Result<()> { )?; // Running `uv sync` should succeed, locking for Python 3.12. - uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- @@ -337,7 +569,7 @@ fn mixed_requires_python() -> Result<()> { + bird-feeder==0.1.0 (from file://[TEMP_DIR]/packages/bird-feeder) + idna==3.6 + sniffio==1.3.1 - "###); + "); // Running `uv sync` again should fail. uv_snapshot!(context.filters(), context.sync().arg("-p").arg("3.9"), @r" @@ -660,23 +892,23 @@ fn check() -> Result<()> { )?; // Running `uv sync --check` should fail. - uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--check"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Would create lockfile at: uv.lock Would download 1 package Would install 1 package + iniconfig==2.0.0 error: The environment is outdated; run `uv sync` to update the environment - "###); + "); // Sync the environment. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -686,23 +918,23 @@ fn check() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); // Running `uv sync --check` should pass now that the environment is up to date. - uv_snapshot!(context.filters(), context.sync().arg("--check"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--check"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Found up-to-date lockfile at: uv.lock Audited 1 package in [TIME] Would make no changes - "###); + "); Ok(()) } @@ -750,7 +982,7 @@ fn sync_legacy_non_project_dev_dependencies() -> Result<()> { .touch()?; // Syncing with `--no-dev` should omit all dependencies except `iniconfig`. - uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -761,11 +993,11 @@ fn sync_legacy_non_project_dev_dependencies() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 - "###); + "); // Syncing without `--no-dev` should include `anyio`, `requests`, `pysocks`, and their // dependencies, but not `typing-extensions`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -782,7 +1014,7 @@ fn sync_legacy_non_project_dev_dependencies() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); Ok(()) } @@ -830,7 +1062,7 @@ fn sync_legacy_non_project_frozen() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--package").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--package").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -839,9 +1071,9 @@ fn sync_legacy_non_project_frozen() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -850,7 +1082,7 @@ fn sync_legacy_non_project_frozen() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -903,7 +1135,7 @@ fn sync_legacy_non_project_group() -> Result<()> { .child("__init__.py") .touch()?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -914,9 +1146,9 @@ fn sync_legacy_non_project_group() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -928,9 +1160,9 @@ fn sync_legacy_non_project_group() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -946,9 +1178,9 @@ fn sync_legacy_non_project_group() -> Result<()> { - iniconfig==2.0.0 - sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- @@ -958,9 +1190,9 @@ fn sync_legacy_non_project_group() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bop"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bop"), @r" success: false exit_code: 2 ----- stdout ----- @@ -968,7 +1200,7 @@ fn sync_legacy_non_project_group() -> Result<()> { ----- stderr ----- Resolved 6 packages in [TIME] error: Group `bop` is not defined in any project's `dependency-groups` table - "###); + "); Ok(()) } @@ -993,7 +1225,7 @@ fn sync_legacy_non_project_frozen_modification() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1004,7 +1236,7 @@ fn sync_legacy_non_project_frozen_modification() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Modify the "live" dependency groups. pyproject_toml.write_str( @@ -1018,14 +1250,14 @@ fn sync_legacy_non_project_frozen_modification() -> Result<()> { )?; // This should succeed. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 3 packages in [TIME] - "###); + "); Ok(()) } @@ -1074,7 +1306,7 @@ fn sync_build_isolation() -> Result<()> { "###); // Running `uv sync` should succeed. - uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1092,7 +1324,7 @@ fn sync_build_isolation() -> Result<()> { + source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz) - trove-classifiers==2024.3.3 - wheel==0.43.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1122,7 +1354,7 @@ fn sync_build_isolation_package() -> Result<()> { )?; // Running `uv sync` should fail for iniconfig. - uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -1140,7 +1372,7 @@ fn sync_build_isolation_package() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `source-distribution` was included because `project` (v0.1.0) depends on `source-distribution` - "###); + "#); // Install `hatchling` for `source-distribution`. uv_snapshot!(context.filters(), context.pip_install().arg("hatchling"), @r###" @@ -1160,7 +1392,7 @@ fn sync_build_isolation_package() -> Result<()> { "###); // Running `uv sync` should succeed. - uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-isolation-package").arg("source-distribution"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1177,7 +1409,7 @@ fn sync_build_isolation_package() -> Result<()> { + project==0.1.0 (from file://[TEMP_DIR]/) + source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz) - trove-classifiers==2024.3.3 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1212,7 +1444,7 @@ fn sync_build_isolation_extra() -> Result<()> { )?; // Running `uv sync` should fail for the `compile` extra. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -1230,10 +1462,10 @@ fn sync_build_isolation_extra() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `source-distribution` was included because `project[compile]` (v0.1.0) depends on `source-distribution` - "###); + "#); // Running `uv sync` with `--all-extras` should also fail. - uv_snapshot!(context.filters(), context.sync().arg("--all-extras"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-extras"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -1251,10 +1483,10 @@ fn sync_build_isolation_extra() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `source-distribution` was included because `project[compile]` (v0.1.0) depends on `source-distribution` - "###); + "#); // Install the build dependencies. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("build"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("build"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1269,10 +1501,10 @@ fn sync_build_isolation_extra() -> Result<()> { + pluggy==1.4.0 + project==0.1.0 (from file://[TEMP_DIR]/) + trove-classifiers==2024.3.3 - "###); + "); // Running `uv sync` for the `compile` extra should succeed, and remove the build dependencies. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("compile"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1288,7 +1520,7 @@ fn sync_build_isolation_extra() -> Result<()> { - pluggy==1.4.0 + source-distribution==0.0.1 (from https://files.pythonhosted.org/packages/10/1f/57aa4cce1b1abf6b433106676e15f9fa2c92ed2bd4cf77c3b50a9e9ac773/source_distribution-0.0.1.tar.gz) - trove-classifiers==2024.3.3 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1342,7 +1574,7 @@ fn sync_reset_state() -> Result<()> { init.touch()?; // Running `uv sync` should succeed. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1354,7 +1586,7 @@ fn sync_reset_state() -> Result<()> { + project==0.1.0 (from file://[TEMP_DIR]/) + pydantic-core==2.17.0 + typing-extensions==4.10.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1396,7 +1628,7 @@ fn sync_relative_wheel() -> Result<()> { context.temp_dir.join("wheels/ok-1.0.0-py3-none-any.whl"), )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1407,7 +1639,7 @@ fn sync_relative_wheel() -> Result<()> { Installed 2 packages in [TIME] + ok==1.0.0 (from file://[TEMP_DIR]/wheels/ok-1.0.0-py3-none-any.whl) + relative-wheel==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); let lock = context.read("uv.lock"); @@ -1449,7 +1681,7 @@ fn sync_relative_wheel() -> Result<()> { ); // Check that we can re-read the lockfile. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1457,7 +1689,7 @@ fn sync_relative_wheel() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 2 packages in [TIME] - "###); + "); Ok(()) } @@ -1481,7 +1713,7 @@ fn sync_environment() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 2 ----- stdout ----- @@ -1489,7 +1721,7 @@ fn sync_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] error: The current Python platform is not compatible with the lockfile's supported environments: `python_full_version < '3.11'` - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -1516,7 +1748,7 @@ fn sync_dev() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--only-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1528,9 +1760,9 @@ fn sync_dev() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1544,9 +1776,9 @@ fn sync_dev() -> Result<()> { - idna==3.6 - sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1557,10 +1789,10 @@ fn sync_dev() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Using `--no-default-groups` should remove dev dependencies - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1571,7 +1803,7 @@ fn sync_dev() -> Result<()> { - anyio==4.3.0 - idna==3.6 - sniffio==1.3.1 - "###); + "); Ok(()) } @@ -1600,7 +1832,7 @@ fn sync_group() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1611,9 +1843,9 @@ fn sync_group() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1625,9 +1857,9 @@ fn sync_group() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1645,9 +1877,9 @@ fn sync_group() -> Result<()> { - sniffio==1.3.1 - typing-extensions==4.10.0 + urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1659,9 +1891,9 @@ fn sync_group() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1669,9 +1901,9 @@ fn sync_group() -> Result<()> { ----- stderr ----- Resolved 10 packages in [TIME] Audited 9 packages in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups").arg("--no-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups").arg("--no-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1683,9 +1915,9 @@ fn sync_group() -> Result<()> { - charset-normalizer==3.3.2 - requests==2.31.0 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups").arg("--no-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups").arg("--no-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1699,9 +1931,9 @@ fn sync_group() -> Result<()> { - iniconfig==2.0.0 + requests==2.31.0 + urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1718,9 +1950,9 @@ fn sync_group() -> Result<()> { - requests==2.31.0 - sniffio==1.3.1 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--dev").arg("--no-group").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dev").arg("--no-group").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1729,9 +1961,9 @@ fn sync_group() -> Result<()> { Resolved 10 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev").arg("--no-dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev").arg("--no-dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1739,9 +1971,9 @@ fn sync_group() -> Result<()> { ----- stderr ----- Resolved 10 packages in [TIME] Audited 1 package in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1757,10 +1989,10 @@ fn sync_group() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); // Using `--no-default-groups` should exclude all groups - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1776,9 +2008,9 @@ fn sync_group() -> Result<()> { - requests==2.31.0 - sniffio==1.3.1 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1794,11 +2026,11 @@ fn sync_group() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); // Using `--no-default-groups` with `--group foo` and `--group bar` should include those groups, // excluding the remaining `dev` group. - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1807,7 +2039,7 @@ fn sync_group() -> Result<()> { Resolved 10 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -1833,7 +2065,7 @@ fn sync_include_group() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -1843,9 +2075,9 @@ fn sync_include_group() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1858,9 +2090,9 @@ fn sync_include_group() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1872,9 +2104,9 @@ fn sync_include_group() -> Result<()> { - idna==3.6 - sniffio==1.3.1 - typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1886,9 +2118,9 @@ fn sync_include_group() -> Result<()> { + idna==3.6 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1897,9 +2129,9 @@ fn sync_include_group() -> Result<()> { Resolved 6 packages in [TIME] Uninstalled 1 package in [TIME] - typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1908,9 +2140,9 @@ fn sync_include_group() -> Result<()> { Resolved 6 packages in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1922,9 +2154,9 @@ fn sync_include_group() -> Result<()> { - idna==3.6 - iniconfig==2.0.0 - sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1936,9 +2168,9 @@ fn sync_include_group() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1946,7 +2178,7 @@ fn sync_include_group() -> Result<()> { ----- stderr ----- Resolved 6 packages in [TIME] Audited 5 packages in [TIME] - "###); + "); Ok(()) } @@ -1972,7 +2204,7 @@ fn sync_exclude_group() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -1986,9 +2218,9 @@ fn sync_exclude_group() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--no-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--no-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2000,9 +2232,9 @@ fn sync_exclude_group() -> Result<()> { - idna==3.6 - iniconfig==2.0.0 - sniffio==1.3.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2013,9 +2245,9 @@ fn sync_exclude_group() -> Result<()> { Installed 1 package in [TIME] + iniconfig==2.0.0 - typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar").arg("--no-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar").arg("--no-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2024,7 +2256,7 @@ fn sync_exclude_group() -> Result<()> { Resolved 6 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -2052,7 +2284,7 @@ fn sync_dev_group() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -2066,7 +2298,7 @@ fn sync_dev_group() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -2093,7 +2325,7 @@ fn sync_non_existent_group() -> Result<()> { context.lock().assert().success(); // Requesting a non-existent group should fail. - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -2101,9 +2333,9 @@ fn sync_non_existent_group() -> Result<()> { ----- stderr ----- Resolved 7 packages in [TIME] error: Group `baz` is not defined in the project's `dependency-groups` table - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--no-group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-group").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -2111,10 +2343,10 @@ fn sync_non_existent_group() -> Result<()> { ----- stderr ----- Resolved 7 packages in [TIME] error: Group `baz` is not defined in the project's `dependency-groups` table - "###); + "); // Requesting an empty group should succeed. - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2124,11 +2356,11 @@ fn sync_non_existent_group() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + typing-extensions==4.10.0 - "###); + "); // Requesting with `--frozen` should respect the groups in the lockfile, rather than the // `pyproject.toml`. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2141,7 +2373,7 @@ fn sync_non_existent_group() -> Result<()> { + idna==3.6 + requests==2.31.0 + urllib3==2.2.1 - "###); + "); // Replace `bar` with `baz`. pyproject_toml.write_str( @@ -2157,23 +2389,23 @@ fn sync_non_existent_group() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 6 packages in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Group `baz` is not defined in the project's `dependency-groups` table - "###); + "); Ok(()) } @@ -2453,7 +2685,7 @@ fn sync_default_groups() -> Result<()> { context.lock().assert().success(); // The `dev` group should be synced by default. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -2464,7 +2696,7 @@ fn sync_default_groups() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); // If we remove it from the `default-groups` list, it should be removed. pyproject_toml.write_str( @@ -2485,7 +2717,7 @@ fn sync_default_groups() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -2494,7 +2726,7 @@ fn sync_default_groups() -> Result<()> { Resolved 10 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); // If we set a different default group, it should be synced instead. pyproject_toml.write_str( @@ -2515,7 +2747,7 @@ fn sync_default_groups() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -2527,7 +2759,7 @@ fn sync_default_groups() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // `--no-group` should remove from the defaults. pyproject_toml.write_str( @@ -2548,7 +2780,7 @@ fn sync_default_groups() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync().arg("--no-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2559,10 +2791,10 @@ fn sync_default_groups() -> Result<()> { - anyio==4.3.0 - idna==3.6 - sniffio==1.3.1 - "###); + "); // Using `--group` should include the defaults - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2574,10 +2806,10 @@ fn sync_default_groups() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); // Using `--all-groups` should include the defaults - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2590,10 +2822,10 @@ fn sync_default_groups() -> Result<()> { + charset-normalizer==3.3.2 + requests==2.31.0 + urllib3==2.2.1 - "###); + "); // Using `--only-group` should exclude the defaults - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2609,9 +2841,9 @@ fn sync_default_groups() -> Result<()> { - sniffio==1.3.1 - typing-extensions==4.10.0 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2627,10 +2859,10 @@ fn sync_default_groups() -> Result<()> { + sniffio==1.3.1 + typing-extensions==4.10.0 + urllib3==2.2.1 - "###); + "); // Using `--no-default-groups` should exclude all groups - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2646,9 +2878,9 @@ fn sync_default_groups() -> Result<()> { - requests==2.31.0 - sniffio==1.3.1 - urllib3==2.2.1 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2664,11 +2896,11 @@ fn sync_default_groups() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); // Using `--no-default-groups` with `--group foo` and `--group bar` should include those groups, // excluding the remaining `dev` group. - uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo").arg("--group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups").arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2677,7 +2909,7 @@ fn sync_default_groups() -> Result<()> { Resolved 10 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -2749,7 +2981,7 @@ fn sync_default_groups_all() -> Result<()> { "); // Using `--all-groups` should be redundant and work fine - uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-groups"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2765,7 +2997,7 @@ fn sync_default_groups_all() -> Result<()> { + requests==2.31.0 + sniffio==1.3.1 + urllib3==2.2.1 - "###); + "); // Using `--no-dev` should exclude just the dev group uv_snapshot!(context.filters(), context.sync().arg("--no-dev"), @r" @@ -2900,7 +3132,7 @@ fn sync_group_member() -> Result<()> { // Generate a lockfile. context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -2912,7 +3144,7 @@ fn sync_group_member() -> Result<()> { + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -3023,7 +3255,7 @@ fn sync_group_legacy_non_project_member() -> Result<()> { ); }); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3035,7 +3267,7 @@ fn sync_group_legacy_non_project_member() -> Result<()> { + child==0.1.0 (from file://[TEMP_DIR]/child) + iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -3157,7 +3389,7 @@ fn sync_group_self() -> Result<()> { ); }); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3169,9 +3401,9 @@ fn sync_group_self() -> Result<()> { + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/) + typing-extensions==4.10.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3183,7 +3415,7 @@ fn sync_group_self() -> Result<()> { Installed 1 package in [TIME] + idna==3.6 - typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -3208,7 +3440,7 @@ fn sync_non_existent_extra() -> Result<()> { context.lock().assert().success(); // Requesting a non-existent extra should fail. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3216,10 +3448,10 @@ fn sync_non_existent_extra() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] error: Extra `baz` is not defined in the project's `optional-dependencies` table - "###); + "); // Excluding a non-existing extra when requesting all extras should fail. - uv_snapshot!(context.filters(), context.sync().arg("--all-extras").arg("--no-extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-extras").arg("--no-extra").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3227,7 +3459,7 @@ fn sync_non_existent_extra() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] error: Extra `baz` is not defined in the project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -3249,7 +3481,7 @@ fn sync_non_existent_extra_no_optional_dependencies() -> Result<()> { context.lock().assert().success(); // Requesting a non-existent extra should fail. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3257,10 +3489,10 @@ fn sync_non_existent_extra_no_optional_dependencies() -> Result<()> { ----- stderr ----- Resolved 1 package in [TIME] error: Extra `baz` is not defined in the project's `optional-dependencies` table - "###); + "); // Excluding a non-existing extra when requesting all extras should fail. - uv_snapshot!(context.filters(), context.sync().arg("--all-extras").arg("--no-extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-extras").arg("--no-extra").arg("baz"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3268,7 +3500,7 @@ fn sync_non_existent_extra_no_optional_dependencies() -> Result<()> { ----- stderr ----- Resolved 1 package in [TIME] error: Extra `baz` is not defined in the project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -3321,14 +3553,14 @@ fn sync_ignore_extras_check_when_no_provides_extras() -> Result<()> { "#})?; // Requesting a non-existent extra should not fail, as no validation should be performed. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--extra").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited in [TIME] - "###); + "); Ok(()) } @@ -3376,7 +3608,7 @@ fn sync_non_existent_extra_workspace_member() -> Result<()> { context.lock().assert().success(); // Requesting an extra that only exists in the child should fail. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("async"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3384,10 +3616,10 @@ fn sync_non_existent_extra_workspace_member() -> Result<()> { ----- stderr ----- Resolved 5 packages in [TIME] error: Extra `async` is not defined in the project's `optional-dependencies` table - "###); + "); // Unless we sync from the child directory. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--extra").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3399,7 +3631,7 @@ fn sync_non_existent_extra_workspace_member() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -3449,7 +3681,7 @@ fn sync_non_existent_extra_non_project_workspace() -> Result<()> { // Requesting an extra that only exists in the child should succeed, since we sync all members // by default. - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3461,10 +3693,10 @@ fn sync_non_existent_extra_non_project_workspace() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Syncing from the child should also succeed. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--extra").arg("async"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3472,10 +3704,10 @@ fn sync_non_existent_extra_non_project_workspace() -> Result<()> { ----- stderr ----- Resolved 5 packages in [TIME] Audited 3 packages in [TIME] - "###); + "); // Syncing from an unrelated child should fail. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("other").arg("--extra").arg("async"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("other").arg("--extra").arg("async"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3483,7 +3715,7 @@ fn sync_non_existent_extra_non_project_workspace() -> Result<()> { ----- stderr ----- Resolved 5 packages in [TIME] error: Extra `async` is not defined in the project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -3551,7 +3783,7 @@ fn no_install_project() -> Result<()> { context.lock().assert().success(); // Running with `--no-install-project` should install `anyio`, but not `project`. - uv_snapshot!(context.filters(), context.sync().arg("--no-install-project"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-project"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3563,7 +3795,7 @@ fn no_install_project() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // However, we do require the `pyproject.toml`. fs_err::remove_file(pyproject_toml)?; @@ -3633,7 +3865,7 @@ fn no_install_workspace() -> Result<()> { // Running with `--no-install-workspace` should install `anyio` and `iniconfig`, but not // `project` or `child`. - uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3646,7 +3878,7 @@ fn no_install_workspace() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); // Remove the virtual environment. fs_err::remove_dir_all(&context.venv)?; @@ -3654,7 +3886,7 @@ fn no_install_workspace() -> Result<()> { // We don't require the `pyproject.toml` for non-root members, if `--frozen` is provided. fs_err::remove_file(child.join("pyproject.toml"))?; - uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace").arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-workspace").arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3667,10 +3899,10 @@ fn no_install_workspace() -> Result<()> { + idna==3.6 + iniconfig==2.0.0 + sniffio==1.3.1 - "###); + "); // Even if `--package` is used. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--no-install-workspace").arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").arg("--no-install-workspace").arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3680,20 +3912,20 @@ fn no_install_workspace() -> Result<()> { - anyio==3.7.0 - idna==3.6 - sniffio==1.3.1 - "###); + "); // Unless the package doesn't exist. - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("fake").arg("--no-install-workspace").arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("fake").arg("--no-install-workspace").arg("--frozen"), @r" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- error: Could not find root package `fake` - "###); + "); // Even if `--all-packages` is used. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--no-install-workspace").arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--no-install-workspace").arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3703,7 +3935,7 @@ fn no_install_workspace() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // But we do require the root `pyproject.toml`. fs_err::remove_file(context.temp_dir.join("pyproject.toml"))?; @@ -3744,7 +3976,7 @@ fn no_install_package() -> Result<()> { context.lock().assert().success(); // Running with `--no-install-package anyio` should skip anyio but include everything else - uv_snapshot!(context.filters(), context.sync().arg("--no-install-package").arg("anyio"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-package").arg("anyio"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3756,11 +3988,11 @@ fn no_install_package() -> Result<()> { + idna==3.6 + project==0.1.0 (from file://[TEMP_DIR]/) + sniffio==1.3.1 - "###); + "); // Running with `--no-install-package project` should skip the project itself (not as a special // case, that's just the name of the project) - uv_snapshot!(context.filters(), context.sync().arg("--no-install-package").arg("project"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-package").arg("project"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3772,7 +4004,7 @@ fn no_install_package() -> Result<()> { Installed 1 package in [TIME] + anyio==3.7.0 - project==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); Ok(()) } @@ -3801,7 +4033,7 @@ fn no_install_project_no_build() -> Result<()> { context.lock().assert().success(); // `--no-build` should raise an error, since we try to install the project. - uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r" success: false exit_code: 2 ----- stdout ----- @@ -3809,11 +4041,11 @@ fn no_install_project_no_build() -> Result<()> { ----- stderr ----- Resolved 4 packages in [TIME] error: Distribution `project==0.1.0 @ editable+.` can't be installed because it is marked as `--no-build` but has no binary distribution - "###); + "); // But it's fine to combine `--no-install-project` with `--no-build`. We shouldn't error, since // we aren't building the project. - uv_snapshot!(context.filters(), context.sync().arg("--no-install-project").arg("--no-build").arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-install-project").arg("--no-build").arg("--locked"), @r" success: true exit_code: 0 ----- stdout ----- @@ -3825,7 +4057,7 @@ fn no_install_project_no_build() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -3978,7 +4210,7 @@ fn convert_to_virtual() -> Result<()> { )?; // Running `uv sync` should install the project itself. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -3989,7 +4221,7 @@ fn convert_to_virtual() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); let lock = context.read("uv.lock"); @@ -4040,7 +4272,7 @@ fn convert_to_virtual() -> Result<()> { )?; // Running `uv sync` should remove the project itself. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4049,7 +4281,7 @@ fn convert_to_virtual() -> Result<()> { Resolved 2 packages in [TIME] Uninstalled 1 package in [TIME] - project==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); let lock = context.read("uv.lock"); @@ -4108,7 +4340,7 @@ fn convert_to_package() -> Result<()> { )?; // Running `uv sync` should not install the project itself. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4118,7 +4350,7 @@ fn convert_to_package() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); let lock = context.read("uv.lock"); @@ -4173,7 +4405,7 @@ fn convert_to_package() -> Result<()> { )?; // Running `uv sync` should install the project itself. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4183,7 +4415,7 @@ fn convert_to_package() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + project==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); let lock = context.read("uv.lock"); @@ -4243,7 +4475,7 @@ fn sync_custom_environment_path() -> Result<()> { )?; // Running `uv sync` should create `.venv` by default - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4255,7 +4487,7 @@ fn sync_custom_environment_path() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4263,7 +4495,7 @@ fn sync_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // Running `uv sync` should create `foo` in the project directory when customized - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4274,7 +4506,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4288,7 +4520,7 @@ fn sync_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // An absolute path can be provided - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foobar/.venv"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foobar/.venv"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4299,7 +4531,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4313,7 +4545,7 @@ fn sync_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // An absolute path can be provided - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, context.temp_dir.join("bar")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, context.temp_dir.join("bar")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4324,7 +4556,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4334,7 +4566,7 @@ fn sync_custom_environment_path() -> Result<()> { // And, it can be outside the project let tempdir = tempdir_in(TestContext::test_bucket_dir())?; context = context.with_filtered_path(tempdir.path(), "OTHER_TEMPDIR"); - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, tempdir.path().join(".venv")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, tempdir.path().join(".venv")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4345,7 +4577,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); ChildPath::new(tempdir.path()) .child(".venv") @@ -4382,7 +4614,7 @@ fn sync_custom_environment_path() -> Result<()> { fs_err::write(context.temp_dir.join("foo").join("file"), b"")?; // We can delete and use it - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4394,7 +4626,7 @@ fn sync_custom_environment_path() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -4417,7 +4649,7 @@ fn sync_active_project_environment() -> Result<()> { )?; // Running `uv sync` with `VIRTUAL_ENV` should warn - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4430,7 +4662,7 @@ fn sync_active_project_environment() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4443,7 +4675,7 @@ fn sync_active_project_environment() -> Result<()> { .assert(predicate::path::missing()); // Using `--active` should create the environment - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4454,7 +4686,7 @@ fn sync_active_project_environment() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4462,7 +4694,7 @@ fn sync_active_project_environment() -> Result<()> { .assert(predicate::path::is_dir()); // A subsequent sync will re-use the environment - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4470,13 +4702,13 @@ fn sync_active_project_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // Setting both the `VIRTUAL_ENV` and `UV_PROJECT_ENVIRONMENT` is fine if they agree uv_snapshot!(context.filters(), context.sync() .arg("--active") .env(EnvVars::VIRTUAL_ENV, "foo") - .env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + .env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4484,13 +4716,13 @@ fn sync_active_project_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // If they disagree, we use `VIRTUAL_ENV` because of `--active` uv_snapshot!(context.filters(), context.sync() .arg("--active") .env(EnvVars::VIRTUAL_ENV, "foo") - .env(EnvVars::UV_PROJECT_ENVIRONMENT, "bar"), @r###" + .env(EnvVars::UV_PROJECT_ENVIRONMENT, "bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4498,7 +4730,7 @@ fn sync_active_project_environment() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); context .temp_dir @@ -4507,7 +4739,7 @@ fn sync_active_project_environment() -> Result<()> { // Requesting another Python version will invalidate the environment uv_snapshot!(context.filters(), context.sync() - .env(EnvVars::VIRTUAL_ENV, "foo").arg("--active").arg("-p").arg("3.12"), @r###" + .env(EnvVars::VIRTUAL_ENV, "foo").arg("--active").arg("-p").arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4519,7 +4751,7 @@ fn sync_active_project_environment() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -4553,7 +4785,7 @@ fn sync_active_script_environment() -> Result<()> { .collect::>(); // Running `uv sync --script` with `VIRTUAL_ENV` should warn - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4567,7 +4799,7 @@ fn sync_active_script_environment() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); context .temp_dir @@ -4575,7 +4807,7 @@ fn sync_active_script_environment() -> Result<()> { .assert(predicate::path::missing()); // Using `--active` should create the environment - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4587,7 +4819,7 @@ fn sync_active_script_environment() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); context .temp_dir @@ -4595,7 +4827,7 @@ fn sync_active_script_environment() -> Result<()> { .assert(predicate::path::is_dir()); // A subsequent sync will re-use the environment - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4604,7 +4836,7 @@ fn sync_active_script_environment() -> Result<()> { Using script environment at: foo Resolved 3 packages in [TIME] Audited 3 packages in [TIME] - "###); + "); // Requesting another Python version will invalidate the environment uv_snapshot!(&filters, context.sync() @@ -4613,19 +4845,198 @@ fn sync_active_script_environment() -> Result<()> { .env(EnvVars::VIRTUAL_ENV, "foo") .arg("--active") .arg("-p") - .arg("3.12"), @r###" + .arg("3.12"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Recreating script environment at: foo + Updating script environment at: foo Resolved 3 packages in [TIME] Installed 3 packages in [TIME] + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); + + Ok(()) +} + +#[test] +fn sync_active_script_environment_json() -> Result<()> { + let context = TestContext::new_with_versions(&["3.11", "3.12"]) + .with_filtered_virtualenv_bin() + .with_filtered_python_names(); + + let script = context.temp_dir.child("script.py"); + script.write_str(indoc! { r#" + # /// script + # requires-python = ">=3.11" + # dependencies = [ + # "anyio", + # ] + # /// + + import anyio + "# + })?; + + let filters = context + .filters() + .into_iter() + .chain(vec![ + ( + r"environments-v2/script-[a-z0-9]+", + "environments-v2/script-[HASH]", + ), + ("bin/python3", "[PYTHON]"), + ("Scripts/python.exe", "[PYTHON]"), + ]) + .collect::>(); + + // Running `uv sync --script` with `VIRTUAL_ENV` should warn + uv_snapshot!(&filters, context.sync() + .arg("--script").arg("script.py") + .arg("--output-format").arg("json") + .env(EnvVars::VIRTUAL_ENV, "foo"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "script", + "script": { + "path": "[TEMP_DIR]/script.py" + }, + "sync": { + "environment": { + "path": "[CACHE_DIR]/environments-v2/script-[HASH]", + "python": { + "path": "[CACHE_DIR]/environments-v2/script-[HASH]/[BIN]/python", + "version": "3.11.[X]", + "implementation": "cpython" + } + }, + "action": "create" + }, + "lock": null, + "dry_run": false + } + + ----- stderr ----- + warning: `VIRTUAL_ENV=foo` does not match the script environment path `[CACHE_DIR]/environments-v2/script-[HASH]` and will be ignored; use `--active` to target the active environment instead + Resolved 3 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "#); + + context + .temp_dir + .child("foo") + .assert(predicate::path::missing()); + + // Using `--active` should create the environment + uv_snapshot!(&filters, context.sync() + .arg("--script").arg("script.py") + .arg("--output-format").arg("json") + .env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "script", + "script": { + "path": "[TEMP_DIR]/script.py" + }, + "sync": { + "environment": { + "path": "[TEMP_DIR]/foo", + "python": { + "path": "[TEMP_DIR]/foo/[BIN]/python", + "version": "3.11.[X]", + "implementation": "cpython" + } + }, + "action": "create" + }, + "lock": null, + "dry_run": false + } + + ----- stderr ----- + Resolved 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "#); + + context + .temp_dir + .child("foo") + .assert(predicate::path::is_dir()); + + // A subsequent sync will re-use the environment + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").env(EnvVars::VIRTUAL_ENV, "foo").arg("--active"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Using script environment at: foo + Resolved 3 packages in [TIME] + Audited 3 packages in [TIME] + "); + + // Requesting another Python version will invalidate the environment + uv_snapshot!(&filters, context.sync() + .arg("--script").arg("script.py") + .arg("--output-format").arg("json") + .env(EnvVars::VIRTUAL_ENV, "foo") + .arg("--active") + .arg("-p") + .arg("3.12"), @r#" + success: true + exit_code: 0 + ----- stdout ----- + { + "schema": { + "version": "preview" + }, + "target": "script", + "script": { + "path": "[TEMP_DIR]/script.py" + }, + "sync": { + "environment": { + "path": "[TEMP_DIR]/foo", + "python": { + "path": "[TEMP_DIR]/foo/[BIN]/python", + "version": "3.12.[X]", + "implementation": "cpython" + } + }, + "action": "update" + }, + "lock": null, + "dry_run": false + } + + ----- stderr ----- + Resolved 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "#); Ok(()) } @@ -4650,7 +5061,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { context.init().arg("child").assert().success(); // Running `uv sync` should create `.venv` in the workspace root - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4660,7 +5071,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4668,7 +5079,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // Similarly, `uv sync` from the child project uses `.venv` in the workspace root - uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.join("child")), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.join("child")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4677,7 +5088,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { Resolved 3 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4691,7 +5102,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { .assert(predicate::path::missing()); // Running `uv sync` should create `foo` in the workspace root when customized - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4702,7 +5113,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { Resolved 3 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4716,7 +5127,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { .assert(predicate::path::is_dir()); // Similarly, `uv sync` from the child project uses `foo` relative to the workspace root - uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(context.temp_dir.join("child")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(context.temp_dir.join("child")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4725,7 +5136,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { Resolved 3 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); context .temp_dir @@ -4739,7 +5150,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { .assert(predicate::path::missing()); // And, `uv sync --package child` uses `foo` relative to the workspace root - uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--package").arg("child").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4747,7 +5158,7 @@ fn sync_workspace_custom_environment_path() -> Result<()> { ----- stderr ----- Resolved 3 packages in [TIME] Audited in [TIME] - "###); + "); context .temp_dir @@ -4782,7 +5193,7 @@ fn sync_empty_virtual_environment() -> Result<()> { )?; // Running `uv sync` should work - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4794,7 +5205,7 @@ fn sync_empty_virtual_environment() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -4816,7 +5227,7 @@ fn sync_legacy_non_project_warning() -> Result<()> { )?; // We should not warn if it matches the project environment - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join(".venv")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join(".venv")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4826,10 +5237,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Including if it's a relative path that matches - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, ".venv"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, ".venv"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4837,7 +5248,7 @@ fn sync_legacy_non_project_warning() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // Or, if it's a link that resolves to the same path #[cfg(unix)] @@ -4847,7 +5258,7 @@ fn sync_legacy_non_project_warning() -> Result<()> { let link = context.temp_dir.join("link"); symlink(context.temp_dir.join(".venv"), &link)?; - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, link), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, link), @r" success: true exit_code: 0 ----- stdout ----- @@ -4855,11 +5266,11 @@ fn sync_legacy_non_project_warning() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); } // But we should warn if it's a different path - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4868,10 +5279,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { warning: `VIRTUAL_ENV=foo` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // Including absolute paths - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join("foo")), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join("foo")), @r" success: true exit_code: 0 ----- stdout ----- @@ -4880,10 +5291,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { warning: `VIRTUAL_ENV=foo` does not match the project environment path `.venv` and will be ignored; use `--active` to target the active environment instead Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // We should not warn if the project environment has been customized and matches - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4894,10 +5305,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // But we should warn if they don't match still - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "bar"), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "bar"), @r" success: true exit_code: 0 ----- stdout ----- @@ -4909,14 +5320,14 @@ fn sync_legacy_non_project_warning() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); let child = context.temp_dir.child("child"); child.create_dir_all()?; // And `VIRTUAL_ENV` is resolved relative to the project root so with relative paths we should // warn from a child too - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(&child), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, "foo").env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(&child), @r" success: true exit_code: 0 ----- stdout ----- @@ -4925,10 +5336,10 @@ fn sync_legacy_non_project_warning() -> Result<()> { warning: `VIRTUAL_ENV=foo` does not match the project environment path `[TEMP_DIR]/foo` and will be ignored; use `--active` to target the active environment instead Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); // But, a matching absolute path shouldn't warn - uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join("foo")).env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(&child), @r###" + uv_snapshot!(context.filters(), context.sync().env(EnvVars::VIRTUAL_ENV, context.temp_dir.join("foo")).env(EnvVars::UV_PROJECT_ENVIRONMENT, "foo").current_dir(&child), @r" success: true exit_code: 0 ----- stdout ----- @@ -4936,7 +5347,7 @@ fn sync_legacy_non_project_warning() -> Result<()> { ----- stderr ----- Resolved 2 packages in [TIME] Audited 1 package in [TIME] - "###); + "); Ok(()) } @@ -4956,7 +5367,7 @@ fn sync_update_project() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4968,7 +5379,7 @@ fn sync_update_project() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Bump the project version. pyproject_toml.write_str( @@ -4985,7 +5396,7 @@ fn sync_update_project() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -4995,7 +5406,7 @@ fn sync_update_project() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + my-project==0.2.0 (from file://[TEMP_DIR]/) - "###); + "); Ok(()) } @@ -5016,7 +5427,7 @@ fn sync_environment_prompt() -> Result<()> { )?; // Running `uv sync` should create `.venv` - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5028,7 +5439,7 @@ fn sync_environment_prompt() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // The `pyvenv.cfg` should contain the prompt matching the project name let pyvenv_cfg = context.read(".venv/pyvenv.cfg"); @@ -5055,7 +5466,7 @@ fn no_binary() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("iniconfig"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("iniconfig"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5065,11 +5476,11 @@ fn no_binary() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--no-binary"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").arg("--no-binary"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5080,9 +5491,9 @@ fn no_binary() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY_PACKAGE", "iniconfig"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY_PACKAGE", "iniconfig"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5093,9 +5504,9 @@ fn no_binary() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY", "1"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY", "1"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5106,7 +5517,7 @@ fn no_binary() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ iniconfig==2.0.0 - "###); + "); uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BINARY", "iniconfig"), @r###" success: false @@ -5139,7 +5550,7 @@ fn no_binary_error() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("odrive"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-binary-package").arg("odrive"), @r" success: false exit_code: 2 ----- stdout ----- @@ -5147,7 +5558,7 @@ fn no_binary_error() -> Result<()> { ----- stderr ----- Resolved 31 packages in [TIME] error: Distribution `odrive==0.6.8 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-binary` but has no source distribution - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); @@ -5171,7 +5582,7 @@ fn no_build() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("iniconfig"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("iniconfig"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5181,11 +5592,11 @@ fn no_build() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "iniconfig"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "iniconfig"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5196,7 +5607,7 @@ fn no_build() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ iniconfig==2.0.0 - "###); + "); Ok(()) } @@ -5218,7 +5629,7 @@ fn no_build_error() -> Result<()> { context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("django-allauth"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-build-package").arg("django-allauth"), @r" success: false exit_code: 2 ----- stdout ----- @@ -5226,7 +5637,7 @@ fn no_build_error() -> Result<()> { ----- stderr ----- Resolved 19 packages in [TIME] error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution - "###); + "); uv_snapshot!(context.filters(), context.sync().arg("--no-build"), @r" success: false @@ -5248,7 +5659,7 @@ fn no_build_error() -> Result<()> { error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution "); - uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "django-allauth"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD_PACKAGE", "django-allauth"), @r" success: false exit_code: 2 ----- stdout ----- @@ -5256,7 +5667,7 @@ fn no_build_error() -> Result<()> { ----- stderr ----- Resolved 19 packages in [TIME] error: Distribution `django-allauth==0.51.0 @ registry+https://pypi.org/simple` can't be installed because it is marked as `--no-build` but has no binary distribution - "###); + "); uv_snapshot!(context.filters(), context.sync().arg("--reinstall").env("UV_NO_BUILD", "django-allauth"), @r###" success: false @@ -5300,7 +5711,7 @@ fn sync_wheel_url_source_error() -> Result<()> { Resolved 3 packages in [TIME] "###); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 2 ----- stdout ----- @@ -5310,7 +5721,7 @@ fn sync_wheel_url_source_error() -> Result<()> { error: Distribution `cffi==1.17.1 @ direct+https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl` can't be installed because the binary distribution is incompatible with the current platform hint: You're using CPython 3.12 (`cp312`), but `cffi` (v1.17.1) only has wheels with the following Python ABI tag: `cp310` - "###); + "); Ok(()) } @@ -5351,7 +5762,7 @@ fn sync_wheel_path_source_error() -> Result<()> { Resolved 3 packages in [TIME] "###); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 2 ----- stdout ----- @@ -5361,7 +5772,7 @@ fn sync_wheel_path_source_error() -> Result<()> { error: Distribution `cffi==1.17.1 @ path+cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl` can't be installed because the binary distribution is incompatible with the current platform hint: You're using CPython 3.12 (`cp312`), but `cffi` (v1.17.1) only has wheels with the following Python ABI tag: `cp310` - "###); + "); Ok(()) } @@ -5423,7 +5834,7 @@ fn sync_override_package() -> Result<()> { .touch()?; // Syncing the project should _not_ install `core`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5433,7 +5844,7 @@ fn sync_override_package() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + project==0.0.0 (from file://[TEMP_DIR]/) - "###); + "); // Mark the source as `package = true`. let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -5455,7 +5866,7 @@ fn sync_override_package() -> Result<()> { )?; // Syncing the project _should_ install `core`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5467,7 +5878,7 @@ fn sync_override_package() -> Result<()> { Installed 2 packages in [TIME] + core==0.1.0 (from file://[TEMP_DIR]/core) ~ project==0.0.0 (from file://[TEMP_DIR]/) - "###); + "); // Remove `package = false`. let pyproject_toml = context.temp_dir.child("core").child("pyproject.toml"); @@ -5485,7 +5896,7 @@ fn sync_override_package() -> Result<()> { )?; // Syncing the project _should_ install `core`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5496,7 +5907,7 @@ fn sync_override_package() -> Result<()> { Uninstalled 1 package in [TIME] Installed 1 package in [TIME] ~ core==0.1.0 (from file://[TEMP_DIR]/core) - "###); + "); // Mark the source as `package = false`. let pyproject_toml = context.temp_dir.child("pyproject.toml"); @@ -5518,7 +5929,7 @@ fn sync_override_package() -> Result<()> { )?; // Syncing the project should _not_ install `core`. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5530,7 +5941,7 @@ fn sync_override_package() -> Result<()> { Installed 1 package in [TIME] - core==0.1.0 (from file://[TEMP_DIR]/core) ~ project==0.0.0 (from file://[TEMP_DIR]/) - "###); + "); Ok(()) } @@ -5592,7 +6003,7 @@ fn transitive_dev() -> Result<()> { let init = src.child("__init__.py"); init.touch()?; - uv_snapshot!(context.filters(), context.sync().arg("--dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5605,7 +6016,7 @@ fn transitive_dev() -> Result<()> { + child==0.1.0 (from file://[TEMP_DIR]/child) + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -5665,7 +6076,7 @@ fn sync_no_editable() -> Result<()> { let init = src.child("__init__.py"); init.touch()?; - uv_snapshot!(context.filters(), context.sync().arg("--no-editable"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-editable"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5676,7 +6087,7 @@ fn sync_no_editable() -> Result<()> { Installed 2 packages in [TIME] + child==0.1.0 (from file://[TEMP_DIR]/child) + root==0.1.0 (from file://[TEMP_DIR]/) - "###); + "); uv_snapshot!(context.filters(), context.sync().env(EnvVars::UV_NO_EDITABLE, "1"), @r" success: true @@ -5731,7 +6142,7 @@ fn sync_scripts_without_build_system() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5740,7 +6151,7 @@ fn sync_scripts_without_build_system() -> Result<()> { warning: Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` Resolved 1 package in [TIME] Audited in [TIME] - "###); + "); Ok(()) } @@ -5780,7 +6191,7 @@ fn sync_scripts_project_not_packaged() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -5789,7 +6200,7 @@ fn sync_scripts_project_not_packaged() -> Result<()> { warning: Skipping installation of entry points (`project.scripts`) because this project is not packaged; to install entry points, set `tool.uv.package = true` or define a `build-system` Resolved 1 package in [TIME] Audited in [TIME] - "###); + "); Ok(()) } @@ -5822,7 +6233,7 @@ fn sync_dynamic_extra() -> Result<()> { .child("requirements-dev.txt") .write_str("typing-extensions")?; - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5834,7 +6245,7 @@ fn sync_dynamic_extra() -> Result<()> { + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/) + typing-extensions==4.10.0 - "###); + "); let lock = context.read("uv.lock"); @@ -5895,7 +6306,7 @@ fn sync_dynamic_extra() -> Result<()> { ); // Check that we can re-read the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--locked"), @r" success: true exit_code: 0 ----- stdout ----- @@ -5904,7 +6315,7 @@ fn sync_dynamic_extra() -> Result<()> { Resolved 3 packages in [TIME] Uninstalled 1 package in [TIME] - typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -5974,7 +6385,7 @@ fn build_system_requires_workspace() -> Result<()> { ", })?; - uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.child("project")), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.child("project")), @r" success: true exit_code: 0 ----- stdout ----- @@ -5987,7 +6398,7 @@ fn build_system_requires_workspace() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/project) - "###); + "); Ok(()) } @@ -6054,7 +6465,7 @@ fn build_system_requires_path() -> Result<()> { ", })?; - uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.child("project")), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(context.temp_dir.child("project")), @r" success: true exit_code: 0 ----- stdout ----- @@ -6067,7 +6478,7 @@ fn build_system_requires_path() -> Result<()> { Installed 2 packages in [TIME] + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/project) - "###); + "); Ok(()) } @@ -6119,7 +6530,7 @@ fn sync_invalid_environment() -> Result<()> { fs_err::write(context.temp_dir.join(".venv").join("file"), b"")?; // We can delete and use it - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6132,7 +6543,7 @@ fn sync_invalid_environment() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); let bin = venv_bin_path(context.temp_dir.join(".venv")); @@ -6141,7 +6552,7 @@ fn sync_invalid_environment() -> Result<()> { { fs_err::remove_file(bin.join("python"))?; fs_err::os::unix::fs::symlink(context.temp_dir.join("does-not-exist"), bin.join("python"))?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6154,7 +6565,7 @@ fn sync_invalid_environment() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); } // But if the Python executable is missing entirely we should also fail @@ -6242,7 +6653,7 @@ fn sync_no_sources_missing_member() -> Result<()> { let init = src.child("__init__.py"); init.touch()?; - uv_snapshot!(context.filters(), context.sync().arg("--no-sources"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--no-sources"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6254,7 +6665,7 @@ fn sync_no_sources_missing_member() -> Result<()> { + anyio==4.3.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -6273,7 +6684,7 @@ fn sync_python_version() -> Result<()> { "#})?; // We should respect the project's required version, not the first on the path - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6287,7 +6698,7 @@ fn sync_python_version() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Unless explicitly requested... uv_snapshot!(context.filters(), context.sync().arg("--python").arg("3.10"), @r" @@ -6310,7 +6721,7 @@ fn sync_python_version() -> Result<()> { ----- stderr ----- "###); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6324,7 +6735,7 @@ fn sync_python_version() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Create a pin that's incompatible with the project uv_snapshot!(context.filters(), context.python_pin().arg("3.10").arg("--no-workspace"), @r###" @@ -6363,7 +6774,7 @@ fn sync_python_version() -> Result<()> { "#}) .unwrap(); - uv_snapshot!(context.filters(), context.sync().current_dir(&child_dir), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(&child_dir), @r" success: true exit_code: 0 ----- stdout ----- @@ -6376,7 +6787,7 @@ fn sync_python_version() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -6406,7 +6817,7 @@ fn sync_explicit() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6416,13 +6827,13 @@ fn sync_explicit() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + idna==2.7 - "###); + "); // Clear the environment. fs_err::remove_dir_all(&context.venv)?; // The package should be drawn from the cache. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -6433,7 +6844,7 @@ fn sync_explicit() -> Result<()> { Resolved 2 packages in [TIME] Installed 1 package in [TIME] + idna==2.7 - "###); + "); Ok(()) } @@ -6495,7 +6906,7 @@ fn sync_all() -> Result<()> { context.lock().assert().success(); // Sync all workspace members. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6510,7 +6921,7 @@ fn sync_all() -> Result<()> { + iniconfig==2.0.0 + project==0.1.0 (from file://[TEMP_DIR]/) + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -6576,7 +6987,7 @@ fn sync_all_extras() -> Result<()> { context.lock().assert().success(); // Sync an extra that exists in both the parent and child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6589,10 +7000,10 @@ fn sync_all_extras() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // Sync an extra that only exists in the child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("testing"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("testing"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6605,10 +7016,10 @@ fn sync_all_extras() -> Result<()> { + packaging==24.0 - sniffio==1.3.1 - typing-extensions==4.10.0 - "###); + "); // Sync all extras. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6621,10 +7032,10 @@ fn sync_all_extras() -> Result<()> { + idna==3.6 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // Sync all extras excluding an extra that exists in both the parent and child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras").arg("--no-extra").arg("types"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras").arg("--no-extra").arg("types"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6633,10 +7044,10 @@ fn sync_all_extras() -> Result<()> { Resolved 8 packages in [TIME] Uninstalled 1 package in [TIME] - typing-extensions==4.10.0 - "###); + "); // Sync an extra that doesn't exist. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("foo"), @r" success: false exit_code: 2 ----- stdout ----- @@ -6644,10 +7055,10 @@ fn sync_all_extras() -> Result<()> { ----- stderr ----- Resolved 8 packages in [TIME] error: Extra `foo` is not defined in any project's `optional-dependencies` table - "###); + "); // Sync all extras excluding an extra that doesn't exist. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras").arg("--no-extra").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--all-extras").arg("--no-extra").arg("foo"), @r" success: false exit_code: 2 ----- stdout ----- @@ -6655,7 +7066,7 @@ fn sync_all_extras() -> Result<()> { ----- stderr ----- Resolved 8 packages in [TIME] error: Extra `foo` is not defined in any project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -6731,7 +7142,7 @@ fn sync_all_extras_dynamic() -> Result<()> { context.lock().assert().success(); // Sync an extra that exists in the parent. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("types"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6743,10 +7154,10 @@ fn sync_all_extras_dynamic() -> Result<()> { + child==0.1.0 (from file://[TEMP_DIR]/child) + project==0.1.0 (from file://[TEMP_DIR]/) + sniffio==1.3.1 - "###); + "); // Sync a dynamic extra that exists in the child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("dev"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("dev"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6758,10 +7169,10 @@ fn sync_all_extras_dynamic() -> Result<()> { Installed 1 package in [TIME] - sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // Sync a dynamic extra that doesn't exist in the child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--extra").arg("foo"), @r" success: false exit_code: 2 ----- stdout ----- @@ -6769,7 +7180,7 @@ fn sync_all_extras_dynamic() -> Result<()> { ----- stderr ----- Resolved 6 packages in [TIME] error: Extra `foo` is not defined in any project's `optional-dependencies` table - "###); + "); Ok(()) } @@ -6836,7 +7247,7 @@ fn sync_all_groups() -> Result<()> { context.lock().assert().success(); // Sync a group that exists in both the parent and child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("types"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("types"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6849,10 +7260,10 @@ fn sync_all_groups() -> Result<()> { + iniconfig==2.0.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // Sync a group that only exists in the child. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("testing"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("testing"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6865,10 +7276,10 @@ fn sync_all_groups() -> Result<()> { + packaging==24.0 - sniffio==1.3.1 - typing-extensions==4.10.0 - "###); + "); // Sync a group that doesn't exist. - uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("foo"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--all-packages").arg("--group").arg("foo"), @r" success: false exit_code: 2 ----- stdout ----- @@ -6876,10 +7287,10 @@ fn sync_all_groups() -> Result<()> { ----- stderr ----- Resolved 8 packages in [TIME] error: Group `foo` is not defined in any project's `dependency-groups` table - "###); + "); // Sync an empty group. - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("empty"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("empty"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6888,7 +7299,7 @@ fn sync_all_groups() -> Result<()> { Resolved 8 packages in [TIME] Uninstalled 1 package in [TIME] - packaging==24.0 - "###); + "); Ok(()) } @@ -6940,7 +7351,7 @@ fn sync_multiple_sources_index_disjoint_extras() -> Result<()> { // Generate a lockfile. context.lock().assert().success(); - uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("cu124"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--extra").arg("cu124"), @r" success: true exit_code: 0 ----- stdout ----- @@ -6951,7 +7362,7 @@ fn sync_multiple_sources_index_disjoint_extras() -> Result<()> { Installed 2 packages in [TIME] + jinja2==3.1.3 + markupsafe==2.1.5 - "###); + "); Ok(()) } @@ -6982,7 +7393,7 @@ fn sync_derivation_chain() -> Result<()> { .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); - uv_snapshot!(filters, context.sync(), @r###" + uv_snapshot!(filters, context.sync(), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7013,7 +7424,7 @@ fn sync_derivation_chain() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `wsgiref` (v0.1.2) was included because `project` (v0.1.0) depends on `wsgiref` - "###); + "#); Ok(()) } @@ -7045,7 +7456,7 @@ fn sync_derivation_chain_extra() -> Result<()> { .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); - uv_snapshot!(filters, context.sync().arg("--extra").arg("wsgi"), @r###" + uv_snapshot!(filters, context.sync().arg("--extra").arg("wsgi"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7076,7 +7487,7 @@ fn sync_derivation_chain_extra() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `wsgiref` (v0.1.2) was included because `project[wsgi]` (v0.1.0) depends on `wsgiref` - "###); + "#); Ok(()) } @@ -7110,7 +7521,7 @@ fn sync_derivation_chain_group() -> Result<()> { .chain([(r"/.*/src", "/[TMP]/src")]) .collect::>(); - uv_snapshot!(filters, context.sync().arg("--group").arg("wsgi"), @r###" + uv_snapshot!(filters, context.sync().arg("--group").arg("wsgi"), @r#" success: false exit_code: 1 ----- stdout ----- @@ -7141,7 +7552,7 @@ fn sync_derivation_chain_group() -> Result<()> { hint: This usually indicates a problem with the package or the build environment. help: `wsgiref` (v0.1.2) was included because `project:wsgi` (v0.1.0) depends on `wsgiref` - "###); + "#); Ok(()) } @@ -7235,7 +7646,7 @@ fn sync_stale_egg_info() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7247,7 +7658,7 @@ fn sync_stale_egg_info() -> Result<()> { + member==0.1.dev5+gfea1041 (from git+https://github.com/astral-sh/uv-stale-egg-info-test.git@fea10416b9c479ac88fb217e14e40249b63bfbee#subdirectory=member) + root==0.1.dev5+gfea1041 (from git+https://github.com/astral-sh/uv-stale-egg-info-test.git@fea10416b9c479ac88fb217e14e40249b63bfbee) + setuptools==69.2.0 - "###); + "); Ok(()) } @@ -7330,7 +7741,7 @@ fn sync_git_repeated_member_static_metadata() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7341,7 +7752,7 @@ fn sync_git_repeated_member_static_metadata() -> Result<()> { Installed 2 packages in [TIME] + uv-git-workspace-in-root==0.1.0 (from git+https://github.com/astral-sh/workspace-in-root-test.git@d3ab48d2338296d47e28dbb2fb327c5e2ac4ac68) + workspace-member-in-subdir==0.1.0 (from git+https://github.com/astral-sh/workspace-in-root-test.git@d3ab48d2338296d47e28dbb2fb327c5e2ac4ac68#subdirectory=workspace-member-in-subdir) - "###); + "); Ok(()) } @@ -7446,7 +7857,7 @@ fn sync_git_repeated_member_dynamic_metadata() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7459,7 +7870,7 @@ fn sync_git_repeated_member_dynamic_metadata() -> Result<()> { + iniconfig==2.0.0 + package==0.1.0 (from git+https://github.com/astral-sh/uv-dynamic-metadata-test.git@6c5aa0a65db737c9e7e2e60dc865bd8087012e64) + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -7542,7 +7953,7 @@ fn sync_git_repeated_member_backwards_path() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7553,7 +7964,7 @@ fn sync_git_repeated_member_backwards_path() -> Result<()> { Installed 2 packages in [TIME] + dependency==0.1.0 (from git+https://github.com/astral-sh/uv-backwards-path-test@4bcc7fcd2e548c2ab7ba6b97b1c4e3ababccc7a9#subdirectory=dependency) + package==0.1.0 (from git+https://github.com/astral-sh/uv-backwards-path-test@4bcc7fcd2e548c2ab7ba6b97b1c4e3ababccc7a9#subdirectory=root) - "###); + "); Ok(()) } @@ -7578,7 +7989,7 @@ fn mismatched_name_self_editable() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -7588,7 +7999,7 @@ fn mismatched_name_self_editable() -> Result<()> { × Failed to build `foo @ file://[TEMP_DIR]/` ╰─▶ Package metadata name `project` does not match given name `foo` help: `foo` was included because `project` (v0.1.0) depends on `foo` - "###); + "); Ok(()) } @@ -7610,7 +8021,7 @@ fn mismatched_name_cached_wheel() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7620,7 +8031,7 @@ fn mismatched_name_cached_wheel() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 (from https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz) - "###); + "); pyproject_toml.write_str( r#" @@ -7632,7 +8043,7 @@ fn mismatched_name_cached_wheel() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -7640,7 +8051,7 @@ fn mismatched_name_cached_wheel() -> Result<()> { ----- stderr ----- × Failed to download and build `foo @ https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz` ╰─▶ Package metadata name `iniconfig` does not match given name `foo` - "###); + "); Ok(()) } @@ -7720,7 +8131,7 @@ fn sync_git_path_dependency() -> Result<()> { } ); - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -7731,7 +8142,7 @@ fn sync_git_path_dependency() -> Result<()> { Installed 2 packages in [TIME] + package1==0.1.0 (from git+https://github.com/astral-sh/uv-path-dependency-test.git@28781b32cf1f260cdb2c8040628079eb265202bd#subdirectory=package1) + package2==0.1.0 (from git+https://github.com/astral-sh/uv-path-dependency-test.git@28781b32cf1f260cdb2c8040628079eb265202bd#subdirectory=package2) - "###); + "); Ok(()) } @@ -7835,7 +8246,7 @@ fn sync_build_tag() -> Result<()> { "###); // Install from the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -7844,7 +8255,7 @@ fn sync_build_tag() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + build-tag==1.0.0 - "###); + "); // Ensure that we choose the highest build tag (5). uv_snapshot!(context.filters(), context.run().arg("--no-sync").arg("python").arg("-c").arg("import build_tag; build_tag.main()"), @r###" @@ -7904,7 +8315,7 @@ fn url_hash_mismatch() -> Result<()> { "#})?; // Running `uv sync` should fail. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -7920,7 +8331,7 @@ fn url_hash_mismatch() -> Result<()> { Computed: sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 help: `iniconfig` was included because `project` (v0.1.0) depends on `iniconfig` - "###); + "); Ok(()) } @@ -7977,7 +8388,7 @@ fn path_hash_mismatch() -> Result<()> { "#})?; // Running `uv sync` should fail. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -7993,7 +8404,7 @@ fn path_hash_mismatch() -> Result<()> { Computed: sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3 help: `iniconfig` was included because `project` (v0.1.0) depends on `iniconfig` - "###); + "); Ok(()) } @@ -8029,7 +8440,7 @@ fn find_links_relative_in_config_works_from_subdir() -> Result<()> { subdir.create_dir_all()?; // Run `uv sync --offline` from subdir. We expect it to find the local wheel in ../packages/. - uv_snapshot!(context.filters(), context.sync().current_dir(&subdir).arg("--offline"), @r###" + uv_snapshot!(context.filters(), context.sync().current_dir(&subdir).arg("--offline"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8039,7 +8450,7 @@ fn find_links_relative_in_config_works_from_subdir() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + ok==1.0.0 - "###); + "); Ok(()) } @@ -8060,23 +8471,23 @@ fn sync_dry_run() -> Result<()> { )?; // Perform a `--dry-run`. - uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Using CPython 3.12.[X] interpreter at: [PYTHON-3.12] - Would create virtual environment at: .venv + Would create project environment at: .venv Resolved 2 packages in [TIME] Would create lockfile at: uv.lock Would download 1 package Would install 1 package + iniconfig==2.0.0 - "###); + "); // Perform a full sync. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -8088,7 +8499,7 @@ fn sync_dry_run() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); // Update the requirements. pyproject_toml.write_str( @@ -8101,13 +8512,13 @@ fn sync_dry_run() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--dry-run"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Would update lockfile at: uv.lock Would download 1 package @@ -8115,7 +8526,7 @@ fn sync_dry_run() -> Result<()> { Would install 1 package - iniconfig==2.0.0 + typing-extensions==4.10.0 - "###); + "); // Update the `requires-python`. pyproject_toml.write_str( @@ -8135,7 +8546,7 @@ fn sync_dry_run() -> Result<()> { ----- stderr ----- Using CPython 3.9.[X] interpreter at: [PYTHON-3.9] - Would replace existing virtual environment at: .venv + Would replace project environment at: .venv warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.12'` vs `python_full_version == '3.9.*'` Resolved 2 packages in [TIME] Would update lockfile at: uv.lock @@ -8175,7 +8586,7 @@ fn sync_dry_run() -> Result<()> { ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Found up-to-date lockfile at: uv.lock Audited 1 package in [TIME] @@ -8223,7 +8634,7 @@ fn sync_dry_run_and_locked() -> Result<()> { ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv + Would use project environment at: .venv Resolved 2 packages in [TIME] Would download 1 package Would install 1 package @@ -8275,8 +8686,7 @@ fn sync_dry_run_and_frozen() -> Result<()> { ----- stdout ----- ----- stderr ----- - Discovered existing environment at: .venv - Found up-to-date lockfile at: uv.lock + Would use project environment at: .venv Would download 3 packages Would install 3 packages + anyio==3.7.0 @@ -8371,7 +8781,7 @@ fn sync_script() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8381,7 +8791,7 @@ fn sync_script() -> Result<()> { Resolved 3 packages in [TIME] Uninstalled 1 package in [TIME] - iniconfig==2.0.0 - "###); + "); // Modify the `requires-python`. script.write_str(indoc! { r#" @@ -8396,13 +8806,13 @@ fn sync_script() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- - Recreating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] + Updating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] Resolved 5 packages in [TIME] Prepared 2 packages in [TIME] Installed 5 packages in [TIME] @@ -8411,7 +8821,7 @@ fn sync_script() -> Result<()> { + idna==3.6 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); // `--locked` and `--frozen` should fail with helpful error messages. uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py").arg("--locked"), @r" @@ -8662,7 +9072,7 @@ fn sync_locked_script() -> Result<()> { ----- stdout ----- ----- stderr ----- - Recreating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] + Updating script environment at: [CACHE_DIR]/environments-v2/script-[HASH] warning: Ignoring existing lockfile due to fork markers being disjoint with `requires-python`: `python_full_version >= '3.11'` vs `python_full_version >= '3.8' and python_full_version < '3.11'` Resolved 6 packages in [TIME] error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. @@ -8722,7 +9132,7 @@ fn sync_script_with_compatible_build_constraints() -> Result<()> { )]) .collect::>(); - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8738,7 +9148,7 @@ fn sync_script_with_compatible_build_constraints() -> Result<()> { + requests==1.2.0 + sniffio==1.3.1 + typing-extensions==4.10.0 - "###); + "); Ok(()) } @@ -8774,7 +9184,7 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> { "# })?; - uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r###" + uv_snapshot!(&filters, context.sync().arg("--script").arg("script.py"), @r" success: false exit_code: 1 ----- stdout ----- @@ -8785,7 +9195,7 @@ fn sync_script_with_incompatible_build_constraints() -> Result<()> { ├─▶ Failed to resolve requirements from `setup.py` build ├─▶ No solution found when resolving: `setuptools>=40.8.0` ╰─▶ Because you require setuptools>=40.8.0 and setuptools==1, we can conclude that your requirements are unsatisfiable. - "###); + "); Ok(()) } @@ -8808,7 +9218,7 @@ fn unsupported_git_scheme() -> Result<()> { "#}, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: false exit_code: 1 ----- stdout ----- @@ -8819,7 +9229,7 @@ fn unsupported_git_scheme() -> Result<()> { × Failed to build `foo @ file://[TEMP_DIR]/` ├─▶ Failed to parse entry: `foo` ╰─▶ Unsupported Git URL scheme `c:` in `c:/home/ferris/projects/foo` (expected one of `https:`, `ssh:`, or `file:`) - "###); + "); Ok(()) } @@ -8858,7 +9268,7 @@ fn multiple_group_conflicts() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -8866,9 +9276,9 @@ fn multiple_group_conflicts() -> Result<()> { ----- stderr ----- Resolved 3 packages in [TIME] Audited in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8878,9 +9288,9 @@ fn multiple_group_conflicts() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8888,9 +9298,9 @@ fn multiple_group_conflicts() -> Result<()> { ----- stderr ----- Resolved 3 packages in [TIME] Audited 1 package in [TIME] - "###); + "); - uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar").arg("--group").arg("baz"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar").arg("--group").arg("baz"), @r" success: true exit_code: 0 ----- stdout ----- @@ -8902,7 +9312,7 @@ fn multiple_group_conflicts() -> Result<()> { Installed 1 package in [TIME] - iniconfig==2.0.0 + iniconfig==1.1.1 - "###); + "); uv_snapshot!(context.filters(), context.sync().arg("--group").arg("foo").arg("--group").arg("bar"), @r" success: false @@ -9316,7 +9726,7 @@ fn prune_cache_url_subdirectory() -> Result<()> { context.prune().arg("--ci").assert().success(); // Install the project. - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -9329,7 +9739,7 @@ fn prune_cache_url_subdirectory() -> Result<()> { + idna==3.6 + root==0.0.1 (from https://github.com/user-attachments/files/18216295/subdirectory-test.tar.gz#subdirectory=packages/root) + sniffio==1.3.1 - "###); + "); Ok(()) } @@ -9745,7 +10155,7 @@ fn sync_upload_time() -> Result<()> { "#)?; // Install from the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- @@ -9756,17 +10166,17 @@ fn sync_upload_time() -> Result<()> { + anyio==3.7.0 + idna==3.6 + sniffio==1.3.1 - "###); + "); // Re-install from the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r###" + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @r" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Audited 3 packages in [TIME] - "###); + "); Ok(()) } @@ -9994,7 +10404,7 @@ fn read_only() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.sync(), @r###" + uv_snapshot!(context.filters(), context.sync(), @r" success: true exit_code: 0 ----- stdout ----- @@ -10004,7 +10414,7 @@ fn read_only() -> Result<()> { Prepared 1 package in [TIME] Installed 1 package in [TIME] + iniconfig==2.0.0 - "###); + "); assert!(context.temp_dir.child("uv.lock").exists()); diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 0364703c2..13df63c19 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -1114,7 +1114,12 @@ uv sync [OPTIONS]
--only-group only-group

Only include dependencies from the specified dependency group.

The project and its dependencies will be omitted.

May be provided multiple times. Implies --no-default-groups.

-
--package package

Sync for a specific package in the workspace.

+
--output-format output-format

Select the output format

+

[default: text]

Possible values:

+
    +
  • text: Display the result in a human-readable format
  • +
  • json: Display the result in JSON format
  • +
--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.

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

--prerelease prerelease

The strategy to use when considering pre-release versions.