From 50721e8e12a9b2395a5df28aa935b40cfe35335f Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Jun 2025 12:30:05 -0700 Subject: [PATCH 01/10] Prepare 19.0.0 release. --- CHANGELOG.md | 5 +++++ tcod/sdl/audio.py | 38 +++++++++++++++++++------------------- tcod/sdl/mouse.py | 4 ++-- tcod/sdl/render.py | 12 ++++++------ tcod/sdl/video.py | 6 +++--- 5 files changed, 35 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cbd924b..2e1eb24b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,11 @@ This project adheres to [Semantic Versioning](https://semver.org/) since version ## [Unreleased] +## [19.0.0] - 2025-06-13 + +Finished port to SDL3, this has caused several breaking changes from SDL such as lowercase key constants now being uppercase and mouse events returning `float` instead of `int`. +Be sure to run [Mypy](https://mypy.readthedocs.io/en/stable/getting_started.html) on your projects to catch any issues from this update. + ### Changed - Updated libtcod to 2.1.1 diff --git a/tcod/sdl/audio.py b/tcod/sdl/audio.py index d15abb32..390bc551 100644 --- a/tcod/sdl/audio.py +++ b/tcod/sdl/audio.py @@ -217,7 +217,7 @@ class AudioDevice: .. versionchanged:: 16.0 Can now be used as a context which will close the device on exit. - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Removed `spec` and `callback` attribute. `queued_samples`, `queue_audio`, and `dequeue_audio` moved to :any:`AudioStream` class. @@ -269,7 +269,7 @@ def __init__( self.is_physical: Final[bool] = bool(lib.SDL_IsAudioDevicePhysical(device_id)) """True of this is a physical device, or False if this is a logical device. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ def __repr__(self) -> str: @@ -294,7 +294,7 @@ def __repr__(self) -> str: def name(self) -> str: """Name of the device. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ return str(ffi.string(_check_p(lib.SDL_GetAudioDeviceName(self.device_id))), encoding="utf-8") @@ -304,7 +304,7 @@ def gain(self) -> float: Default is 1.0 but can be set higher or zero. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ return _check_float(lib.SDL_GetAudioDeviceGain(self.device_id), failure=-1.0) @@ -320,7 +320,7 @@ def open( ) -> Self: """Open a new logical audio device for this device. - .. versionadded:: Unreleased + .. versionadded:: 19.0 .. seealso:: https://wiki.libsdl.org/SDL3/SDL_OpenAudioDevice @@ -349,7 +349,7 @@ def _sample_size(self) -> int: def stopped(self) -> bool: """Is True if the device has failed or was closed. - .. deprecated:: Unreleased + .. deprecated:: 19.0 No longer used by the SDL3 API. """ return bool(not hasattr(self, "device_id")) @@ -417,7 +417,7 @@ def close(self) -> None: def __enter__(self) -> Self: """Return self and enter a managed context. - .. deprecated:: Unreleased + .. deprecated:: 19.0 Use :func:`contextlib.closing` if you want to close this device after a context. """ return self @@ -443,7 +443,7 @@ def new_stream( ) -> AudioStream: """Create, bind, and return a new :any:`AudioStream` for this device. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ new_stream = AudioStream.new(format=format, channels=channels, frequency=frequency) self.bind((new_stream,)) @@ -463,7 +463,7 @@ def bind(self, streams: Iterable[AudioStream], /) -> None: class AudioStreamCallbackData: """Data provided to AudioStream callbacks. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ additional_bytes: int @@ -487,7 +487,7 @@ class AudioStream: This class is commonly created with :any:`AudioDevice.new_stream` which creates a new stream bound to the device. - ..versionadded:: Unreleased + ..versionadded:: 19.0 """ __slots__ = ("__weakref__", "_stream_p") @@ -819,10 +819,10 @@ class BasicMixer: .. versionadded:: 13.6 - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Added `frequency` and `channels` parameters. - .. deprecated:: Unreleased + .. deprecated:: 19.0 Changes in the SDL3 API have made this classes usefulness questionable. This class should be replaced with custom streams. """ @@ -927,7 +927,7 @@ def _sdl_audio_stream_callback(userdata: Any, stream_p: Any, additional_amount: def get_devices() -> dict[str, AudioDevice]: """Iterate over the available audio output devices. - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Now returns a dictionary of :any:`AudioDevice`. """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) @@ -942,7 +942,7 @@ def get_devices() -> dict[str, AudioDevice]: def get_capture_devices() -> dict[str, AudioDevice]: """Iterate over the available audio capture devices. - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Now returns a dictionary of :any:`AudioDevice`. """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) @@ -960,7 +960,7 @@ def get_default_playback() -> AudioDevice: Example: playback_device = tcod.sdl.audio.get_default_playback().open() - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) return AudioDevice(ffi.cast("SDL_AudioDeviceID", lib.SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK)) @@ -972,7 +972,7 @@ def get_default_recording() -> AudioDevice: Example: recording_device = tcod.sdl.audio.get_default_recording().open() - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ tcod.sdl.sys.init(tcod.sdl.sys.Subsystem.AUDIO) return AudioDevice(ffi.cast("SDL_AudioDeviceID", lib.SDL_AUDIO_DEVICE_DEFAULT_RECORDING)) @@ -982,7 +982,7 @@ def get_default_recording() -> AudioDevice: class AllowedChanges(enum.IntFlag): """Which parameters are allowed to be changed when the values given are not supported. - .. deprecated:: Unreleased + .. deprecated:: 19.0 This is no longer used. """ @@ -1033,12 +1033,12 @@ def open( # noqa: A001, PLR0913 If a callback is given then it will be called with the `AudioDevice` and a Numpy buffer of the data stream. This callback will be run on a separate thread. - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 SDL3 returns audio devices differently, exact formatting is set with :any:`AudioDevice.new_stream` instead. `samples` and `allowed_changes` are ignored. - .. deprecated:: Unreleased + .. deprecated:: 19.0 This is an outdated method. Use :any:`AudioDevice.open` instead, for example: ``tcod.sdl.audio.get_default_playback().open()`` diff --git a/tcod/sdl/mouse.py b/tcod/sdl/mouse.py index f4c16ed5..9d9227f7 100644 --- a/tcod/sdl/mouse.py +++ b/tcod/sdl/mouse.py @@ -183,7 +183,7 @@ def set_relative_mode(enable: bool) -> None: :any:`tcod.sdl.mouse.capture` https://wiki.libsdl.org/SDL_SetWindowRelativeMouseMode - .. deprecated:: Unreleased + .. deprecated:: 19.0 Replaced with :any:`tcod.sdl.video.Window.relative_mouse_mode` """ _check(lib.SDL_SetWindowRelativeMouseMode(lib.SDL_GetMouseFocus(), enable)) @@ -193,7 +193,7 @@ def set_relative_mode(enable: bool) -> None: def get_relative_mode() -> bool: """Return True if relative mouse mode is enabled. - .. deprecated:: Unreleased + .. deprecated:: 19.0 Replaced with :any:`tcod.sdl.video.Window.relative_mouse_mode` """ return bool(lib.SDL_GetWindowRelativeMouseMode(lib.SDL_GetMouseFocus())) diff --git a/tcod/sdl/render.py b/tcod/sdl/render.py index 0a1052e1..511b48fc 100644 --- a/tcod/sdl/render.py +++ b/tcod/sdl/render.py @@ -48,7 +48,7 @@ class LogicalPresentation(enum.IntEnum): See https://wiki.libsdl.org/SDL3/SDL_RendererLogicalPresentation - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ DISABLED = 0 @@ -439,7 +439,7 @@ def set_logical_presentation(self, resolution: tuple[int, int], mode: LogicalPre .. seealso:: https://wiki.libsdl.org/SDL3/SDL_SetRenderLogicalPresentation - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ width, height = resolution _check(lib.SDL_SetRenderLogicalPresentation(self.p, width, height, mode)) @@ -455,7 +455,7 @@ def logical_size(self) -> tuple[int, int]: .. versionadded:: 13.5 - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Setter is deprecated, use :any:`set_logical_presentation` instead. """ out = ffi.new("int[2]") @@ -537,7 +537,7 @@ def read_pixels( .. versionadded:: 15.0 - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 `format` no longer accepts `int` values. """ surface = _check_p( @@ -681,7 +681,7 @@ def geometry( .. versionadded:: 13.5 - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 `color` now takes float values instead of 8-bit integers. """ xy = np.ascontiguousarray(xy, np.float32) @@ -741,7 +741,7 @@ def new_renderer( .. seealso:: :func:`tcod.sdl.video.new_window` - .. versionchanged:: Unreleased + .. versionchanged:: 19.0 Removed `software` and `target_textures` parameters. `vsync` now takes an integer. `driver` now take a string. diff --git a/tcod/sdl/video.py b/tcod/sdl/video.py index cee02bf5..4cc0c371 100644 --- a/tcod/sdl/video.py +++ b/tcod/sdl/video.py @@ -276,7 +276,7 @@ def opacity(self, value: float) -> None: def grab(self) -> bool: """Get or set this windows input grab mode. - .. deprecated:: Unreleased + .. deprecated:: 19.0 This attribute as been split into :any:`mouse_grab` and :any:`keyboard_grab`. """ return self.mouse_grab @@ -289,7 +289,7 @@ def grab(self, value: bool) -> None: def mouse_grab(self) -> bool: """Get or set this windows mouse input grab mode. - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ return bool(lib.SDL_GetWindowMouseGrab(self.p)) @@ -303,7 +303,7 @@ def keyboard_grab(self) -> bool: https://wiki.libsdl.org/SDL3/SDL_SetWindowKeyboardGrab - .. versionadded:: Unreleased + .. versionadded:: 19.0 """ return bool(lib.SDL_GetWindowKeyboardGrab(self.p)) From 7054780028d598fb63f0a666df9ab38b9437b3f0 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Jun 2025 12:51:40 -0700 Subject: [PATCH 02/10] Build SDL3 for ReadTheDocs workflow Note PKG_CONFIG_PATH on pkg-config errors to debug issues with the environment sent to Python setup scripts. --- .readthedocs.yaml | 12 +++++++++++- setup.py | 5 +++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 8d733f5e..098d76db 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -9,7 +9,17 @@ build: tools: python: "3.11" apt_packages: - - libsdl3-dev + - build-essential + - make + - pkg-config + - cmake + - ninja-build + jobs: + pre_install: + - git clone --depth 1 --branch release-3.2.16 https://github.com/libsdl-org/SDL.git sdl_repo + - cmake -S sdl_repo -B sdl_build -D CMAKE_INSTALL_PREFIX=~/.local + - cmake --build sdl_build --config Debug + - cmake --install sdl_build submodules: include: all diff --git a/setup.py b/setup.py index 6a835e5b..534cca8b 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ from __future__ import annotations +import os import platform import subprocess import sys @@ -55,6 +56,10 @@ def check_sdl_version() -> None: "\nsdl3-config must be on PATH." ) raise RuntimeError(msg) from exc + except subprocess.CalledProcessError as exc: + if sys.version_info >= (3, 11): + exc.add_note(f"Note: {os.environ.get('PKG_CONFIG_PATH')=}") + raise print(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_VERSION_NEEDED: From 03fbcaabe901fcc1376631eb743de432976a16a5 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 13 Jun 2025 16:06:34 -0700 Subject: [PATCH 03/10] Remove leftover item from changelog This meant to mention changes to logical size, but that is already in the changelog --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e1eb24b..3327d110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,6 @@ Be sure to run [Mypy](https://mypy.readthedocs.io/en/stable/getting_started.html - `tcod.event.KeySym` single letter symbols are now all uppercase. - Relative mouse mode is set via `tcod.sdl.video.Window.relative_mouse_mode` instead of `tcod.sdl.mouse.set_relative_mode`. - `tcod.sdl.render.new_renderer`: Removed `software` and `target_textures` parameters, `vsync` takes `int`, `driver` takes `str` instead of `int`. -- SDL renderer logical - `tcod.sdl.render.Renderer`: `integer_scaling` and `logical_size` are now set with `set_logical_presentation` method. - `tcod.sdl.render.Renderer.geometry` now takes float values for `color` instead of 8-bit integers. - `tcod.event.Point` and other mouse/tile coordinate types now use `float` instead of `int`. From f2e03d0d61637742ac48463422bf7723b867bacc Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 19 Jun 2025 13:01:18 -0700 Subject: [PATCH 04/10] Update pre-commit Apply new Ruff fixes of unused ignores --- .pre-commit-config.yaml | 2 +- build_sdl.py | 2 +- examples/samples_tcod.py | 1 - setup.py | 2 +- tests/conftest.py | 2 -- tests/test_console.py | 2 -- tests/test_deprecated.py | 2 -- tests/test_libtcodpy.py | 2 -- tests/test_noise.py | 2 -- tests/test_parser.py | 2 -- tests/test_random.py | 2 -- tests/test_sdl.py | 2 -- tests/test_sdl_audio.py | 2 -- tests/test_tcod.py | 2 -- tests/test_tileset.py | 2 -- 15 files changed, 3 insertions(+), 26 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43080c04..a294765c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: fix-byte-order-marker - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.13 + rev: v0.12.0 hooks: - id: ruff-check args: [--fix-only, --exit-non-zero-on-fix] diff --git a/build_sdl.py b/build_sdl.py index 1f7d05ec..b073ab77 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -20,7 +20,7 @@ import requests # This script calls a lot of programs. -# ruff: noqa: S603, S607, T201 +# ruff: noqa: S603, S607 # Ignore f-strings in logging, these will eventually be replaced with t-strings. # ruff: noqa: G004 diff --git a/examples/samples_tcod.py b/examples/samples_tcod.py index 7c08906a..0955f2a0 100755 --- a/examples/samples_tcod.py +++ b/examples/samples_tcod.py @@ -37,7 +37,6 @@ if TYPE_CHECKING: from numpy.typing import NDArray -# ruff: noqa: S311 if not sys.warnoptions: warnings.simplefilter("default") # Show all warnings. diff --git a/setup.py b/setup.py index 534cca8b..eff99ff1 100755 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ def check_sdl_version() -> None: ).strip() except FileNotFoundError: try: - sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() # noqa: S603, S607 + sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() # noqa: S607 except FileNotFoundError as exc: msg = ( f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}." diff --git a/tests/conftest.py b/tests/conftest.py index 182cb6d6..79891a38 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,8 +11,6 @@ import tcod from tcod import libtcodpy -# ruff: noqa: D103 - def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption("--no-window", action="store_true", help="Skip tests which need a rendering context.") diff --git a/tests/test_console.py b/tests/test_console.py index 4b8f8435..18668ba6 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -9,8 +9,6 @@ import tcod import tcod.console -# ruff: noqa: D103 - def test_array_read_write() -> None: console = tcod.console.Console(width=12, height=10) diff --git a/tests/test_deprecated.py b/tests/test_deprecated.py index 82001d47..4a960bba 100644 --- a/tests/test_deprecated.py +++ b/tests/test_deprecated.py @@ -14,8 +14,6 @@ with pytest.warns(): import libtcodpy -# ruff: noqa: D103 - def test_deprecate_color() -> None: with pytest.warns(FutureWarning, match=r"\(0, 0, 0\)"): diff --git a/tests/test_libtcodpy.py b/tests/test_libtcodpy.py index 7ace644a..61895c60 100644 --- a/tests/test_libtcodpy.py +++ b/tests/test_libtcodpy.py @@ -11,8 +11,6 @@ import tcod from tcod import libtcodpy -# ruff: noqa: D103 - pytestmark = [ pytest.mark.filterwarnings("ignore::DeprecationWarning"), pytest.mark.filterwarnings("ignore::PendingDeprecationWarning"), diff --git a/tests/test_noise.py b/tests/test_noise.py index 80023f5f..28825328 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -9,8 +9,6 @@ import tcod.noise import tcod.random -# ruff: noqa: D103 - @pytest.mark.parametrize("implementation", tcod.noise.Implementation) @pytest.mark.parametrize("algorithm", tcod.noise.Algorithm) diff --git a/tests/test_parser.py b/tests/test_parser.py index 5494b786..73c54b63 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -7,8 +7,6 @@ import tcod as libtcod -# ruff: noqa: D103 - @pytest.mark.filterwarnings("ignore") def test_parser() -> None: diff --git a/tests/test_random.py b/tests/test_random.py index d20045cc..764ae988 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -8,8 +8,6 @@ import tcod.random -# ruff: noqa: D103 - SCRIPT_DIR = Path(__file__).parent diff --git a/tests/test_sdl.py b/tests/test_sdl.py index f33f4738..9d23d0c8 100644 --- a/tests/test_sdl.py +++ b/tests/test_sdl.py @@ -9,8 +9,6 @@ import tcod.sdl.sys import tcod.sdl.video -# ruff: noqa: D103 - def test_sdl_window(uses_window: None) -> None: assert tcod.sdl.video.get_grabbed_window() is None diff --git a/tests/test_sdl_audio.py b/tests/test_sdl_audio.py index f8da6155..38250758 100644 --- a/tests/test_sdl_audio.py +++ b/tests/test_sdl_audio.py @@ -12,8 +12,6 @@ import tcod.sdl.audio -# ruff: noqa: D103 - def device_works(device: Callable[[], tcod.sdl.audio.AudioDevice]) -> bool: try: diff --git a/tests/test_tcod.py b/tests/test_tcod.py index 25f60d0e..02eb6dbb 100644 --- a/tests/test_tcod.py +++ b/tests/test_tcod.py @@ -12,8 +12,6 @@ import tcod.console from tcod import libtcodpy -# ruff: noqa: D103 - def raise_Exception(*_args: object) -> NoReturn: raise RuntimeError("testing exception") # noqa: TRY003, EM101 diff --git a/tests/test_tileset.py b/tests/test_tileset.py index dd272fe7..c7281cef 100644 --- a/tests/test_tileset.py +++ b/tests/test_tileset.py @@ -2,8 +2,6 @@ import tcod.tileset -# ruff: noqa: D103 - def test_proc_block_elements() -> None: tileset = tcod.tileset.Tileset(8, 8) From 740357d8afed162e22d5699486435d3211828e8a Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 19 Jun 2025 12:43:57 -0700 Subject: [PATCH 05/10] Upgrade cibuildwheel to 3.0.0 Switch to GitHub actions and remove outdated actions Enable PyPy wheels explicitly, required by latest cibuildwheel Configure compile warnings to show but not fail on zlib implicit functions --- .github/workflows/python-package.yml | 19 +++---------------- build_libtcod.py | 1 + pyproject.toml | 3 +++ 3 files changed, 7 insertions(+), 16 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 28ef5efc..4dcbd93a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -233,23 +233,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: ${{ env.git-depth }} - - name: Set up QEMU - if: ${{ matrix.arch == 'aarch64' }} - uses: docker/setup-qemu-action@v3 - name: Checkout submodules - run: | - git submodule update --init --recursive --depth 1 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.x" - - name: Install Python dependencies - run: | - python -m pip install --upgrade pip - pip install cibuildwheel==2.23.3 + run: git submodule update --init --recursive --depth 1 - name: Build wheels - run: | - python -m cibuildwheel --output-dir wheelhouse + uses: pypa/cibuildwheel@v3.0.0 env: CIBW_BUILD: ${{ matrix.build }} CIBW_ARCHS_LINUX: ${{ matrix.arch }} @@ -312,7 +299,7 @@ jobs: # Downloads SDL for the later step. run: python build_sdl.py - name: Build wheels - uses: pypa/cibuildwheel@v2.23.3 + uses: pypa/cibuildwheel@v3.0.0 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS_MACOS: x86_64 arm64 universal2 diff --git a/build_libtcod.py b/build_libtcod.py index f9a5e3dd..df4527e0 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -199,6 +199,7 @@ def walk_sources(directory: str) -> Iterator[str]: "-fPIC", "-Wno-deprecated-declarations", "-Wno-discarded-qualifiers", # Ignore discarded restrict qualifiers. + "-Wno-error=implicit-function-declaration", # From zlib sources ], } diff --git a/pyproject.toml b/pyproject.toml index 27a3dc16..6f741af8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,6 +99,9 @@ filterwarnings = [ "ignore:'import tcod as libtcodpy' is preferred.", ] +[tool.cibuildwheel] # https://cibuildwheel.pypa.io/en/stable/options/ +enable = ["pypy"] + [tool.mypy] files = ["."] python_version = "3.10" From 9392a486aa0e5a3bfbd126c5177f86db4ff83c44 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 20 Jun 2025 03:40:16 -0700 Subject: [PATCH 06/10] Fix zlib implicit declarations --- build_libtcod.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build_libtcod.py b/build_libtcod.py index df4527e0..6de9c2ff 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -180,8 +180,8 @@ def walk_sources(directory: str) -> Iterator[str]: include_dirs.append("libtcod/src/zlib/") -if sys.platform == "darwin": - # Fix "implicit declaration of function 'close'" in zlib. +if sys.platform != "win32": + # Fix implicit declaration of multiple functions in zlib. define_macros.append(("HAVE_UNISTD_H", 1)) @@ -199,7 +199,6 @@ def walk_sources(directory: str) -> Iterator[str]: "-fPIC", "-Wno-deprecated-declarations", "-Wno-discarded-qualifiers", # Ignore discarded restrict qualifiers. - "-Wno-error=implicit-function-declaration", # From zlib sources ], } From fc0f5b34c73281779e13fd449da9f9d69a78fa50 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Thu, 19 Jun 2025 12:25:52 -0700 Subject: [PATCH 07/10] Build Pyodide wheel Disable link flags to let Emscripten take over for SDL3 Disable Py_LIMITED_API definition to workaround issue with cffi Related to #123 --- .github/workflows/python-package.yml | 27 ++++++++++++++++++++++++++- .vscode/settings.json | 1 + build_libtcod.py | 7 ++++++- build_sdl.py | 12 +++++++----- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 4dcbd93a..b6f15916 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -321,8 +321,33 @@ jobs: retention-days: 7 compression-level: 0 + pyodide: + needs: [ruff, mypy, sdist] + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: ${{ env.git-depth }} + - name: Checkout submodules + run: git submodule update --init --recursive --depth 1 + - uses: libsdl-org/setup-sdl@6574e20ac65ce362cd12f9c26b3a5e4d3cd31dee + with: + install-linux-dependencies: true + build-type: "Debug" + version: ${{ env.sdl-version }} + - uses: pypa/cibuildwheel@v3.0.0 + env: + CIBW_PLATFORM: pyodide + - name: Archive wheel + uses: actions/upload-artifact@v4 + with: + name: wheels-pyodide + path: wheelhouse/*.whl + retention-days: 30 + compression-level: 0 + publish: - needs: [sdist, build, build-macos, linux-wheels] + needs: [sdist, build, build-macos, linux-wheels, pyodide] runs-on: ubuntu-latest if: github.ref_type == 'tag' environment: diff --git a/.vscode/settings.json b/.vscode/settings.json index 446a6363..2ef44da6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -350,6 +350,7 @@ "pycall", "pycparser", "pyinstaller", + "pyodide", "pypa", "PYPI", "pypiwin", diff --git a/build_libtcod.py b/build_libtcod.py index 6de9c2ff..c7b0d577 100755 --- a/build_libtcod.py +++ b/build_libtcod.py @@ -162,7 +162,12 @@ def walk_sources(directory: str) -> Iterator[str]: libraries: list[str] = [*build_sdl.libraries] library_dirs: list[str] = [*build_sdl.library_dirs] -define_macros: list[tuple[str, Any]] = [("Py_LIMITED_API", Py_LIMITED_API)] +define_macros: list[tuple[str, Any]] = [] + +if "PYODIDE" not in os.environ: + # Unable to apply Py_LIMITED_API to Pyodide in cffi<=1.17.1 + # https://github.com/python-cffi/cffi/issues/179 + define_macros.append(("Py_LIMITED_API", Py_LIMITED_API)) sources += walk_sources("tcod/") sources += walk_sources("libtcod/src/libtcod/") diff --git a/build_sdl.py b/build_sdl.py index b073ab77..459b37b4 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -350,8 +350,9 @@ def get_cdef() -> tuple[str, dict[str, str]]: libraries: list[str] = [] library_dirs: list[str] = [] - -if sys.platform == "darwin": +if "PYODIDE" in os.environ: + pass +elif sys.platform == "darwin": extra_link_args += ["-framework", "SDL3"] else: libraries += ["SDL3"] @@ -382,6 +383,7 @@ def get_cdef() -> tuple[str, dict[str, str]]: extra_compile_args += ( subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True).strip().split() ) - extra_link_args += ( - subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() - ) + if "PYODIDE" not in os.environ: + extra_link_args += ( + subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() + ) From 6d2b2c7928627d4c6e2010c63e86b00690ca82a6 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Tue, 24 Jun 2025 21:18:22 -0700 Subject: [PATCH 08/10] Use alpha builds of Pyodide supporting SDL3 Stable version is too old and only supports SDL2 Try not to bundle win/mac binaries during Pyodide build --- .github/workflows/python-package.yml | 3 ++- .vscode/settings.json | 1 + build_sdl.py | 35 +++++++++++++++++++--------- pyproject.toml | 6 ++++- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b6f15916..1545558e 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -334,9 +334,10 @@ jobs: with: install-linux-dependencies: true build-type: "Debug" - version: ${{ env.sdl-version }} + version: "3.2.4" # Should be equal or less than the version used by Emscripten - uses: pypa/cibuildwheel@v3.0.0 env: + CIBW_BUILD: cp313-pyodide_wasm32 CIBW_PLATFORM: pyodide - name: Archive wheel uses: actions/upload-artifact@v4 diff --git a/.vscode/settings.json b/.vscode/settings.json index 2ef44da6..6dbcc428 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -141,6 +141,7 @@ "dunder", "DVLINE", "elif", + "Emscripten", "ENDCALL", "endianness", "epel", diff --git a/build_sdl.py b/build_sdl.py index 459b37b4..cc1fb787 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -249,16 +249,27 @@ def on_directive_handle( return super().on_directive_handle(directive, tokens, if_passthru, preceding_tokens) +def get_emscripten_include_dir() -> Path: + """Find and return the Emscripten include dir.""" + # None of the EMSDK environment variables exist! Search PATH for Emscripten as a workaround + for path in os.environ["PATH"].split(os.pathsep)[::-1]: + if Path(path).match("upstream/emscripten"): + return Path(path, "system/include").resolve(strict=True) + raise AssertionError(os.environ["PATH"]) + + check_sdl_version() -if sys.platform == "win32" or sys.platform == "darwin": +SDL_PARSE_PATH: Path | None = None +SDL_BUNDLE_PATH: Path | None = None +if (sys.platform == "win32" or sys.platform == "darwin") and "PYODIDE" not in os.environ: SDL_PARSE_PATH = unpack_sdl(SDL_PARSE_VERSION) SDL_BUNDLE_PATH = unpack_sdl(SDL_BUNDLE_VERSION) SDL_INCLUDE: Path -if sys.platform == "win32": +if sys.platform == "win32" and SDL_PARSE_PATH is not None: SDL_INCLUDE = SDL_PARSE_PATH / "include" -elif sys.platform == "darwin": +elif sys.platform == "darwin" and SDL_PARSE_PATH is not None: SDL_INCLUDE = SDL_PARSE_PATH / "Versions/A/Headers" else: # Unix matches = re.findall( @@ -275,6 +286,7 @@ def on_directive_handle( raise AssertionError(matches) assert SDL_INCLUDE +logger.info(f"{SDL_INCLUDE=}") EXTRA_CDEF = """ #define SDLK_SCANCODE_MASK ... @@ -358,7 +370,7 @@ def get_cdef() -> tuple[str, dict[str, str]]: libraries += ["SDL3"] # Bundle the Windows SDL DLL. -if sys.platform == "win32": +if sys.platform == "win32" and SDL_BUNDLE_PATH is not None: include_dirs.append(str(SDL_INCLUDE)) ARCH_MAPPING = {"32bit": "x86", "64bit": "x64"} SDL_LIB_DIR = Path(SDL_BUNDLE_PATH, "lib/", ARCH_MAPPING[BIT_SIZE]) @@ -372,18 +384,19 @@ def get_cdef() -> tuple[str, dict[str, str]]: # Link to the SDL framework on MacOS. # Delocate will bundle the binaries in a later step. -if sys.platform == "darwin": +if sys.platform == "darwin" and SDL_BUNDLE_PATH is not None: include_dirs.append(SDL_INCLUDE) extra_link_args += [f"-F{SDL_BUNDLE_PATH}/.."] extra_link_args += ["-rpath", f"{SDL_BUNDLE_PATH}/.."] extra_link_args += ["-rpath", "/usr/local/opt/llvm/lib/"] -# Use sdl-config to link to SDL on Linux. -if sys.platform not in ["win32", "darwin"]: +if "PYODIDE" in os.environ: + extra_compile_args += ["--use-port=sdl3"] +elif sys.platform not in ["win32", "darwin"]: + # Use sdl-config to link to SDL on Linux. extra_compile_args += ( subprocess.check_output(["pkg-config", "sdl3", "--cflags"], universal_newlines=True).strip().split() ) - if "PYODIDE" not in os.environ: - extra_link_args += ( - subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() - ) + extra_link_args += ( + subprocess.check_output(["pkg-config", "sdl3", "--libs"], universal_newlines=True).strip().split() + ) diff --git a/pyproject.toml b/pyproject.toml index 6f741af8..46a9285b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,7 +100,11 @@ filterwarnings = [ ] [tool.cibuildwheel] # https://cibuildwheel.pypa.io/en/stable/options/ -enable = ["pypy"] +enable = ["pypy", "pyodide-prerelease"] + +[tool.cibuildwheel.pyodide] +dependency-versions = "latest" # Until pyodide-version is stable on cibuildwheel +pyodide-version = "0.28.0a3" [tool.mypy] files = ["."] From 9c352c541019bd580408caed2ef2eafd98afa853 Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Wed, 25 Jun 2025 15:58:29 -0700 Subject: [PATCH 09/10] Remove redundant SDL version check from setup.py This duplicates build_sdl.py and maybe isn't as useful as it used to be. I could import from that module if I really need the check in setup.py. Ensured updated code was moved to build_sdl.py --- build_sdl.py | 19 +++++++++++++------ setup.py | 33 --------------------------------- 2 files changed, 13 insertions(+), 39 deletions(-) diff --git a/build_sdl.py b/build_sdl.py index cc1fb787..28069f2d 100755 --- a/build_sdl.py +++ b/build_sdl.py @@ -134,12 +134,19 @@ def check_sdl_version() -> None: sdl_version_str = subprocess.check_output( ["pkg-config", "sdl3", "--modversion"], universal_newlines=True ).strip() - except FileNotFoundError as exc: - msg = ( - "libsdl3-dev or equivalent must be installed on your system and must be at least version" - f" {needed_version}.\nsdl3-config must be on PATH." - ) - raise RuntimeError(msg) from exc + except FileNotFoundError: + try: + sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() + except FileNotFoundError as exc: + msg = ( + f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}." + "\nsdl3-config must be on PATH." + ) + raise RuntimeError(msg) from exc + except subprocess.CalledProcessError as exc: + if sys.version_info >= (3, 11): + exc.add_note(f"Note: {os.environ.get('PKG_CONFIG_PATH')=}") + raise logger.info(f"Found SDL {sdl_version_str}.") sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) if sdl_version < SDL_MIN_VERSION: diff --git a/setup.py b/setup.py index eff99ff1..3786a1db 100755 --- a/setup.py +++ b/setup.py @@ -3,9 +3,7 @@ from __future__ import annotations -import os import platform -import subprocess import sys from pathlib import Path @@ -37,42 +35,11 @@ def get_package_data() -> list[str]: return files -def check_sdl_version() -> None: - """Check the local SDL version on Linux distributions.""" - if not sys.platform.startswith("linux"): - return - needed_version = "{}.{}.{}".format(*SDL_VERSION_NEEDED) - try: - sdl_version_str = subprocess.check_output( - ["pkg-config", "sdl3", "--modversion"], # noqa: S607 - universal_newlines=True, - ).strip() - except FileNotFoundError: - try: - sdl_version_str = subprocess.check_output(["sdl3-config", "--version"], universal_newlines=True).strip() # noqa: S607 - except FileNotFoundError as exc: - msg = ( - f"libsdl3-dev or equivalent must be installed on your system and must be at least version {needed_version}." - "\nsdl3-config must be on PATH." - ) - raise RuntimeError(msg) from exc - except subprocess.CalledProcessError as exc: - if sys.version_info >= (3, 11): - exc.add_note(f"Note: {os.environ.get('PKG_CONFIG_PATH')=}") - raise - print(f"Found SDL {sdl_version_str}.") - sdl_version = tuple(int(s) for s in sdl_version_str.split(".")) - if sdl_version < SDL_VERSION_NEEDED: - msg = f"SDL version must be at least {needed_version}, (found {sdl_version_str})" - raise RuntimeError(msg) - - if not (SETUP_DIR / "libtcod/src").exists(): print("Libtcod submodule is uninitialized.") print("Did you forget to run 'git submodule update --init'?") sys.exit(1) -check_sdl_version() setup( py_modules=["libtcodpy"], From 32553fc6b5dba4c937e7006572fe4971e639266d Mon Sep 17 00:00:00 2001 From: Kyle Benesch <4b796c65+github@gmail.com> Date: Fri, 27 Jun 2025 16:53:04 -0700 Subject: [PATCH 10/10] Note that TextInput is no longer enabled by default Caused by SDL3 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3327d110..4bdb3102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Be sure to run [Mypy](https://mypy.readthedocs.io/en/stable/getting_started.html - Sound queueing methods were moved from `AudioDevice` to a new `AudioStream` class. - `BasicMixer` may require manually specifying `frequency` and `channels` to replicate old behavior. - `get_devices` and `get_capture_devices` now return `dict[str, AudioDevice]`. +- `TextInput` events are no longer enabled by default. ### Deprecated