Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 14 additions & 2 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This repository's own CI: lint the workflows it publishes to the rest of
# the org, smoke-test that every reusable workflow is valid YAML, prove the
# site generator's output matches what data/repos_db.json currently says it
# should be, and lint scripts/.
# should be, lint scripts/, and run publishing-script unit tests.
name: Checks

on:
Expand Down Expand Up @@ -110,4 +110,16 @@ jobs:

- run: python3 -m pip install --disable-pip-version-check ruff

- run: ruff check scripts/
- run: ruff check scripts/ tests/

unittest:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: actions/setup-python@v7
with:
python-version: "3.12"

- name: Run publishing-script unit tests
run: python3 -m unittest discover -s tests -v
13 changes: 9 additions & 4 deletions .github/workflows/reusable-synchronize-mip-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,15 @@ jobs:
profile = os.environ["PUBLICATION_PROFILE"]
if profile not in lock:
raise SystemExit(f"{profile!r} is not in {path}; add it before publishing")
lock[profile] = {
"repository": os.environ["SOURCE_REPOSITORY"],
"ref": os.environ["SOURCE_REF"],
}
entry = lock[profile]
expected = entry["repository"]
got = os.environ["SOURCE_REPOSITORY"]
if expected != got:
raise SystemExit(
f"profile {profile!r} is locked to {expected}, not {got}; "
f"edit pydevices-lock.json on the PyDevices branch to move the profile"
)
entry["ref"] = os.environ["SOURCE_REF"]
path.write_text(json.dumps(lock, indent=2) + "\n")
print(f"{profile} -> {lock[profile]['ref']}")
PY
Expand Down
6 changes: 4 additions & 2 deletions scripts/build_pydevices_python_distributions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import sys
from pathlib import Path

from pydevices_package_metadata import PYDEVICES_DESCRIPTIONS

DEBRIS = {"__pycache__", "README.md", "build", "dist"}
# No internal dependency table here: with lib/ shipped as one distribution the
# graph between its components is internal imports, not package requirements.
Expand Down Expand Up @@ -114,7 +116,7 @@ def build(root: Path, output: Path, version: str) -> None:
meta,
"pydevices",
version,
"Portable display, audio, event, and timing foundations for PyDevices",
PYDEVICES_DESCRIPTIONS["pydevices"],
[],
)
stages.append(meta)
Expand All @@ -129,7 +131,7 @@ def build(root: Path, output: Path, version: str) -> None:
desktop,
"pydevices-desktop",
version,
"Complete PyDevices desktop stack and board configuration",
PYDEVICES_DESCRIPTIONS["pydevices-desktop"],
[f"pydevices=={version}"],
)
stages.append(desktop)
Expand Down
6 changes: 6 additions & 0 deletions scripts/pydevices_package_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Descriptions shared by TestPyPI and MIP publications of pydevices packages."""

PYDEVICES_DESCRIPTIONS = {
"pydevices": "Portable display, audio, event, and timing foundations for PyDevices",
"pydevices-desktop": "Complete PyDevices desktop stack and board configuration",
}
52 changes: 37 additions & 15 deletions scripts/synchronize_mip_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
from __future__ import annotations

import argparse
import json
import shutil
from dataclasses import dataclass
from pathlib import Path

from pydevices_package_metadata import PYDEVICES_DESCRIPTIONS


@dataclass(frozen=True)
class Profile:
Expand Down Expand Up @@ -76,16 +79,12 @@ class Profile:
),
}

PROFILE_REPOSITORIES = {
"palettes": "PyDevices/palettes",
"pdwidgets": "PyDevices/pdwidgets",
"pygraphics": "PyDevices/pygraphics",
"pydevices": "PyDevices/pydevices",
# Moved from PyDevices/audioif in the audioif/audiocomponents split
# (audiocomponents#2, 2026-09-03); audioif publishes the core only now.
"audioinstruments": "PyDevices/audiocomponents",
"audioeffects": "PyDevices/audiocomponents",
}
# The source repository for each profile is pydevices-lock.json in the MIP
# checkout, not a second map in this script. reusable-synchronize-mip-package.yml
# already keeps that lockfile on the runner; a hardcoded PROFILE_REPOSITORIES
# table disagreed with it after the audioif/audiocomponents split and blocked
# every publication until a new publishing-tools tag (#35).
LOCKFILE_NAME = "pydevices-lock.json"

# No internal dependency table: lib/ ships as a single MIP package, so the graph
# between its components is imports rather than package requirements. It was
Expand All @@ -98,6 +97,26 @@ class Profile:
PYDEVICES_DESKTOP_DIR = "board_configs/desktop"


def lockfile_repository(mip_root: Path, profile: str) -> str:
"""Return the GitHub repository the MIP lockfile names for *profile*.

A new profile is added to the lockfile deliberately, not auto-created.
"""
lockfile = mip_root / LOCKFILE_NAME
if not lockfile.is_file():
raise SystemExit(f"{lockfile} is missing; add {profile!r} to {LOCKFILE_NAME} before publishing")
try:
lock = json.loads(lockfile.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
raise SystemExit(f"{lockfile} is not valid JSON: {exc}") from exc
if not isinstance(lock, dict) or profile not in lock:
raise SystemExit(f"{profile!r} is not in {lockfile}; add it before publishing")
entry = lock[profile]
if not isinstance(entry, dict) or not entry.get("repository"):
raise SystemExit(f"{profile!r} in {lockfile} has no repository")
return str(entry["repository"])


def ignore_debris(_directory: str, names: list[str]) -> set[str]:
# publishable() gates the top level; this gates everything nested inside a
# package directory, which copytree would otherwise take wholesale. That is
Expand Down Expand Up @@ -155,9 +174,13 @@ def copy_component(source: Path, destination: Path) -> None:


def render_pydevices_manifest(name: str, version: str, requirements: tuple[str, ...], payloads: tuple[str, ...] = ()) -> str:
try:
description = PYDEVICES_DESCRIPTIONS[name]
except KeyError:
raise SystemExit(f"no shared description for {name!r}") from None
lines = [
"metadata(",
f' description="PyDevices {name}",',
f" description={description!r},",
f' version="{version}",',
' author="Brad Barnett",',
' license="MIT",',
Expand Down Expand Up @@ -223,15 +246,14 @@ def main() -> None:
parser.add_argument("--version", required=True)
args = parser.parse_args()

expected_repository = PROFILE_REPOSITORIES[args.profile]
source_repository = args.source_repository.resolve()
mip_root = args.mip_repository.resolve()
expected_repository = lockfile_repository(mip_root, args.profile)
if args.source_repository_name != expected_repository:
raise SystemExit(
f"profile {args.profile!r} requires {expected_repository}, "
f"not {args.source_repository_name}"
)

source_repository = args.source_repository.resolve()
mip_root = args.mip_repository.resolve()
if args.profile == "pydevices":
synchronize_pydevices(source_repository, mip_root, args.version)
return
Expand Down
Loading
Loading