Skip to content

Commit

Permalink
Fix test 3 native extension build for Python >= 3.12
Browse files Browse the repository at this point in the history
  • Loading branch information
touilleMan committed Nov 17, 2024
1 parent 0d50e02 commit 99cdd77
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 36 deletions.
30 changes: 29 additions & 1 deletion tests/3-pythonscript-cython-only/build.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
import platform
from pathlib import Path
import subprocess
Expand All @@ -7,6 +8,19 @@
PROJECT_DIR = Path(__file__).resolve().parent


# Here we run the build with the Python from the host development environment
# (i.e. not the embedded Python that will run the extension).
#
# The reason for this is the embedded Python has issues building native extensions
# since it has been compiled in a totally different environment that the one
# it runs on, leading to incorrect flags passed during extension compilation
# (see https://github.com/indygreg/python-build-standalone/issues/152).
#
# So instead we have to rely on the Python from the development environment, which
# of course means its version (and platform !) must be the same to preserve ABI
# compatibility.


if platform.system() == "Windows":
python_path = PROJECT_DIR / "addons/pythonscript/windows-x86_64/python.exe"
lib_pattern = "my.*.pyd"
Expand All @@ -18,7 +32,21 @@
python_path = PROJECT_DIR / "addons/pythonscript/linux-x86_64/bin/python3"
lib_pattern = "my.*.so"

cmd = [str(python_path), "setup.py", "build_ext", "--build-lib", str(PROJECT_DIR)]

embedded_version = subprocess.check_output([str(python_path), "--version"]).strip()
host_version = subprocess.check_output([sys.executable, "--version"]).strip()
if embedded_version != host_version:
BOLD_RED = "\x1b[1;31m"
NO_COLOR = "\x1b[0;0m"
print(
f"{BOLD_RED}"
"WARNING: Python extension loading may fail: host and embedded versions differ"
f" (host: {host_version.decode().strip()}, embedded: {embedded_version.decode().strip()})"
f"{NO_COLOR}"
)


cmd = [sys.executable, "setup.py", "build_ext", "--build-lib", str(PROJECT_DIR)]
print(" ".join(cmd))
subprocess.check_call(cmd, cwd=PROJECT_DIR)

Expand Down
4 changes: 3 additions & 1 deletion tests/3-pythonscript-cython-only/expected.output
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
Godot Engine .* - https://godotengine.org
Pythonscript .* \(CPython .*\)


MY initialize
Hello, World !
MY deinitialize
42 changes: 10 additions & 32 deletions tests/3-pythonscript-cython-only/setup.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,18 @@
import os
import platform
from setuptools import setup
from setuptools import Extension, setup
from Cython.Build import cythonize
from pathlib import Path


# Retrieve `Python.h`'s include dir.
# In theory setuptools rely on `sysconfig` to find this information, `sysconfig` being
# generated when Python is installed (typically `make install` after it compilation).
# However we use python-build-standalone which (as it name imply) provide us with a
# standalone Python distribution, hence the install step is part of the build process
# and `sysconfig` provide irrelevant include paths (e.g. on Linux it is done on Docker
# with install in `/install`)
# See:
# - https://github.com/indygreg/python-build-standalone/issues/152
# - https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html#references-to-build-time-paths
if platform.system() in ("Linux", "Darwin"):
python_include_dir = next(Path(".").parent.glob("addons/pythonscript/*-*/include/python*"))
elif platform.system() == "Windows":
python_include_dir = next(Path(".").parent.glob("addons/pythonscript/*-*/include"))
else:
raise RuntimeError(f"Unsupported platform `{platform.system()}`")
# Same idea: `sysconfig` defines CC=clang, but who knows if the current machine has it !
os.environ.setdefault("CC", "cc")


# Work around cythonize's `include_path` parameter not configuring the C compiler
# (see: https://github.com/cython/cython/issues/1480)
gdextension_api_include_dir = Path("gdextension_api")


ext_modules = cythonize("my.pyx")
ext_modules[0].include_dirs = [
python_include_dir,
gdextension_api_include_dir,
extensions = [
Extension(
"*",
["my.pyx"],
# C/C++ includes
include_dirs=[str(gdextension_api_include_dir.absolute())],
),
]
setup(
ext_modules=ext_modules,
name="My hello app",
ext_modules=cythonize(extensions),
)
4 changes: 2 additions & 2 deletions tests/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,12 @@ def create_test_workdir(
f"{YELLOW}{test_dir.name}: Create&populate test workdir in {test_workdir}{NO_COLOR}",
flush=True,
)
shutil.copytree(test_dir, test_workdir, dirs_exist_ok=True)
shutil.copytree(test_dir, test_workdir, dirs_exist_ok=True, symlinks=True)
symlink(distrib_workdir / "addons", test_workdir / "addons")
shutil.copy(distrib_workdir / "pythonscript.gdextension", test_workdir)
# GDExtension headers are needed to compile Cython modules
if custom_gdextension_api:
shutil.copytree(custom_gdextension_api, test_workdir / "gdextension_api")
shutil.copytree(custom_gdextension_api, test_workdir / "gdextension_api", symlinks=True)
else:
symlink(build_dir / "gdextension_api", test_workdir / "gdextension_api")

Expand Down

0 comments on commit 99cdd77

Please sign in to comment.