Skip to content

Commit

Permalink
Merge pull request #1524 from rmartin16/gui-plugin-support
Browse files Browse the repository at this point in the history
Add support for third-party GUI framework plugins
  • Loading branch information
rmartin16 authored Nov 18, 2023
2 parents fa275e8 + 9a5a026 commit ccda123
Show file tree
Hide file tree
Showing 25 changed files with 2,292 additions and 373 deletions.
20 changes: 14 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,19 @@ jobs:
name: html-coverage-report-project
path: htmlcov

verify-templates:
name: Verify Templates
needs: unit-tests
uses: beeware/.github/.github/workflows/app-create-verify.yml@main
with:
runner-os: ${{ matrix.runner-os }}
framework: ${{ matrix.framework }}
strategy:
fail-fast: false
matrix:
framework: [ "toga", "pyside6", "ppb", "pygame" ]
runner-os: [ "macos-latest", "ubuntu-22.04", "windows-latest" ]

verify-apps:
name: Build app
needs: unit-tests
Expand All @@ -158,10 +171,5 @@ jobs:
strategy:
fail-fast: false
matrix:
framework: [ "toga", "pyside2", "pyside6", "ppb", "pygame" ]
framework: [ "toga", "pyside6", "ppb", "pygame" ]
runner-os: [ "macos-latest", "ubuntu-22.04", "windows-latest" ]
exclude:
# Pyside2 doesn't publish ARM64 wheels, and is unlikely to ever do so.
# This means we can't make a universal binary.
- framework: "pyside2"
runner-os: "macos-latest"
1 change: 1 addition & 0 deletions changes/1524.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Briefcase now supports GUI bootstrap plugins to customize how new projects are created.
1 change: 1 addition & 0 deletions changes/1524.removal.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support to create new projects using PySide2 has been removed and Briefcase's release testing will no longer explicitly verify compatibility for it.
5 changes: 5 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ where = src
[options.entry_points]
console_scripts =
briefcase = briefcase.__main__:main
briefcase.bootstraps =
Toga = briefcase.bootstraps.toga:TogaGuiBootstrap
PySide6 = briefcase.bootstraps.pyside6:PySide6GuiBootstrap
PursuedPyBear = briefcase.bootstraps.pursuedpybear:PursuedPyBearGuiBootstrap
Pygame = briefcase.bootstraps.pygame:PygameGuiBootstrap
briefcase.platforms =
android = briefcase.platforms.android
iOS = briefcase.platforms.iOS
Expand Down
5 changes: 5 additions & 0 deletions src/briefcase/bootstraps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from briefcase.bootstraps.base import BaseGuiBootstrap # noqa: F401
from briefcase.bootstraps.pursuedpybear import PursuedPyBearGuiBootstrap # noqa: F401
from briefcase.bootstraps.pygame import PygameGuiBootstrap # noqa: F401
from briefcase.bootstraps.pyside6 import PySide6GuiBootstrap # noqa: F401
from briefcase.bootstraps.toga import TogaGuiBootstrap # noqa: F401
115 changes: 115 additions & 0 deletions src/briefcase/bootstraps/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
from __future__ import annotations

from abc import ABC
from typing import Any, TypedDict


class AppContext(TypedDict):
formal_name: str
app_name: str
class_name: str
module_name: str
project_name: str
description: str
author: str
author_email: str
bundle: str
url: str
license: str
briefcase_version: str
template_source: str
template_branch: str


class BaseGuiBootstrap(ABC):
"""Definition for a plugin that defines a new Briefcase app."""

# These are the field names that will be defined in the cookiecutter context.
# Any fields defined here must be implemented as methods that return a ``str``
# or ``None``. Returning ``None`` omits the field as a key in the context, thereby
# deferring the value for the field to the cookiecutter template.
fields: list[str] = [
"app_source",
"app_start_source",
"pyproject_table_briefcase_extra_content",
"pyproject_table_briefcase_app_extra_content",
"pyproject_table_macOS",
"pyproject_table_linux",
"pyproject_table_linux_system_debian",
"pyproject_table_linux_system_rhel",
"pyproject_table_linux_system_suse",
"pyproject_table_linux_system_arch",
"pyproject_table_linux_appimage",
"pyproject_table_linux_flatpak",
"pyproject_table_windows",
"pyproject_table_iOS",
"pyproject_table_android",
"pyproject_table_web",
"pyproject_extra_content",
]

