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 28, 2023
1 parent 6b2657e commit 6fa2b60
Show file tree
Hide file tree
Showing 13 changed files with 239 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. The bootstrap allows for Briefcase
to create a project using the toolkit but when the project's app run, 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 added automation/src/__init__.py
Empty file.
Empty file.
Empty file.
69 changes: 69 additions & 0 deletions automation/src/automation/bootstraps/pursuedpybear.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import tomli_w

try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib

from briefcase.bootstraps import PursuedPyBearGuiBootstrap


class PursuedPyBearAutomationBootstrap(PursuedPyBearGuiBootstrap):
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.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(">>> successfully started...exiting <<<")
print(">>>>>>>>>> EXIT 0 <<<<<<<<<<")
signal(ppb.events.Quit())
def main():
app_module = sys.modules["__main__"].__package__
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)}"
42 changes: 42 additions & 0 deletions automation/src/automation/bootstraps/pygame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from briefcase.bootstraps import PygameGuiBootstrap


class PygameAutomationBootstrap(PygameGuiBootstrap):
def app_source(self):
return """\
import importlib.metadata
import os
import sys
import pygame
SCREEN_WIDTH, SCREEN_HEIGHT = 800, 600
WHITE = (255, 255, 255)
def main():
app_module = sys.modules["__main__"].__package__
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))
pygame.time.set_timer(pygame.QUIT, 2000)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
print(">>> successfully started...exiting <<<")
print(">>>>>>>>>> EXIT 0 <<<<<<<<<<")
running = False
break
screen.fill(WHITE)
pygame.display.flip()
pygame.quit()
"""
40 changes: 40 additions & 0 deletions automation/src/automation/bootstraps/pyside6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from briefcase.bootstraps import PySide6GuiBootstrap


class PySide6AutomationBootstrap(PySide6GuiBootstrap):
def app_source(self):
return """\
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(">>> successfully started...exiting <<<")
print(">>>>>>>>>> EXIT 0 <<<<<<<<<<")
QtWidgets.QApplication.quit()
def main():
app_module = sys.modules["__main__"].__package__
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())
"""
31 changes: 31 additions & 0 deletions automation/src/automation/bootstraps/toga.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from briefcase.bootstraps import TogaGuiBootstrap


class TogaAutomationBootstrap(TogaGuiBootstrap):
def app_source(self):
return '''\
import asyncio
import toga
class {{ cookiecutter.class_name }}(toga.App):
def startup(self):
"""Construct and show the Toga application."""
self.main_window = toga.MainWindow(title=self.formal_name)
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(">>> successfully started...exiting <<<")
print(">>>>>>>>>> EXIT 0 <<<<<<<<<<")
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 6fa2b60

Please sign in to comment.