Skip to content

Commit

Permalink
Introduce package for Briefcase Automation
Browse files Browse the repository at this point in the history
  • Loading branch information
rmartin16 committed Nov 29, 2023
1 parent 0723ee5 commit 0813fba
Show file tree
Hide file tree
Showing 12 changed files with 296 additions and 5 deletions.
15 changes: 12 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ jobs:

package:
name: Python package
uses: beeware/.github/.github/workflows/python-package-create.yml@main
# uses: beeware/.github/.github/workflows/python-package-create.yml@main
uses: rmartin16/.github-beeware/.github/workflows/python-package-create.yml@build-verify-run # TODO:PR: remove me
with:
tox-factors: -with-automation

unit-tests:
name: Unit tests
Expand Down Expand Up @@ -145,10 +148,13 @@ jobs:
verify-projects:
name: Verify project
needs: unit-tests
uses: beeware/.github/.github/workflows/app-create-verify.yml@main
# uses: beeware/.github/.github/workflows/app-create-verify.yml@main
uses: rmartin16/.github-beeware/.github/workflows/app-create-verify.yml@build-verify-run # TODO:PR: remove me
with:
runner-os: ${{ matrix.runner-os }}
framework: ${{ matrix.framework }}
workflow-repo: rmartin16/.github-beeware # TODO:PR: REMOVE ME
workflow-repo-ref: build-verify-run # TODO:PR: REMOVE ME
strategy:
fail-fast: false
matrix:
Expand All @@ -158,7 +164,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@build-verify-run # TODO:PR: remove me
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 @@ -168,6 +175,8 @@ jobs:
python-version: "3.10"
runner-os: ${{ matrix.runner-os }}
framework: ${{ matrix.framework }}
workflow-repo: rmartin16/.github-beeware # TODO:PR: REMOVE ME
workflow-repo-ref: build-verify-run # TODO:PR: REMOVE ME
strategy:
fail-fast: false
matrix:
Expand Down
12 changes: 12 additions & 0 deletions automation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## Briefcase Automation

This package provides Briefcase plugins to facilitate automation in CI.

This package is internal to Briefcase's own development and is not needed to create,
develop, or distribute apps created with Briefcase.

### Bootstraps

There are bootstrap plugins for each GUI toolkit; each allows for Briefcase to create
a project using the toolkit but when the project's app runs, the app automatically
exits after a few seconds.
28 changes: 28 additions & 0 deletions automation/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[build-system]
requires = [
# keep versions in sync with ../pyproject.toml
"setuptools==69.0.0",
"setuptools_scm==8.0.4",
"setuptools_dynamic_dependencies @ git+https://github.com/beeware/setuptools_dynamic_dependencies",
]
build-backend = "setuptools.build_meta"

[project]
name = "x-briefcase-automation"
description = "A Briefcase plugin for CI automation."
readme = "README.md"
license.text = "New BSD"
classifiers = ["Private :: Do Not Upload"]
dynamic = ["version", "dependencies"]

[project.entry-points."briefcase.bootstraps"]
"Toga Automation" = "automation.bootstraps.toga:TogaAutomationBootstrap"
"PySide6 Automation" = "automation.bootstraps.pyside6:PySide6AutomationBootstrap"
"Pygame Automation" = "automation.bootstraps.pygame:PygameAutomationBootstrap"
"PursuedPyBear Automation" = "automation.bootstraps.pursuedpybear:PursuedPyBearAutomationBootstrap"

[tool.setuptools_scm]
root = "../"

[tool.setuptools_dynamic_dependencies]
dependencies = ["briefcase == {version}"]
Empty file.
2 changes: 2 additions & 0 deletions automation/src/automation/bootstraps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
BRIEFCASE_EXIT_SUCCESS_SIGNAL = ">>>>>>>>>> EXIT 0 <<<<<<<<<<"
EXIT_SUCCESS_NOTIFY = ">>> successfully started...exiting <<<"
83 changes: 83 additions & 0 deletions automation/src/automation/bootstraps/pursuedpybear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import sys

import tomli_w

if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib

from automation.bootstraps import BRIEFCASE_EXIT_SUCCESS_SIGNAL, EXIT_SUCCESS_NOTIFY
from briefcase.bootstraps import PursuedPyBearGuiBootstrap


class PursuedPyBearAutomationBootstrap(PursuedPyBearGuiBootstrap):
def app_source(self):
return f"""\
import importlib.metadata
import os
import sys
import ppb
class {{{{ cookiecutter.class_name }}}}(ppb.Scene):
def __init__(self, **props):
super().__init__(**props)
self.updates: int = 0
self.add(
ppb.Sprite(
image=ppb.Image("{{{{ cookiecutter.module_name }}}}/resources/{{{{ cookiecutter.app_name }}}}.png"),
)
)
def on_update(self, event, signal):
self.updates += 1
# quit after 2 seconds since on_update is run 60 times/second
if self.updates > 120:
print("{EXIT_SUCCESS_NOTIFY}")
print("{BRIEFCASE_EXIT_SUCCESS_SIGNAL}")
signal(ppb.events.Quit())
def main():
# Linux desktop environments use an app's .desktop file to integrate the app
# in to their application menus. The .desktop file of this app will include
# the StartupWMClass key, set to app's formal name. This helps associate the
# 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 the SDL_VIDEO_X11_WMCLASS 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"],
)
"""

