From a2d19c0234866dc9d4d55abf3009699c258bb72f Mon Sep 17 00:00:00 2001
From: Matt Davis <6775756+nitzmahone@users.noreply.github.com>
Date: Mon, 10 Jun 2024 15:24:15 -0700
Subject: [PATCH] Conditional support for Cython3.x, CI updates (#808)

* Cython 3.x support needed for Python 3.13
* Move CI to a dynamic matrix
* Use GHA-hosted Apple Silicon runners
* Move Windows builds to cibuildwheel
* Implement rudimentary PEP517 config-settings passthru to setuptools with
  custom in-tree setuptools build backend shim (blocked Windows build
  being moved to cibuildwheel).
* Use build_config.toml to smuggle JSON to cibuildwheel, since it
  trashes JSON via CLI when doing containerized builds.
---
 .github/actions/dynamatrix/action.yml         |  22 +
 .../actions/dynamatrix/matrix_yaml_to_json.py |  71 +++
 .github/workflows/ci.yaml                     | 596 +++++++++++-------
 .github/workflows/manual_artifact_build.yaml  | 446 -------------
 MANIFEST.in                                   |   1 +
 packaging/_pyyaml_pep517.py                   |  51 ++
 pyproject.toml                                |  10 +-
 setup.py                                      |  43 +-
 tests/legacy_tests/conftest.py                |   6 +-
 tests/legacy_tests/test_appliance.py          |   5 +-
 10 files changed, 577 insertions(+), 674 deletions(-)
 create mode 100644 .github/actions/dynamatrix/action.yml
 create mode 100644 .github/actions/dynamatrix/matrix_yaml_to_json.py
 delete mode 100644 .github/workflows/manual_artifact_build.yaml
 create mode 100644 packaging/_pyyaml_pep517.py

diff --git a/packaging/_pyyaml_pep517.py b/packaging/_pyyaml_pep517.py
new file mode 100644
index 00000000..5f390266
--- /dev/null
+++ b/packaging/_pyyaml_pep517.py
@@ -0,0 +1,51 @@
+import inspect
+
+
+def _bridge_build_meta():
+    import functools
+    import sys
+
+    from setuptools import build_meta
+
+    self_module = sys.modules[__name__]
+
+    for attr_name in build_meta.__all__:
+        attr_value = getattr(build_meta, attr_name)
+        if callable(attr_value):
+            setattr(self_module, attr_name, functools.partial(_expose_config_settings, attr_value))
+
+
+class ActiveConfigSettings:
+    _current = {}
+
+    def __init__(self, config_settings):
+        self._config = config_settings
+
+    def __enter__(self):
+        type(self)._current = self._config
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        type(self)._current = {}
+
+    @classmethod
+    def current(cls):
+        return cls._current
+
+
+def _expose_config_settings(real_method, *args, **kwargs):
+    from contextlib import nullcontext
+    import inspect
+
+    sig = inspect.signature(real_method)
+    boundargs = sig.bind(*args, **kwargs)
+
+    config = boundargs.arguments.get('config_settings')
+
+    ctx = ActiveConfigSettings(config) if config else nullcontext()
+
+    with ctx:
+        return real_method(*args, **kwargs)
+
+
+_bridge_build_meta()
+
diff --git a/pyproject.toml b/pyproject.toml
index 4bc04c0d..d8e5b969 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,9 @@
 [build-system]
-requires = ["setuptools", "wheel", "Cython<3.0"]
-build-backend = "setuptools.build_meta"
+requires = [
+    "setuptools",  # FIXME: declare min/max setuptools versions?
+    "wheel",
+    "Cython; python_version < '3.13'",
+    "Cython>=3.0; python_version >= '3.13'"
+]
+backend-path = ["packaging"]
+build-backend = "_pyyaml_pep517"
diff --git a/setup.py b/setup.py
index c9b5dc6f..84bc2e46 100644
--- a/setup.py
+++ b/setup.py
@@ -34,6 +34,8 @@
     "Programming Language :: Python :: 3.9",
     "Programming Language :: Python :: 3.10",
     "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12",
+    "Programming Language :: Python :: 3.13",
     "Programming Language :: Python :: Implementation :: CPython",
     "Programming Language :: Python :: Implementation :: PyPy",
     "Topic :: Software Development :: Libraries :: Python Modules",
@@ -82,7 +84,12 @@
     with_cython = True
 try:
     from Cython.Distutils.extension import Extension as _Extension
-    from Cython.Distutils import build_ext as _build_ext
+    try:
+        # try old_build_ext from Cython > 3 first, until we can dump it entirely
+        from Cython.Distutils.old_build_ext import old_build_ext as _build_ext
+    except ImportError:
+        # Cython < 3
+        from Cython.Distutils import build_ext as _build_ext
     with_cython = True
 except ImportError:
     if with_cython:
@@ -94,6 +101,14 @@
     bdist_wheel = None
 
 
+try:
+    from _pyyaml_pep517 import ActiveConfigSettings
+except ImportError:
+    class ActiveConfigSettings:
+        @staticmethod
+        def current():
+            return {}
+
 # on Windows, disable wheel generation warning noise
 windows_ignore_warnings = [
 "Unknown distribution option: 'python_requires'",
@@ -173,6 +188,31 @@ def __init__(self, name, sources, feature_name, feature_description,
 
 
 class build_ext(_build_ext):
+    def finalize_options(self):
+        super().finalize_options()
+        pep517_config = ActiveConfigSettings.current()
+
+        build_config = pep517_config.get('pyyaml_build_config')
+
+        if build_config:
+            import json
+            build_config = json.loads(build_config)
+            print(f"`pyyaml_build_config`: {build_config}")
+        else:
+            build_config = {}
+            print("No `pyyaml_build_config` setting found.")
+
+        for key, value in build_config.items():
+            existing_value = getattr(self, key, ...)
+            if existing_value is ...:
+                print(f"ignoring unknown config key {key!r}")
+                continue
+
+            if existing_value:
+                print(f"combining {key!r} {existing_value!r} and {value!r}")
+                value = existing_value + value  # FIXME: handle type diff
+
+            setattr(self, key, value)
 
     def run(self):
         optional = True
@@ -230,6 +270,7 @@ def build_extensions(self):
             if with_ext is not None and not with_ext:
                 continue
             if with_cython:
+                print(f"BUILDING CYTHON EXT; {self.include_dirs=} {self.library_dirs=} {self.define=}")
                 ext.sources = self.cython_sources(ext.sources, ext)
             try:
                 self.build_extension(ext)
