diff --git a/crates/uv-platform-tags/src/platform_tag.rs b/crates/uv-platform-tags/src/platform_tag.rs index 1162a83d7..4ee3ec672 100644 --- a/crates/uv-platform-tags/src/platform_tag.rs +++ b/crates/uv-platform-tags/src/platform_tag.rs @@ -105,6 +105,11 @@ impl PlatformTag { } impl PlatformTag { + /// Returns `true` if the platform is "any" (i.e., not specific to a platform). + pub fn is_any(&self) -> bool { + matches!(self, Self::Any) + } + /// Returns `true` if the platform is manylinux-only. pub fn is_manylinux(&self) -> bool { matches!( diff --git a/crates/uv-resolver/src/lock/mod.rs b/crates/uv-resolver/src/lock/mod.rs index 44aa915e6..cb87fa2cc 100644 --- a/crates/uv-resolver/src/lock/mod.rs +++ b/crates/uv-resolver/src/lock/mod.rs @@ -106,6 +106,51 @@ static X86_MARKERS: LazyLock = LazyLock::new(|| { .unwrap(); UniversalMarker::new(pep508, ConflictMarker::TRUE) }); +static LINUX_ARM_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *LINUX_MARKERS; + marker.and(*ARM_MARKERS); + marker +}); +static LINUX_X86_64_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *LINUX_MARKERS; + marker.and(*X86_64_MARKERS); + marker +}); +static LINUX_X86_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *LINUX_MARKERS; + marker.and(*X86_MARKERS); + marker +}); +static WINDOWS_ARM_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *WINDOWS_MARKERS; + marker.and(*ARM_MARKERS); + marker +}); +static WINDOWS_X86_64_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *WINDOWS_MARKERS; + marker.and(*X86_64_MARKERS); + marker +}); +static WINDOWS_X86_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *WINDOWS_MARKERS; + marker.and(*X86_MARKERS); + marker +}); +static MAC_ARM_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *MAC_MARKERS; + marker.and(*ARM_MARKERS); + marker +}); +static MAC_X86_64_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *MAC_MARKERS; + marker.and(*X86_64_MARKERS); + marker +}); +static MAC_X86_MARKERS: LazyLock = LazyLock::new(|| { + let mut marker = *MAC_MARKERS; + marker.and(*X86_MARKERS); + marker +}); #[derive(Clone, Debug, serde::Deserialize)] #[serde(try_from = "LockWire")] @@ -336,14 +381,61 @@ impl Lock { // a single disjointness check with the intersection is sufficient, so we have one // constant per platform. let platform_tags = wheel.filename.platform_tags(); + + if platform_tags.iter().all(PlatformTag::is_any) { + return true; + } + if platform_tags.iter().all(PlatformTag::is_linux) { - if graph.graph[node_index].marker().is_disjoint(*LINUX_MARKERS) { + if platform_tags.iter().all(PlatformTag::is_arm) { + if graph.graph[node_index] + .marker() + .is_disjoint(*LINUX_ARM_MARKERS) + { + return false; + } + } else if platform_tags.iter().all(PlatformTag::is_x86_64) { + if graph.graph[node_index] + .marker() + .is_disjoint(*LINUX_X86_64_MARKERS) + { + return false; + } + } else if platform_tags.iter().all(PlatformTag::is_x86) { + if graph.graph[node_index] + .marker() + .is_disjoint(*LINUX_X86_MARKERS) + { + return false; + } + } else if graph.graph[node_index].marker().is_disjoint(*LINUX_MARKERS) { return false; } } if platform_tags.iter().all(PlatformTag::is_windows) { - if graph.graph[node_index] + if platform_tags.iter().all(PlatformTag::is_arm) { + if graph.graph[node_index] + .marker() + .is_disjoint(*WINDOWS_ARM_MARKERS) + { + return false; + } + } else if platform_tags.iter().all(PlatformTag::is_x86_64) { + if graph.graph[node_index] + .marker() + .is_disjoint(*WINDOWS_X86_64_MARKERS) + { + return false; + } + } else if platform_tags.iter().all(PlatformTag::is_x86) { + if graph.graph[node_index] + .marker() + .is_disjoint(*WINDOWS_X86_MARKERS) + { + return false; + } + } else if graph.graph[node_index] .marker() .is_disjoint(*WINDOWS_MARKERS) { @@ -352,7 +444,28 @@ impl Lock { } if platform_tags.iter().all(PlatformTag::is_macos) { - if graph.graph[node_index].marker().is_disjoint(*MAC_MARKERS) { + if platform_tags.iter().all(PlatformTag::is_arm) { + if graph.graph[node_index] + .marker() + .is_disjoint(*MAC_ARM_MARKERS) + { + return false; + } + } else if platform_tags.iter().all(PlatformTag::is_x86_64) { + if graph.graph[node_index] + .marker() + .is_disjoint(*MAC_X86_64_MARKERS) + { + return false; + } + } else if platform_tags.iter().all(PlatformTag::is_x86) { + if graph.graph[node_index] + .marker() + .is_disjoint(*MAC_X86_MARKERS) + { + return false; + } + } else if graph.graph[node_index].marker().is_disjoint(*MAC_MARKERS) { return false; } } diff --git a/crates/uv/tests/it/lock.rs b/crates/uv/tests/it/lock.rs index acc36eacf..71cdffd52 100644 --- a/crates/uv/tests/it/lock.rs +++ b/crates/uv/tests/it/lock.rs @@ -30040,3 +30040,106 @@ fn lock_circular_path_dependency_explicit_index() -> Result<()> { Ok(()) } + +#[test] +fn lock_required_intersection() -> Result<()> { + let context = TestContext::new("3.12"); + + let pyproject_toml = context.temp_dir.child("pyproject.toml"); + pyproject_toml.write_str( + r#" + [project] + name = "project" + version = "0.1.0" + requires-python = ">=3.12" + dependencies = [ + "numpy", + ] + + [tool.uv] + environments = [ + "(sys_platform=='linux' and platform_machine=='x86_64')", + "(platform_machine=='arm64' and sys_platform=='darwin')" + ] + "#, + )?; + + uv_snapshot!(context.filters(), context.lock(), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + 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.12" + resolution-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux'", + "platform_machine == 'arm64' and sys_platform == 'darwin'", + ] + supported-markers = [ + "platform_machine == 'x86_64' and sys_platform == 'linux'", + "platform_machine == 'arm64' and sys_platform == 'darwin'", + ] + + [options] + exclude-newer = "2024-03-25T00:00:00Z" + + [[package]] + name = "numpy" + version = "1.26.4" + source = { registry = "https://pypi.org/simple" } + sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } + wheels = [ + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + ] + + [[package]] + name = "project" + version = "0.1.0" + source = { virtual = "." } + dependencies = [ + { name = "numpy", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'x86_64' and sys_platform == 'linux')" }, + ] + + [package.metadata] + requires-dist = [{ name = "numpy" }] + "# + ); + }); + + // Re-run with `--locked`. + uv_snapshot!(context.filters(), context.lock().arg("--locked"), @r" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + 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"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + + ----- stderr ----- + Resolved 2 packages in [TIME] + "###); + + Ok(()) +}