# The constraint of pysdl2-dll==2.0.22 is required for ppb==1.1.0;
# the libraries in later versions of pysdl2-dll are not compatible.

def pyproject_table_linux_flatpak(self):
table = tomllib.loads(super().pyproject_table_linux_flatpak())
table.setdefault("requires", []).append("pysdl2-dll==2.0.22")
return f"\n{tomli_w.dumps(table)}"

def pyproject_table_windows(self):
table = tomllib.loads(super().pyproject_table_windows())
table.setdefault("requires", []).append("pysdl2-dll==2.0.22")
return f"\n{tomli_w.dumps(table)}"

def pyproject_table_macOS(self):
table = tomllib.loads(super().pyproject_table_macOS())
table.setdefault("requires", []).append("pysdl2-dll==2.0.22")
return f"\n{tomli_w.dumps(table)}"
61 changes: 61 additions & 0 deletions automation/src/automation/bootstraps/pygame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from automation.bootstraps import BRIEFCASE_EXIT_SUCCESS_SIGNAL, EXIT_SUCCESS_NOTIFY
from briefcase.bootstraps import PygameGuiBootstrap


class PygameAutomationBootstrap(PygameGuiBootstrap):
def app_source(self):
return f"""\
import importlib.metadata
import os
import sys
from pathlib import Path
import pygame
SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
WHITE = (255, 255, 255)
def main():
# Linux desktop environments use an app's .desktop file to integrate the app
# in to their application menus. The .desktop file of this app will include
# the StartupWMClass key, set to app's formal name. This helps associate the
# 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 PyGame, this is set
# using the SDL_VIDEO_X11_WMCLASS 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"]
# Set the app's runtime icon
pygame.display.set_icon(
pygame.image.load(Path(__file__).parent / "resources/{{{{ cookiecutter.app_name }}}}.png")
)
pygame.init()
pygame.display.set_caption(metadata["Formal-Name"])
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.time.set_timer(pygame.QUIT, 2000)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
print("{EXIT_SUCCESS_NOTIFY}")
print("{BRIEFCASE_EXIT_SUCCESS_SIGNAL}")
running = False
break
screen.fill(WHITE)
pygame.display.flip()
pygame.quit()
"""
52 changes: 52 additions & 0 deletions automation/src/automation/bootstraps/pyside6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from automation.bootstraps import BRIEFCASE_EXIT_SUCCESS_SIGNAL, EXIT_SUCCESS_NOTIFY
from briefcase.bootstraps import PySide6GuiBootstrap


class PySide6AutomationBootstrap(PySide6GuiBootstrap):
def app_source(self):
return f"""\
import importlib.metadata
import sys
from PySide6 import QtWidgets
from PySide6.QtCore import QTimer
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()
QTimer.singleShot(2000, self.exit_app)
def exit_app(self):
print("{EXIT_SUCCESS_NOTIFY}")
print("{BRIEFCASE_EXIT_SUCCESS_SIGNAL}")
QtWidgets.QApplication.quit()
def main():
# Linux desktop environments use an app's .desktop file to integrate the app
# in to their application menus. The .desktop file of this app will include
# the StartupWMClass key, set to app's formal name. This helps associate the
# 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 PySide6, 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())
"""
41 changes: 41 additions & 0 deletions automation/src/automation/bootstraps/toga.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from automation.bootstraps import BRIEFCASE_EXIT_SUCCESS_SIGNAL, EXIT_SUCCESS_NOTIFY
from briefcase.bootstraps import TogaGuiBootstrap


class TogaAutomationBootstrap(TogaGuiBootstrap):
def app_source(self):
return f'''\
import asyncio
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()
self.add_background_task(self.exit_soon)
async def exit_soon(self, app: toga.App, **kwargs):
"""Background task that closes the app after a few seconds."""
await asyncio.sleep(2)
print("{EXIT_SUCCESS_NOTIFY}")
print("{BRIEFCASE_EXIT_SUCCESS_SIGNAL}")
self.exit()
def main():
return {{{{ cookiecutter.class_name }}}}()
'''
1 change: 1 addition & 0 deletions changes/1549.misc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The Briefcase Automation package was created to facilitate automated testing in CI; for example, starting apps built in CI that can automatically exit.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[build-system]
requires = [
# keep versions in sync with automation/pyproject.toml
"setuptools==69.0.0",
"setuptools_scm==8.0.4",
]
Expand Down
5 changes: 3 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -127,12 +127,13 @@ commands =
lint : python -m sphinx {[docs]sphinx_args_extra} -b linkcheck . {[docs]build_dir}/links
all : python -m sphinx {[docs]sphinx_args_extra} -b html . {[docs]build_dir}/html

[testenv:package]
[testenv:package{,-with-automation}]
skip_install = True
passenv = FORCE_COLOR
deps =
build==1.0.3
twine==4.0.2
commands =
python -m build --outdir dist/ .
python -m build . --outdir dist/
with-automation: python -m build automation/ --outdir dist/
python -m twine check dist/*

0 comments on commit 0813fba

Please sign in to comment.