From 6c508b3943c292fdb33e058fdfde3532530a43c6 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 25 Mar 2026 10:41:19 +0000 Subject: [PATCH 1/2] Adjust packaging of catalog games into Python build. This makes a few adjustments to how we include the catalog games in the Python build. Support for 'package data' in Python isn't great currently, and further we have a desire to keep the catalog games elsewhere in our tree and not embedded inside the Python source. Our solution is this: * We have a custom build class, which is responsible for copying the contents of the catalog directory from where it lives into the build directory. * We put this in "catalog_data" to avoid clashes with e.g. just having a "catalog" source file or module. The extra build step is moderately annoying but straightforward. However, it avoids e.g. `pip` thinking that the catalog data is Python code and complaining about the package not being listed. Closes #823. --- pyproject.toml | 6 +++--- setup.py | 18 +++++++++++++++++- catalog/__init__.py => src/pygambit/catalog.py | 4 ++-- 3 files changed, 22 insertions(+), 6 deletions(-) rename catalog/__init__.py => src/pygambit/catalog.py (92%) diff --git a/pyproject.toml b/pyproject.toml index 71134753d..553e949fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,11 +114,11 @@ markers = [ ] [tool.setuptools] -packages = ["pygambit", "pygambit.catalog"] -package-dir = { "pygambit" = "src/pygambit", "pygambit.catalog" = "catalog" } +packages = ["pygambit"] +package-dir = { "pygambit" = "src/pygambit" } [tool.setuptools.package-data] -"pygambit.catalog" = ["*"] +pygambit = ["catalog_data/**/*"] [tool.setuptools.dynamic] version = {file = "build_support/GAMBIT_VERSION"} diff --git a/setup.py b/setup.py index 4a1e82b5a..9b47eb533 100644 --- a/setup.py +++ b/setup.py @@ -21,10 +21,12 @@ # import glob +import pathlib import platform +import shutil import Cython.Build -import setuptools +import setuptools.command.build_py cppgambit_include_dirs = ["src"] cppgambit_cflags = ( @@ -78,6 +80,19 @@ def solver_library_config(library_name: str, paths: list) -> tuple: ) +class GambitBuildPy(setuptools.command.build_py.build_py): + """Extend `build_py` to copy the catalog games data into the build library.""" + def run(self) -> None: + super().run() + + catalog_source = pathlib.Path("catalog") + catalog_target = pathlib.Path(self.build_lib) / "pygambit/catalog_data" + if catalog_target.exists(): + shutil.rmtree(catalog_target) + catalog_target.mkdir(exist_ok=True, parents=True) + shutil.copytree(catalog_source, catalog_target, dirs_exist_ok=True) + + cppgambit_bimatrix = solver_library_config("cppgambit_bimatrix", ["linalg", "lp", "lcp", "enummixed"]) cppgambit_liap = solver_library_config("cppgambit_liap", ["liap"]) @@ -100,6 +115,7 @@ def solver_library_config(library_name: str, paths: list) -> tuple: ) setuptools.setup( + cmdclass={"build_py": GambitBuildPy}, libraries=[cppgambit_bimatrix, cppgambit_liap, cppgambit_logit, cppgambit_simpdiv, cppgambit_gtracer, cppgambit_enumpoly, cppgambit_games, cppgambit_core], diff --git a/catalog/__init__.py b/src/pygambit/catalog.py similarity index 92% rename from catalog/__init__.py rename to src/pygambit/catalog.py index 4a972d917..282ee6f11 100644 --- a/catalog/__init__.py +++ b/src/pygambit/catalog.py @@ -5,8 +5,8 @@ import pygambit as gbt -# Use the full string path to the virtual package we created -_CATALOG_RESOURCE = files(__name__) +# Use the full string path to where the catalog data are placed in the package +_CATALOG_RESOURCE = files("pygambit")/"catalog_data" READERS = { ".nfg": gbt.read_nfg, From d157d8d6734743373fe368dd96fe694caf649fc5 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 25 Mar 2026 12:04:33 +0000 Subject: [PATCH 2/2] Revising documentation to explain how catalog games are packaged in Python. --- doc/developer.catalog.rst | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/doc/developer.catalog.rst b/doc/developer.catalog.rst index a967369ba..c68ee2527 100644 --- a/doc/developer.catalog.rst +++ b/doc/developer.catalog.rst @@ -8,18 +8,6 @@ To do so, you will need to have the `gambit` GitHub repo cloned and be able to s you may wish to first review the :ref:`contributor guidelines `. You'll also need to have a developer install of `pygambit` available in your Python environment, see :ref:`build-python`. -The catalog module ------------------- - -Although the ``catalog`` directory is located at the project root outside of ``src/pygambit/``, it is installed and bundled as the ``pygambit.catalog`` subpackage. - -This is handled by the package build configuration in ``pyproject.toml`` under ``[tool.setuptools]``: - -- The ``package-dir`` mapping instructs ``setuptools`` to source the ``pygambit.catalog`` subpackage from the physical ``catalog`` directory. -- The ``package-data`` configuration ensures all non-Python data files (like ``.efg`` and ``.nfg`` files) inside the catalog are correctly bundled during installation. - -As a developer, this means you will need to reinstall the package (e.g., passing ``pip install .``) for any new game files or internal catalog changes to be reflected in the ``pygambit`` module. - Add new game files ------------------ @@ -58,3 +46,20 @@ Currently supported representations are: .. warning:: Make sure you commit all changed files e.g. run ``git add --all`` before committing and pushing. + + +Access from pygambit +-------------------- + +We keep the ``catalog`` directory at the top level of the repository because it is in principle independent +of the Python and C++ code. However, in order to include these games with the Python package, there is a bit +of extra infrastructure. + +In ``setup.py`` we have a custom build command which first copies the contents of ``catalog/`` into the build +directory for the Python package. These are then exposed as data in the ``catalog_data`` directory (changing +the name to avoid confusion or clashes with ``catalog.py``, which is responsible for accessing the catalog). + +The main implication is that if you are working via the Python package and you add new games to the catalog, +you will need to rebuild and reinstall the Python extension in order to access the new games. That is, changing +the contents of the catalog is no different than changing any other source code in the Python package; you'll +need to execute ``pip install .`` after the addition or change.