revert(pypi): bring back Python PEP508 code with tests (#2831)

This just adds the code back at the original state before the following
PRs have been made to remove them: #2629, #2781. This has not been
hooked up yet in `evaluate_markers` and `whl_library` yet and I'll need
extra PRs to do that.

No CHANGELOG entries for now, will be done once the integration is back.

Work towards #2830

(cherry picked from commit 61c91fe9bd322f91af77db2f57e5b6b40792628f)
diff --git a/python/private/pypi/requirements_parser/BUILD.bazel b/python/private/pypi/requirements_parser/BUILD.bazel
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/private/pypi/requirements_parser/BUILD.bazel
diff --git a/python/private/pypi/requirements_parser/resolve_target_platforms.py b/python/private/pypi/requirements_parser/resolve_target_platforms.py
new file mode 100755
index 0000000..c899a94
--- /dev/null
+++ b/python/private/pypi/requirements_parser/resolve_target_platforms.py
@@ -0,0 +1,63 @@
+"""A CLI to evaluate env markers for requirements files.
+
+A simple script to evaluate the `requirements.txt` files. Currently it is only
+handling environment markers in the requirements files, but in the future it
+may handle more things. We require a `python` interpreter that can run on the
+host platform and then we depend on the [packaging] PyPI wheel.
+
+In order to be able to resolve requirements files for any platform, we are
+re-using the same code that is used in the `whl_library` installer. See
+[here](../whl_installer/wheel.py).
+
+Requirements for the code are:
+- Depends only on `packaging` and core Python.
+- Produces the same result irrespective of the Python interpreter platform or version.
+
+[packaging]: https://packaging.pypa.io/en/stable/
+"""
+
+import argparse
+import json
+import pathlib
+
+from packaging.requirements import Requirement
+
+from python.private.pypi.whl_installer.platform import Platform
+
+INPUT_HELP = """\
+Input path to read the requirements as a json file, the keys in the dictionary
+are the requirements lines and the values are strings of target platforms.
+"""
+OUTPUT_HELP = """\
+Output to write the requirements as a json filepath, the keys in the dictionary
+are the requirements lines and the values are strings of target platforms, which
+got changed based on the evaluated markers.
+"""
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument("input_path", type=pathlib.Path, help=INPUT_HELP.strip())
+    parser.add_argument("output_path", type=pathlib.Path, help=OUTPUT_HELP.strip())
+    args = parser.parse_args()
+
+    with args.input_path.open() as f:
+        reqs = json.load(f)
+
+    response = {}
+    for requirement_line, target_platforms in reqs.items():
+        entry, prefix, hashes = requirement_line.partition("--hash")
+        hashes = prefix + hashes
+
+        req = Requirement(entry)
+        for p in target_platforms:
+            (platform,) = Platform.from_string(p)
+            if not req.marker or req.marker.evaluate(platform.env_markers("")):
+                response.setdefault(requirement_line, []).append(p)
+
+    with args.output_path.open("w") as f:
+        json.dump(response, f)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/python/private/pypi/whl_installer/BUILD.bazel b/python/private/pypi/whl_installer/BUILD.bazel
index 49f1a11..5fb6170 100644
--- a/python/private/pypi/whl_installer/BUILD.bazel
+++ b/python/private/pypi/whl_installer/BUILD.bazel
@@ -6,6 +6,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 bb841ea..29bea80 100644
--- a/python/private/pypi/whl_installer/arguments.py
+++ b/python/private/pypi/whl_installer/arguments.py
@@ -17,6 +17,8 @@
 import pathlib
 from typing import Any, Dict, Set
 
+from python.private.pypi.whl_installer.platform import Platform
+
 
 def parser(**kwargs: Any) -> argparse.ArgumentParser:
     """Create a parser for the wheel_installer tool."""
@@ -40,6 +42,12 @@
         help="Extra arguments to pass down to pip.",
     )
     parser.add_argument(
+        "--platform",
+        action="extend",
+        type=Platform.from_string,
+        help="Platforms to target dependencies. Can be used multiple times.",
+    )
+    parser.add_argument(
         "--pip_data_exclude",
         action="store",
         help="Additional data exclusion parameters to add to the pip packages BUILD file.",
diff --git a/python/private/pypi/whl_installer/platform.py b/python/private/pypi/whl_installer/platform.py
new file mode 100644
index 0000000..11dd6e3
--- /dev/null
+++ b/python/private/pypi/whl_installer/platform.py
@@ -0,0 +1,304 @@
+# 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
+    ppc64le = 5
+    s390x = 6
+    arm = 7
+    amd64 = x86_64
+    arm64 = aarch64
+    i386 = x86_32
+    i686 = x86_32
+    x86 = x86_32
+
+    @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.ppc:
+            return "ppc"
+        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 da81b5e..d95b33a 100644
--- a/python/private/pypi/whl_installer/wheel.py
+++ b/python/private/pypi/whl_installer/wheel.py
@@ -25,6 +25,275 @@
 from packaging.requirements import Requirement
 from pip._vendor.packaging.utils import canonicalize_name
 
+from python.private.pypi.whl_installer.platform import (
+    Platform,
+    host_interpreter_minor_version,
+)
+
+
+@dataclass(frozen=True)
+class FrozenDeps:
+    deps: List[str]
+    deps_select: Dict[str, List[str]]
+
+
+class Deps:
+    """Deps is a dependency builder that has a build() method to return FrozenDeps."""
+
+    def __init__(
+        self,
+        name: str,
+        requires_dist: List[str],
+        *,
+        extras: Optional[Set[str]] = None,
+        platforms: Optional[Set[Platform]] = None,
+    ):
+        """Create a new instance and parse the requires_dist
+
+        Args:
+            name (str): The name of the whl distribution
+            requires_dist (list[Str]): The Requires-Dist from the METADATA of the whl
+                distribution.
+            extras (set[str], optional): The list of requested extras, defaults to None.
+            platforms (set[Platform], optional): The list of target platforms, defaults to
+                None. If the list of platforms has multiple `minor_version` values, it
+                will change the code to generate the select statements using
+                `@rules_python//python/config_settings:is_python_3.y` conditions.
+        """
+        self.name: str = Deps._normalize(name)
+        self._platforms: Set[Platform] = platforms or set()
+        self._target_versions = {p.minor_version for p in platforms or {}}
+        self._default_minor_version = None
+        if platforms and len(self._target_versions) > 2:
+            # TODO @aignas 2024-06-23: enable this to be set via a CLI arg
+            # for being more explicit.
+            self._default_minor_version = host_interpreter_minor_version()
+
+        if None in self._target_versions and len(self._target_versions) > 2:
+            raise ValueError(
+                f"all python versions need to be specified explicitly, got: {platforms}"
+            )
+
+        # Sort so that the dictionary order in the FrozenDeps is deterministic
+        # without the final sort because Python retains insertion order. That way
+        # the sorting by platform is limited within the Platform class itself and
+        # the unit-tests for the Deps can be simpler.
+        reqs = sorted(
+            (Requirement(wheel_req) for wheel_req in requires_dist),
+            key=lambda x: f"{x.name}:{sorted(x.extras)}",
+        )
+
+        want_extras = self._resolve_extras(reqs, extras)
+
+        # Then add all of the requirements in order
+        self._deps: Set[str] = set()
+        self._select: Dict[Platform, Set[str]] = defaultdict(set)
+        for req in reqs:
+            self._add_req(req, want_extras)
+
+    def _add(self, dep: str, platform: Optional[Platform]):
+        dep = Deps._normalize(dep)
+
+        # Self-edges are processed in _resolve_extras
+        if dep == self.name:
+            return
+
+        if not platform:
+            self._deps.add(dep)
+
+            # If the dep is in the platform-specific list, remove it from the select.
+            pop_keys = []
+            for p, deps in self._select.items():
+                if dep not in deps:
+                    continue
+
+                deps.remove(dep)
+                if not deps:
+                    pop_keys.append(p)
+
+            for p in pop_keys:
+                self._select.pop(p)
+            return
+
+        if dep in self._deps:
+            # If the dep is already in the main dependency list, no need to add it in the
+            # platform-specific dependency list.
+            return
+
+        # Add the platform-specific dep
+        self._select[platform].add(dep)
+
+        # Add the dep to specializations of the given platform if they
+        # exist in the select statement.
+        for p in platform.all_specializations():
+            if p not in self._select:
+                continue
+
+            self._select[p].add(dep)
+
+        if len(self._select[platform]) == 1:
+            # We are adding a new item to the select and we need to ensure that
+            # existing dependencies from less specialized platforms are propagated
+            # to the newly added dependency set.
+            for p, deps in self._select.items():
+                # Check if the existing platform overlaps with the given platform
+                if p == platform or platform not in p.all_specializations():
+                    continue
+
+                self._select[platform].update(self._select[p])
+
+    def _maybe_add_common_dep(self, dep):
+        if len(self._target_versions) < 2:
+            return
+
+        platforms = [Platform()] + [
+            Platform(minor_version=v) for v in self._target_versions
+        ]
+
+        # If the dep is targeting all target python versions, lets add it to
+        # the common dependency list to simplify the select statements.
+        for p in platforms:
+            if p not in self._select:
+                return
+
+            if dep not in self._select[p]:
+                return
+
+        # All of the python version-specific branches have the dep, so lets add
+        # it to the common deps.
+        self._deps.add(dep)
+        for p in platforms:
+            self._select[p].remove(dep)
+            if not self._select[p]:
+                self._select.pop(p)
+
+    @staticmethod
+    def _normalize(name: str) -> str:
+        return re.sub(r"[-_.]+", "_", name).lower()
+
+    def _resolve_extras(
+        self, reqs: List[Requirement], extras: Optional[Set[str]]
+    ) -> Set[str]:
+        """Resolve extras which are due to depending on self[some_other_extra].
+
+        Some packages may have cyclic dependencies resulting from extras being used, one example is
+        `etils`, where we have one set of extras as aliases for other extras
+        and we have an extra called 'all' that includes all other extras.
+
+        Example: github.com/google/etils/blob/a0b71032095db14acf6b33516bca6d885fe09e35/pyproject.toml#L32.
+
+        When the `requirements.txt` is generated by `pip-tools`, then it is likely that
+        this step is not needed, but for other `requirements.txt` files this may be useful.
+
+        NOTE @aignas 2023-12-08: the extra resolution is not platform dependent,
+        but in order for it to become platform dependent we would have to have
+        separate targets for each extra in extras.
+        """
+
+        # Resolve any extra extras due to self-edges, empty string means no
+        # extras The empty string in the set is just a way to make the handling
+        # of no extras and a single extra easier and having a set of {"", "foo"}
+        # is equivalent to having {"foo"}.
+        extras = extras or {""}
+
+        self_reqs = []
+        for req in reqs:
+            if Deps._normalize(req.name) != self.name:
+                continue
+
+            if req.marker is None:
+                # I am pretty sure we cannot reach this code as it does not
+                # make sense to specify packages in this way, but since it is
+                # easy to handle, lets do it.
+                #
+                # TODO @aignas 2023-12-08: add a test
+                extras = extras | req.extras
+            else:
+                # process these in a separate loop
+                self_reqs.append(req)
+
+        # A double loop is not strictly optimal, but always correct without recursion
+        for req in self_reqs:
+            if any(req.marker.evaluate({"extra": extra}) for extra in extras):
+                extras = extras | req.extras
+            else:
+                continue
+
+            # Iterate through all packages to ensure that we include all of the extras from previously
+            # visited packages.
+            for req_ in self_reqs:
+                if any(req_.marker.evaluate({"extra": extra}) for extra in extras):
+                    extras = extras | req_.extras
+
+        return extras
+
+    def _add_req(self, req: Requirement, extras: Set[str]) -> None:
+        if req.marker is None:
+            self._add(req.name, None)
+            return
+
+        marker_str = str(req.marker)
+
+        if not self._platforms:
+            if any(req.marker.evaluate({"extra": extra}) for extra in extras):
+                self._add(req.name, None)
+            return
+
+        # NOTE @aignas 2023-12-08: in order to have reasonable select statements
+        # we do have to have some parsing of the markers, so it begs the question
+        # if packaging should be reimplemented in Starlark to have the best solution
+        # for now we will implement it in Python and see what the best parsing result
+        # can be before making this decision.
+        match_os = any(
+            tag in marker_str
+            for tag in [
+                "os_name",
+                "sys_platform",
+                "platform_system",
+            ]
+        )
+        match_arch = "platform_machine" in marker_str
+        match_version = "version" in marker_str
+
+        if not (match_os or match_arch or match_version):
+            if any(req.marker.evaluate({"extra": extra}) for extra in extras):
+                self._add(req.name, None)
+            return
+
+        for plat in self._platforms:
+            if not any(
+                req.marker.evaluate(plat.env_markers(extra)) for extra in extras
+            ):
+                continue
+
+            if match_arch and self._default_minor_version:
+                self._add(req.name, plat)
+                if plat.minor_version == self._default_minor_version:
+                    self._add(req.name, Platform(plat.os, plat.arch))
+            elif match_arch:
+                self._add(req.name, Platform(plat.os, plat.arch))
+            elif match_os and self._default_minor_version:
+                self._add(req.name, Platform(plat.os, minor_version=plat.minor_version))
+                if plat.minor_version == self._default_minor_version:
+                    self._add(req.name, Platform(plat.os))
+            elif match_os:
+                self._add(req.name, Platform(plat.os))
+            elif match_version and self._default_minor_version:
+                self._add(req.name, Platform(minor_version=plat.minor_version))
+                if plat.minor_version == self._default_minor_version:
+                    self._add(req.name, Platform())
+            elif match_version:
+                self._add(req.name, None)
+
+        # Merge to common if possible after processing all platforms
+        self._maybe_add_common_dep(req.name)
+
+    def build(self) -> FrozenDeps:
+        return FrozenDeps(
+            deps=sorted(self._deps),
+            deps_select={str(p): sorted(deps) for p, deps in self._select.items()},
+        )
+
 
 class Wheel:
     """Representation of the compressed .whl file"""
@@ -75,6 +344,18 @@
 
             return entry_points_mapping
 
+    def dependencies(
+        self,
+        extras_requested: Set[str] = None,
+        platforms: Optional[Set[Platform]] = None,
+    ) -> FrozenDeps:
+        return Deps(
+            self.name,
+            extras=extras_requested,
+            platforms=platforms,
+            requires_dist=self.metadata.get_all("Requires-Dist", []),
+        ).build()
+
     def unzip(self, directory: str) -> None:
         installation_schemes = {
             "purelib": "/site-packages",
diff --git a/python/private/pypi/whl_installer/wheel_installer.py b/python/private/pypi/whl_installer/wheel_installer.py
index c7695d9..a48df69 100644
--- a/python/private/pypi/whl_installer/wheel_installer.py
+++ b/python/private/pypi/whl_installer/wheel_installer.py
@@ -23,7 +23,7 @@
 import sys
 from pathlib import Path
 from tempfile import NamedTemporaryFile
-from typing import Dict, Optional, Set, Tuple
+from typing import Dict, List, Optional, Set, Tuple
 
 from pip._vendor.packaging.utils import canonicalize_name
 
@@ -103,7 +103,9 @@
 
 def _extract_wheel(
     wheel_file: str,
+    extras: Dict[str, Set[str]],
     enable_implicit_namespace_pkgs: bool,
+    platforms: List[wheel.Platform],
     installation_dir: Path = Path("."),
 ) -> None:
     """Extracts wheel into given directory and creates py_library and filegroup targets.
@@ -111,6 +113,7 @@
     Args:
         wheel_file: the filepath of the .whl
         installation_dir: the destination directory for installation of the wheel.
+        extras: a list of extras to add as dependencies for the installed wheel
         enable_implicit_namespace_pkgs: if true, disables conversion of implicit namespace packages and will unzip as-is
     """
 
@@ -120,19 +123,26 @@
     if not enable_implicit_namespace_pkgs:
         _setup_namespace_pkg_compatibility(installation_dir)
 
-    metadata = {
-        "python_version": sys.version.partition(" ")[0],
-        "entry_points": [
-            {
-                "name": name,
-                "module": module,
-                "attribute": attribute,
-            }
-            for name, (module, attribute) in sorted(whl.entry_points().items())
-        ],
-    }
+    extras_requested = extras[whl.name] if whl.name in extras else set()
+
+    dependencies = whl.dependencies(extras_requested, platforms)
 
     with open(os.path.join(installation_dir, "metadata.json"), "w") as f:
+        metadata = {
+            "name": whl.name,
+            "version": whl.version,
+            "deps": dependencies.deps,
+            "python_version": f"{sys.version_info[0]}.{sys.version_info[1]}.{sys.version_info[2]}",
+            "deps_by_platform": dependencies.deps_select,
+            "entry_points": [
+                {
+                    "name": name,
+                    "module": module,
+                    "attribute": attribute,
+                }
+                for name, (module, attribute) in sorted(whl.entry_points().items())
+            ],
+        }
         json.dump(metadata, f)
 
 
@@ -146,9 +156,13 @@
     if args.whl_file:
         whl = Path(args.whl_file)
 
+        name, extras_for_pkg = _parse_requirement_for_extra(args.requirement)
+        extras = {name: extras_for_pkg} if extras_for_pkg and name else dict()
         _extract_wheel(
             wheel_file=whl,
+            extras=extras,
             enable_implicit_namespace_pkgs=args.enable_implicit_namespace_pkgs,
+            platforms=arguments.get_platforms(args),
         )
         return
 
diff --git a/tests/pypi/whl_installer/BUILD.bazel b/tests/pypi/whl_installer/BUILD.bazel
index fea6a46..040e4d7 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 = [
@@ -38,3 +50,15 @@
         ":lib",
     ],
 )
+
+py_test(
+    name = "wheel_test",
+    size = "small",
+    srcs = [
+        "wheel_test.py",
+    ],
+    data = ["//examples/wheel:minimal_with_py_package"],
+    deps = [
+        ":lib",
+    ],
+)
diff --git a/tests/pypi/whl_installer/arguments_test.py b/tests/pypi/whl_installer/arguments_test.py
index 9f73ae9..5538054 100644
--- a/tests/pypi/whl_installer/arguments_test.py
+++ b/tests/pypi/whl_installer/arguments_test.py
@@ -15,7 +15,7 @@
 import json
 import unittest
 
-from python.private.pypi.whl_installer import arguments
+from python.private.pypi.whl_installer import arguments, wheel
 
 
 class ArgumentsTestCase(unittest.TestCase):
@@ -49,6 +49,18 @@
         self.assertEqual(args["environment"], {"PIP_DO_SOMETHING": "True"})
         self.assertEqual(args["extra_pip_args"], [])
 
+    def test_platform_aggregation(self) -> None:
+        parser = arguments.parser()
+        args = parser.parse_args(
+            args=[
+                "--platform=linux_*",
+                "--platform=osx_*",
+                "--platform=windows_*",
+                "--requirement=foo",
+            ]
+        )
+        self.assertEqual(set(wheel.Platform.all()), arguments.get_platforms(args))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/tests/pypi/whl_installer/platform_test.py b/tests/pypi/whl_installer/platform_test.py
new file mode 100644
index 0000000..2aeb4ca
--- /dev/null
+++ b/tests/pypi/whl_installer/platform_test.py
@@ -0,0 +1,154 @@
+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(21, 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(7, 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(7, 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.ppc64le),
+            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.ppc64le),
+            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_installer_test.py b/tests/pypi/whl_installer/wheel_installer_test.py
index 3c118af..b736877 100644
--- a/tests/pypi/whl_installer/wheel_installer_test.py
+++ b/tests/pypi/whl_installer/wheel_installer_test.py
@@ -22,6 +22,39 @@
 from python.private.pypi.whl_installer import wheel_installer
 
 
+class TestRequirementExtrasParsing(unittest.TestCase):
+    def test_parses_requirement_for_extra(self) -> None:
+        cases = [
+            ("name[foo]", ("name", frozenset(["foo"]))),
+            ("name[ Foo123 ]", ("name", frozenset(["Foo123"]))),
+            (" name1[ foo ] ", ("name1", frozenset(["foo"]))),
+            ("Name[foo]", ("name", frozenset(["foo"]))),
+            ("name_foo[bar]", ("name-foo", frozenset(["bar"]))),
+            (
+                "name [fred,bar] @ http://foo.com ; python_version=='2.7'",
+                ("name", frozenset(["fred", "bar"])),
+            ),
+            (
+                "name[quux, strange];python_version<'2.7' and platform_version=='2'",
+                ("name", frozenset(["quux", "strange"])),
+            ),
+            (
+                "name; (os_name=='a' or os_name=='b') and os_name=='c'",
+                (None, None),
+            ),
+            (
+                "name@http://foo.com",
+                (None, None),
+            ),
+        ]
+
+        for case, expected in cases:
+            with self.subTest():
+                self.assertTupleEqual(
+                    wheel_installer._parse_requirement_for_extra(case), expected
+                )
+
+
 class TestWhlFilegroup(unittest.TestCase):
     def setUp(self) -> None:
         self.wheel_name = "example_minimal_package-0.0.1-py3-none-any.whl"
@@ -35,8 +68,10 @@
     def test_wheel_exists(self) -> None:
         wheel_installer._extract_wheel(
             Path(self.wheel_path),
-            enable_implicit_namespace_pkgs=False,
             installation_dir=Path(self.wheel_dir),
+            extras={},
+            enable_implicit_namespace_pkgs=False,
+            platforms=[],
         )
 
         want_files = [
@@ -57,8 +92,12 @@
             metadata_file_content = json.load(metadata_file)
 
         want = dict(
+            deps=[],
+            deps_by_platform={},
             entry_points=[],
+            name="example-minimal-package",
             python_version="3.11.11",
+            version="0.0.1",
         )
         self.assertEqual(want, metadata_file_content)
 
diff --git a/tests/pypi/whl_installer/wheel_test.py b/tests/pypi/whl_installer/wheel_test.py
new file mode 100644
index 0000000..404218e
--- /dev/null
+++ b/tests/pypi/whl_installer/wheel_test.py
@@ -0,0 +1,371 @@
+import unittest
+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):
+    def test_simple(self):
+        deps = wheel.Deps("foo", requires_dist=["bar"])
+
+        got = deps.build()
+
+        self.assertIsInstance(got, wheel.FrozenDeps)
+        self.assertEqual(["bar"], got.deps)
+        self.assertEqual({}, got.deps_select)
+
+    def test_can_add_os_specific_deps(self):
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=[
+                "bar",
+                "an_osx_dep; sys_platform=='darwin'",
+                "posix_dep; os_name=='posix'",
+                "win_dep; os_name=='nt'",
+            ],
+            platforms={
+                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),
+            },
+        )
+
+        got = deps.build()
+
+        self.assertEqual(["bar"], got.deps)
+        self.assertEqual(
+            {
+                "@platforms//os:linux": ["posix_dep"],
+                "@platforms//os:osx": ["an_osx_dep", "posix_dep"],
+                "@platforms//os:windows": ["win_dep"],
+            },
+            got.deps_select,
+        )
+
+    def test_can_add_os_specific_deps_with_specific_python_version(self):
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=[
+                "bar",
+                "an_osx_dep; sys_platform=='darwin'",
+                "posix_dep; os_name=='posix'",
+                "win_dep; os_name=='nt'",
+            ],
+            platforms={
+                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),
+            },
+        )
+
+        got = deps.build()
+
+        self.assertEqual(["bar"], got.deps)
+        self.assertEqual(
+            {
+                "@platforms//os:linux": ["posix_dep"],
+                "@platforms//os:osx": ["an_osx_dep", "posix_dep"],
+                "@platforms//os:windows": ["win_dep"],
+            },
+            got.deps_select,
+        )
+
+    def test_deps_are_added_to_more_specialized_platforms(self):
+        got = wheel.Deps(
+            "foo",
+            requires_dist=[
+                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
+                "mac_dep; sys_platform=='darwin'",
+            ],
+            platforms={
+                Platform(os=OS.osx, arch=Arch.x86_64),
+                Platform(os=OS.osx, arch=Arch.aarch64),
+            },
+        ).build()
+
+        self.assertEqual(
+            wheel.FrozenDeps(
+                deps=[],
+                deps_select={
+                    "osx_aarch64": ["m1_dep", "mac_dep"],
+                    "@platforms//os:osx": ["mac_dep"],
+                },
+            ),
+            got,
+        )
+
+    def test_deps_from_more_specialized_platforms_are_propagated(self):
+        got = wheel.Deps(
+            "foo",
+            requires_dist=[
+                "a_mac_dep; sys_platform=='darwin'",
+                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
+            ],
+            platforms={
+                Platform(os=OS.osx, arch=Arch.x86_64),
+                Platform(os=OS.osx, arch=Arch.aarch64),
+            },
+        ).build()
+
+        self.assertEqual([], got.deps)
+        self.assertEqual(
+            {
+                "osx_aarch64": ["a_mac_dep", "m1_dep"],
+                "@platforms//os:osx": ["a_mac_dep"],
+            },
+            got.deps_select,
+        )
+
+    def test_non_platform_markers_are_added_to_common_deps(self):
+        got = wheel.Deps(
+            "foo",
+            requires_dist=[
+                "bar",
+                "baz; implementation_name=='cpython'",
+                "m1_dep; sys_platform=='darwin' and platform_machine=='arm64'",
+            ],
+            platforms={
+                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()
+
+        self.assertEqual(["bar", "baz"], got.deps)
+        self.assertEqual(
+            {
+                "osx_aarch64": ["m1_dep"],
+            },
+            got.deps_select,
+        )
+
+    def test_self_is_ignored(self):
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=[
+                "bar",
+                "req_dep; extra == 'requests'",
+                "foo[requests]; extra == 'ssl'",
+                "ssl_lib; extra == 'ssl'",
+            ],
+            extras={"ssl"},
+        )
+
+        got = deps.build()
+
+        self.assertEqual(["bar", "req_dep", "ssl_lib"], got.deps)
+        self.assertEqual({}, got.deps_select)
+
+    def test_self_dependencies_can_come_in_any_order(self):
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=[
+                "bar",
+                "baz; extra == 'feat'",
+                "foo[feat2]; extra == 'all'",
+                "foo[feat]; extra == 'feat2'",
+                "zdep; extra == 'all'",
+            ],
+            extras={"all"},
+        )
+
+        got = deps.build()
+
+        self.assertEqual(["bar", "baz", "zdep"], got.deps)
+        self.assertEqual({}, got.deps_select)
+
+    def test_can_get_deps_based_on_specific_python_version(self):
+        requires_dist = [
+            "bar",
+            "baz; python_version < '3.8'",
+            "posix_dep; os_name=='posix' and python_version >= '3.8'",
+        ]
+
+        py38_deps = wheel.Deps(
+            "foo",
+            requires_dist=requires_dist,
+            platforms=[
+                Platform(os=OS.linux, arch=Arch.x86_64, minor_version=8),
+            ],
+        ).build()
+        py37_deps = wheel.Deps(
+            "foo",
+            requires_dist=requires_dist,
+            platforms=[
+                Platform(os=OS.linux, arch=Arch.x86_64, minor_version=7),
+            ],
+        ).build()
+
+        self.assertEqual(["bar", "baz"], py37_deps.deps)
+        self.assertEqual({}, py37_deps.deps_select)
+        self.assertEqual(["bar"], py38_deps.deps)
+        self.assertEqual({"@platforms//os:linux": ["posix_dep"]}, py38_deps.deps_select)
+
+    @mock.patch(_HOST_INTERPRETER_FN)
+    def test_no_version_select_when_single_version(self, mock_host_interpreter_version):
+        requires_dist = [
+            "bar",
+            "baz; python_version >= '3.8'",
+            "posix_dep; os_name=='posix'",
+            "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'",
+            "arch_dep; platform_machine=='x86_64' and python_version >= '3.8'",
+        ]
+        mock_host_interpreter_version.return_value = 7
+
+        self.maxDiff = None
+
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=requires_dist,
+            platforms=[
+                Platform(os=os, arch=Arch.x86_64, minor_version=minor)
+                for minor in [8]
+                for os in [OS.linux, OS.windows]
+            ],
+        )
+        got = deps.build()
+
+        self.assertEqual(["bar", "baz"], got.deps)
+        self.assertEqual(
+            {
+                "@platforms//os:linux": ["posix_dep", "posix_dep_with_version"],
+                "linux_x86_64": ["arch_dep", "posix_dep", "posix_dep_with_version"],
+                "windows_x86_64": ["arch_dep"],
+            },
+            got.deps_select,
+        )
+
+    @mock.patch(_HOST_INTERPRETER_FN)
+    def test_can_get_version_select(self, mock_host_interpreter_version):
+        requires_dist = [
+            "bar",
+            "baz; python_version < '3.8'",
+            "baz_new; python_version >= '3.8'",
+            "posix_dep; os_name=='posix'",
+            "posix_dep_with_version; os_name=='posix' and python_version >= '3.8'",
+            "arch_dep; platform_machine=='x86_64' and python_version < '3.8'",
+        ]
+        mock_host_interpreter_version.return_value = 7
+
+        self.maxDiff = None
+
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=requires_dist,
+            platforms=[
+                Platform(os=os, arch=Arch.x86_64, minor_version=minor)
+                for minor in [7, 8, 9]
+                for os in [OS.linux, OS.windows]
+            ],
+        )
+        got = deps.build()
+
+        self.assertEqual(["bar"], got.deps)
+        self.assertEqual(
+            {
+                "//conditions:default": ["baz"],
+                "@//python/config_settings:is_python_3.7": ["baz"],
+                "@//python/config_settings:is_python_3.8": ["baz_new"],
+                "@//python/config_settings:is_python_3.9": ["baz_new"],
+                "@platforms//os:linux": ["baz", "posix_dep"],
+                "cp37_linux_x86_64": ["arch_dep", "baz", "posix_dep"],
+                "cp37_windows_x86_64": ["arch_dep", "baz"],
+                "cp37_linux_anyarch": ["baz", "posix_dep"],
+                "cp38_linux_anyarch": [
+                    "baz_new",
+                    "posix_dep",
+                    "posix_dep_with_version",
+                ],
+                "cp39_linux_anyarch": [
+                    "baz_new",
+                    "posix_dep",
+                    "posix_dep_with_version",
+                ],
+                "linux_x86_64": ["arch_dep", "baz", "posix_dep"],
+                "windows_x86_64": ["arch_dep", "baz"],
+            },
+            got.deps_select,
+        )
+
+    @mock.patch(_HOST_INTERPRETER_FN)
+    def test_deps_spanning_all_target_py_versions_are_added_to_common(
+        self, mock_host_version
+    ):
+        requires_dist = [
+            "bar",
+            "baz (<2,>=1.11) ; python_version < '3.8'",
+            "baz (<2,>=1.14) ; python_version >= '3.8'",
+        ]
+        mock_host_version.return_value = 8
+
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=requires_dist,
+            platforms=Platform.from_string(["cp37_*", "cp38_*", "cp39_*"]),
+        )
+        got = deps.build()
+
+        self.assertEqual(["bar", "baz"], got.deps)
+        self.assertEqual({}, got.deps_select)
+
+    @mock.patch(_HOST_INTERPRETER_FN)
+    def test_deps_are_not_duplicated(self, mock_host_version):
+        mock_host_version.return_value = 7
+
+        # See an example in
+        # https://files.pythonhosted.org/packages/76/9e/db1c2d56c04b97981c06663384f45f28950a73d9acf840c4006d60d0a1ff/opencv_python-4.9.0.80-cp37-abi3-win32.whl.metadata
+        requires_dist = [
+            "bar >=0.1.0 ; python_version < '3.7'",
+            "bar >=0.2.0 ; python_version >= '3.7'",
+            "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'",
+            "bar >=0.4.0 ; python_version >= '3.9'",
+            "bar >=0.5.0 ; python_version <= '3.9' and platform_system == 'Darwin' and platform_machine == 'arm64'",
+            "bar >=0.5.0 ; python_version >= '3.10' and platform_system == 'Darwin'",
+            "bar >=0.5.0 ; python_version >= '3.10'",
+            "bar >=0.6.0 ; python_version >= '3.11'",
+        ]
+
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=requires_dist,
+            platforms=Platform.from_string(["cp37_*", "cp310_*"]),
+        )
+        got = deps.build()
+
+        self.assertEqual(["bar"], got.deps)
+        self.assertEqual({}, got.deps_select)
+
+    @mock.patch(_HOST_INTERPRETER_FN)
+    def test_deps_are_not_duplicated_when_encountering_platform_dep_first(
+        self, mock_host_version
+    ):
+        mock_host_version.return_value = 7
+
+        # Note, that we are sorting the incoming `requires_dist` and we need to ensure that we are not getting any
+        # issues even if the platform-specific line comes first.
+        requires_dist = [
+            "bar >=0.4.0 ; python_version >= '3.6' and platform_system == 'Linux' and platform_machine == 'aarch64'",
+            "bar >=0.5.0 ; python_version >= '3.9'",
+        ]
+
+        deps = wheel.Deps(
+            "foo",
+            requires_dist=requires_dist,
+            platforms=Platform.from_string(["cp37_*", "cp310_*"]),
+        )
+        got = deps.build()
+
+        self.assertEqual(["bar"], got.deps)
+        self.assertEqual({}, got.deps_select)
+
+
+if __name__ == "__main__":
+    unittest.main()