Repository: pex-tool/pex

Issue:
Create lockfile for `xmlsec` fails
```
$ python --version
Python 3.11.1
# but same issue on Python 3.8.13

$ pex3 --version
2.1.122

$ pex3 lock create "xmlsec==1.3.13"
Building lxml version 4.9.2.
Building without Cython.
Building against libxml2 2.9.4 and libxslt 1.1.29
running dist_info
creating /private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.egg-info
writing /private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.egg-info/PKG-INFO
writing dependency_links to /private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.egg-info/dependency_links.txt
writing requirements to /private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.egg-info/requires.txt
writing top-level names to /private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.egg-info/top_level.txt
writing manifest file '/private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.egg-info/SOURCES.txt'
reading manifest file '/private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file '/private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.egg-info/SOURCES.txt'
creating '/private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpqn87v27s/build/lxml.dist-info'
adding license file "LICENSE.txt" (matched pattern "LICEN[CS]E*")
adding license file "LICENSES.txt" (matched pattern "LICEN[CS]E*")
Could not gather lock metadata for 1 project with source artifacts:
1. /private/var/folders/y2/6t5bbk554wv5h6w_qlgpcpnm0000gr/T/tmpkqwg1011/Users.andreas.stenius.tmp.venv.bin.python3.11/xmlsec-1.3.13.tar.gz: 'build-backend'

```

Regression from previous version:
```
$ pex3 --version
2.1.121

$ pex3 lock create "xmlsec==1.3.13"
{"allow_builds": true, "allow_prereleases": false, "allow_wheels": true, "build_isolation": true, "constraints": [], "locked_resolves": [{"locked_requirements": [{"artifacts": [{"algorithm": "sha256", "hash": "2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67", "url": "https://files.pythonhosted.org/packages/06/5a/e11cad7b79f2cf3dd2ff8f81fa8ca667e7591d3d8451768589996b65dec1/lxml-4.9.2.tar.gz"}], "project_name": "lxml", "requires_dists": ["BeautifulSoup4; extra == \"htmlsoup\"", "Cython>=0.29.7; extra == \"source\"", "cssselect>=0.7; extra == \"cssselect\"", "html5lib; extra == \"html5\""], "requires_python": "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7", "version": "4.9.2"}, {"artifacts": [{"algorithm": "sha256", "hash": "916f5d78e8041f6cd9391abba659da8c94a4fef7196d126d40af1ff417f2cf86", "url": "https://files.pythonhosted.org/packages/37/9f/342d4562eac99178d0d515c780285e107c6828cefad37d02f05b7b7d8751/xmlsec-1.3.13.tar.gz"}], "project_name": "xmlsec", "requires_dists": ["lxml>=3.8"], "requires_python": ">=3.5", "version": "1.3.13"}], "platform_tag": ["cp311", "cp311", "macosx_13_0_arm64"]}], "path_mappings": {}, "pex_version": "2.1.121", "pip_version": "20.3.4-patched", "prefer_older_binary": false, "requirements": ["xmlsec==1.3.13"], "requires_python": [], "resolver_version": "pip-legacy-resolver", "style": "strict", "target_systems": [], "transitive": true, "use_pep517": null}
```



Discussion:
Lovely:
https://github.com/xmlsec/python-xmlsec/blob/b483b644b6033e539f303509f5ee85bebf178a73/pyproject.toml#L46-L47

No build backend. I didn't even think that was legal.



Resolving patch:
diff --git a/pex/build_system/__init__.py b/pex/build_system/__init__.py
--- a/pex/build_system/__init__.py
+++ b/pex/build_system/__init__.py
@@ -1,2 +1,12 @@
 # Copyright 2022 Pants project contributors (see CONTRIBUTORS.md).
 # Licensed under the Apache License, Version 2.0 (see LICENSE).
+
+from __future__ import absolute_import
+
+# The split of PEP-517 / PEP-518 is quite awkward. PEP-518 doesn't really work without also
+# specifying a build backend or knowing a default value for one, but the concept is not defined
+# until PEP-517. As such, we break this historical? strange division and define the default outside
+# both PEPs.
+#
+# See: https://peps.python.org/pep-0517/#source-trees
+DEFAULT_BUILD_BACKEND = "setuptools.build_meta:__legacy__"
diff --git a/pex/build_system/pep_517.py b/pex/build_system/pep_517.py
--- a/pex/build_system/pep_517.py
+++ b/pex/build_system/pep_517.py
@@ -9,6 +9,7 @@
 from textwrap import dedent
 
 from pex import third_party
