refactor(pypi): split out code for env marker evaluation for reuse (#2068)
This is just a small PR to reduce the scope of #2059.
This just moves some code from one python file to a separate one.
Work towards #260, #1105, #1868.
diff --git a/python/private/pypi/whl_installer/BUILD.bazel b/python/private/pypi/whl_installer/BUILD.bazel
index fc9c0e6..5bce1a5 100644
--- a/python/private/pypi/whl_installer/BUILD.bazel
+++ b/python/private/pypi/whl_installer/BUILD.bazel
@@ -5,6 +5,7 @@
srcs = [
"arguments.py",
"namespace_pkgs.py",
+ "platform.py",
"wheel.py",
"wheel_installer.py",
],
diff --git a/python/private/pypi/whl_installer/arguments.py b/python/private/pypi/whl_installer/arguments.py
index 173d3a3..29bea80 100644
--- a/python/private/pypi/whl_installer/arguments.py
+++ b/python/private/pypi/whl_installer/arguments.py
@@ -17,7 +17,7 @@
import pathlib
from typing import Any, Dict, Set
-from python.private.pypi.whl_installer import wheel
+from python.private.pypi.whl_installer.platform import Platform
def parser(**kwargs: Any) -> argparse.ArgumentParser:
@@ -44,7 +44,7 @@
parser.add_argument(
"--platform",
action="extend",
- type=wheel.Platform.from_string,
+ type=Platform.from_string,
help="Platforms to target dependencies. Can be used multiple times.",
)
parser.add_argument(
diff --git a/python/private/pypi/whl_installer/platform.py b/python/private/pypi/whl_installer/platform.py
new file mode 100644
index 0000000..83e42b0
--- /dev/null
+++ b/python/private/pypi/whl_installer/platform.py
@@ -0,0 +1,302 @@
+# Copyright 2024 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utility class to inspect an extracted wheel directory"""
+
+import platform
+import sys
+from dataclasses import dataclass
+from enum import Enum
+from typing import Any, Dict, Iterator, List, Optional, Union
+
+
+class OS(Enum):
+ linux = 1
+ osx = 2
+ windows = 3
+ darwin = osx
+ win32 = windows
+
+ @classmethod
+ def interpreter(cls) -> "OS":
+ "Return the interpreter operating system."
+ return cls[sys.platform.lower()]
+
+ def __str__(self) -> str:
+ return self.name.lower()
+
+
+class Arch(Enum):
+ x86_64 = 1
+ x86_32 = 2
+ aarch64 = 3
+ ppc = 4
+ s390x = 5
+ arm = 6
+ amd64 = x86_64
+ arm64 = aarch64
+ i386 = x86_32
+ i686 = x86_32
+ x86 = x86_32
+ ppc64le = ppc
+
+ @classmethod
+ def interpreter(cls) -> "Arch":
+ "Return the currently running interpreter architecture."
+ # FIXME @aignas 2023-12-13: Hermetic toolchain on Windows 3.11.6
+ # is returning an empty string here, so lets default to x86_64
+ return cls[platform.machine().lower() or "x86_64"]
+
+ def __str__(self) -> str:
+ return self.name.lower()
+
+
+def _as_int(value: Optional[Union[OS, Arch]]) -> int:
+ """Convert one of the enums above to an int for easier sorting algorithms.
+
+ Args:
+ value: The value of an enum or None.
+
+ Returns:
+ -1 if we get None, otherwise, the numeric value of the given enum.
+ """
+ if value is None:
+ return -1
+
+ return int(value.value)
+
+
+def host_interpreter_minor_version() -> int:
+ return sys.version_info.minor
+
+
+@dataclass(frozen=True)
+class Platform:
+ os: Optional[OS] = None
+ arch: Optional[Arch] = None
+ minor_version: Optional[int] = None
+
+ @classmethod
+ def all(
+ cls,
+ want_os: Optional[OS] = None,
+ minor_version: Optional[int] = None,
+ ) -> List["Platform"]:
+ return sorted(
+ [
+ cls(os=os, arch=arch, minor_version=minor_version)
+ for os in OS
+ for arch in Arch
+ if not want_os or want_os == os
+ ]
+ )
+
+ @classmethod
+ def host(cls) -> List["Platform"]:
+ """Use the Python interpreter to detect the platform.
+
+ We extract `os` from sys.platform and `arch` from platform.machine
+
+ Returns:
+ A list of parsed values which makes the signature the same as
+ `Platform.all` and `Platform.from_string`.
+ """
+ return [
+ Platform(
+ os=OS.interpreter(),
+ arch=Arch.interpreter(),
+ minor_version=host_interpreter_minor_version(),
+ )
+ ]
+
+ def all_specializations(self) -> Iterator["Platform"]:
+ """Return the platform itself and all its unambiguous specializations.
+
+ For more info about specializations see
+ https://bazel.build/docs/configurable-attributes
+ """
+ yield self
+ if self.arch is None:
+ for arch in Arch:
+ yield Platform(os=self.os, arch=arch, minor_version=self.minor_version)
+ if self.os is None:
+ for os in OS:
+ yield Platform(os=os, arch=self.arch, minor_version=self.minor_version)
+ if self.arch is None and self.os is None:
+ for os in OS:
+ for arch in Arch:
+ yield Platform(os=os, arch=arch, minor_version=self.minor_version)
+
+ def __lt__(self, other: Any) -> bool:
+ """Add a comparison method, so that `sorted` returns the most specialized platforms first."""
+ if not isinstance(other, Platform) or other is None:
+ raise ValueError(f"cannot compare {other} with Platform")
+
+ self_arch, self_os = _as_int(self.arch), _as_int(self.os)
+ other_arch, other_os = _as_int(other.arch), _as_int(other.os)
+
+ if self_os == other_os:
+ return self_arch < other_arch
+ else:
+ return self_os < other_os
+
+ def __str__(self) -> str:
+ if self.minor_version is None:
+ if self.os is None and self.arch is None:
+ return "//conditions:default"
+
+ if self.arch is None:
+ return f"@platforms//os:{self.os}"
+ else:
+ return f"{self.os}_{self.arch}"
+
+ if self.arch is None and self.os is None:
+ return f"@//python/config_settings:is_python_3.{self.minor_version}"
+
+ if self.arch is None:
+ return f"cp3{self.minor_version}_{self.os}_anyarch"
+
+ if self.os is None:
+ return f"cp3{self.minor_version}_anyos_{self.arch}"
+
+ return f"cp3{self.minor_version}_{self.os}_{self.arch}"
+
+ @classmethod
+ def from_string(cls, platform: Union[str, List[str]]) -> List["Platform"]:
+ """Parse a string and return a list of platforms"""
+ platform = [platform] if isinstance(platform, str) else list(platform)
+ ret = set()
+ for p in platform:
+ if p == "host":
+ ret.update(cls.host())
+ continue
+
+ abi, _, tail = p.partition("_")
+ if not abi.startswith("cp"):
+ # The first item is not an abi
+ tail = p
+ abi = ""
+ os, _, arch = tail.partition("_")
+ arch = arch or "*"
+
+ minor_version = int(abi[len("cp3") :]) if abi else None
+
+ if arch != "*":
+ ret.add(
+ cls(
+ os=OS[os] if os != "*" else None,
+ arch=Arch[arch],
+ minor_version=minor_version,
+ )
+ )
+
+ else:
+ ret.update(
+ cls.all(
+ want_os=OS[os] if os != "*" else None,
+ minor_version=minor_version,
+ )
+ )
+
+ return sorted(ret)
+
+ # NOTE @aignas 2023-12-05: below is the minimum number of accessors that are defined in
+ # https://peps.python.org/pep-0496/ to make rules_python generate dependencies.
+ #
+ # WARNING: It may not work in cases where the python implementation is different between
+ # different platforms.
+
+ # derived from OS
+ @property
+ def os_name(self) -> str:
+ if self.os == OS.linux or self.os == OS.osx:
+ return "posix"
+ elif self.os == OS.windows:
+ return "nt"
+ else:
+ return ""
+
+ @property
+ def sys_platform(self) -> str:
+ if self.os == OS.linux:
+ return "linux"
+ elif self.os == OS.osx:
+ return "darwin"
+ elif self.os == OS.windows:
+ return "win32"
+ else:
+ return ""
+
+ @property
+ def platform_system(self) -> str:
+ if self.os == OS.linux:
+ return "Linux"
+ elif self.os == OS.osx:
+ return "Darwin"
+ elif self.os == OS.windows:
+ return "Windows"
+ else:
+ return ""
+
+ # derived from OS and Arch
+ @property
+ def platform_machine(self) -> str:
+ """Guess the target 'platform_machine' marker.
+
+ NOTE @aignas 2023-12-05: this may not work on really new systems, like
+ Windows if they define the platform markers in a different way.
+ """
+ if self.arch == Arch.x86_64:
+ return "x86_64"
+ elif self.arch == Arch.x86_32 and self.os != OS.osx:
+ return "i386"
+ elif self.arch == Arch.x86_32:
+ return ""
+ elif self.arch == Arch.aarch64 and self.os == OS.linux:
+ return "aarch64"
+ elif self.arch == Arch.aarch64:
+ # Assuming that OSX and Windows use this one since the precedent is set here:
+ # https://github.com/cgohlke/win_arm64-wheels
+ return "arm64"
+ elif self.os != OS.linux:
+ return ""
+ elif self.arch == Arch.ppc64le:
+ return "ppc64le"
+ elif self.arch == Arch.s390x:
+ return "s390x"
+ else:
+ return ""
+
+ def env_markers(self, extra: str) -> Dict[str, str]:
+ # If it is None, use the host version
+ minor_version = self.minor_version or host_interpreter_minor_version()
+
+ return {
+ "extra": extra,
+ "os_name": self.os_name,
+ "sys_platform": self.sys_platform,
+ "platform_machine": self.platform_machine,
+ "platform_system": self.platform_system,
+ "platform_release": "", # unset
+ "platform_version": "", # unset
+ "python_version": f"3.{minor_version}",
+ # FIXME @aignas 2024-01-14: is putting zero last a good idea? Maybe we should
+ # use `20` or something else to avoid having weird issues where the full version is used for
+ # matching and the author decides to only support 3.y.5 upwards.
+ "implementation_version": f"3.{minor_version}.0",
+ "python_full_version": f"3.{minor_version}.0",
+ # we assume that the following are the same as the interpreter used to setup the deps:
+ # "implementation_name": "cpython"
+ # "platform_python_implementation: "CPython",
+ }
diff --git a/python/private/pypi/whl_installer/wheel.py b/python/private/pypi/whl_installer/wheel.py
index c167df9..0f6bd27 100644
--- a/python/private/pypi/whl_installer/wheel.py
+++ b/python/private/pypi/whl_installer/wheel.py
@@ -15,299 +15,20 @@
"""Utility class to inspect an extracted wheel directory"""
import email
-import platform
import re
-import sys
from collections import defaultdict
from dataclasses import dataclass
-from enum import Enum
from pathlib import Path
-from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
+from typing import Dict, List, Optional, Set, Tuple
import installer
from packaging.requirements import Requirement
from pip._vendor.packaging.utils import canonicalize_name
-
-class OS(Enum):
- linux = 1
- osx = 2
- windows = 3
- darwin = osx
- win32 = windows
-
- @classmethod
- def interpreter(cls) -> "OS":
- "Return the interpreter operating system."
- return cls[sys.platform.lower()]
-
- def __str__(self) -> str:
- return self.name.lower()
-
-
-class Arch(Enum):
- x86_64 = 1
- x86_32 = 2
- aarch64 = 3
- ppc = 4
- s390x = 5
- arm = 6
- amd64 = x86_64
- arm64 = aarch64
- i386 = x86_32
- i686 = x86_32
- x86 = x86_32
- ppc64le = ppc
-
- @classmethod
- def interpreter(cls) -> "Arch":
- "Return the currently running interpreter architecture."
- # FIXME @aignas 2023-12-13: Hermetic toolchain on Windows 3.11.6
- # is returning an empty string here, so lets default to x86_64
- return cls[platform.machine().lower() or "x86_64"]
-
- def __str__(self) -> str:
- return self.name.lower()
-
-
-def _as_int(value: Optional[Union[OS, Arch]]) -> int:
- """Convert one of the enums above to an int for easier sorting algorithms.
-
- Args:
- value: The value of an enum or None.
-
- Returns:
- -1 if we get None, otherwise, the numeric value of the given enum.
- """
- if value is None:
- return -1
-
- return int(value.value)
-
-
-def host_interpreter_minor_version() -> int:
- return sys.version_info.minor
-
-
-@dataclass(frozen=True)
-class Platform:
- os: Optional[OS] = None
- arch: Optional[Arch] = None
- minor_version: Optional[int] = None
-
- @classmethod
- def all(
- cls,
- want_os: Optional[OS] = None,
- minor_version: Optional[int] = None,
- ) -> List["Platform"]:
- return sorted(
- [
- cls(os=os, arch=arch, minor_version=minor_version)
- for os in OS
- for arch in Arch
- if not want_os or want_os == os
- ]
- )
-
- @classmethod
- def host(cls) -> List["Platform"]:
- """Use the Python interpreter to detect the platform.
-
- We extract `os` from sys.platform and `arch` from platform.machine
-
- Returns:
- A list of parsed values which makes the signature the same as
- `Platform.all` and `Platform.from_string`.
- """
- return [
- Platform(
- os=OS.interpreter(),
- arch=Arch.interpreter(),
- minor_version=host_interpreter_minor_version(),
- )
- ]
-
- def all_specializations(self) -> Iterator["Platform"]:
- """Return the platform itself and all its unambiguous specializations.
-
- For more info about specializations see
- https://bazel.build/docs/configurable-attributes
- """
- yield self
- if self.arch is None:
- for arch in Arch:
- yield Platform(os=self.os, arch=arch, minor_version=self.minor_version)
- if self.os is None:
- for os in OS:
- yield Platform(os=os, arch=self.arch, minor_version=self.minor_version)
- if self.arch is None and self.os is None:
- for os in OS:
- for arch in Arch:
- yield Platform(os=os, arch=arch, minor_version=self.minor_version)
-
- def __lt__(self, other: Any) -> bool:
- """Add a comparison method, so that `sorted` returns the most specialized platforms first."""
- if not isinstance(other, Platform) or other is None:
- raise ValueError(f"cannot compare {other} with Platform")
-
- self_arch, self_os = _as_int(self.arch), _as_int(self.os)
- other_arch, other_os = _as_int(other.arch), _as_int(other.os)
-
- if self_os == other_os:
- return self_arch < other_arch
- else:
- return self_os < other_os
-
- def __str__(self) -> str:
- if self.minor_version is None:
- if self.os is None and self.arch is None:
- return "//conditions:default"
-
- if self.arch is None:
- return f"@platforms//os:{self.os}"
- else:
- return f"{self.os}_{self.arch}"
-
- if self.arch is None and self.os is None:
- return f"@//python/config_settings:is_python_3.{self.minor_version}"
-
- if self.arch is None:
- return f"cp3{self.minor_version}_{self.os}_anyarch"
-
- if self.os is None:
- return f"cp3{self.minor_version}_anyos_{self.arch}"
-
- return f"cp3{self.minor_version}_{self.os}_{self.arch}"
-
- @classmethod
- def from_string(cls, platform: Union[str, List[str]]) -> List["Platform"]:
- """Parse a string and return a list of platforms"""
- platform = [platform] if isinstance(platform, str) else list(platform)
- ret = set()
- for p in platform:
- if p == "host":
- ret.update(cls.host())
- continue
-
- abi, _, tail = p.partition("_")
- if not abi.startswith("cp"):
- # The first item is not an abi
- tail = p
- abi = ""
- os, _, arch = tail.partition("_")
- arch = arch or "*"
-
- minor_version = int(abi[len("cp3") :]) if abi else None
-
- if arch != "*":
- ret.add(
- cls(
- os=OS[os] if os != "*" else None,
- arch=Arch[arch],
- minor_version=minor_version,
- )
- )
-
- else:
- ret.update(
- cls.all(
- want_os=OS[os] if os != "*" else None,
- minor_version=minor_version,
- )
- )
-
- return sorted(ret)
-
- # NOTE @aignas 2023-12-05: below is the minimum number of accessors that are defined in
- # https://peps.python.org/pep-0496/ to make rules_python generate dependencies.
- #
- # WARNING: It may not work in cases where the python implementation is different between
- # different platforms.
-
- # derived from OS
- @property
- def os_name(self) -> str:
- if self.os == OS.linux or self.os == OS.osx:
- return "posix"
- elif self.os == OS.windows:
- return "nt"
- else:
- return ""
-
- @property
- def sys_platform(self) -> str:
- if self.os == OS.linux:
- return "linux"
- elif self.os == OS.osx:
- return "darwin"
- elif self.os == OS.windows:
- return "win32"
- else:
- return ""
-
- @property
- def platform_system(self) -> str:
- if self.os == OS.linux:
- return "Linux"
- elif self.os == OS.osx:
- return "Darwin"
- elif self.os == OS.windows:
- return "Windows"
- else:
- return ""
-
- # derived from OS and Arch
- @property
- def platform_machine(self) -> str:
- """Guess the target 'platform_machine' marker.
-
- NOTE @aignas 2023-12-05: this may not work on really new systems, like
- Windows if they define the platform markers in a different way.
- """
- if self.arch == Arch.x86_64:
- return "x86_64"
- elif self.arch == Arch.x86_32 and self.os != OS.osx:
- return "i386"
- elif self.arch == Arch.x86_32:
- return ""
- elif self.arch == Arch.aarch64 and self.os == OS.linux:
- return "aarch64"
- elif self.arch == Arch.aarch64:
- # Assuming that OSX and Windows use this one since the precedent is set here:
- # https://github.com/cgohlke/win_arm64-wheels
- return "arm64"
- elif self.os != OS.linux:
- return ""
- elif self.arch == Arch.ppc64le:
- return "ppc64le"
- elif self.arch == Arch.s390x:
- return "s390x"
- else:
- return ""
-
- def env_markers(self, extra: str) -> Dict[str, str]:
- # If it is None, use the host version
- minor_version = self.minor_version or host_interpreter_minor_version()
-
- return {
- "extra": extra,
- "os_name": self.os_name,
- "sys_platform": self.sys_platform,
- "platform_machine": self.platform_machine,
- "platform_system": self.platform_system,
- "platform_release": "", # unset
- "platform_version": "", # unset
- "python_version": f"3.{minor_version}",
- # FIXME @aignas 2024-01-14: is putting zero last a good idea? Maybe we should
- # use `20` or something else to avoid having weird issues where the full version is used for
- # matching and the author decides to only support 3.y.5 upwards.
- "implementation_version": f"3.{minor_version}.0",
- "python_full_version": f"3.{minor_version}.0",
- # we assume that the following are the same as the interpreter used to setup the deps:
- # "implementation_name": "cpython"
- # "platform_python_implementation: "CPython",
- }
+from python.private.pypi.whl_installer.platform import (
+ Platform,
+ host_interpreter_minor_version,
+)
@dataclass(frozen=True)
diff --git a/tests/pypi/whl_installer/BUILD.bazel b/tests/pypi/whl_installer/BUILD.bazel
index 048a877..e25c4a0 100644
--- a/tests/pypi/whl_installer/BUILD.bazel
+++ b/tests/pypi/whl_installer/BUILD.bazel
@@ -28,6 +28,18 @@
)
py_test(
+ name = "platform_test",
+ size = "small",
+ srcs = [
+ "platform_test.py",
+ ],
+ data = ["//examples/wheel:minimal_with_py_package"],
+ deps = [
+ ":lib",
+ ],
+)
+
+py_test(
name = "wheel_installer_test",
size = "small",
srcs = [
diff --git a/tests/pypi/whl_installer/platform_test.py b/tests/pypi/whl_installer/platform_test.py
new file mode 100644
index 0000000..7ced1e9
--- /dev/null
+++ b/tests/pypi/whl_installer/platform_test.py
@@ -0,0 +1,152 @@
+import unittest
+from random import shuffle
+
+from python.private.pypi.whl_installer.platform import (
+ OS,
+ Arch,
+ Platform,
+ host_interpreter_minor_version,
+)
+
+
+class MinorVersionTest(unittest.TestCase):
+ def test_host(self):
+ host = host_interpreter_minor_version()
+ self.assertIsNotNone(host)
+
+
+class PlatformTest(unittest.TestCase):
+ def test_can_get_host(self):
+ host = Platform.host()
+ self.assertIsNotNone(host)
+ self.assertEqual(1, len(Platform.from_string("host")))
+ self.assertEqual(host, Platform.from_string("host"))
+
+ def test_can_get_linux_x86_64_without_py_version(self):
+ got = Platform.from_string("linux_x86_64")
+ want = Platform(os=OS.linux, arch=Arch.x86_64)
+ self.assertEqual(want, got[0])
+
+ def test_can_get_specific_from_string(self):
+ got = Platform.from_string("cp33_linux_x86_64")
+ want = Platform(os=OS.linux, arch=Arch.x86_64, minor_version=3)
+ self.assertEqual(want, got[0])
+
+ def test_can_get_all_for_py_version(self):
+ cp39 = Platform.all(minor_version=9)
+ self.assertEqual(18, len(cp39), f"Got {cp39}")
+ self.assertEqual(cp39, Platform.from_string("cp39_*"))
+
+ def test_can_get_all_for_os(self):
+ linuxes = Platform.all(OS.linux, minor_version=9)
+ self.assertEqual(6, len(linuxes))
+ self.assertEqual(linuxes, Platform.from_string("cp39_linux_*"))
+
+ def test_can_get_all_for_os_for_host_python(self):
+ linuxes = Platform.all(OS.linux)
+ self.assertEqual(6, len(linuxes))
+ self.assertEqual(linuxes, Platform.from_string("linux_*"))
+
+ def test_specific_version_specializations(self):
+ any_py33 = Platform(minor_version=3)
+
+ # When
+ all_specializations = list(any_py33.all_specializations())
+
+ want = (
+ [any_py33]
+ + [
+ Platform(arch=arch, minor_version=any_py33.minor_version)
+ for arch in Arch
+ ]
+ + [Platform(os=os, minor_version=any_py33.minor_version) for os in OS]
+ + Platform.all(minor_version=any_py33.minor_version)
+ )
+ self.assertEqual(want, all_specializations)
+
+ def test_aarch64_specializations(self):
+ any_aarch64 = Platform(arch=Arch.aarch64)
+ all_specializations = list(any_aarch64.all_specializations())
+ want = [
+ Platform(os=None, arch=Arch.aarch64),
+ Platform(os=OS.linux, arch=Arch.aarch64),
+ Platform(os=OS.osx, arch=Arch.aarch64),
+ Platform(os=OS.windows, arch=Arch.aarch64),
+ ]
+ self.assertEqual(want, all_specializations)
+
+ def test_linux_specializations(self):
+ any_linux = Platform(os=OS.linux)
+ all_specializations = list(any_linux.all_specializations())
+ want = [
+ Platform(os=OS.linux, arch=None),
+ Platform(os=OS.linux, arch=Arch.x86_64),
+ Platform(os=OS.linux, arch=Arch.x86_32),
+ Platform(os=OS.linux, arch=Arch.aarch64),
+ Platform(os=OS.linux, arch=Arch.ppc),
+ Platform(os=OS.linux, arch=Arch.s390x),
+ Platform(os=OS.linux, arch=Arch.arm),
+ ]
+ self.assertEqual(want, all_specializations)
+
+ def test_osx_specializations(self):
+ any_osx = Platform(os=OS.osx)
+ all_specializations = list(any_osx.all_specializations())
+ # NOTE @aignas 2024-01-14: even though in practice we would only have
+ # Python on osx aarch64 and osx x86_64, we return all arch posibilities
+ # to make the code simpler.
+ want = [
+ Platform(os=OS.osx, arch=None),
+ Platform(os=OS.osx, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.x86_32),
+ Platform(os=OS.osx, arch=Arch.aarch64),
+ Platform(os=OS.osx, arch=Arch.ppc),
+ Platform(os=OS.osx, arch=Arch.s390x),
+ Platform(os=OS.osx, arch=Arch.arm),
+ ]
+ self.assertEqual(want, all_specializations)
+
+ def test_platform_sort(self):
+ platforms = [
+ Platform(os=OS.linux, arch=None),
+ Platform(os=OS.linux, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=None),
+ Platform(os=OS.osx, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.aarch64),
+ ]
+ shuffle(platforms)
+ platforms.sort()
+ want = [
+ Platform(os=OS.linux, arch=None),
+ Platform(os=OS.linux, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=None),
+ Platform(os=OS.osx, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.aarch64),
+ ]
+
+ self.assertEqual(want, platforms)
+
+ def test_wheel_os_alias(self):
+ self.assertEqual("osx", str(OS.osx))
+ self.assertEqual(str(OS.darwin), str(OS.osx))
+
+ def test_wheel_arch_alias(self):
+ self.assertEqual("x86_64", str(Arch.x86_64))
+ self.assertEqual(str(Arch.amd64), str(Arch.x86_64))
+
+ def test_wheel_platform_alias(self):
+ give = Platform(
+ os=OS.darwin,
+ arch=Arch.amd64,
+ )
+ alias = Platform(
+ os=OS.osx,
+ arch=Arch.x86_64,
+ )
+
+ self.assertEqual("osx_x86_64", str(give))
+ self.assertEqual(str(alias), str(give))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pypi/whl_installer/wheel_test.py b/tests/pypi/whl_installer/wheel_test.py
index 76bfe72..404218e 100644
--- a/tests/pypi/whl_installer/wheel_test.py
+++ b/tests/pypi/whl_installer/wheel_test.py
@@ -1,8 +1,12 @@
import unittest
-from random import shuffle
from unittest import mock
from python.private.pypi.whl_installer import wheel
+from python.private.pypi.whl_installer.platform import OS, Arch, Platform
+
+_HOST_INTERPRETER_FN = (
+ "python.private.pypi.whl_installer.wheel.host_interpreter_minor_version"
+)
class DepsTest(unittest.TestCase):
@@ -25,10 +29,10 @@
"win_dep; os_name=='nt'",
],
platforms={
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
- wheel.Platform(os=wheel.OS.windows, arch=wheel.Arch.x86_64),
+ Platform(os=OS.linux, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.aarch64),
+ Platform(os=OS.windows, arch=Arch.x86_64),
},
)
@@ -54,18 +58,10 @@
"win_dep; os_name=='nt'",
],
platforms={
- wheel.Platform(
- os=wheel.OS.linux, arch=wheel.Arch.x86_64, minor_version=8
- ),
- wheel.Platform(
- os=wheel.OS.osx, arch=wheel.Arch.x86_64, minor_version=8
- ),
- wheel.Platform(
- os=wheel.OS.osx, arch=wheel.Arch.aarch64, minor_version=8
- ),
- wheel.Platform(
- os=wheel.OS.windows, arch=wheel.Arch.x86_64, minor_version=8
- ),
+ Platform(os=OS.linux, arch=Arch.x86_64, minor_version=8),
+ Platform(os=OS.osx, arch=Arch.x86_64, minor_version=8),
+ Platform(os=OS.osx, arch=Arch.aarch64, minor_version=8),
+ Platform(os=OS.windows, arch=Arch.x86_64, minor_version=8),
},
)
@@ -89,8 +85,8 @@
"mac_dep; sys_platform=='darwin'",
],
platforms={
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
+ Platform(os=OS.osx, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.aarch64),
},
).build()
@@ -113,8 +109,8 @@
"m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
],
platforms={
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
+ Platform(os=OS.osx, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.aarch64),
},
).build()
@@ -136,10 +132,10 @@
"m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
],
platforms={
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
- wheel.Platform(os=wheel.OS.windows, arch=wheel.Arch.x86_64),
+ Platform(os=OS.linux, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.x86_64),
+ Platform(os=OS.osx, arch=Arch.aarch64),
+ Platform(os=OS.windows, arch=Arch.x86_64),
},
).build()
@@ -197,18 +193,14 @@
"foo",
requires_dist=requires_dist,
platforms=[
- wheel.Platform(
- os=wheel.OS.linux, arch=wheel.Arch.x86_64, minor_version=8
- ),
+ Platform(os=OS.linux, arch=Arch.x86_64, minor_version=8),
],
).build()
py37_deps = wheel.Deps(
"foo",
requires_dist=requires_dist,
platforms=[
- wheel.Platform(
- os=wheel.OS.linux, arch=wheel.Arch.x86_64, minor_version=7
- ),
+ Platform(os=OS.linux, arch=Arch.x86_64, minor_version=7),
],
).build()
@@ -217,9 +209,7 @@
self.assertEqual(["bar"], py38_deps.deps)
self.assertEqual({"@platforms//os:linux": ["posix_dep"]}, py38_deps.deps_select)
- @mock.patch(
- "python.private.pypi.whl_installer.wheel.host_interpreter_minor_version"
- )
+ @mock.patch(_HOST_INTERPRETER_FN)
def test_no_version_select_when_single_version(self, mock_host_interpreter_version):
requires_dist = [
"bar",
@@ -236,9 +226,9 @@
"foo",
requires_dist=requires_dist,
platforms=[
- wheel.Platform(os=os, arch=wheel.Arch.x86_64, minor_version=minor)
+ Platform(os=os, arch=Arch.x86_64, minor_version=minor)
for minor in [8]
- for os in [wheel.OS.linux, wheel.OS.windows]
+ for os in [OS.linux, OS.windows]
],
)
got = deps.build()
@@ -253,9 +243,7 @@
got.deps_select,
)
- @mock.patch(
- "python.private.pypi.whl_installer.wheel.host_interpreter_minor_version"
- )
+ @mock.patch(_HOST_INTERPRETER_FN)
def test_can_get_version_select(self, mock_host_interpreter_version):
requires_dist = [
"bar",
@@ -273,9 +261,9 @@
"foo",
requires_dist=requires_dist,
platforms=[
- wheel.Platform(os=os, arch=wheel.Arch.x86_64, minor_version=minor)
+ Platform(os=os, arch=Arch.x86_64, minor_version=minor)
for minor in [7, 8, 9]
- for os in [wheel.OS.linux, wheel.OS.windows]
+ for os in [OS.linux, OS.windows]
],
)
got = deps.build()
@@ -307,9 +295,7 @@
got.deps_select,
)
- @mock.patch(
- "python.private.pypi.whl_installer.wheel.host_interpreter_minor_version"
- )
+ @mock.patch(_HOST_INTERPRETER_FN)
def test_deps_spanning_all_target_py_versions_are_added_to_common(
self, mock_host_version
):
@@ -323,16 +309,14 @@
deps = wheel.Deps(
"foo",
requires_dist=requires_dist,
- platforms=wheel.Platform.from_string(["cp37_*", "cp38_*", "cp39_*"]),
+ platforms=Platform.from_string(["cp37_*", "cp38_*", "cp39_*"]),
)
got = deps.build()
self.assertEqual(["bar", "baz"], got.deps)
self.assertEqual({}, got.deps_select)
- @mock.patch(
- "python.private.pypi.whl_installer.wheel.host_interpreter_minor_version"
- )
+ @mock.patch(_HOST_INTERPRETER_FN)
def test_deps_are_not_duplicated(self, mock_host_version):
mock_host_version.return_value = 7
@@ -352,16 +336,14 @@
deps = wheel.Deps(
"foo",
requires_dist=requires_dist,
- platforms=wheel.Platform.from_string(["cp37_*", "cp310_*"]),
+ platforms=Platform.from_string(["cp37_*", "cp310_*"]),
)
got = deps.build()
self.assertEqual(["bar"], got.deps)
self.assertEqual({}, got.deps_select)
- @mock.patch(
- "python.private.pypi.whl_installer.wheel.host_interpreter_minor_version"
- )
+ @mock.patch(_HOST_INTERPRETER_FN)
def test_deps_are_not_duplicated_when_encountering_platform_dep_first(
self, mock_host_version
):
@@ -377,7 +359,7 @@
deps = wheel.Deps(
"foo",
requires_dist=requires_dist,
- platforms=wheel.Platform.from_string(["cp37_*", "cp310_*"]),
+ platforms=Platform.from_string(["cp37_*", "cp310_*"]),
)
got = deps.build()
@@ -385,149 +367,5 @@
self.assertEqual({}, got.deps_select)
-class MinorVersionTest(unittest.TestCase):
- def test_host(self):
- host = wheel.host_interpreter_minor_version()
- self.assertIsNotNone(host)
-
-
-class PlatformTest(unittest.TestCase):
- def test_can_get_host(self):
- host = wheel.Platform.host()
- self.assertIsNotNone(host)
- self.assertEqual(1, len(wheel.Platform.from_string("host")))
- self.assertEqual(host, wheel.Platform.from_string("host"))
-
- def test_can_get_linux_x86_64_without_py_version(self):
- got = wheel.Platform.from_string("linux_x86_64")
- want = wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64)
- self.assertEqual(want, got[0])
-
- def test_can_get_specific_from_string(self):
- got = wheel.Platform.from_string("cp33_linux_x86_64")
- want = wheel.Platform(
- os=wheel.OS.linux, arch=wheel.Arch.x86_64, minor_version=3
- )
- self.assertEqual(want, got[0])
-
- def test_can_get_all_for_py_version(self):
- cp39 = wheel.Platform.all(minor_version=9)
- self.assertEqual(18, len(cp39), f"Got {cp39}")
- self.assertEqual(cp39, wheel.Platform.from_string("cp39_*"))
-
- def test_can_get_all_for_os(self):
- linuxes = wheel.Platform.all(wheel.OS.linux, minor_version=9)
- self.assertEqual(6, len(linuxes))
- self.assertEqual(linuxes, wheel.Platform.from_string("cp39_linux_*"))
-
- def test_can_get_all_for_os_for_host_python(self):
- linuxes = wheel.Platform.all(wheel.OS.linux)
- self.assertEqual(6, len(linuxes))
- self.assertEqual(linuxes, wheel.Platform.from_string("linux_*"))
-
- def test_specific_version_specializations(self):
- any_py33 = wheel.Platform(minor_version=3)
-
- # When
- all_specializations = list(any_py33.all_specializations())
-
- want = (
- [any_py33]
- + [
- wheel.Platform(arch=arch, minor_version=any_py33.minor_version)
- for arch in wheel.Arch
- ]
- + [
- wheel.Platform(os=os, minor_version=any_py33.minor_version)
- for os in wheel.OS
- ]
- + wheel.Platform.all(minor_version=any_py33.minor_version)
- )
- self.assertEqual(want, all_specializations)
-
- def test_aarch64_specializations(self):
- any_aarch64 = wheel.Platform(arch=wheel.Arch.aarch64)
- all_specializations = list(any_aarch64.all_specializations())
- want = [
- wheel.Platform(os=None, arch=wheel.Arch.aarch64),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.aarch64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
- wheel.Platform(os=wheel.OS.windows, arch=wheel.Arch.aarch64),
- ]
- self.assertEqual(want, all_specializations)
-
- def test_linux_specializations(self):
- any_linux = wheel.Platform(os=wheel.OS.linux)
- all_specializations = list(any_linux.all_specializations())
- want = [
- wheel.Platform(os=wheel.OS.linux, arch=None),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_32),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.aarch64),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.ppc),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.s390x),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.arm),
- ]
- self.assertEqual(want, all_specializations)
-
- def test_osx_specializations(self):
- any_osx = wheel.Platform(os=wheel.OS.osx)
- all_specializations = list(any_osx.all_specializations())
- # NOTE @aignas 2024-01-14: even though in practice we would only have
- # Python on osx aarch64 and osx x86_64, we return all arch posibilities
- # to make the code simpler.
- want = [
- wheel.Platform(os=wheel.OS.osx, arch=None),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_32),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.ppc),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.s390x),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.arm),
- ]
- self.assertEqual(want, all_specializations)
-
- def test_platform_sort(self):
- platforms = [
- wheel.Platform(os=wheel.OS.linux, arch=None),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=None),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
- ]
- shuffle(platforms)
- platforms.sort()
- want = [
- wheel.Platform(os=wheel.OS.linux, arch=None),
- wheel.Platform(os=wheel.OS.linux, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=None),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.x86_64),
- wheel.Platform(os=wheel.OS.osx, arch=wheel.Arch.aarch64),
- ]
-
- self.assertEqual(want, platforms)
-
- def test_wheel_os_alias(self):
- self.assertEqual("osx", str(wheel.OS.osx))
- self.assertEqual(str(wheel.OS.darwin), str(wheel.OS.osx))
-
- def test_wheel_arch_alias(self):
- self.assertEqual("x86_64", str(wheel.Arch.x86_64))
- self.assertEqual(str(wheel.Arch.amd64), str(wheel.Arch.x86_64))
-
- def test_wheel_platform_alias(self):
- give = wheel.Platform(
- os=wheel.OS.darwin,
- arch=wheel.Arch.amd64,
- )
- alias = wheel.Platform(
- os=wheel.OS.osx,
- arch=wheel.Arch.x86_64,
- )
-
- self.assertEqual("osx_x86_64", str(give))
- self.assertEqual(str(alias), str(give))
-
-
if __name__ == "__main__":
unittest.main()