Skip to content

Commit 2b8bec4

Browse files
committed
Encode mutually-incompatible pairs of markers
1 parent d187535 commit 2b8bec4

File tree

4 files changed

+194
-33
lines changed

4 files changed

+194
-33
lines changed

crates/uv-distribution/src/metadata/lowering.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,9 @@ impl LoweredRequirement {
314314
})
315315
.chain(std::iter::once(Ok(remaining)))
316316
.filter(|requirement| match requirement {
317-
Ok(requirement) => !requirement.0.marker.is_false(),
317+
Ok(requirement) => {
318+
!(requirement.0.marker.is_false() || requirement.0.marker.is_conflicting())
319+
}
318320
Err(_) => true,
319321
}),
320322
)
@@ -466,7 +468,9 @@ impl LoweredRequirement {
466468
})
467469
.chain(std::iter::once(Ok(remaining)))
468470
.filter(|requirement| match requirement {
469-
Ok(requirement) => !requirement.0.marker.is_false(),
471+
Ok(requirement) => {
472+
!(requirement.0.marker.is_false() || requirement.0.marker.is_conflicting())
473+
}
470474
Err(_) => true,
471475
}),
472476
)

crates/uv-pep508/src/marker/tree.rs

Lines changed: 183 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
use itertools::Itertools;
2+
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
13
use std::cmp::Ordering;
24
use std::fmt::{self, Display, Formatter};
35
use std::ops::{Bound, Deref};
46
use std::str::FromStr;
5-
6-
use itertools::Itertools;
7-
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
7+
use std::sync::LazyLock;
88
use uv_normalize::ExtraName;
99
use uv_pep440::{Version, VersionParseError, VersionSpecifier};
1010
use version_ranges::Ranges;
@@ -685,6 +685,186 @@ impl MarkerTree {
685685
self.0.is_false()
686686
}
687687

