Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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, 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;
Expand Down Expand Up @@ -130,8 +132,9 @@ 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)
Comment on lines -133 to +135

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like we should restructure and rename these types at some point? A virtual project is now a valid workspace.

.await?;
LockTarget::Workspace(workspace.workspace())
};

// Determine the lock mode.
Expand Down
98 changes: 97 additions & 1 deletion crates/uv/tests/it/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18630,6 +18630,101 @@ 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: true
exit_code: 0
----- stdout -----

----- stderr -----
warning: No `requires-python` value found in the workspace. Defaulting to `>=3.10`.
Resolved 2 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.10"

[options]
exclude-newer = "2024-03-25T00:00:00Z"

[manifest]

[manifest.dependency-groups]
dev = [{ name = "typing-extensions" }]
lint = [{ name = "iniconfig" }]

[[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 = "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 2 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 2 packages in [TIME]
");

Ok(())
}

/// Lock a non-project workspace root with `tool.uv.sources`.
#[test]
fn lock_non_project_sources() -> Result<()> {
Expand Down Expand Up @@ -19875,7 +19970,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`
Expand Down
63 changes: 63 additions & 0 deletions crates/uv/tests/it/lock_conflict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2259,6 +2259,69 @@ 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).
/// 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");

// 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 -----
warning: No `requires-python` value found in the workspace. Defaulting to `>=3.12`.
× No solution found when resolving dependencies:
╰─▶ 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
// 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(), @r#"
success: false
exit_code: 2
----- stdout -----

----- stderr -----
error: Expected `package` field in conflicting entry: { group = "group1" }
"#);

Ok(())
}

/// Ref: <https://github.com/astral-sh/uv/issues/18428>
#[test]
fn groups_respect_supported_environments_when_filtering_wheels() -> Result<()> {
Expand Down
81 changes: 81 additions & 0 deletions crates/uv/tests/it/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down
Loading