Repository: pypi/warehouse

Issue:
macOS 11 wheel upload is not allowed
<!--
    NOTE: This issue should be for problems with PyPI itself, including:
    * pypi.org
    * test.pypi.org
    * files.pythonhosted.org

    This issue should NOT be for a project installed from PyPI. If you are
    having an issue with a specific package, you should reach out to the
    maintainers of that project directly instead.

    Furthermore, this issue should NOT be for any non-PyPI properties (like
    python.org, docs.python.org, etc.)
-->

**Describe the bug**
<!-- A clear and concise description the bug -->

Upload of wheels targeting latest stable macOS (11.0) is rejected due to unsupported platform tag:

```
HTTPError: 400 Bad Request from https://upload.pypi.org/legacy/
Binary wheel 'pyzmq-20.0.0-cp38-cp38-macosx_11_0_x86_64.whl' has an unsupported platform tag 'macosx_11_0_x86_64'.
```


**Expected behavior**
<!-- A clear and concise description of what you expected to happen -->

Upload of wheels built for current stable macOS (11.0) should be allowed.

**To Reproduce**
<!-- Steps to reproduce the bug, or a link to PyPI where the bug is visible -->

Build a wheel with tag `macosx_11_0_x86_64` and try to upload with `twine`. The simplest path is probably to `brew install python` on macOS 11.0 and build a wheel with that. I'm using pyzmq-20.0.0, but anything should do:

```python
brew install python@3.8
/usr/local/opt/python@3.8/bin/python3 -m venv ./env
source ./env/bin/activate
pip wheel pyzmq
twine upload ...
```

Folks who know more about wheels could probably hand-craft an os-tagged wheel on another platform without needing to install the latest macOS.


**My Platform**
<!--
    Any details about your specific platform:
    * If the problem is in the browser, what browser, version, and OS?
    * If the problem is with a command-line tool, what version of that tool?
    * If the problem is with connecting to PyPI, include some details about
      your network, including SSL/TLS implementation in use, internet service
      provider, and if there are any firewalls or proxies in use.
-->

macOS 11.0.1, Python 3.8, 3.9 from homebrew. Only homebrew Python targets the latest OS as the minimum version, but probably pyenv or similar would as well, or any Python built from source, I think. Pythons with an earlier target OS (conda, Python.org) do not have this issue.

**Additional context**
<!-- Add any other context, links, etc. about the feature here. -->

This is related to https://github.com/pypa/packaging/pull/319. I don't know where warehouse gets its supported tag list, and it may be that that PR to packaging is actually the only fix necessary, but as of today upload of valid tags is not allowed.



Discussion:
We'll need to make an update here: https://github.com/pypa/warehouse/blob/b9316cb12d0e0d91bf52f6a88f612db40ebf137a/warehouse/forklift/legacy.py#L126-L138

Resolving patch:
diff --git a/warehouse/forklift/legacy.py b/warehouse/forklift/legacy.py
--- a/warehouse/forklift/legacy.py
+++ b/warehouse/forklift/legacy.py
@@ -124,18 +124,25 @@ def namespace_stdlib_list(module_list):
     "linux_armv7l",
 }
 # macosx is a little more complicated:
-_macosx_platform_re = re.compile(r"macosx_10_(\d+)_(?P<arch>.*)")
+_macosx_platform_re = re.compile(r"macosx_(?P<major>\d+)_(\d+)_(?P<arch>.*)")
 _macosx_arches = {
     "ppc",
     "ppc64",
     "i386",
     "x86_64",
+    "arm64",
     "intel",
     "fat",
     "fat32",
     "fat64",
     "universal",
+    "universal2",
 }
+_macosx_major_versions = {
+    "10",
+    "11",
+}
+
 # manylinux pep600 is a little more complicated:
 _manylinux_platform_re = re.compile(r"manylinux_(\d+)_(\d+)_(?P<arch>.*)")
 _manylinux_arches = {
@@ -154,7 +161,11 @@ def _valid_platform_tag(platform_tag):
     if platform_tag in _allowed_platforms:
         return True
     m = _macosx_platform_re.match(platform_tag)
-    if m and m.group("arch") in _macosx_arches:
+    if (
+        m
+        and m.group("major") in _macosx_major_versions
+        and m.group("arch") in _macosx_arches
+    ):
         return True
     m = _manylinux_platform_re.match(platform_tag)
     if m and m.group("arch") in _manylinux_arches:


Test patch:
diff --git a/tests/unit/forklift/test_legacy.py b/tests/unit/forklift/test_legacy.py
--- a/tests/unit/forklift/test_legacy.py
+++ b/tests/unit/forklift/test_legacy.py
@@ -2566,6 +2566,9 @@ def test_upload_fails_without_permission(self, pyramid_config, db_request):
             "manylinux_3_0_s390x",
             "macosx_10_6_intel",
             "macosx_10_13_x86_64",
+            "macosx_11_0_x86_64",
+            "macosx_10_15_arm64",
+            "macosx_11_10_universal2",
             # A real tag used by e.g. some numpy wheels
             (
                 "macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64."
@@ -2790,7 +2793,16 @@ def storage_service_store(path, file_path, *, meta):
             )
         ]
 
-    @pytest.mark.parametrize("plat", ["linux_x86_64", "linux_x86_64.win32"])
+    @pytest.mark.parametrize(
+        "plat",
+        [
+            "linux_x86_64",
+            "linux_x86_64.win32",
+            "macosx_9_2_x86_64",
+            "macosx_12_2_arm64",
+            "macosx_10_15_amd64",
+        ],
+    )
     def test_upload_fails_with_unsupported_wheel_plat(
         self, monkeypatch, pyramid_config, db_request, plat
     ):