688+
/// Whether the marker is known to be unsatisfiable.
689+
///
690+
/// For example, while the marker specification and grammar do not _forbid_ it, we know that
691+
/// both `sys_platform == 'win32'` and `platform_system == 'Darwin'` will never true at the
692+
/// same time.
693+
///
694+
/// This method thus encodes assumptions about the environment that are not guaranteed by the
695+
/// PEP 508 specification alone.
696+
pub fn is_conflicting(&self) -> bool {
697+
static MUTUAL_EXCLUSIONS: LazyLock<MarkerTree> = LazyLock::new(|| {
698+
let mut tree = MarkerTree::FALSE;
699+
for (a, b) in [
700+
// sys_platform == 'darwin' and platform_system == 'Windows'
701+
(
702+
MarkerExpression::String {
703+
key: MarkerValueString::SysPlatform,
704+
operator: MarkerOperator::Equal,
705+
value: "darwin".to_string(),
706+
},
707+
MarkerExpression::String {
708+
key: MarkerValueString::PlatformSystem,
709+
operator: MarkerOperator::Equal,
710+
value: "Windows".to_string(),
711+
},
712+
),
713+
// sys_platform == 'darwin' and platform_system == 'Linux'
714+
(
715+
MarkerExpression::String {
716+
key: MarkerValueString::SysPlatform,
717+
operator: MarkerOperator::Equal,
718+
value: "darwin".to_string(),
719+
},
720+
MarkerExpression::String {
721+
key: MarkerValueString::PlatformSystem,
722+
operator: MarkerOperator::Equal,
723+
value: "Linux".to_string(),
724+
},
725+
),
726+
// sys_platform == 'win32' and platform_system == 'Darwin'
727+
(
728+
MarkerExpression::String {
729+
key: MarkerValueString::SysPlatform,
730+
operator: MarkerOperator::Equal,
731+
value: "win32".to_string(),
732+
},
733+
MarkerExpression::String {
734+
key: MarkerValueString::PlatformSystem,
735+
operator: MarkerOperator::Equal,
736+
value: "Darwin".to_string(),
737+
},
738+
),
739+
// sys_platform == 'win32' and platform_system == 'Linux'
740+
(
741+
MarkerExpression::String {
742+
key: MarkerValueString::SysPlatform,
743+
operator: MarkerOperator::Equal,
744+
value: "win32".to_string(),
745+
},
746+
MarkerExpression::String {
747+
key: MarkerValueString::PlatformSystem,
748+
operator: MarkerOperator::Equal,
749+
value: "Linux".to_string(),
750+
},
751+
),
752+
// sys_platform == 'linux' and platform_system == 'Darwin'
753+
(
754+
MarkerExpression::String {
755+
key: MarkerValueString::SysPlatform,
756+
operator: MarkerOperator::Equal,
757+
value: "linux".to_string(),
758+
},
759+
MarkerExpression::String {
760+
key: MarkerValueString::PlatformSystem,
761+
operator: MarkerOperator::Equal,
762+
value: "Darwin".to_string(),
763+
},
764+
),
765+
// sys_platform == 'linux' and platform_system == 'Windows'
766+
(
767+
MarkerExpression::String {
768+
key: MarkerValueString::SysPlatform,
769+
operator: MarkerOperator::Equal,
770+
value: "linux".to_string(),
771+
},
772+
MarkerExpression::String {
773+
key: MarkerValueString::PlatformSystem,
774+
operator: MarkerOperator::Equal,
775+
value: "Windows".to_string(),
776+
},
777+
),
778+
// os_name == 'nt' and sys_platform == 'darwin'
779+
(
780+
MarkerExpression::String {
781+
key: MarkerValueString::OsName,
782+
operator: MarkerOperator::Equal,
783+
value: "nt".to_string(),
784+
},
785+
MarkerExpression::String {
786+
key: MarkerValueString::SysPlatform,
787+
operator: MarkerOperator::Equal,
788+
value: "darwin".to_string(),
789+
},
790+
),
791+
// os_name == 'nt' and sys_platform == 'linux'
792+
(
793+
MarkerExpression::String {
794+
key: MarkerValueString::OsName,
795+
operator: MarkerOperator::Equal,
796+
value: "nt".to_string(),
797+
},
798+
MarkerExpression::String {
799+
key: MarkerValueString::SysPlatform,
800+
operator: MarkerOperator::Equal,
801+
value: "linux".to_string(),
802+
},
803+
),
804+
// os_name == 'posix' and sys_platform == 'win32'
805+
(
806+
MarkerExpression::String {
807+
key: MarkerValueString::OsName,
808+
operator: MarkerOperator::Equal,
809+
value: "posix".to_string(),
810+
},
811+
MarkerExpression::String {
812+
key: MarkerValueString::SysPlatform,
813+
operator: MarkerOperator::Equal,
814+
value: "win32".to_string(),
815+
},
816+
),
817+
// os_name == 'nt' and platform_system == 'Darwin'
818+
(
819+
MarkerExpression::String {
820+
key: MarkerValueString::OsName,
821+
operator: MarkerOperator::Equal,
822+
value: "nt".to_string(),
823+
},
824+
MarkerExpression::String {
825+
key: MarkerValueString::PlatformSystem,
826+
operator: MarkerOperator::Equal,
827+
value: "Darwin".to_string(),
828+
},
829+
),
830+
// os_name == 'nt' and platform_system == 'Linux'
831+
(
832+
MarkerExpression::String {
833+
key: MarkerValueString::OsName,
834+
operator: MarkerOperator::Equal,
835+
value: "nt".to_string(),
836+
},
837+
MarkerExpression::String {
838+
key: MarkerValueString::PlatformSystem,
839+
operator: MarkerOperator::Equal,
840+
value: "Linux".to_string(),
841+
},
842+
),
843+
// os_name == 'posix' and platform_system == 'Windows'
844+
(
845+
MarkerExpression::String {
846+
key: MarkerValueString::OsName,
847+
operator: MarkerOperator::Equal,
848+
value: "posix".to_string(),
849+
},
850+
MarkerExpression::String {
851+
key: MarkerValueString::PlatformSystem,
852+
operator: MarkerOperator::Equal,
853+
value: "Windows".to_string(),
854+
},
855+
),
856+
] {
857+
let mut a = MarkerTree::expression(a);
858+
let b = MarkerTree::expression(b);
859+
a.and(b);
860+
tree.or(a);
861+
}
862+
tree.negate()
863+
});
864+
865+
self.is_disjoint(&MUTUAL_EXCLUSIONS)
866+
}
867+
688868
/// Returns a new marker tree that is the negation of this one.
689869
#[must_use]
690870
pub fn negate(&self) -> MarkerTree {

crates/uv-resolver/src/resolver/environment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ impl<'d> Forker<'d> {
494494
let mut envs = vec![];
495495
{
496496
let not_marker = self.marker.negate();
497-
if !env_marker.is_disjoint(&not_marker) {
497+
if !env_marker.is_disjoint(&not_marker) && !env_marker.is_conflicting() {
498498
envs.push(env.narrow_environment(not_marker));
499499
}
500500
}

crates/uv/tests/it/lock.rs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18829,7 +18829,7 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
1882918829
----- stdout -----
1883018830

1883118831
----- stderr -----
18832-
Resolved 3 packages in [TIME]
18832+
Resolved 2 packages in [TIME]
1883318833
"###);
1883418834

1883518835
let lock = context.read("uv.lock");
@@ -18841,11 +18841,6 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
1884118841
lock, @r###"
1884218842
version = 1
1884318843
requires-python = ">=3.12"
18844-
resolution-markers = [
18845-
"platform_system == 'Windows' and sys_platform == 'darwin'",
18846-
"platform_system == 'Windows' and sys_platform != 'darwin'",
18847-
"platform_system != 'Windows'",
18848-
]
1884918844

1885018845
[options]
1885118846
exclude-newer = "2024-03-25T00:00:00Z"
@@ -18854,39 +18849,21 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
1885418849
name = "iniconfig"
1885518850
version = "2.0.0"
1885618851
source = { registry = "https://pypi.org/simple" }
18857-
resolution-markers = [
18858-
"platform_system == 'Windows' and sys_platform != 'darwin'",
18859-
]
1886018852
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
1886118853
wheels = [
1886218854
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
1886318855
]
1886418856

18865-
[[package]]
18866-
name = "iniconfig"
18867-
version = "2.0.0"
18868-
source = { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }
18869-
resolution-markers = [
18870-
"platform_system == 'Windows' and sys_platform == 'darwin'",
18871-
]
18872-
wheels = [
18873-
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" },
18874-
]
18875-
1887618857
[[package]]
1887718858
name = "project"
1887818859
version = "0.1.0"
1887918860
source = { virtual = "." }
1888018861
dependencies = [
18881-
{ name = "iniconfig", version = "2.0.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_system == 'Windows' and sys_platform != 'darwin'" },
18882-
{ name = "iniconfig", version = "2.0.0", source = { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" }, marker = "platform_system == 'Windows' and sys_platform == 'darwin'" },
18862+
{ name = "iniconfig", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" },
1888318863
]
1888418864

1888518865
[package.metadata]
18886-
requires-dist = [
18887-
{ name = "iniconfig", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" },
18888-
{ name = "iniconfig", marker = "platform_system == 'Windows' and sys_platform == 'darwin'", url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl" },
18889-
]
18866+
requires-dist = [{ name = "iniconfig", marker = "platform_system == 'Windows' and sys_platform != 'darwin'" }]
1889018867
"###
1889118868
);
1889218869
});
@@ -18898,7 +18875,7 @@ fn lock_multiple_sources_respect_marker() -> Result<()> {
1889818875
----- stdout -----
1889918876

1890018877
----- stderr -----
18901-
Resolved 3 packages in [TIME]
18878+
Resolved 2 packages in [TIME]
1890218879
"###);
1890318880

1890418881
Ok(())

0 commit comments

Comments
 (0)