diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 529fae190..c83c2bcd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -156,7 +156,8 @@ jobs: verify-apps: name: Build app needs: unit-tests - uses: beeware/.github/.github/workflows/app-build-verify.yml@main +# uses: beeware/.github/.github/workflows/app-build-verify.yml@main + uses: rmartin16/.github-beeware/.github/workflows/app-build-verify.yml@gui-plugin-support with: # This *must* be the version of Python that is the system Python on the # Ubuntu version used to run Linux tests. We use a fixed ubuntu-22.04 @@ -166,6 +167,8 @@ jobs: python-version: "3.10" runner-os: ${{ matrix.runner-os }} framework: ${{ matrix.framework }} + briefcase-template-source: https://github.com/rmartin16/briefcase-template.git + briefcase-template-branch: gui-plugin-support strategy: fail-fast: false matrix: diff --git a/changes/1524.feature.rst b/changes/1524.feature.rst new file mode 100644 index 000000000..cd52cfa51 --- /dev/null +++ b/changes/1524.feature.rst @@ -0,0 +1 @@ +Creating new projects with arbitrary third-party GUI frameworks is now supported via plugins. diff --git a/setup.cfg b/setup.cfg index 810541366..eedd1a3d2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -112,6 +112,12 @@ where = src [options.entry_points] console_scripts = briefcase = briefcase.__main__:main +briefcase.wizard.frameworks = + Toga = briefcase.plugins.frameworks.toga + PySide2 = briefcase.plugins.frameworks.pyside2 + PySide6 = briefcase.plugins.frameworks.pyside6 + PursuedPyBear = briefcase.plugins.frameworks.pursuedpybear + Pygame = briefcase.plugins.frameworks.pygame briefcase.platforms = android = briefcase.platforms.android iOS = briefcase.platforms.iOS diff --git a/src/briefcase/commands/new.py b/src/briefcase/commands/new.py index aa24ce22b..23c2106ba 100644 --- a/src/briefcase/commands/new.py +++ b/src/briefcase/commands/new.py @@ -1,7 +1,10 @@ +from __future__ import annotations + +import contextlib import re import unicodedata from email.utils import parseaddr -from typing import Optional +from types import ModuleType from urllib.parse import urlparse from packaging.version import Version @@ -17,6 +20,11 @@ from .base import BaseCommand +try: + from importlib_metadata import entry_points +except ImportError: # pragma: no-cover-if-lt-py310 + from importlib.metadata import entry_points + def titlecase(s): """Convert a string to titlecase. @@ -60,6 +68,14 @@ def titlecase(s): ) +def get_frameworks() -> dict[str, ModuleType]: + """Loads built-in and third party GUI frameworks.""" + return { + entry_point.name: entry_point.load() + for entry_point in entry_points(group="briefcase.wizard.frameworks") + } + + class NewCommand(BaseCommand): cmd_line = "briefcase new" command = "new" @@ -401,21 +417,27 @@ def build_app_context(self): ], ) + frameworks = get_frameworks() + framework_choices = [ + "Toga", + "PySide2 (does not support iOS/Android deployment)", + "PySide6 (does not support iOS/Android deployment)", + "PursuedPyBear (does not support iOS/Android deployment)", + "Pygame (does not support iOS/Android deployment)", + ] + builtin_framework_names = [n.split(" ")[0] for n in framework_choices] + framework_choices += [ + f for f in frameworks.keys() if f not in builtin_framework_names + ] + ["None"] + gui_framework = self.input_select( intro=""" What GUI toolkit do you want to use for this project?""", variable="GUI framework", - options=[ - "Toga", - "PySide2 (does not support iOS/Android deployment)", - "PySide6 (does not support iOS/Android deployment)", - "PursuedPyBear (does not support iOS/Android deployment)", - "Pygame (does not support iOS/Android deployment)", - "None", - ], + options=framework_choices, ) - return { + context = { "formal_name": formal_name, "app_name": app_name, "class_name": class_name, @@ -427,13 +449,28 @@ def build_app_context(self): "bundle": bundle, "url": url, "license": project_license, - "gui_framework": (gui_framework.split())[0], + } + + plugin_context = {} + if gui_framework != "None": + try: + plugin = frameworks[gui_framework].plugin(context=context) + except KeyError: + plugin = frameworks[gui_framework.split(" ")[0]].plugin(context=context) + for context_field in plugin.fields: + with contextlib.suppress(AttributeError): + if (context_value := getattr(plugin, context_field)()) is not None: + plugin_context[context_field] = context_value + + return { + **context, + **plugin_context, } def new_app( self, - template: Optional[str] = None, - template_branch: Optional[str] = None, + template: str | None = None, + template_branch: str | None = None, **options, ): """Ask questions to generate a new application, and generate a stub project from @@ -520,8 +557,8 @@ def verify_tools(self): def __call__( self, - template: Optional[str] = None, - template_branch: Optional[str] = None, + template: str | None = None, + template_branch: str | None = None, **options, ): # Confirm host compatibility, and that all required tools are available. diff --git a/src/briefcase/plugins/__init__.py b/src/briefcase/plugins/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/briefcase/plugins/frameworks/__init__.py b/src/briefcase/plugins/frameworks/__init__.py new file mode 100644 index 000000000..bcf0c7eab --- /dev/null +++ b/src/briefcase/plugins/frameworks/__init__.py @@ -0,0 +1,8 @@ +from briefcase.plugins.frameworks.base import BaseGuiPlugin # noqa: F401 +from briefcase.plugins.frameworks.pursuedpybear import ( # noqa: F401 + PursuedPyBearGuiPlugin, +) +from briefcase.plugins.frameworks.pygame import PygameGuiPlugin # noqa: F401 +from briefcase.plugins.frameworks.pyside2 import PySide2GuiPlugin # noqa: F401 +from briefcase.plugins.frameworks.pyside6 import PySide6GuiPlugin # noqa: F401 +from briefcase.plugins.frameworks.toga import TogaGuiPlugin # noqa: F401 diff --git a/src/briefcase/plugins/frameworks/base.py b/src/briefcase/plugins/frameworks/base.py new file mode 100644 index 000000000..f2aa00ee7 --- /dev/null +++ b/src/briefcase/plugins/frameworks/base.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +from abc import ABC +from typing import Literal + + +class BaseGuiPlugin(ABC): + name: str + fields: list[str] = [ + "app_source", + "start_app_source", + "requires", + "macos_requires", + "macos_universal_build", + "linux_requires", + "linux_system_debian_system_requires", + "linux_system_debian_system_runtime_requires", + "linux_system_rhel_system_requires", + "linux_system_rhel_system_runtime_requires", + "linux_system_suse_system_requires", + "linux_system_suse_system_runtime_requires", + "linux_system_arch_system_requires", + "linux_system_arch_system_runtime_requires", + "linux_appimage_manylinux", + "linux_appimage_system_requires", + "linux_appimage_linuxdeploy_plugins", + "linux_flatpak_runtime", + "linux_flatpak_runtime_version", + "linux_flatpak_sdk", + "windows_requires", + "ios_requires", + "ios_supported", + "android_requires", + "android_supported", + "web_requires", + "web_supported", + "web_style_framework", + ] + + def __init__(self, context: dict[str, str | int | bool]): + # context contains metadata about the app: + # formal_name + # app_name + # class_name + # module_name + # project_name + # description + # author + # author_email + # bundle + # url + # license + self.context = context + + def app_source(self) -> str | None: + """The Python source code for the project.""" + + def start_app_source(self) -> str | None: + """The Python source code to start the app from __main__.py.""" + + def requires(self) -> str | None: + """List of package requirements for all platforms.""" + + def macos_requires(self) -> str | None: + """List of package requirements for macOS.""" + + def macos_universal_build(self) -> Literal["true", "false"] | None: + """Whether to create a universal build for macOS.""" + + def linux_requires(self) -> str | None: + """List of package requirements for Linux.""" + + def linux_system_debian_system_requires(self) -> str | None: + """List of system package requirements to build the app.""" + + def linux_system_debian_system_runtime_requires(self) -> str | None: + """List of system package requirements to run the app on Debian.""" + + def linux_system_rhel_system_requires(self) -> str | None: + """List of system package requirements to build the app on RHEL.""" + + def linux_system_rhel_system_runtime_requires(self) -> str | None: + """List of system package requirements to run the app on RHEL.""" + + def linux_system_suse_system_requires(self) -> str | None: + """List of system package requirements to build the app on SUSE.""" + + def linux_system_suse_system_runtime_requires(self) -> str | None: + """List of system package requirements to run the app on SUSE.""" + + def linux_system_arch_system_requires(self) -> str | None: + """List of system package requirements to build the app on Arch.""" + + def linux_system_arch_system_runtime_requires(self) -> str | None: + """List of system package requirements to run the app on Arch.""" + + def linux_appimage_manylinux(self) -> str | None: + """The manylinux base, e.g. manylinux2014, to use to build the app.""" + + def linux_appimage_system_requires(self) -> str | None: + """List of system package requirements to build the app in to an AppImage.""" + + def linux_appimage_linuxdeploy_plugins(self) -> str | None: + """List of linuxdeploy plugins to use to build the app in to an AppImage.""" + + def linux_flatpak_runtime(self) -> str | None: + """The Flatpak runtime, e.g. org.gnome.Platform, for the app.""" + + def linux_flatpak_runtime_version(self) -> str | None: + """The Flatpak runtime version, e.g. 44, for the app.""" + + def linux_flatpak_sdk(self) -> str | None: + """The Flatpak SDK, e.g. org.gnome.Sdk, for the app.""" + + def windows_requires(self) -> str | None: + """List of package requirements for Windows.""" + + def ios_requires(self) -> str | None: + """List of package requirements for iOS.""" + + def ios_supported(self) -> Literal["true", "false"] | None: + """Whether the GUI framework supports iOS.""" + + def android_requires(self) -> str | None: + """List of package requirements for Android.""" + + def android_supported(self) -> Literal["true", "false"] | None: + """Whether the GUI framework supports Android.""" + + def web_requires(self) -> str | None: + """List of package requirements for Web.""" + + def web_supported(self) -> Literal["true", "false"] | None: + """Whether the GUI framework supports Web.""" + + def web_style_framework(self) -> str | None: + """The style framework, e.g. Bootstrap or Shoelace, for web.""" diff --git a/src/briefcase/plugins/frameworks/pursuedpybear.py b/src/briefcase/plugins/frameworks/pursuedpybear.py new file mode 100644 index 000000000..cc852b50c --- /dev/null +++ b/src/briefcase/plugins/frameworks/pursuedpybear.py @@ -0,0 +1,71 @@ +from briefcase.plugins.frameworks.base import BaseGuiPlugin + + +class PursuedPyBearGuiPlugin(BaseGuiPlugin): + name = "PursuedPyBear" + + def app_source(self): + return """ +import os +import sys + +try: + from importlib import metadata as importlib_metadata +except ImportError: + # Backwards compatibility - importlib.metadata was added in Python 3.8 + import importlib_metadata + +import ppb + + +class {{ cookiecutter.class_name }}(ppb.Scene): + def __init__(self, **props): + super().__init__(**props) + + self.add(ppb.Sprite( + image=ppb.Image('{{ cookiecutter.module_name }}/resources/{{ cookiecutter.app_name }}.png'), + )) + + +def main(): + # Linux desktop environments use app's .desktop file to integrate the app + # to their application menus. The .desktop file of this app will include + # StartupWMClass key, set to app's formal name, which helps associate + # app's windows to its menu item. + # + # For association to work any windows of the app must have WMCLASS + # property set to match the value set in app's desktop file. For PPB this + # is set using environment variable. + + # Find the name of the module that was used to start the app + app_module = sys.modules['__main__'].__package__ + # Retrieve the app's metadata + metadata = importlib_metadata.metadata(app_module) + + os.environ['SDL_VIDEO_X11_WMCLASS'] = metadata['Formal-Name'] + + ppb.run( + starting_scene={{ cookiecutter.class_name }}, + title=metadata['Formal-Name'], + ) + """ + + def requires(self): + return """ + "ppb~=1.1", +""" + + def linux_appimage_manylinux(self): + return "manylinux2014" + + def linux_flatpak_runtime(self): + return "org.freedesktop.Platform" + + def linux_flatpak_runtime_version(self): + return "22.08" + + def linux_flatpak_sdk(self): + return "org.freedesktop.Sdk" + + +plugin = PursuedPyBearGuiPlugin diff --git a/src/briefcase/plugins/frameworks/pygame.py b/src/briefcase/plugins/frameworks/pygame.py new file mode 100644 index 000000000..e9ebfe7b8 --- /dev/null +++ b/src/briefcase/plugins/frameworks/pygame.py @@ -0,0 +1,76 @@ +from briefcase.plugins.frameworks.base import BaseGuiPlugin + + +class PygameGuiPlugin(BaseGuiPlugin): + name = "Pygame" + + def app_source(self): + return """ +import os +import sys + +import pygame + +try: + from importlib import metadata as importlib_metadata +except ImportError: + # Backwards compatibility - importlib.metadata was added in Python 3.8 + import importlib_metadata + +SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600 +WHITE = (255, 255, 255) + + +def main(): + # Linux desktop environments use app's .desktop file to integrate the app + # to their application menus. The .desktop file of this app will include + # StartupWMClass key, set to app's formal name, which helps associate + # app's windows to its menu item. + # + # For association to work any windows of the app must have WMCLASS + # property set to match the value set in app's desktop file. For PPB this + # is set using environment variable. + + # Find the name of the module that was used to start the app + app_module = sys.modules["__main__"].__package__ + # Retrieve the app's metadata + metadata = importlib_metadata.metadata(app_module) + + os.environ["SDL_VIDEO_X11_WMCLASS"] = metadata["Formal-Name"] + + pygame.init() + pygame.display.set_caption(metadata["Formal-Name"]) + screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) + + running = True + while running: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + break + + screen.fill(WHITE) + pygame.display.flip() + + pygame.quit() + """ + + def requires(self): + return """ + "pygame~=2.2", +""" + + def linux_appimage_manylinux(self): + return "manylinux2014" + + def linux_flatpak_runtime(self): + return "org.freedesktop.Platform" + + def linux_flatpak_runtime_version(self): + return "22.08" + + def linux_flatpak_sdk(self): + return "org.freedesktop.Sdk" + + +plugin = PygameGuiPlugin diff --git a/src/briefcase/plugins/frameworks/pyside2.py b/src/briefcase/plugins/frameworks/pyside2.py new file mode 100644 index 000000000..f2e155a1c --- /dev/null +++ b/src/briefcase/plugins/frameworks/pyside2.py @@ -0,0 +1,133 @@ +from briefcase.plugins.frameworks.base import BaseGuiPlugin + + +class PySide2GuiPlugin(BaseGuiPlugin): + name = "PySide2" + + def app_source(self): + return """ +import sys + +try: + from importlib import metadata as importlib_metadata +except ImportError: + # Backwards compatibility - importlib.metadata was added in Python 3.8 + import importlib_metadata + +from PySide2 import QtWidgets + + +class {{ cookiecutter.class_name }}(QtWidgets.QMainWindow): + def __init__(self): + super().__init__() + self.init_ui() + + def init_ui(self): + self.setWindowTitle('{{ cookiecutter.app_name }}') + self.show() + + +def main(): + # Linux desktop environments use app's .desktop file to integrate the app + # to their application menus. The .desktop file of this app will include + # StartupWMClass key, set to app's formal name, which helps associate + # app's windows to its menu item. + # + # For association to work any windows of the app must have WMCLASS + # property set to match the value set in app's desktop file. For PySide2 + # this is set with setApplicationName(). + + # Find the name of the module that was used to start the app + app_module = sys.modules['__main__'].__package__ + # Retrieve the app's metadata + metadata = importlib_metadata.metadata(app_module) + + QtWidgets.QApplication.setApplicationName(metadata['Formal-Name']) + + app = QtWidgets.QApplication(sys.argv) + main_window = {{ cookiecutter.class_name }}() + sys.exit(app.exec_()) +""" + + def requires(self): + return """ + "pyside2~=5.15", +""" + + def macos_requires(self): + return """ + "toga-cocoa~=0.4.0", +""" + + def linux_system_debian_system_requires(self): + return "" + + def linux_system_debian_system_runtime_requires(self): + return """ + # Derived from https://doc.qt.io/qt-6/linux-requirements.html + "libxrender1", + "libxcb-render0", + "libxcb-render-util0", + "libxcb-shape0", + "libxcb-randr0", + "libxcb-xfixes0", + "libxcb-xkb1", + "libxcb-sync1", + "libxcb-shm0", + "libxcb-icccm4", + "libxcb-keysyms1", + "libxcb-image0", + "libxcb-util1", + "libxkbcommon0", + "libxkbcommon-x11-0", + "libfontconfig1", + "libfreetype6", + "libxext6", + "libx11-6", + "libxcb1", + "libx11-xcb1", + "libsm6", + "libice6", + "libglib2.0-0", + "libgl1", + "libegl1-mesa", + "libdbus-1-3", + "libgssapi-krb5-2", +""" + + def linux_system_rhel_system_requires(self): + return "" + + def linux_system_rhel_system_runtime_requires(self): + return """ + "qt5-qtbase-gui", +""" + + def linux_system_suse_system_requires(self): + return "" + + def linux_system_suse_system_runtime_requires(self): + return """ + "libQt5Gui5", +""" + + def linux_system_arch_system_requires(self): + return "" + + def linux_system_arch_system_runtime_requires(self): + return "" + + def linux_appimage_manylinux(self): + return "manylinux2014" + + def linux_flatpak_runtime(self): + return "org.kde.Platform" + + def linux_flatpak_runtime_version(self): + return "6.4" + + def linux_flatpak_sdk(self): + return "org.kde.Sdk" + + +plugin = PySide2GuiPlugin diff --git a/src/briefcase/plugins/frameworks/pyside6.py b/src/briefcase/plugins/frameworks/pyside6.py new file mode 100644 index 000000000..40cbb0822 --- /dev/null +++ b/src/briefcase/plugins/frameworks/pyside6.py @@ -0,0 +1,133 @@ +from briefcase.plugins.frameworks.base import BaseGuiPlugin + + +class PySide6GuiPlugin(BaseGuiPlugin): + name = "PySide6" + + def app_source(self): + return """ +import sys + +try: + from importlib import metadata as importlib_metadata +except ImportError: + # Backwards compatibility - importlib.metadata was added in Python 3.8 + import importlib_metadata + +from PySide6 import QtWidgets + + +class {{ cookiecutter.class_name }}(QtWidgets.QMainWindow): + def __init__(self): + super().__init__() + self.init_ui() + + def init_ui(self): + self.setWindowTitle('{{ cookiecutter.app_name }}') + self.show() + + +def main(): + # Linux desktop environments use app's .desktop file to integrate the app + # to their application menus. The .desktop file of this app will include + # StartupWMClass key, set to app's formal name, which helps associate + # app's windows to its menu item. + # + # For association to work any windows of the app must have WMCLASS + # property set to match the value set in app's desktop file. For PySide2 + # this is set with setApplicationName(). + + # Find the name of the module that was used to start the app + app_module = sys.modules['__main__'].__package__ + # Retrieve the app's metadata + metadata = importlib_metadata.metadata(app_module) + + QtWidgets.QApplication.setApplicationName(metadata['Formal-Name']) + + app = QtWidgets.QApplication(sys.argv) + main_window = {{ cookiecutter.class_name }}() + sys.exit(app.exec()) +""" + + def requires(self): + return """ + "PySide6-Essentials~=6.5", + # "PySide6-Addons~=6.5", +""" + + def linux_system_debian_system_requires(self): + return "" + + def linux_system_debian_system_runtime_requires(self): + return """ + # Derived from https://doc.qt.io/qt-6/linux-requirements.html + "libxrender1", + "libxcb-render0", + "libxcb-render-util0", + "libxcb-shape0", + "libxcb-randr0", + "libxcb-xfixes0", + "libxcb-xkb1", + "libxcb-sync1", + "libxcb-shm0", + "libxcb-icccm4", + "libxcb-keysyms1", + "libxcb-image0", + "libxcb-util1", + "libxkbcommon0", + "libxkbcommon-x11-0", + "libfontconfig1", + "libfreetype6", + "libxext6", + "libx11-6", + "libxcb1", + "libx11-xcb1", + "libsm6", + "libice6", + "libglib2.0-0", + "libgl1", + "libegl1-mesa", + "libdbus-1-3", + "libgssapi-krb5-2", +""" + + def linux_system_rhel_system_requires(self): + return "" + + def linux_system_rhel_system_runtime_requires(self): + return """ + "qt6-qtbase-gui", +""" + + def linux_system_suse_system_requires(self): + return "" + + def linux_system_suse_system_runtime_requires(self): + return """ + "libQt6Gui6", +""" + + def linux_system_arch_system_requires(self): + return """ + "qt6-base", +""" + + def linux_system_arch_system_runtime_requires(self): + return """ + "qt6-base", +""" + + def linux_appimage_manylinux(self): + return "manylinux_2_28" + + def linux_flatpak_runtime(self): + return "org.kde.Platform" + + def linux_flatpak_runtime_version(self): + return "6.4" + + def linux_flatpak_sdk(self): + return "org.kde.Sdk" + + +plugin = PySide6GuiPlugin diff --git a/src/briefcase/plugins/frameworks/toga.py b/src/briefcase/plugins/frameworks/toga.py new file mode 100644 index 000000000..16ec6fdd0 --- /dev/null +++ b/src/briefcase/plugins/frameworks/toga.py @@ -0,0 +1,207 @@ +from briefcase.plugins.frameworks.base import BaseGuiPlugin + + +class TogaGuiPlugin(BaseGuiPlugin): + name = "Toga" + + def app_source(self): + return ''' +import toga +from toga.style import Pack +from toga.style.pack import COLUMN, ROW + + +class {{ cookiecutter.class_name }}(toga.App): + + def startup(self): + """Construct and show the Toga application. + + Usually, you would add your application to a main content box. We then create a + main window (with a name matching the app), and show the main window. + """ + main_box = toga.Box() + + self.main_window = toga.MainWindow(title=self.formal_name) + self.main_window.content = main_box + self.main_window.show() + + +def main(): + return {{ cookiecutter.class_name }}() +''' + + def start_app_source(self): + return " main().main_loop()" + + def requires(self): + return None + + def macos_requires(self): + return """ + "toga-cocoa~=0.4.0", + "std-nslog~=1.0.0", +""" + + def macos_universal_build(self): + return "true" + + def linux_requires(self): + return """ + "toga-gtk~=0.4.0", +""" + + def linux_system_debian_system_requires(self): + return """ + # Needed to compile pycairo wheel + "libcairo2-dev", + # Needed to compile PyGObject wheel + "libgirepository1.0-dev", +""" + + def linux_system_debian_system_runtime_requires(self): + return """ + # Needed to provide GTK and its GI bindings + "gir1.2-gtk-3.0", + "libgirepository-1.0-1", + # Dependencies that GTK looks for at runtime + "libcanberra-gtk3-module", + # Needed to provide WebKit2 at runtime + # "gir1.2-webkit2-4.0", +""" + + def linux_system_rhel_system_requires(self): + return """ + # Needed to compile pycairo wheel + "cairo-gobject-devel", + # Needed to compile PyGObject wheel + "gobject-introspection-devel", +""" + + def linux_system_rhel_system_runtime_requires(self): + return """ + # Needed to support Python bindings to GTK + "gobject-introspection", + # Needed to provide GTK + "gtk3", + # Dependencies that GTK looks for at runtime + "libcanberra-gtk3", + # Needed to provide WebKit2 at runtime + # "webkit2gtk3", +""" + + def linux_system_suse_system_requires(self): + return """ + # Needed to compile pycairo wheel + "cairo-devel", + # Needed to compile PyGObject wheel + "gobject-introspection-devel", +""" + + def linux_system_suse_system_runtime_requires(self): + return """ + # Needed to provide GTK + "gtk3", + # Needed to support Python bindings to GTK + "gobject-introspection", "typelib(Gtk)=3.0", + # Dependencies that GTK looks for at runtime + "libcanberra-gtk3-0", + # Needed to provide WebKit2 at runtime + # "libwebkit2gtk3", + # "typelib(WebKit2)", +""" + + def linux_system_arch_system_requires(self): + return """ + # Needed to compile pycairo wheel + "cairo", + # Needed to compile PyGObject wheel + "gobject-introspection", + # Runtime dependencies that need to exist so that the + # Arch package passes final validation. + # Needed to provide GTK + "gtk3", + # Dependencies that GTK looks for at runtime + "libcanberra", + # Needed to provide WebKit2 + # "webkit2gtk", +""" + + def linux_system_arch_system_runtime_requires(self): + return """ + # Needed to provide GTK + "gtk3", + # Needed to provide PyGObject bindings + "gobject-introspection-runtime", + # Dependencies that GTK looks for at runtime + "libcanberra", + # Needed to provide WebKit2 at runtime + # "webkit2gtk", +""" + + def linux_appimage_manylinux(self): + return "manylinux2014" + + def linux_appimage_system_requires(self): + return """ + # Needed to compile pycairo wheel + "cairo-gobject-devel", + # Needed to compile PyGObject wheel + "gobject-introspection-devel", + # Needed to provide GTK + "gtk3-devel", + # Dependencies that GTK looks for at runtime, that need to be + # in the build environment to be picked up by linuxdeploy + "libcanberra-gtk3", + "PackageKit-gtk3-module", + "gvfs-client", +""" + + def linux_appimage_linuxdeploy_plugins(self): + return """ + "DEPLOY_GTK_VERSION=3 gtk", +""" + + def linux_flatpak_runtime(self): + return "org.gnome.Platform" + + def linux_flatpak_runtime_version(self): + return "44" + + def linux_flatpak_sdk(self): + return "org.gnome.Sdk" + + def windows_requires(self): + return """ + "toga-winforms~=0.4.0", +""" + + def ios_requires(self): + return """ + "toga-iOS~=0.4.0", + "std-nslog~=1.0.0", +""" + + def ios_supported(self): + return "true" + + def android_requires(self): + return """ + "toga-android~=0.4.0", +""" + + def android_supported(self): + return "true" + + def web_requires(self): + return """ + "toga-web~=0.4.0", +""" + + def web_supported(self): + return "true" + + def web_style_framework(self): + return "Shoelace v2.3" + + +plugin = TogaGuiPlugin diff --git a/tests/commands/new/test_build_app_context.py b/tests/commands/new/test_build_app_context.py index 238983738..08f0e58ac 100644 --- a/tests/commands/new/test_build_app_context.py +++ b/tests/commands/new/test_build_app_context.py @@ -1,4 +1,4 @@ -def test_question_sequence(new_command): +def test_question_sequence_toga(new_command): """Questions are asked, a context is constructed.""" # Prime answers for all the questions. @@ -12,28 +12,296 @@ def test_question_sequence(new_command): "grace@navy.mil", # author email "https://navy.mil/myapplication", # URL "4", # license - "1", # GUI toolkit + "1", # Toga GUI toolkit ] assert new_command.build_app_context() == { - "formal_name": "My Application", - "class_name": "MyApplication", + "android_requires": '\n "toga-android~=0.4.0",\n', + "android_supported": "true", "app_name": "myapplication", - "module_name": "myapplication", + "app_source": "\n" + "import toga\n" + "from toga.style import Pack\n" + "from toga.style.pack import COLUMN, ROW\n" + "\n" + "\n" + "class {{ cookiecutter.class_name }}(toga.App):\n" + "\n" + " def startup(self):\n" + ' """Construct and show the Toga application.\n' + "\n" + " Usually, you would add your application to a main " + "content box. We then create a\n" + " main window (with a name matching the app), and show " + "the main window.\n" + ' """\n' + " main_box = toga.Box()\n" + "\n" + " self.main_window = " + "toga.MainWindow(title=self.formal_name)\n" + " self.main_window.content = main_box\n" + " self.main_window.show()\n" + "\n" + "\n" + "def main():\n" + " return {{ cookiecutter.class_name }}()\n", + "author": "Grace Hopper", + "author_email": "grace@navy.mil", "bundle": "org.beeware", - "project_name": "My Project", + "class_name": "MyApplication", "description": "Cool stuff", + "formal_name": "My Application", + "ios_requires": '\n "toga-iOS~=0.4.0",\n "std-nslog~=1.0.0",\n', + "ios_supported": "true", + "license": "GNU General Public License v2 (GPLv2)", + "linux_appimage_linuxdeploy_plugins": '\n "DEPLOY_GTK_VERSION=3 gtk",\n', + "linux_appimage_manylinux": "manylinux2014", + "linux_appimage_system_requires": "\n" + " # Needed to compile pycairo wheel\n" + ' "cairo-gobject-devel",\n' + " # Needed to compile PyGObject wheel\n" + ' "gobject-introspection-devel",\n' + " # Needed to provide GTK\n" + ' "gtk3-devel",\n' + " # Dependencies that GTK looks for at " + "runtime, that need to be\n" + " # in the build environment to be " + "picked up by linuxdeploy\n" + ' "libcanberra-gtk3",\n' + ' "PackageKit-gtk3-module",\n' + ' "gvfs-client",\n', + "linux_flatpak_runtime": "org.gnome.Platform", + "linux_flatpak_runtime_version": "44", + "linux_flatpak_sdk": "org.gnome.Sdk", + "linux_requires": '\n "toga-gtk~=0.4.0",\n', + "linux_system_arch_system_requires": "\n" + " # Needed to compile pycairo wheel\n" + ' "cairo",\n' + " # Needed to compile PyGObject " + "wheel\n" + ' "gobject-introspection",\n' + " # Runtime dependencies that need to " + "exist so that the\n" + " # Arch package passes final " + "validation.\n" + " # Needed to provide GTK\n" + ' "gtk3",\n' + " # Dependencies that GTK looks for " + "at runtime\n" + ' "libcanberra",\n' + " # Needed to provide WebKit2\n" + ' # "webkit2gtk",\n', + "linux_system_arch_system_runtime_requires": "\n" + " # Needed to provide GTK\n" + ' "gtk3",\n' + " # Needed to provide " + "PyGObject bindings\n" + " " + '"gobject-introspection-runtime",\n' + " # Dependencies that GTK " + "looks for at runtime\n" + ' "libcanberra",\n' + " # Needed to provide WebKit2 " + "at runtime\n" + ' # "webkit2gtk",\n', + "linux_system_debian_system_requires": "\n" + " # Needed to compile pycairo " + "wheel\n" + ' "libcairo2-dev",\n' + " # Needed to compile PyGObject " + "wheel\n" + ' "libgirepository1.0-dev",\n', + "linux_system_debian_system_runtime_requires": "\n" + " # Needed to provide GTK " + "and its GI bindings\n" + ' "gir1.2-gtk-3.0",\n' + ' "libgirepository-1.0-1",\n' + " # Dependencies that GTK " + "looks for at runtime\n" + " " + '"libcanberra-gtk3-module",\n' + " # Needed to provide " + "WebKit2 at runtime\n" + ' # "gir1.2-webkit2-4.0",\n', + "linux_system_rhel_system_requires": "\n" + " # Needed to compile pycairo wheel\n" + ' "cairo-gobject-devel",\n' + " # Needed to compile PyGObject " + "wheel\n" + ' "gobject-introspection-devel",\n', + "linux_system_rhel_system_runtime_requires": "\n" + " # Needed to support Python " + "bindings to GTK\n" + ' "gobject-introspection",\n' + " # Needed to provide GTK\n" + ' "gtk3",\n' + " # Dependencies that GTK " + "looks for at runtime\n" + ' "libcanberra-gtk3",\n' + " # Needed to provide WebKit2 " + "at runtime\n" + ' # "webkit2gtk3",\n', + "linux_system_suse_system_requires": "\n" + " # Needed to compile pycairo wheel\n" + ' "cairo-devel",\n' + " # Needed to compile PyGObject " + "wheel\n" + ' "gobject-introspection-devel",\n', + "linux_system_suse_system_runtime_requires": "\n" + " # Needed to provide GTK\n" + ' "gtk3",\n' + " # Needed to support Python " + "bindings to GTK\n" + ' "gobject-introspection", ' + '"typelib(Gtk)=3.0",\n' + " # Dependencies that GTK " + "looks for at runtime\n" + ' "libcanberra-gtk3-0",\n' + " # Needed to provide WebKit2 " + "at runtime\n" + ' # "libwebkit2gtk3",\n' + ' # "typelib(WebKit2)",\n', + "macos_requires": '\n "toga-cocoa~=0.4.0",\n "std-nslog~=1.0.0",\n', + "macos_universal_build": "true", + "module_name": "myapplication", + "project_name": "My Project", + "start_app_source": " main().main_loop()", + "url": "https://navy.mil/myapplication", + "web_requires": '\n "toga-web~=0.4.0",\n', + "web_style_framework": "Shoelace v2.3", + "web_supported": "true", + "windows_requires": '\n "toga-winforms~=0.4.0",\n', + } + + +def test_question_sequence_pyside2(new_command): + """Questions are asked, a context is constructed.""" + + # Prime answers for all the questions. + new_command.input.values = [ + "My Application", # formal name + "", # app name - accept the default + "org.beeware", # bundle ID + "My Project", # project name + "Cool stuff", # description + "Grace Hopper", # author + "grace@navy.mil", # author email + "https://navy.mil/myapplication", # URL + "4", # license + "2", # PySide2 GUI toolkit + ] + + assert new_command.build_app_context() == { + "app_name": "myapplication", + "app_source": "\n" + "import sys\n" + "\n" + "try:\n" + " from importlib import metadata as importlib_metadata\n" + "except ImportError:\n" + " # Backwards compatibility - importlib.metadata was added " + "in Python 3.8\n" + " import importlib_metadata\n" + "\n" + "from PySide2 import QtWidgets\n" + "\n" + "\n" + "class {{ cookiecutter.class_name }}(QtWidgets.QMainWindow):\n" + " def __init__(self):\n" + " super().__init__()\n" + " self.init_ui()\n" + "\n" + " def init_ui(self):\n" + " self.setWindowTitle('{{ cookiecutter.app_name }}')\n" + " self.show()\n" + "\n" + "\n" + "def main():\n" + " # Linux desktop environments use app's .desktop file to " + "integrate the app\n" + " # to their application menus. The .desktop file of this " + "app will include\n" + " # StartupWMClass key, set to app's formal name, which " + "helps associate\n" + " # app's windows to its menu item.\n" + " #\n" + " # For association to work any windows of the app must have " + "WMCLASS\n" + " # property set to match the value set in app's desktop " + "file. For PySide2\n" + " # this is set with setApplicationName().\n" + "\n" + " # Find the name of the module that was used to start the " + "app\n" + " app_module = sys.modules['__main__'].__package__\n" + " # Retrieve the app's metadata\n" + " metadata = importlib_metadata.metadata(app_module)\n" + "\n" + " " + "QtWidgets.QApplication.setApplicationName(metadata['Formal-Name'])\n" + "\n" + " app = QtWidgets.QApplication(sys.argv)\n" + " main_window = {{ cookiecutter.class_name }}()\n" + " sys.exit(app.exec_())\n", "author": "Grace Hopper", "author_email": "grace@navy.mil", - "url": "https://navy.mil/myapplication", + "bundle": "org.beeware", + "class_name": "MyApplication", + "description": "Cool stuff", + "formal_name": "My Application", "license": "GNU General Public License v2 (GPLv2)", - "gui_framework": "Toga", + "linux_appimage_manylinux": "manylinux2014", + "linux_flatpak_runtime": "org.kde.Platform", + "linux_flatpak_runtime_version": "6.4", + "linux_flatpak_sdk": "org.kde.Sdk", + "linux_system_arch_system_requires": "", + "linux_system_arch_system_runtime_requires": "", + "linux_system_debian_system_requires": "", + "linux_system_debian_system_runtime_requires": "\n" + " # Derived from " + "https://doc.qt.io/qt-6/linux-requirements.html\n" + ' "libxrender1",\n' + ' "libxcb-render0",\n' + ' "libxcb-render-util0",\n' + ' "libxcb-shape0",\n' + ' "libxcb-randr0",\n' + ' "libxcb-xfixes0",\n' + ' "libxcb-xkb1",\n' + ' "libxcb-sync1",\n' + ' "libxcb-shm0",\n' + ' "libxcb-icccm4",\n' + ' "libxcb-keysyms1",\n' + ' "libxcb-image0",\n' + ' "libxcb-util1",\n' + ' "libxkbcommon0",\n' + ' "libxkbcommon-x11-0",\n' + ' "libfontconfig1",\n' + ' "libfreetype6",\n' + ' "libxext6",\n' + ' "libx11-6",\n' + ' "libxcb1",\n' + ' "libx11-xcb1",\n' + ' "libsm6",\n' + ' "libice6",\n' + ' "libglib2.0-0",\n' + ' "libgl1",\n' + ' "libegl1-mesa",\n' + ' "libdbus-1-3",\n' + ' "libgssapi-krb5-2",\n', + "linux_system_rhel_system_requires": "", + "linux_system_rhel_system_runtime_requires": '\n "qt5-qtbase-gui",\n', + "linux_system_suse_system_requires": "", + "linux_system_suse_system_runtime_requires": '\n "libQt5Gui5",\n', + "macos_requires": '\n "toga-cocoa~=0.4.0",\n', + "module_name": "myapplication", + "project_name": "My Project", + "requires": '\n "pyside2~=5.15",\n', + "url": "https://navy.mil/myapplication", } -def test_question_sequence_with_nondefault_gui(new_command): - """Questions are asked, a context is constructed, but the GUI option is formatted to - extract the GUI name.""" +def test_question_sequence_pyside6(new_command): + """Questions are asked, a context is constructed.""" # Prime answers for all the questions. new_command.input.values = [ @@ -46,22 +314,331 @@ def test_question_sequence_with_nondefault_gui(new_command): "grace@navy.mil", # author email "https://navy.mil/myapplication", # URL "4", # license - "2", # GUI toolkit + "3", # PySide6 GUI toolkit ] assert new_command.build_app_context() == { - "formal_name": "My Application", - "class_name": "MyApplication", "app_name": "myapplication", - "module_name": "myapplication", + "app_source": "\n" + "import sys\n" + "\n" + "try:\n" + " from importlib import metadata as importlib_metadata\n" + "except ImportError:\n" + " # Backwards compatibility - importlib.metadata was added " + "in Python 3.8\n" + " import importlib_metadata\n" + "\n" + "from PySide6 import QtWidgets\n" + "\n" + "\n" + "class {{ cookiecutter.class_name }}(QtWidgets.QMainWindow):\n" + " def __init__(self):\n" + " super().__init__()\n" + " self.init_ui()\n" + "\n" + " def init_ui(self):\n" + " self.setWindowTitle('{{ cookiecutter.app_name }}')\n" + " self.show()\n" + "\n" + "\n" + "def main():\n" + " # Linux desktop environments use app's .desktop file to " + "integrate the app\n" + " # to their application menus. The .desktop file of this " + "app will include\n" + " # StartupWMClass key, set to app's formal name, which " + "helps associate\n" + " # app's windows to its menu item.\n" + " #\n" + " # For association to work any windows of the app must have " + "WMCLASS\n" + " # property set to match the value set in app's desktop " + "file. For PySide2\n" + " # this is set with setApplicationName().\n" + "\n" + " # Find the name of the module that was used to start the " + "app\n" + " app_module = sys.modules['__main__'].__package__\n" + " # Retrieve the app's metadata\n" + " metadata = importlib_metadata.metadata(app_module)\n" + "\n" + " " + "QtWidgets.QApplication.setApplicationName(metadata['Formal-Name'])\n" + "\n" + " app = QtWidgets.QApplication(sys.argv)\n" + " main_window = {{ cookiecutter.class_name }}()\n" + " sys.exit(app.exec())\n", + "author": "Grace Hopper", + "author_email": "grace@navy.mil", "bundle": "org.beeware", + "class_name": "MyApplication", + "description": "Cool stuff", + "formal_name": "My Application", + "license": "GNU General Public License v2 (GPLv2)", + "linux_appimage_manylinux": "manylinux_2_28", + "linux_flatpak_runtime": "org.kde.Platform", + "linux_flatpak_runtime_version": "6.4", + "linux_flatpak_sdk": "org.kde.Sdk", + "linux_system_arch_system_requires": '\n "qt6-base",\n', + "linux_system_arch_system_runtime_requires": '\n "qt6-base",\n', + "linux_system_debian_system_requires": "", + "linux_system_debian_system_runtime_requires": "\n" + " # Derived from " + "https://doc.qt.io/qt-6/linux-requirements.html\n" + ' "libxrender1",\n' + ' "libxcb-render0",\n' + ' "libxcb-render-util0",\n' + ' "libxcb-shape0",\n' + ' "libxcb-randr0",\n' + ' "libxcb-xfixes0",\n' + ' "libxcb-xkb1",\n' + ' "libxcb-sync1",\n' + ' "libxcb-shm0",\n' + ' "libxcb-icccm4",\n' + ' "libxcb-keysyms1",\n' + ' "libxcb-image0",\n' + ' "libxcb-util1",\n' + ' "libxkbcommon0",\n' + ' "libxkbcommon-x11-0",\n' + ' "libfontconfig1",\n' + ' "libfreetype6",\n' + ' "libxext6",\n' + ' "libx11-6",\n' + ' "libxcb1",\n' + ' "libx11-xcb1",\n' + ' "libsm6",\n' + ' "libice6",\n' + ' "libglib2.0-0",\n' + ' "libgl1",\n' + ' "libegl1-mesa",\n' + ' "libdbus-1-3",\n' + ' "libgssapi-krb5-2",\n', + "linux_system_rhel_system_requires": "", + "linux_system_rhel_system_runtime_requires": '\n "qt6-qtbase-gui",\n', + "linux_system_suse_system_requires": "", + "linux_system_suse_system_runtime_requires": '\n "libQt6Gui6",\n', + "module_name": "myapplication", "project_name": "My Project", + "requires": '\n "PySide6-Essentials~=6.5",\n # "PySide6-Addons~=6.5",\n', + "url": "https://navy.mil/myapplication", + } + + +def test_question_sequence_pursuedpybear(new_command): + """Questions are asked, a context is constructed.""" + + # Prime answers for all the questions. + new_command.input.values = [ + "My Application", # formal name + "", # app name - accept the default + "org.beeware", # bundle ID + "My Project", # project name + "Cool stuff", # description + "Grace Hopper", # author + "grace@navy.mil", # author email + "https://navy.mil/myapplication", # URL + "4", # license + "4", # PursuedPyBear GUI toolkit + ] + + assert new_command.build_app_context() == { + "app_name": "myapplication", + "app_source": "\n" + "import os\n" + "import sys\n" + "\n" + "try:\n" + " from importlib import metadata as importlib_metadata\n" + "except ImportError:\n" + " # Backwards compatibility - importlib.metadata was added " + "in Python 3.8\n" + " import importlib_metadata\n" + "\n" + "import ppb\n" + "\n" + "\n" + "class {{ cookiecutter.class_name }}(ppb.Scene):\n" + " def __init__(self, **props):\n" + " super().__init__(**props)\n" + "\n" + " self.add(ppb.Sprite(\n" + " image=ppb.Image('{{ cookiecutter.module_name " + "}}/resources/{{ cookiecutter.app_name }}.png'),\n" + " ))\n" + "\n" + "\n" + "def main():\n" + " # Linux desktop environments use app's .desktop file to " + "integrate the app\n" + " # to their application menus. The .desktop file of this " + "app will include\n" + " # StartupWMClass key, set to app's formal name, which " + "helps associate\n" + " # app's windows to its menu item.\n" + " #\n" + " # For association to work any windows of the app must have " + "WMCLASS\n" + " # property set to match the value set in app's desktop " + "file. For PPB this\n" + " # is set using environment variable.\n" + "\n" + " # Find the name of the module that was used to start the " + "app\n" + " app_module = sys.modules['__main__'].__package__\n" + " # Retrieve the app's metadata\n" + " metadata = importlib_metadata.metadata(app_module)\n" + "\n" + " os.environ['SDL_VIDEO_X11_WMCLASS'] = " + "metadata['Formal-Name']\n" + "\n" + " ppb.run(\n" + " starting_scene={{ cookiecutter.class_name }},\n" + " title=metadata['Formal-Name'],\n" + " )\n" + " ", + "author": "Grace Hopper", + "author_email": "grace@navy.mil", + "bundle": "org.beeware", + "class_name": "MyApplication", "description": "Cool stuff", + "formal_name": "My Application", + "license": "GNU General Public License v2 (GPLv2)", + "linux_appimage_manylinux": "manylinux2014", + "linux_flatpak_runtime": "org.freedesktop.Platform", + "linux_flatpak_runtime_version": "22.08", + "linux_flatpak_sdk": "org.freedesktop.Sdk", + "module_name": "myapplication", + "project_name": "My Project", + "requires": '\n "ppb~=1.1",\n', + "url": "https://navy.mil/myapplication", + } + + +def test_question_sequence_pygame(new_command): + """Questions are asked, a context is constructed.""" + + # Prime answers for all the questions. + new_command.input.values = [ + "My Application", # formal name + "", # app name - accept the default + "org.beeware", # bundle ID + "My Project", # project name + "Cool stuff", # description + "Grace Hopper", # author + "grace@navy.mil", # author email + "https://navy.mil/myapplication", # URL + "4", # license + "5", # Pygame GUI toolkit + ] + + assert new_command.build_app_context() == { + "app_name": "myapplication", + "app_source": "\n" + "import os\n" + "import sys\n" + "\n" + "import pygame\n" + "\n" + "try:\n" + " from importlib import metadata as importlib_metadata\n" + "except ImportError:\n" + " # Backwards compatibility - importlib.metadata was added " + "in Python 3.8\n" + " import importlib_metadata\n" + "\n" + "SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600\n" + "WHITE = (255, 255, 255)\n" + "\n" + "\n" + "def main():\n" + " # Linux desktop environments use app's .desktop file to " + "integrate the app\n" + " # to their application menus. The .desktop file of this " + "app will include\n" + " # StartupWMClass key, set to app's formal name, which " + "helps associate\n" + " # app's windows to its menu item.\n" + " #\n" + " # For association to work any windows of the app must have " + "WMCLASS\n" + " # property set to match the value set in app's desktop " + "file. For PPB this\n" + " # is set using environment variable.\n" + "\n" + " # Find the name of the module that was used to start the " + "app\n" + ' app_module = sys.modules["__main__"].__package__\n' + " # Retrieve the app's metadata\n" + " metadata = importlib_metadata.metadata(app_module)\n" + "\n" + ' os.environ["SDL_VIDEO_X11_WMCLASS"] = ' + 'metadata["Formal-Name"]\n' + "\n" + " pygame.init()\n" + ' pygame.display.set_caption(metadata["Formal-Name"])\n' + " screen = pygame.display.set_mode((SCREEN_WIDTH, " + "SCREEN_HEIGHT))\n" + "\n" + " running = True\n" + " while running:\n" + " for event in pygame.event.get():\n" + " if event.type == pygame.QUIT:\n" + " running = False\n" + " break\n" + "\n" + " screen.fill(WHITE)\n" + " pygame.display.flip()\n" + "\n" + " pygame.quit()\n" + " ", "author": "Grace Hopper", "author_email": "grace@navy.mil", + "bundle": "org.beeware", + "class_name": "MyApplication", + "description": "Cool stuff", + "formal_name": "My Application", + "license": "GNU General Public License v2 (GPLv2)", + "linux_appimage_manylinux": "manylinux2014", + "linux_flatpak_runtime": "org.freedesktop.Platform", + "linux_flatpak_runtime_version": "22.08", + "linux_flatpak_sdk": "org.freedesktop.Sdk", + "module_name": "myapplication", + "project_name": "My Project", + "requires": '\n "pygame~=2.2",\n', "url": "https://navy.mil/myapplication", + } + + +def test_question_sequence_none(new_command): + """Questions are asked, a context is constructed.""" + + # Prime answers for all the questions. + new_command.input.values = [ + "My Application", # formal name + "", # app name - accept the default + "org.beeware", # bundle ID + "My Project", # project name + "Cool stuff", # description + "Grace Hopper", # author + "grace@navy.mil", # author email + "https://navy.mil/myapplication", # URL + "4", # license + "6", # None + ] + + assert new_command.build_app_context() == { + "app_name": "myapplication", + "author": "Grace Hopper", + "author_email": "grace@navy.mil", + "bundle": "org.beeware", + "class_name": "MyApplication", + "description": "Cool stuff", + "formal_name": "My Application", "license": "GNU General Public License v2 (GPLv2)", - "gui_framework": "PySide2", + "module_name": "myapplication", + "project_name": "My Project", + "url": "https://navy.mil/myapplication", } @@ -71,16 +648,159 @@ def test_question_sequence_with_no_user_input(new_command): new_command.input.enabled = False assert new_command.build_app_context() == { + "android_requires": '\n "toga-android~=0.4.0",\n', + "android_supported": "true", "app_name": "helloworld", + "app_source": "\n" + "import toga\n" + "from toga.style import Pack\n" + "from toga.style.pack import COLUMN, ROW\n" + "\n" + "\n" + "class {{ cookiecutter.class_name }}(toga.App):\n" + "\n" + " def startup(self):\n" + ' """Construct and show the Toga application.\n' + "\n" + " Usually, you would add your application to a main " + "content box. We then create a\n" + " main window (with a name matching the app), and show " + "the main window.\n" + ' """\n' + " main_box = toga.Box()\n" + "\n" + " self.main_window = " + "toga.MainWindow(title=self.formal_name)\n" + " self.main_window.content = main_box\n" + " self.main_window.show()\n" + "\n" + "\n" + "def main():\n" + " return {{ cookiecutter.class_name }}()\n", "author": "Jane Developer", "author_email": "jane@example.com", "bundle": "com.example", "class_name": "HelloWorld", "description": "My first application", "formal_name": "Hello World", - "gui_framework": "Toga", + "ios_requires": '\n "toga-iOS~=0.4.0",\n "std-nslog~=1.0.0",\n', + "ios_supported": "true", "license": "BSD license", + "linux_appimage_linuxdeploy_plugins": '\n "DEPLOY_GTK_VERSION=3 gtk",\n', + "linux_appimage_manylinux": "manylinux2014", + "linux_appimage_system_requires": "\n" + " # Needed to compile pycairo wheel\n" + ' "cairo-gobject-devel",\n' + " # Needed to compile PyGObject wheel\n" + ' "gobject-introspection-devel",\n' + " # Needed to provide GTK\n" + ' "gtk3-devel",\n' + " # Dependencies that GTK looks for at " + "runtime, that need to be\n" + " # in the build environment to be " + "picked up by linuxdeploy\n" + ' "libcanberra-gtk3",\n' + ' "PackageKit-gtk3-module",\n' + ' "gvfs-client",\n', + "linux_flatpak_runtime": "org.gnome.Platform", + "linux_flatpak_runtime_version": "44", + "linux_flatpak_sdk": "org.gnome.Sdk", + "linux_requires": '\n "toga-gtk~=0.4.0",\n', + "linux_system_arch_system_requires": "\n" + " # Needed to compile pycairo wheel\n" + ' "cairo",\n' + " # Needed to compile PyGObject " + "wheel\n" + ' "gobject-introspection",\n' + " # Runtime dependencies that need to " + "exist so that the\n" + " # Arch package passes final " + "validation.\n" + " # Needed to provide GTK\n" + ' "gtk3",\n' + " # Dependencies that GTK looks for " + "at runtime\n" + ' "libcanberra",\n' + " # Needed to provide WebKit2\n" + ' # "webkit2gtk",\n', + "linux_system_arch_system_runtime_requires": "\n" + " # Needed to provide GTK\n" + ' "gtk3",\n' + " # Needed to provide " + "PyGObject bindings\n" + " " + '"gobject-introspection-runtime",\n' + " # Dependencies that GTK " + "looks for at runtime\n" + ' "libcanberra",\n' + " # Needed to provide WebKit2 " + "at runtime\n" + ' # "webkit2gtk",\n', + "linux_system_debian_system_requires": "\n" + " # Needed to compile pycairo " + "wheel\n" + ' "libcairo2-dev",\n' + " # Needed to compile PyGObject " + "wheel\n" + ' "libgirepository1.0-dev",\n', + "linux_system_debian_system_runtime_requires": "\n" + " # Needed to provide GTK " + "and its GI bindings\n" + ' "gir1.2-gtk-3.0",\n' + ' "libgirepository-1.0-1",\n' + " # Dependencies that GTK " + "looks for at runtime\n" + " " + '"libcanberra-gtk3-module",\n' + " # Needed to provide " + "WebKit2 at runtime\n" + ' # "gir1.2-webkit2-4.0",\n', + "linux_system_rhel_system_requires": "\n" + " # Needed to compile pycairo wheel\n" + ' "cairo-gobject-devel",\n' + " # Needed to compile PyGObject " + "wheel\n" + ' "gobject-introspection-devel",\n', + "linux_system_rhel_system_runtime_requires": "\n" + " # Needed to support Python " + "bindings to GTK\n" + ' "gobject-introspection",\n' + " # Needed to provide GTK\n" + ' "gtk3",\n' + " # Dependencies that GTK " + "looks for at runtime\n" + ' "libcanberra-gtk3",\n' + " # Needed to provide WebKit2 " + "at runtime\n" + ' # "webkit2gtk3",\n', + "linux_system_suse_system_requires": "\n" + " # Needed to compile pycairo wheel\n" + ' "cairo-devel",\n' + " # Needed to compile PyGObject " + "wheel\n" + ' "gobject-introspection-devel",\n', + "linux_system_suse_system_runtime_requires": "\n" + " # Needed to provide GTK\n" + ' "gtk3",\n' + " # Needed to support Python " + "bindings to GTK\n" + ' "gobject-introspection", ' + '"typelib(Gtk)=3.0",\n' + " # Dependencies that GTK " + "looks for at runtime\n" + ' "libcanberra-gtk3-0",\n' + " # Needed to provide WebKit2 " + "at runtime\n" + ' # "libwebkit2gtk3",\n' + ' # "typelib(WebKit2)",\n', + "macos_requires": '\n "toga-cocoa~=0.4.0",\n "std-nslog~=1.0.0",\n', + "macos_universal_build": "true", "module_name": "helloworld", "project_name": "Hello World", + "start_app_source": " main().main_loop()", "url": "https://example.com/helloworld", + "web_requires": '\n "toga-web~=0.4.0",\n', + "web_style_framework": "Shoelace v2.3", + "web_supported": "true", + "windows_requires": '\n "toga-winforms~=0.4.0",\n', }