Repository: pex-tool/pex

Issue:
Pex `--platform` does not populate the `platform_machine` env marker.
The failure looks like so:
```console
$ python -mpex --platform linux_x86_64-cp-39-cp39 sqlalchemy -o sqlalchemy.pex 
ERROR: Exception:
Traceback (most recent call last):
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/pkg_resources/__init__.py", line 3021, in _dep_map
    return self.__dep_map
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/pkg_resources/__init__.py", line 2815, in __getattr__
    raise AttributeError(attr)
AttributeError: _DistInfoDistribution__dep_map

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_internal/cli/base_command.py", line 223, in _main
    status = self.run(options, args)
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_internal/cli/req_command.py", line 180, in wrapper
    return func(self, options, args)
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_internal/commands/download.py", line 130, in run
    requirement_set = resolver.resolve(
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_internal/resolution/legacy/resolver.py", line 180, in resolve
    discovered_reqs.extend(self._resolve_one(requirement_set, req))
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_internal/resolution/legacy/resolver.py", line 431, in _resolve_one
    set(req_to_install.extras) - set(dist.extras)
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/pkg_resources/__init__.py", line 2978, in extras
    return [dep for dep in self._dep_map if dep]
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/pkg_resources/__init__.py", line 3023, in _dep_map
    self.__dep_map = self._compute_dependencies()
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/pkg_resources/__init__.py", line 3040, in _compute_dependencies
    common = frozenset(reqs_for_extra(None))
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/pkg_resources/__init__.py", line 3037, in reqs_for_extra
    if not req.marker or req.marker.evaluate({'extra': extra}):
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/packaging/markers.py", line 328, in evaluate
    return _evaluate_markers(self._markers, current_environment)
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/packaging/markers.py", line 239, in _evaluate_markers
    groups[-1].append(_evaluate_markers(marker, environment))
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/packaging/markers.py", line 244, in _evaluate_markers
    lhs_value = _get_env(environment, lhs.value)
  File "/home/jsirois/.pex/venvs/short/9d336279/lib/python3.9/site-packages/pip/_vendor/packaging/markers.py", line 224, in _get_env
    raise UndefinedEnvironmentName(
pip._vendor.packaging.markers.UndefinedEnvironmentName: 'platform_machine' does not exist in evaluation environment.
pid 14893 -> /home/jsirois/.pex/venvs/1bfd51a9d830d9b6d4e3075cebe5d9107b1b9d2a/108a3ddc84230ab282ea6312e06cb68f51008ce5/pex --disable-pip-version-check --no-python-version-warning --exists-action a --use-deprecated legacy-resolver --isolated -q --cache-dir /home/jsirois/.pex --log /tmp/tmpscrxmds4/pip.log download --dest /tmp/tmp2i5ufoqi/linux_x86_64-cp-39-cp39 --platform manylinux2014_x86_64 --platform linux_x86_64 --implementation cp --python-version 39 --abi cp39 --only-binary :all: sqlalchemy --index-url https://pypi.org/simple --retries 5 --timeout 15 exited with 2 and STDERR:
Failed to resolve for platform linux_x86_64-cp-39-cp39. Resolve requires evaluation of unknown environment marker: 'platform_machine' does not exist in evaluation environment.
```

This is because SQLAlchemy has a `platform_machine` specific dependency on greenlet:
```console
$ unzip -qc wheelhouse/SQLAlchemy-1.4.25-cp39-cp39-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl SQLAlchemy-1.4.25.dist-info/METADATA | grep Requires-Dist
Requires-Dist: importlib-metadata ; python_version < "3.8"
Requires-Dist: greenlet (!=0.4.17) ; python_version >= "3" and (platform_machine == "aarch64" or (platform_machine == "ppc64le" or (platform_machine == "x86_64" or (platform_machine == "amd64" or (platform_machine == "AMD64" or (platform_machine == "win32" or platform_machine == "WIN32"))))))
Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'aiomysql'
Requires-Dist: aiomysql ; (python_version >= "3") and extra == 'aiomysql'
Requires-Dist: typing-extensions (!=3.10.0.1) ; extra == 'aiosqlite'
Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'aiosqlite'
Requires-Dist: aiosqlite ; (python_version >= "3") and extra == 'aiosqlite'
Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'asyncio'
Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'asyncmy'
Requires-Dist: asyncmy (>=0.2.0) ; (python_version >= "3") and extra == 'asyncmy'
Requires-Dist: mariadb (>=1.0.1) ; (python_version >= "3") and extra == 'mariadb_connector'
Requires-Dist: pyodbc ; extra == 'mssql'
Requires-Dist: pymssql ; extra == 'mssql_pymssql'
Requires-Dist: pyodbc ; extra == 'mssql_pyodbc'
Requires-Dist: sqlalchemy2-stubs ; extra == 'mypy'
Requires-Dist: mypy (>=0.910) ; (python_version >= "3") and extra == 'mypy'
Requires-Dist: mysqlclient (<2,>=1.4.0) ; (python_version < "3") and extra == 'mysql'
Requires-Dist: mysqlclient (>=1.4.0) ; (python_version >= "3") and extra == 'mysql'
Requires-Dist: mysql-connector-python ; extra == 'mysql_connector'
Requires-Dist: cx-oracle (<8,>=7) ; (python_version < "3") and extra == 'oracle'
Requires-Dist: cx-oracle (>=7) ; (python_version >= "3") and extra == 'oracle'
Requires-Dist: psycopg2 (>=2.7) ; extra == 'postgresql'
Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'postgresql_asyncpg'
Requires-Dist: asyncpg ; (python_version >= "3") and extra == 'postgresql_asyncpg'
Requires-Dist: pg8000 (>=1.16.6) ; extra == 'postgresql_pg8000'
Requires-Dist: psycopg2-binary ; extra == 'postgresql_psycopg2binary'
Requires-Dist: psycopg2cffi ; extra == 'postgresql_psycopg2cffi'
Requires-Dist: pymysql (<1) ; (python_version < "3") and extra == 'pymysql'
Requires-Dist: pymysql ; (python_version >= "3") and extra == 'pymysql'
Requires-Dist: sqlcipher3-binary ; (python_version >= "3") and extra == 'sqlcipher'
```