# A short annotation that's appended to the name of the GUI toolkit when the user
# is presented with the options to create a new project.
display_name_annotation: str = ""

def __init__(self, context: AppContext):
# context contains metadata about the app being created
self.context = context

def extra_context(self) -> dict[str, Any] | None:
"""Runs prior to other plugin hooks to provide additional context.
This can be used to prompt the user with additional questions or run arbitrary
logic to supplement the context provided to cookiecutter.
"""

def app_source(self) -> str | None:
"""The Python source code for app.py."""

def app_start_source(self) -> str | None:
"""The Python source code for __main__.py to start the app."""

def pyproject_table_briefcase_extra_content(self) -> str | None:
"""Additional content for ``tool.briefcase`` table."""

def pyproject_table_briefcase_app_extra_content(self) -> str | None:
"""Additional content for ``tool.briefcase.app.<app-name>`` table."""

def pyproject_table_macOS(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.macOS`` table."""

def pyproject_table_linux(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.linux`` table."""

def pyproject_table_linux_system_debian(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.linux.debian`` table."""

def pyproject_table_linux_system_rhel(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.linux.rhel`` table."""

def pyproject_table_linux_system_suse(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.linux.suse`` table."""

def pyproject_table_linux_system_arch(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.linux.arch`` table."""

def pyproject_table_linux_appimage(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.linux.appimage`` table."""

def pyproject_table_linux_flatpak(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.linux.flatpak`` table."""

def pyproject_table_windows(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.windows`` table."""

def pyproject_table_iOS(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.iOS`` table."""

def pyproject_table_android(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.android`` table."""

def pyproject_table_web(self) -> str | None:
"""Content for ``tool.briefcase.app.<app-name>.web`` table."""

def pyproject_extra_content(self) -> str | None:
"""Additional TOML to add to the bottom of pyproject.toml."""
159 changes: 159 additions & 0 deletions src/briefcase/bootstraps/pursuedpybear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
from briefcase.bootstraps.base import BaseGuiBootstrap


class PursuedPyBearGuiBootstrap(BaseGuiBootstrap):
display_name_annotation = "does not support iOS/Android deployment"

def app_source(self):
return """\
import importlib.metadata
import os
import sys
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 pyproject_table_briefcase_app_extra_content(self):
return """
requires = [
"ppb~=1.1",
]
test_requires = [
{%- if cookiecutter.test_framework == "pytest" %}
"pytest",
{%- endif %}
]
"""

def pyproject_table_macOS(self):
return """
universal_build = true
requires = [
"std-nslog~=1.0.0",
]
"""

def pyproject_table_linux(self):
return """
requires = [
]
"""

def pyproject_table_linux_system_debian(self):
return """
system_requires = [
# ?? FIXME
]
system_runtime_requires = [
# ?? FIXME
]
"""

def pyproject_table_linux_system_rhel(self):
return """
system_requires = [
# ?? FIXME
]
system_runtime_requires = [
# ?? FIXME
]
"""

def pyproject_table_linux_system_suse(self):
return """
system_requires = [
# ?? FIXME
]
system_runtime_requires = [
# ?? FIXME
]
"""

def pyproject_table_linux_system_arch(self):
return """
system_requires = [
# ?? FIXME
]
system_runtime_requires = [
# ?? FIXME
]
"""

def pyproject_table_linux_appimage(self):
return """
manylinux = "manylinux2014"
system_requires = [
# ?? FIXME
]
linuxdeploy_plugins = [
]
"""

def pyproject_table_linux_flatpak(self):
return """
flatpak_runtime = "org.freedesktop.Platform"
flatpak_runtime_version = "22.08"
flatpak_sdk = "org.freedesktop.Sdk"
"""

def pyproject_table_windows(self):
return """
requires = [
]
"""

def pyproject_table_iOS(self):
return """
supported = false
"""

def pyproject_table_android(self):
return """
supported = false
"""

def pyproject_table_web(self):
return """
supported = false
"""
Loading

0 comments on commit ccda123

Please sign in to comment.