+from pex.build_system import DEFAULT_BUILD_BACKEND
 from pex.build_system.pep_518 import BuildSystem, load_build_system
 from pex.common import safe_mkdtemp
 from pex.dist_metadata import DistMetadata, Distribution
@@ -21,9 +22,8 @@
 from pex.util import named_temporary_file
 
 if TYPE_CHECKING:
-    from typing import Any, Dict, Iterable, Mapping, Optional, Text, Tuple, Union
+    from typing import Any, Dict, Iterable, Mapping, Optional, Text, Union
 
-_DEFAULT_BUILD_BACKEND = "setuptools.build_meta"
 _DEFAULT_BUILD_SYSTEMS = {}  # type: Dict[PipVersionValue, BuildSystem]
 
 
@@ -36,9 +36,7 @@ def _default_build_system(
     build_system = _DEFAULT_BUILD_SYSTEMS.get(pip_version)
     if build_system is None:
         with TRACER.timed(
-            "Building {build_backend} build_backend PEX".format(
-                build_backend=_DEFAULT_BUILD_BACKEND
-            )
+            "Building {build_backend} build_backend PEX".format(build_backend=DEFAULT_BUILD_BACKEND)
         ):
             extra_env = {}  # type: Dict[str, str]
             if pip_version is PipVersion.VENDORED:
@@ -59,7 +57,7 @@ def _default_build_system(
             build_system = BuildSystem.create(
                 requires=requires,
                 resolved=resolved,
-                build_backend=_DEFAULT_BUILD_BACKEND,
+                build_backend=DEFAULT_BUILD_BACKEND,
                 backend_path=(),
                 **extra_env
             )
diff --git a/pex/build_system/pep_518.py b/pex/build_system/pep_518.py
--- a/pex/build_system/pep_518.py
+++ b/pex/build_system/pep_518.py
@@ -4,8 +4,8 @@
 from __future__ import absolute_import
 
 import os.path
-import sys
 
+from pex.build_system import DEFAULT_BUILD_BACKEND
 from pex.dist_metadata import Distribution
 from pex.pex import PEX
 from pex.pex_bootstrapper import VenvPex, ensure_venv
@@ -29,7 +29,7 @@
 @attr.s(frozen=True)
 class BuildSystemTable(object):
     requires = attr.ib()  # type: Tuple[str, ...]
-    build_backend = attr.ib()  # type: str
+    build_backend = attr.ib(default=DEFAULT_BUILD_BACKEND)  # type: str
     backend_path = attr.ib(default=())  # type: Tuple[str, ...]
 
 
@@ -57,7 +57,7 @@ def _read_build_system_table(
 
     return BuildSystemTable(
         requires=tuple(requires),
-        build_backend=build_system["build-backend"],
+        build_backend=build_system.get("build-backend", DEFAULT_BUILD_BACKEND),
         backend_path=tuple(
             os.path.join(os.path.dirname(pyproject_toml), entry)
             for entry in build_system.get("backend-path", ())


Test patch:
diff --git a/tests/integration/build_system/test_issue_2063.py b/tests/integration/build_system/test_issue_2063.py
new file mode 100644
--- /dev/null
+++ b/tests/integration/build_system/test_issue_2063.py
@@ -0,0 +1,22 @@
+# Copyright 2023 Pants project contributors (see CONTRIBUTORS.md).
+# Licensed under the Apache License, Version 2.0 (see LICENSE).
+
+import sys
+
+import pytest
+
+from pex.cli.testing import run_pex3
+from pex.typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from typing import Any
+
+
+@pytest.mark.skipif(
+    sys.version_info[:2] < (3, 5),
+    reason="The tested distribution is only compatible with Python >= 3.5",
+)
+def test_build_system_no_build_backend(tmpdir):
+    # type: (Any) -> None
+
+    run_pex3("lock", "create", "xmlsec==1.3.13").assert_success()