Discussion:
It seems like we have all the information needed to populate this environment marker from the `--platform`; so this was just an oversight in #1367.

Resolving patch:
diff --git a/pex/platforms.py b/pex/platforms.py
--- a/pex/platforms.py
+++ b/pex/platforms.py
@@ -310,10 +310,29 @@ def marker_environment(self, default_unknown=True):
 
         if "linux" in self.platform:
             env["os_name"] = "posix"
+            if self.platform.startswith(
+                ("linux_", "manylinux1_", "manylinux2010_", "manylinux2014_")
+            ):
+                # E.G.:
+                # + linux_x86_64
+                # + manylinux{1,2010,2014}_x86_64
+                # For the manylinux* See:
+                # + manylinux1: https://www.python.org/dev/peps/pep-0513/
+                # + manylinux2010: https://www.python.org/dev/peps/pep-0571/
+                # + manylinux2014: https://www.python.org/dev/peps/pep-0599/
+                env["platform_machine"] = self.platform.split("_", 1)[-1]
+            else:
+                # E.G.: manylinux_<glibc major>_<glibc_minor>_x86_64
+                # See: https://www.python.org/dev/peps/pep-0600/
+                env["platform_machine"] = self.platform.split("_", 3)[-1]
             env["platform_system"] = "Linux"
             env["sys_platform"] = "linux2" if major_version == 2 else "linux"
         elif "mac" in self.platform:
             env["os_name"] = "posix"
+            # E.G:
+            # + macosx_10_15_x86_64
+            # + macosx_11_0_arm64
+            env["platform_machine"] = self.platform.split("_", 3)[-1]
             env["platform_system"] = "Darwin"
             env["sys_platform"] = "darwin"
 


Test patch:
diff --git a/tests/test_pip.py b/tests/test_pip.py
--- a/tests/test_pip.py
+++ b/tests/test_pip.py
@@ -202,3 +202,38 @@ def test_download_platform_markers_issue_1366_indeterminate(
         "evaluation of unknown environment marker: 'python_full_version' does not exist in "
         "evaluation environment."
     ) in str(exc_info.value)
+
+
+def test_download_platform_markers_issue_1488(
+    create_pip,  # type: CreatePip
+    tmpdir,  # type: Any
+):
+    # type: (...) -> None
+
+    constraints_file = os.path.join(str(tmpdir), "constraints.txt")
+    with open(constraints_file, "w") as fp:
+        fp.write("greenlet==1.1.2")
+
+    download_dir = os.path.join(str(tmpdir), "downloads")
+
+    python39_platform = Platform.create("linux-x86_64-cp-39-cp39")
+    create_pip(None).spawn_download_distributions(
+        target=DistributionTarget.for_platform(python39_platform, manylinux="manylinux2014"),
+        requirements=["SQLAlchemy==1.4.25"],
+        constraint_files=[constraints_file],
+        download_dir=download_dir,
+        transitive=True,
+    ).wait()
+
+    assert (
+        sorted(
+            [
+                (
+                    "SQLAlchemy-1.4.25-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64"
+                    ".manylinux_2_17_x86_64.manylinux2014_x86_64.whl"
+                ),
+                "greenlet-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
+            ]
+        )
+        == sorted(os.listdir(download_dir))
+    )
diff --git a/tests/test_platform.py b/tests/test_platform.py
--- a/tests/test_platform.py
+++ b/tests/test_platform.py
@@ -113,6 +113,7 @@ def assert_known_marker(expression):
     assert_known_marker("python_version == '3.7'")
     assert_known_marker("implementation_name == 'cpython'")
     assert_known_marker("platform_system == 'Linux'")
+    assert_known_marker("platform_machine == 'x86_64'")
 
     def assert_unknown_marker(expression):
         # type: (str) -> None
@@ -123,4 +124,23 @@ def assert_unknown_marker(expression):
     assert_unknown_marker("python_full_version == '3.7.10'")
     assert_unknown_marker("platform_release == '5.12.12-arch1-1'")
     assert_unknown_marker("platform_version == '#1 SMP PREEMPT Fri, 18 Jun 2021 21:59:22 +0000'")
-    assert_unknown_marker("platform_machine == 'x86_64'")
+
+
+def test_platform_marker_environment_issue_1488():
+    # type: () -> None
+
+    def assert_platform_machine(
+        expected,  # type: str
+        platform,  # type: str
+    ):
+        assert expected == Platform.create(platform).marker_environment()["platform_machine"]
+
+    assert_platform_machine("x86_64", "linux-x86_64-cp-37-cp37m")
+    assert_platform_machine("x86_64", "manylinux1-x86_64-cp-37-cp37m")
+    assert_platform_machine("x86_64", "manylinux2010-x86_64-cp-37-cp37m")
+    assert_platform_machine("x86_64", "manylinux2014-x86_64-cp-37-cp37m")
+    assert_platform_machine("x86_64", "manylinux_2_5-x86_64-cp-37-cp37m")
+    assert_platform_machine("aarch64", "manylinux_2_77-aarch64-cp-37-cp37m")
+
+    assert_platform_machine("x86_64", "macosx-10.15-x86_64-cp-38-m")
+    assert_platform_machine("arm64", "macosx-11.0-arm64-cp-39-cp39")