From eb6baf41181ebd6eb94ac62c9ce9f3a3f53091df Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 20 Apr 2026 16:22:51 -0400 Subject: [PATCH 1/4] add test --- crates/uv/tests/it/lock.rs | 139 ++++++++++++++++++++++ crates/uv/tests/it/lock_conflict.rs | 172 ++++++++++++++++++++++++++++ crates/uv/tests/it/sync.rs | 81 +++++++++++++ 3 files changed, 392 insertions(+) diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index 5455fd6e222c6..fdfd6d06a02a8 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -18630,6 +18630,145 @@ fn lock_non_project_group() -> Result<()> { Ok(()) } +/// Lock a non-project workspace root with `dependency-groups`. +/// +/// Here instead of leaning on `tool.uv.workspace` we use the +/// officially blessed "pyproject.toml with no `[project]` that +/// declares `[dependency-groups]`". +#[test] +fn lock_non_project_group_standard() -> Result<()> { + let context = uv_test::test_context!("3.10"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [dependency-groups] + lint = ["iniconfig"] + dev = ["typing-extensions"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No `project` table found in: [TEMP_DIR]/pyproject.toml + "); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 3 + requires-python = ">=3.10" + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [manifest] + + [manifest.dependency-groups] + dev = [ + { name = "anyio" }, + { name = "typing-extensions" }, + ] + lint = [{ name = "iniconfig" }] + + [[package]] + name = "anyio" + version = "4.3.0" + source = { registry = "https://pypi.org/simple" } + dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + ] + sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, + ] + + [[package]] + name = "exceptiongroup" + version = "1.2.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/8e/1c/beef724eaf5b01bb44b6338c8c3494eff7cab376fab4904cfbbc3585dc79/exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68", size = 26264, upload-time = "2023-11-21T08:42:17.407Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/9a/5028fd52db10e600f1c4674441b968cf2ea4959085bfb5b99fb1250e5f68/exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", size = 16210, upload-time = "2023-11-21T08:42:15.525Z" }, + ] + + [[package]] + name = "idna" + version = "3.6" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, + ] + + [[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 = "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" }, + ] + + [[package]] + name = "typing-extensions" + version = "4.10.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/16/3a/0d26ce356c7465a19c9ea8814b960f8a36c3b0d07c323176620b7b483e44/typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb", size = 77558, upload-time = "2024-02-25T22:12:49.693Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/de/dc04a3ea60b22624b51c703a84bbe0184abcd1d0b9bc8074b5d6b7ab90bb/typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475", size = 33926, upload-time = "2024-02-25T22:12:47.72Z" }, + ] + "# + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`. + Resolved 6 packages in [TIME] + "); + + // Re-run with `--offline`. We shouldn't need a network connection to validate an + // already-correct lockfile with immutable metadata. + uv_snapshot!(context.filters(), context.lock().arg("--locked").arg("--offline").arg("--no-cache"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`. + Resolved 6 packages in [TIME] + "); + + Ok(()) +} + /// Lock a non-project workspace root with `tool.uv.sources`. #[test] fn lock_non_project_sources() -> Result<()> { diff --git a/crates/uv/tests/it/lock_conflict.rs b/crates/uv/tests/it/lock_conflict.rs index 2a029001702c8..0c352db8472cd 100644 --- a/crates/uv/tests/it/lock_conflict.rs +++ b/crates/uv/tests/it/lock_conflict.rs @@ -2259,6 +2259,178 @@ fn group_default() -> Result<()> { Ok(()) } +/// This tests conflicting groups in a virtual pyproject.toml +/// +/// (One with no `[project]` which is allowed for specifically dependency-groups) +#[test] +fn group_virtual() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + // First we test that resolving with two groups that have + // conflicting dependencies fails. + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [dependency-groups] + group1 = ["sortedcontainers==2.3.0"] + group2 = ["sortedcontainers==2.4.0"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × No solution found when resolving dependencies: + ╰─▶ Because project:group2 depends on sortedcontainers==2.4.0 and project:group1 depends on sortedcontainers==2.3.0, we can conclude that project:group1 and project:group2 are incompatible. + And because your project requires project:group1 and project:group2, we can conclude that your project's requirements are unsatisfiable. + "); + + // And now with the same group configuration, we tell uv about + // the conflicting groups, which forces it to resolve each in + // their own fork. + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [tool.uv] + conflicts = [ + [ + { group = "group1" }, + { group = "group2" }, + ], + ] + + [dependency-groups] + group1 = ["sortedcontainers==2.3.0"] + group2 = ["sortedcontainers==2.4.0"] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + "); + + let lock = context.read("uv.lock"); + + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r#" + version = 1 + revision = 3 + requires-python = ">=3.12" + conflicts = [[ + { package = "project", group = "group1" }, + { package = "project", group = "group2" }, + ]] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + + [package.dev-dependencies] + group1 = [ + { name = "sortedcontainers", version = "2.3.0", source = { registry = "https://pypi.org/simple" } }, + ] + group2 = [ + { name = "sortedcontainers", version = "2.4.0", source = { registry = "https://pypi.org/simple" } }, + ] + + [package.metadata] + + [package.metadata.requires-dev] + group1 = [{ name = "sortedcontainers", specifier = "==2.3.0" }] + group2 = [{ name = "sortedcontainers", specifier = "==2.4.0" }] + + [[package]] + name = "sortedcontainers" + version = "2.3.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1", size = 30509, upload-time = "2020-11-09T00:03:52.258Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", size = 29479, upload-time = "2020-11-09T00:03:50.723Z" }, + ] + + [[package]] + name = "sortedcontainers" + version = "2.4.0" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, + ] + "# + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 3 packages in [TIME] + "); + + // Install from the lockfile. + uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Checked in [TIME] + "); + // Another install, but with one of the groups enabled. + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=group1"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + sortedcontainers==2.3.0 + "); + // Another install, but with the other group enabled. + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=group2"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Prepared 1 package in [TIME] + Uninstalled 1 package in [TIME] + Installed 1 package in [TIME] + - sortedcontainers==2.3.0 + + sortedcontainers==2.4.0 + "); + // And finally, installing both groups should error. + uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=group1").arg("--group=group2"), @" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Groups `group1` and `group2` are incompatible with the conflicts: {`project:group1`, `project:group2`} + "); + + Ok(()) +} + /// Ref: #[test] fn groups_respect_supported_environments_when_filtering_wheels() -> Result<()> { diff --git a/crates/uv/tests/it/sync.rs b/crates/uv/tests/it/sync.rs index ef4b0f3417803..f304ae0189a44 100644 --- a/crates/uv/tests/it/sync.rs +++ b/crates/uv/tests/it/sync.rs @@ -1329,6 +1329,87 @@ fn sync_non_project_frozen() -> Result<()> { Ok(()) } +/// Sync dependency groups in a non-project workspace root. +/// +/// Here instead of leaning on `tool.uv.workspace` we use the +/// officially blessed "pyproject.toml with no `[project]` that +/// declares `[dependency-groups]`". +#[test] +fn sync_non_project_group_standard() -> Result<()> { + let context = uv_test::test_context!("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [dependency-groups] + dev = ["anyio"] + bar = ["typing-extensions"] + "#, + )?; + + context + .temp_dir + .child("src") + .child("albatross") + .child("__init__.py") + .touch()?; + + uv_snapshot!(context.filters(), context.sync(), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + Resolved 4 packages in [TIME] + Prepared 3 packages in [TIME] + Installed 3 packages in [TIME] + + anyio==4.3.0 + + idna==3.6 + + sniffio==1.3.1 + "); + + uv_snapshot!(context.filters(), context.sync().arg("--group").arg("bar"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + Resolved 4 packages in [TIME] + Prepared 1 package in [TIME] + Installed 1 package in [TIME] + + typing-extensions==4.10.0 + "); + + uv_snapshot!(context.filters(), context.sync().arg("--only-group").arg("bar"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + Resolved 4 packages in [TIME] + Uninstalled 3 packages in [TIME] + - anyio==4.3.0 + - idna==3.6 + - sniffio==1.3.1 + "); + + uv_snapshot!(context.filters(), context.sync().arg("--no-default-groups"), @" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. + Resolved 4 packages in [TIME] + Uninstalled 1 package in [TIME] + - typing-extensions==4.10.0 + "); + Ok(()) +} + /// Sync dependency groups in a non-project workspace root. #[test] fn sync_non_project_group() -> Result<()> { From 7ccb80db5df1e4d41c917798427be87c432dce00 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 20 Apr 2026 16:08:24 -0400 Subject: [PATCH 2/4] Support locking virtual workspaces --- crates/uv/src/commands/project/lock.rs | 6 +- crates/uv/tests/it/lock.rs | 3 +- crates/uv/tests/it/lock_conflict.rs | 125 ++----------------------- 3 files changed, 13 insertions(+), 121 deletions(-) diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index c44703d0eef65..bdebfab9f14d7 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -40,7 +40,7 @@ use uv_types::{ BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, SourceTreeEditablePolicy, }; use uv_warnings::{warn_user, warn_user_once}; -use uv_workspace::{DiscoveryOptions, Editability, Workspace, WorkspaceCache, WorkspaceMember}; +use uv_workspace::{DiscoveryOptions, Editability, VirtualProject, WorkspaceCache, WorkspaceMember}; use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, SummaryResolveLogger}; use crate::commands::project::lock_target::LockTarget; @@ -130,8 +130,8 @@ pub(crate) async fn lock( LockTarget::Script(script) } else { workspace = - Workspace::discover(project_dir, &DiscoveryOptions::default(), workspace_cache).await?; - LockTarget::Workspace(&workspace) + VirtualProject::discover(project_dir, &DiscoveryOptions::default(), workspace_cache).await?; + LockTarget::Workspace(workspace.workspace()) }; // Determine the lock mode. diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index fdfd6d06a02a8..e202a536b5e99 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -20014,7 +20014,8 @@ fn lock_explicit_default_index() -> Result<()> { ----- stderr ----- DEBUG uv [VERSION] ([COMMIT] DATE) - DEBUG Found workspace root: `[TEMP_DIR]/` + DEBUG Found project root: `[TEMP_DIR]/` + DEBUG No workspace root found, using project root DEBUG No Python version file found in workspace: [TEMP_DIR]/ DEBUG Using Python request `>=3.12` from `requires-python` metadata DEBUG Checking for Python environment at: `.venv` diff --git a/crates/uv/tests/it/lock_conflict.rs b/crates/uv/tests/it/lock_conflict.rs index 0c352db8472cd..1ac2f808354b9 100644 --- a/crates/uv/tests/it/lock_conflict.rs +++ b/crates/uv/tests/it/lock_conflict.rs @@ -2261,7 +2261,9 @@ fn group_default() -> Result<()> { /// This tests conflicting groups in a virtual pyproject.toml /// -/// (One with no `[project]` which is allowed for specifically dependency-groups) +/// (One with no `[project]` which is allowed for specifically dependency-groups). +/// Currently this isn't supported, as we require a PackageName when representing +/// Conflicts internally. #[test] fn group_virtual() -> Result<()> { let context = uv_test::test_context!("3.12"); @@ -2283,9 +2285,9 @@ fn group_virtual() -> Result<()> { ----- stdout ----- ----- stderr ----- + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`. × No solution found when resolving dependencies: - ╰─▶ Because project:group2 depends on sortedcontainers==2.4.0 and project:group1 depends on sortedcontainers==2.3.0, we can conclude that project:group1 and project:group2 are incompatible. - And because your project requires project:group1 and project:group2, we can conclude that your project's requirements are unsatisfiable. + ╰─▶ Because you require sortedcontainers==2.3.0 and sortedcontainers==2.4.0, we can conclude that your requirements are unsatisfiable. "); // And now with the same group configuration, we tell uv about @@ -2308,125 +2310,14 @@ fn group_virtual() -> Result<()> { "#, )?; - uv_snapshot!(context.filters(), context.lock(), @" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Resolved 3 packages in [TIME] - "); - - let lock = context.read("uv.lock"); - - insta::with_settings!({ - filters => context.filters(), - }, { - assert_snapshot!( - lock, @r#" - version = 1 - revision = 3 - requires-python = ">=3.12" - conflicts = [[ - { package = "project", group = "group1" }, - { package = "project", group = "group2" }, - ]] - - [options] - exclude-newer = "2024-03-25T00:00:00Z" - - [[package]] - name = "project" - version = "0.1.0" - source = { virtual = "." } - - [package.dev-dependencies] - group1 = [ - { name = "sortedcontainers", version = "2.3.0", source = { registry = "https://pypi.org/simple" } }, - ] - group2 = [ - { name = "sortedcontainers", version = "2.4.0", source = { registry = "https://pypi.org/simple" } }, - ] - - [package.metadata] - - [package.metadata.requires-dev] - group1 = [{ name = "sortedcontainers", specifier = "==2.3.0" }] - group2 = [{ name = "sortedcontainers", specifier = "==2.4.0" }] - - [[package]] - name = "sortedcontainers" - version = "2.3.0" - source = { registry = "https://pypi.org/simple" } - sdist = { url = "https://files.pythonhosted.org/packages/14/10/6a9481890bae97da9edd6e737c9c3dec6aea3fc2fa53b0934037b35c89ea/sortedcontainers-2.3.0.tar.gz", hash = "sha256:59cc937650cf60d677c16775597c89a960658a09cf7c1a668f86e1e4464b10a1", size = 30509, upload-time = "2020-11-09T00:03:52.258Z" } - wheels = [ - { url = "https://files.pythonhosted.org/packages/20/4d/a7046ae1a1a4cc4e9bbed194c387086f06b25038be596543d026946330c9/sortedcontainers-2.3.0-py2.py3-none-any.whl", hash = "sha256:37257a32add0a3ee490bb170b599e93095eed89a55da91fa9f48753ea12fd73f", size = 29479, upload-time = "2020-11-09T00:03:50.723Z" }, - ] - - [[package]] - name = "sortedcontainers" - version = "2.4.0" - source = { registry = "https://pypi.org/simple" } - sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } - wheels = [ - { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, - ] - "# - ); - }); - - // Re-run with `--locked`. - uv_snapshot!(context.filters(), context.lock().arg("--locked"), @" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Resolved 3 packages in [TIME] - "); - - // Install from the lockfile. - uv_snapshot!(context.filters(), context.sync().arg("--frozen"), @" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Checked in [TIME] - "); - // Another install, but with one of the groups enabled. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=group1"), @" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Prepared 1 package in [TIME] - Installed 1 package in [TIME] - + sortedcontainers==2.3.0 - "); - // Another install, but with the other group enabled. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=group2"), @" - success: true - exit_code: 0 - ----- stdout ----- - - ----- stderr ----- - Prepared 1 package in [TIME] - Uninstalled 1 package in [TIME] - Installed 1 package in [TIME] - - sortedcontainers==2.3.0 - + sortedcontainers==2.4.0 - "); - // And finally, installing both groups should error. - uv_snapshot!(context.filters(), context.sync().arg("--frozen").arg("--group=group1").arg("--group=group2"), @" + uv_snapshot!(context.filters(), context.lock(), @r#" success: false exit_code: 2 ----- stdout ----- ----- stderr ----- - error: Groups `group1` and `group2` are incompatible with the conflicts: {`project:group1`, `project:group2`} - "); + error: Expected `package` field in conflicting entry: { group = "group1" } + "#); Ok(()) } From bda83073793be2bb67efad64fa28884aab0f421e Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 20 Apr 2026 16:28:10 -0400 Subject: [PATCH 3/4] regen tests --- crates/uv/src/commands/project/lock.rs | 7 +++- crates/uv/tests/it/lock.rs | 58 ++++---------------------- 2 files changed, 12 insertions(+), 53 deletions(-) diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index bdebfab9f14d7..e2d23c0d2b024 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -40,7 +40,9 @@ use uv_types::{ BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy, SourceTreeEditablePolicy, }; use uv_warnings::{warn_user, warn_user_once}; -use uv_workspace::{DiscoveryOptions, Editability, VirtualProject, WorkspaceCache, WorkspaceMember}; +use uv_workspace::{ + DiscoveryOptions, Editability, VirtualProject, WorkspaceCache, WorkspaceMember, +}; use crate::commands::pip::loggers::{DefaultResolveLogger, ResolveLogger, SummaryResolveLogger}; use crate::commands::project::lock_target::LockTarget; @@ -130,7 +132,8 @@ pub(crate) async fn lock( LockTarget::Script(script) } else { workspace = - VirtualProject::discover(project_dir, &DiscoveryOptions::default(), workspace_cache).await?; + VirtualProject::discover(project_dir, &DiscoveryOptions::default(), workspace_cache) + .await?; LockTarget::Workspace(workspace.workspace()) }; diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index e202a536b5e99..228f03c9c6a38 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -18649,12 +18649,13 @@ fn lock_non_project_group_standard() -> Result<()> { )?; uv_snapshot!(context.filters(), context.lock(), @" - success: false - exit_code: 2 + success: true + exit_code: 0 ----- stdout ----- ----- stderr ----- - error: No `project` table found in: [TEMP_DIR]/pyproject.toml + warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`. + Resolved 2 packages in [TIME] "); let lock = context.read("uv.lock"); @@ -18674,45 +18675,9 @@ fn lock_non_project_group_standard() -> Result<()> { [manifest] [manifest.dependency-groups] - dev = [ - { name = "anyio" }, - { name = "typing-extensions" }, - ] + dev = [{ name = "typing-extensions" }] lint = [{ name = "iniconfig" }] - [[package]] - name = "anyio" - version = "4.3.0" - source = { registry = "https://pypi.org/simple" } - dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, - { name = "idna" }, - { name = "sniffio" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, - ] - sdist = { url = "https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6", size = 159642, upload-time = "2024-02-19T08:36:28.641Z" } - wheels = [ - { url = "https://files.pythonhosted.org/packages/14/fd/2f20c40b45e4fb4324834aea24bd4afdf1143390242c0b33774da0e2e34f/anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8", size = 85584, upload-time = "2024-02-19T08:36:26.842Z" }, - ] - - [[package]] - name = "exceptiongroup" - version = "1.2.0" - source = { registry = "https://pypi.org/simple" } - sdist = { url = "https://files.pythonhosted.org/packages/8e/1c/beef724eaf5b01bb44b6338c8c3494eff7cab376fab4904cfbbc3585dc79/exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68", size = 26264, upload-time = "2023-11-21T08:42:17.407Z" } - wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/9a/5028fd52db10e600f1c4674441b968cf2ea4959085bfb5b99fb1250e5f68/exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14", size = 16210, upload-time = "2023-11-21T08:42:15.525Z" }, - ] - - [[package]] - name = "idna" - version = "3.6" - source = { registry = "https://pypi.org/simple" } - sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" } - wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" }, - ] - [[package]] name = "iniconfig" version = "2.0.0" @@ -18722,15 +18687,6 @@ fn lock_non_project_group_standard() -> Result<()> { { 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 = "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" }, - ] - [[package]] name = "typing-extensions" version = "4.10.0" @@ -18751,7 +18707,7 @@ fn lock_non_project_group_standard() -> Result<()> { ----- stderr ----- warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`. - Resolved 6 packages in [TIME] + Resolved 2 packages in [TIME] "); // Re-run with `--offline`. We shouldn't need a network connection to validate an @@ -18763,7 +18719,7 @@ fn lock_non_project_group_standard() -> Result<()> { ----- stderr ----- warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`. - Resolved 6 packages in [TIME] + Resolved 2 packages in [TIME] "); Ok(()) From 4bb8b2c30a5e9edf8e37a372fa7ef37cec69f8d0 Mon Sep 17 00:00:00 2001 From: Aria Desires Date: Mon, 20 Apr 2026 16:36:35 -0400 Subject: [PATCH 4/4] fmt --- crates/uv/tests/it/lock_conflict.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/uv/tests/it/lock_conflict.rs b/crates/uv/tests/it/lock_conflict.rs index 1ac2f808354b9..4120066f2c109 100644 --- a/crates/uv/tests/it/lock_conflict.rs +++ b/crates/uv/tests/it/lock_conflict.rs @@ -2262,7 +2262,7 @@ fn group_default() -> Result<()> { /// This tests conflicting groups in a virtual pyproject.toml /// /// (One with no `[project]` which is allowed for specifically dependency-groups). -/// Currently this isn't supported, as we require a PackageName when representing +/// Currently this isn't supported, as we require a `PackageName` when representing /// Conflicts internally. #[test] fn group_virtual() -> Result<()> {