From c6ed0254b209deef6ce1e2864069c9515198b077 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Wed, 17 Jun 2026 17:23:31 +0100 Subject: [PATCH 1/3] Update lockfile during uv check --no-sync Treat --no-sync as an environment synchronization control rather than a frozen lock mode. Lock with a compatible interpreter, preserve explicit lock modes and isolated dry-runs, and leave the project environment unsynchronized. Add focused integration coverage for missing, stale, invalid, locked, frozen, isolated, and incompatible-environment cases. --- crates/uv-cli/src/lib.rs | 3 +- crates/uv/src/commands/project/check.rs | 213 +++++++------ crates/uv/tests/project/check.rs | 396 +++++++++++++++++++++++- 3 files changed, 512 insertions(+), 100 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index bffce38d89c53..0d83946eeb49f 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5353,8 +5353,7 @@ pub struct CheckArgs { /// Avoid syncing the virtual environment [env: UV_NO_SYNC=] /// - /// Implies `--frozen`, as the project dependencies will be ignored (i.e., the lockfile will not - /// be updated, since the environment will not be synced regardless). + /// The project lockfile will still be created or updated as needed. #[arg(long)] pub no_sync: bool, diff --git a/crates/uv/src/commands/project/check.rs b/crates/uv/src/commands/project/check.rs index 16fb52160cfd0..980b73035e497 100644 --- a/crates/uv/src/commands/project/check.rs +++ b/crates/uv/src/commands/project/check.rs @@ -22,10 +22,9 @@ use crate::commands::pip::loggers::{SummaryInstallLogger, SummaryResolveLogger}; use crate::commands::pip::operations::Modifications; use crate::commands::project::install_target::InstallTarget; use crate::commands::project::lock::LockMode; -use crate::commands::project::lock_target::LockTarget; use crate::commands::project::{ - ProjectEnvironment, ProjectError, UniversalState, WorkspacePython, default_dependency_groups, - validate_project_requires_python, + ProjectEnvironment, ProjectError, ProjectInterpreter, UniversalState, WorkspacePython, + default_dependency_groups, validate_project_requires_python, }; use crate::commands::reporters::PythonDownloadReporter; use crate::commands::{ExitStatus, diagnostics, project}; @@ -230,83 +229,111 @@ pub(crate) async fn check( .into_environment()? }; - let state = UniversalState::default(); - let _environment_lock; - let lock = if no_sync { - debug!("Skipping environment synchronization due to `--no-sync`"); + // `--no-sync` intentionally permits an incompatible project environment, but locking must + // still use an interpreter that satisfies the project and any explicit Python request. + let lock_interpreter = if no_sync && !isolated && frozen.is_none() { + let workspace_python = WorkspacePython::from_request( + python.as_deref().map(PythonRequest::parse), + Some(project.workspace()), + &groups, + project_dir, + no_config, + ) + .await?; + Some( + ProjectInterpreter::discover( + project.workspace(), + &groups, + workspace_python, + &client_builder, + python_preference, + python_downloads, + &install_mirrors, + false, + None, + cache, + printer, + ) + .await? + .into_interpreter(), + ) + } else { + None + }; + let lock_interpreter = lock_interpreter + .as_ref() + .unwrap_or_else(|| venv.interpreter()); - match LockTarget::Workspace(project.workspace()).read().await { - Ok(lock) => lock, - Err(err) => { - debug!("Failed to read lockfile; skipping workspace metadata: {err}"); - None - } - } + let state = UniversalState::default(); + // Keep the environment locked through synchronization and metadata collection. + let _environment_lock = if no_sync { + None } else { - // Keep the environment locked through synchronization and metadata collection. - _environment_lock = venv - .lock() + venv.lock() .await .inspect_err(|err| { tracing::warn!("Failed to acquire environment lock: {err}"); }) - .ok(); - - let sync_state = state.fork(); + .ok() + }; - let mode = if let Some(frozen_source) = frozen { - LockMode::Frozen(frozen_source.into()) - } else if let LockCheck::Enabled(lock_check) = lock_check { - LockMode::Locked(venv.interpreter(), lock_check) - } else if isolated { - LockMode::DryRun(venv.interpreter()) - } else { - LockMode::Write(venv.interpreter()) - }; + let mode = if let Some(frozen_source) = frozen { + LockMode::Frozen(frozen_source.into()) + } else if let LockCheck::Enabled(lock_check) = lock_check { + LockMode::Locked(lock_interpreter, lock_check) + } else if isolated { + LockMode::DryRun(lock_interpreter) + } else { + LockMode::Write(lock_interpreter) + }; - let result = match Box::pin( - project::lock::LockOperation::new( - mode, - &settings.resolver, - &client_builder, - &state, - Box::new(SummaryResolveLogger), - &concurrency, - cache, - workspace_cache, - printer, - preview, - ) - .execute(project.workspace().into()), + let result = match Box::pin( + project::lock::LockOperation::new( + mode, + &settings.resolver, + &client_builder, + &state, + Box::new(SummaryResolveLogger), + &concurrency, + cache, + workspace_cache, + printer, + preview, ) - .await - { - Ok(result) => result, - Err(ProjectError::Operation(err)) => { - return diagnostics::OperationDiagnostic::with_system_certs( - client_builder.system_certs(), - ) - .report(err) - .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); - } - Err(err) => return Err(err.into()), - }; + .execute(project.workspace().into()), + ) + .await + { + Ok(result) => result, + Err(ProjectError::Operation(err)) => { + return diagnostics::OperationDiagnostic::with_system_certs( + client_builder.system_certs(), + ) + .report(err) + .map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); + } + Err(err) => return Err(err.into()), + }; - let target = match project { - VirtualProject::Project(project) => InstallTarget::Project { - workspace: project.workspace(), - name: project.project_name(), - lock: result.lock(), - }, - VirtualProject::NonProject(workspace) => InstallTarget::NonProjectWorkspace { - workspace, - lock: result.lock(), - }, - }; + let target = match project { + VirtualProject::Project(project) => InstallTarget::Project { + workspace: project.workspace(), + name: project.project_name(), + lock: result.lock(), + }, + VirtualProject::NonProject(workspace) => InstallTarget::NonProjectWorkspace { + workspace, + lock: result.lock(), + }, + }; - target.validate_extras(&extras)?; - target.validate_groups(&groups)?; + target.validate_extras(&extras)?; + target.validate_groups(&groups)?; + if no_sync { + debug!("Skipping environment synchronization due to `--no-sync`"); + } else { + let sync_state = state.fork(); match project::sync::do_sync( target, &venv, @@ -341,34 +368,32 @@ pub(crate) async fn check( } Err(err) => return Err(err.into()), } + } - Some(result.into_lock()) - }; + let lock = result.into_lock(); - if let Some(lock) = lock { - let target = match project { - VirtualProject::Project(project) => InstallTarget::Project { - workspace: project.workspace(), - name: project.project_name(), - lock: &lock, - }, - VirtualProject::NonProject(workspace) => InstallTarget::NonProjectWorkspace { - workspace, - lock: &lock, - }, - }; - let metadata = crate::commands::workspace::metadata::metadata_from_target( - project.workspace(), - (!no_sync).then_some(&venv), - target, - &extras, - &groups, - &settings.resolver, - )?; - let mut metadata = metadata.to_json()?; - metadata.push('\n'); - workspace_metadata = Some(metadata); - } + let target = match project { + VirtualProject::Project(project) => InstallTarget::Project { + workspace: project.workspace(), + name: project.project_name(), + lock: &lock, + }, + VirtualProject::NonProject(workspace) => InstallTarget::NonProjectWorkspace { + workspace, + lock: &lock, + }, + }; + let metadata = crate::commands::workspace::metadata::metadata_from_target( + project.workspace(), + (!no_sync).then_some(&venv), + target, + &extras, + &groups, + &settings.resolver, + )?; + let mut metadata = metadata.to_json()?; + metadata.push('\n'); + workspace_metadata = Some(metadata); Some(venv.root().to_owned()) } else { diff --git a/crates/uv/tests/project/check.rs b/crates/uv/tests/project/check.rs index e521bed56bbd4..e206b85c4c57f 100644 --- a/crates/uv/tests/project/check.rs +++ b/crates/uv/tests/project/check.rs @@ -38,6 +38,389 @@ fn check_project() -> Result<()> { Ok(()) } +#[test] +fn check_no_sync_creates_lock_without_sync() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + context + .temp_dir + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#})?; + context.temp_dir.child("main.py").write_str(indoc! {r" + x: int = 1 + "})?; + + uv_snapshot!( + context.filters(), + context + .check() + .arg("--no-sync") + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z") + .arg("--ty-version") + .arg("0.0.17"), + @" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + " + ); + + assert!(context.temp_dir.child("uv.lock").exists()); + assert!(!context.site_packages().join("iniconfig").exists()); + + Ok(()) +} + +#[test] +fn check_no_sync_uses_compatible_lock_interpreter() -> Result<()> { + let context = uv_test::test_context_with_versions!(&["3.12", "3.11"]); + + context + .temp_dir + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.11" + dependencies = [] + "#})?; + context.temp_dir.child("main.py").write_str(indoc! {r" + x: int = 1 + "})?; + context + .venv() + .arg("--python") + .arg("3.12") + .assert() + .success(); + + uv_snapshot!( + context.filters(), + context + .check() + .arg("--no-sync") + .arg("--python") + .arg("3.11") + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z") + .arg("--ty-version") + .arg("0.0.17"), + @" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + warning: Using incompatible environment (`.venv`) due to `--no-sync` (The project environment's Python version does not satisfy the request: `Python 3.11`) + Using CPython 3.11.[X] interpreter at: [PYTHON-3.11] + " + ); + + assert!(context.temp_dir.child("uv.lock").exists()); + context + .assert_command("import sys; assert sys.version_info[:2] == (3, 12)") + .success(); + + Ok(()) +} + +#[test] +fn check_no_sync_updates_stale_lock_without_sync() -> Result<()> { + let context = uv_test::test_context!("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + "#})?; + context + .lock() + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z") + .assert() + .success(); + let stale_lock = context.read("uv.lock"); + + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#})?; + context.temp_dir.child("main.py").write_str(indoc! {r" + x: int = 1 + "})?; + + uv_snapshot!( + context.filters(), + context + .check() + .arg("--no-sync") + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z") + .arg("--ty-version") + .arg("0.0.17"), + @" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + " + ); + + let updated_lock = context.read("uv.lock"); + assert_ne!(stale_lock, updated_lock); + assert!(updated_lock.contains("iniconfig")); + assert!(!context.site_packages().join("iniconfig").exists()); + + Ok(()) +} + +#[test] +fn check_no_sync_locked_rejects_stale_lock_without_update() -> Result<()> { + let context = uv_test::test_context!("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + "#})?; + context + .lock() + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z") + .assert() + .success(); + let stale_lock = context.read("uv.lock"); + + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#})?; + + uv_snapshot!( + context.filters(), + context + .check() + .arg("--no-sync") + .arg("--locked") + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z"), + @" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + error: The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. + " + ); + + assert_eq!(stale_lock, context.read("uv.lock")); + assert!(!context.site_packages().join("iniconfig").exists()); + + Ok(()) +} + +#[test] +fn check_no_sync_locked_requires_existing_lock() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + context + .temp_dir + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + uv_snapshot!( + context.filters(), + context.check().arg("--no-sync").arg("--locked"), + @" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + error: Unable to find lockfile at `uv.lock`, but `--locked` was provided. To create a lockfile, run `uv lock` or `uv sync` without the flag. + " + ); + + assert!(!context.temp_dir.child("uv.lock").exists()); + + Ok(()) +} + +#[test] +fn check_no_sync_frozen_uses_existing_lock_without_update() -> Result<()> { + let context = uv_test::test_context!("3.12"); + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["anyio==3.7.0"] + "#})?; + context + .lock() + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z") + .assert() + .success(); + let stale_lock = context.read("uv.lock"); + + pyproject_toml.write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#})?; + context.temp_dir.child("main.py").write_str(indoc! {r" + x: int = 1 + "})?; + + uv_snapshot!( + context.filters(), + context + .check() + .arg("--no-sync") + .arg("--frozen") + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z") + .arg("--ty-version") + .arg("0.0.17"), + @" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + " + ); + + assert_eq!(stale_lock, context.read("uv.lock")); + assert!(!context.site_packages().join("iniconfig").exists()); + + Ok(()) +} + +#[test] +fn check_no_sync_frozen_requires_existing_lock() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + context + .temp_dir + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [] + "#})?; + + uv_snapshot!( + context.filters(), + context.check().arg("--no-sync").arg("--frozen"), + @" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + error: Unable to find lockfile at `uv.lock`, but `--frozen` was provided. To create a lockfile, run `uv lock` or `uv sync` without the flag. + " + ); + + assert!(!context.temp_dir.child("uv.lock").exists()); + + Ok(()) +} + +#[test] +fn check_no_sync_isolated_does_not_write_lock_or_sync() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + context + .temp_dir + .child("pyproject.toml") + .write_str(indoc! {r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = ["iniconfig==2.0.0"] + "#})?; + context.temp_dir.child("main.py").write_str(indoc! {r" + x: int = 1 + "})?; + + uv_snapshot!( + context.filters(), + context + .check() + .arg("--no-sync") + .arg("--isolated") + .arg("--exclude-newer") + .arg("2026-02-15T00:00:00Z") + .arg("--ty-version") + .arg("0.0.17"), + @" + success: true + exit_code: 0 + ----- stdout ----- + All checks passed! + + ----- stderr ----- + warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + " + ); + + assert!(!context.temp_dir.child("uv.lock").exists()); + assert!(!context.site_packages().join("iniconfig").exists()); + + Ok(()) +} + #[test] #[cfg(feature = "test-pypi")] fn check_uses_ty_from_environment() -> Result<()> { @@ -126,7 +509,7 @@ fn check_passes_workspace_metadata_to_ty() -> Result<()> { } #[test] -fn check_no_sync_ignores_invalid_lockfile() -> Result<()> { +fn check_no_sync_errors_on_invalid_lockfile() -> Result<()> { let context = uv_test::test_context!("3.12"); context @@ -153,13 +536,18 @@ fn check_no_sync_ignores_invalid_lockfile() -> Result<()> { .arg("0.0.17") .env(EnvVars::RUST_LOG, "error"), @" - success: true - exit_code: 0 + success: false + exit_code: 2 ----- stdout ----- - All checks passed! ----- stderr ----- warning: `uv check` is experimental and may change without warning. Pass `--preview-features check-command` to disable this warning. + error: Failed to parse `uv.lock` + Caused by: TOML parse error at line 1, column 8 + | + 1 | invalid + | ^ + key with no value, expected `=` " ); From 91a230fad57d92fd4d30c0872aa18657526f04eb Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Thu, 18 Jun 2026 12:19:34 +0100 Subject: [PATCH 2/3] Update check no-sync snapshot tests --- crates/uv-cli/src/lib.rs | 2 - crates/uv/tests/project/check.rs | 102 +++++++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index 0d83946eeb49f..1489fbb4f0e49 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -5352,8 +5352,6 @@ pub struct CheckArgs { pub frozen: bool, /// Avoid syncing the virtual environment [env: UV_NO_SYNC=] - /// - /// The project lockfile will still be created or updated as needed. #[arg(long)] pub no_sync: bool, diff --git a/crates/uv/tests/project/check.rs b/crates/uv/tests/project/check.rs index e206b85c4c57f..bb22bf9d46435 100644 --- a/crates/uv/tests/project/check.rs +++ b/crates/uv/tests/project/check.rs @@ -3,9 +3,10 @@ use anyhow::Result; use assert_cmd::assert::OutputAssertExt; use assert_fs::prelude::*; use indoc::indoc; +use insta::assert_snapshot; use uv_static::EnvVars; -use uv_test::uv_snapshot; +use uv_test::{diff_snapshot, uv_snapshot}; #[test] fn check_project() -> Result<()> { @@ -76,7 +77,38 @@ fn check_no_sync_creates_lock_without_sync() -> Result<()> { " ); - assert!(context.temp_dir.child("uv.lock").exists()); + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!(context.read("uv.lock"), @r#" + version = 1 + revision = 3 + requires-python = ">=3.12" + + [options] + exclude-newer = "2026-02-15T00:00:00Z" + + [[package]] + name = "iniconfig" + version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "iniconfig" }, + ] + + [package.metadata] + requires-dist = [{ name = "iniconfig", specifier = "==2.0.0" }] + "#); + }); assert!(!context.site_packages().join("iniconfig").exists()); Ok(()) @@ -190,8 +222,70 @@ fn check_no_sync_updates_stale_lock_without_sync() -> Result<()> { ); let updated_lock = context.read("uv.lock"); - assert_ne!(stale_lock, updated_lock); - assert!(updated_lock.contains("iniconfig")); + let diff = diff_snapshot(&stale_lock, &updated_lock, 10); + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!(diff, @r#" + --- old + +++ new + @@ -1,48 +1,26 @@ + version = 1 + revision = 3 + requires-python = ">=3.12" + + [options] + exclude-newer = "2026-02-15T00:00:00Z" + + [[package]] + -name = "anyio" + -version = "3.7.0" + +name = "iniconfig" + +version = "2.0.0" + source = { registry = "https://pypi.org/simple" } + -dependencies = [ + - { name = "idna" }, + - { name = "sniffio" }, + -] + -sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" } + +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646, upload-time = "2023-01-07T11:08:11.254Z" } + wheels = [ + - { url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873, upload-time = "2023-05-27T11:12:44.474Z" }, + -] + - + -[[package]] + -name = "idna" + -version = "3.11" + -source = { registry = "https://pypi.org/simple" } + -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } + -wheels = [ + - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + - { name = "anyio" }, + + { name = "iniconfig" }, + ] + + [package.metadata] + -requires-dist = [{ name = "anyio", specifier = "==3.7.0" }] + - + -[[package]] + -name = "sniffio" + -version = "1.3.1" + -source = { registry = "https://pypi.org/simple" } + -sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } + -wheels = [ + - { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + -] + +requires-dist = [{ name = "iniconfig", specifier = "==2.0.0" }] + "#); + }); assert!(!context.site_packages().join("iniconfig").exists()); Ok(()) From 8ce928063af7596ca14ff9e4a451f7ab5a07f9b7 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Thu, 18 Jun 2026 18:13:06 +0100 Subject: [PATCH 3/3] Guard environment lock acquisition behind no-sync --- crates/uv/src/commands/project/check.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/uv/src/commands/project/check.rs b/crates/uv/src/commands/project/check.rs index 980b73035e497..da70f788f61ff 100644 --- a/crates/uv/src/commands/project/check.rs +++ b/crates/uv/src/commands/project/check.rs @@ -266,16 +266,16 @@ pub(crate) async fn check( let state = UniversalState::default(); // Keep the environment locked through synchronization and metadata collection. - let _environment_lock = if no_sync { - None - } else { - venv.lock() + let _environment_lock; + if !no_sync { + _environment_lock = venv + .lock() .await .inspect_err(|err| { tracing::warn!("Failed to acquire environment lock: {err}"); }) - .ok() - }; + .ok(); + } let mode = if let Some(frozen_source) = frozen { LockMode::Frozen(frozen_source.into())