Poetry, pdm (https://pdm-project.org/latest/usage/lock-targets/) and uv (https://docs.astral.sh/uv/reference/resolver-internals/) perform a universal resolution that works on all platforms. This is in contrast to pip, which only resolves for the current platform.
pip evaluates the markers on the requirements and ignores all that don't match the current platform. Universal resolution requires that for each (supported) possible set of markers, we can determine the subset of packages from the lockfile to install. Python packages or dependency trees can has incompatible requirements, as long they are for different markers. There's a more detailed explanation in https://docs.astral.sh/uv/reference/resolver-internals/, the short version is that we have to split the resolution into two if there are conflicting markers.
[project]
name = "myproject"
requires-python = ">=3.9"
dependencies = [
"numpy<1.25; python_version < '3.10'",
"numpy>=1.25; python_version >= '3.10'",
"pandas"
]
[project]
name = "myproject"
requires-python = ">=3.9"
dependencies = [
"numpy<1.25; sys_platform == 'linux'",
"numpy>=1.25; sys_platform == 'darwin'",
"pandas"
]
In the first case above, we do one resolution for python_version < '3.10' and one for python_version >= '3.10', resolving different numpy versions. In the second case, we perform one resolution for linux, one for darwin and one for not(linux or darwin). (You can run both of these examples with uv. The real forks are more complex, but the lockfile still shows the different version and their disjoint platform markers). In each of these so-called forks, there is only one version of a package. Forks don't overlap and all forks together are a universal resolution. We ensure those two properties so that at install time, we only need to determine the current environment and get a consistent set of packages to install.
Now consider the following requirements specification:
Requires-Dist: nvidia-cublas==12.6.4.1; platform_system == "Linux" and platform_machine == "x86_64" and "nvidia :: ctk :: 12.6" in variant_properties
Requires-Dist: nvidia-cublas==12.8.4.1; platform_system == "Linux" and platform_machine == "x86_64" and "nvidia :: ctk :: 12.8" in variant_properties
Requires-Dist: nvidia-cublas==12.9.1.4; platform_system == "Linux" and platform_machine == "x86_64" and "nvidia :: ctk :: 12.9" in variant_properties
If we try to perform a universal resolution, we run into a conflict between nvidia-cublas==12.6.4.1 and nvidia-cublas==12.8.4.1, since "nvidia :: ctk :: 12.6" in variant_properties and "nvidia :: ctk :: 12.8" in variant_properties is a reasonable environment in the eyes of a resolver. With our knowledge about the nvidia provider, we know that those markers are disjoint and there is no conflict.
There are different general strategies we could apply: One is that a resolver needs to allow overlapping forks and ensure at install time, that there is no overlap. The other is that a provider needs to communicate that markers are disjoint, e.g. it could state in variants.json that nvidia :: ctk always only has a single value. The third would be that neither of them changes and we push the problem down to users, so that users have to avoid conflicting versions or write something like:
Requires-Dist: nvidia-cublas==12.6.4.1; "nvidia :: ctk :: 12.6" in variant_properties and "nvidia :: ctk :: 12.8" not in variant_properties and "nvidia :: ctk :: 12.9" not in variant_properties
Requires-Dist: nvidia-cublas==12.8.4.1; "nvidia :: ctk :: 12.6" not in variant_properties and "nvidia :: ctk :: 12.8" in variant_properties and "nvidia :: ctk :: 12.9" not in variant_properties
Requires-Dist: nvidia-cublas==12.9.1.4; "nvidia :: ctk :: 12.6" not in variant_properties and "nvidia :: ctk :: 12.8" not in variant_properties and "nvidia :: ctk :: 12.9" in variant_properties
A fourth, rather narrow optiona is to add more syntax:
Requires-Dist: nvidia-cublas==12.6.4.1; ["nvidia :: ctk :: 12.6"] == variant_properties
Requires-Dist: nvidia-cublas==12.8.4.1; ["nvidia :: ctk :: 12.8"] == variant_properties
Requires-Dist: nvidia-cublas==12.9.1.4; ["nvidia :: ctk :: 12.9"] == variant_properties
Poetry, pdm (https://pdm-project.org/latest/usage/lock-targets/) and uv (https://docs.astral.sh/uv/reference/resolver-internals/) perform a universal resolution that works on all platforms. This is in contrast to pip, which only resolves for the current platform.
pip evaluates the markers on the requirements and ignores all that don't match the current platform. Universal resolution requires that for each (supported) possible set of markers, we can determine the subset of packages from the lockfile to install. Python packages or dependency trees can has incompatible requirements, as long they are for different markers. There's a more detailed explanation in https://docs.astral.sh/uv/reference/resolver-internals/, the short version is that we have to split the resolution into two if there are conflicting markers.
In the first case above, we do one resolution for
python_version < '3.10'and one forpython_version >= '3.10', resolving different numpy versions. In the second case, we perform one resolution for linux, one for darwin and one fornot(linux or darwin). (You can run both of these examples with uv. The real forks are more complex, but the lockfile still shows the different version and their disjoint platform markers). In each of these so-called forks, there is only one version of a package. Forks don't overlap and all forks together are a universal resolution. We ensure those two properties so that at install time, we only need to determine the current environment and get a consistent set of packages to install.Now consider the following requirements specification:
If we try to perform a universal resolution, we run into a conflict between
nvidia-cublas==12.6.4.1andnvidia-cublas==12.8.4.1, since"nvidia :: ctk :: 12.6" in variant_properties and "nvidia :: ctk :: 12.8" in variant_propertiesis a reasonable environment in the eyes of a resolver. With our knowledge about the nvidia provider, we know that those markers are disjoint and there is no conflict.There are different general strategies we could apply: One is that a resolver needs to allow overlapping forks and ensure at install time, that there is no overlap. The other is that a provider needs to communicate that markers are disjoint, e.g. it could state in variants.json that
nvidia :: ctkalways only has a single value. The third would be that neither of them changes and we push the problem down to users, so that users have to avoid conflicting versions or write something like:A fourth, rather narrow optiona is to add more syntax: