Skip to content

Commit

Permalink
Merge pull request #1549 from rmartin16/automation-plugin
Browse files Browse the repository at this point in the history
Introduce package for Briefcase Automation
  • Loading branch information
freakboy3742 authored Dec 4, 2023
2 parents 003715b + b1cafce commit 19215d9
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 2 deletions.
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 <<<"
56 changes: 56 additions & 0 deletions automation/src/automation/bootstraps/pursuedpybear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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"],
)
"""
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
1 change: 1 addition & 0 deletions src/briefcase/bootstraps/pursuedpybear.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def pyproject_table_briefcase_app_extra_content(self):
requires = [
"ppb~=1.1",
"pysdl2-dll==2.0.22",
]
test_requires = [
{%- if cookiecutter.test_framework == "pytest" %}
Expand Down
1 change: 1 addition & 0 deletions tests/commands/new/test_build_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@ def main():
requires = [
"ppb~=1.1",
"pysdl2-dll==2.0.22",
]
test_requires = [
{%- if cookiecutter.test_framework == "pytest" %}
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 19215d9

Please sign in to comment.