Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for third-party GUI framework plugins #1524

Merged
merged 14 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,27 @@ jobs:
name: html-coverage-report-project
path: htmlcov

verify-templates:
name: Verify Templates
needs: unit-tests
# !!!!! TODO:PR: use real repo !!!!!
# uses: beeware/.github/.github/workflows/app-create-verify.yml@main
uses: rmartin16/.github-beeware/.github/workflows/app-create-verify.yml@create-new-project
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
uses: beeware/.github/.github/workflows/app-build-verify.yml@main
# !!!!! TODO:PR: use real repo !!!!!
# uses: beeware/.github/.github/workflows/app-build-verify.yml@main
uses: rmartin16/.github-beeware/.github/workflows/app-build-verify.yml@create-new-project
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
Expand All @@ -158,10 +175,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.
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",
freakboy3742 marked this conversation as resolved.
Show resolved Hide resolved
]

# 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
freakboy3742 marked this conversation as resolved.
Show resolved Hide resolved
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
rmartin16 marked this conversation as resolved.
Show resolved Hide resolved
]

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
Loading