Bundle PyQtWebEngine on macOS and ship the app as a DMG
release / gui-build (linux) (push) Successful in 1m13s
release / gui-build (macos) (push) Successful in 1m15s
release / gui-build (windows) (push) Failing after 1s

pywebview[qt] no longer depends on PyQtWebEngine, so fresh CI builds
produced a macOS app without its Chromium engine that crashed on launch.
Package MediaHive.app as a compressed DMG instead of zipping the raw
onedir folder; release.py accepts .dmg artifacts.
This commit is contained in:
2026-09-22 04:07:30 +00:00
parent ff10c86e84
commit 5300fd0c9c
3 changed files with 55 additions and 20 deletions
+18 -15
View File
@@ -67,17 +67,17 @@ def load_token() -> str:
# ZIP + dist helpers
# ---------------------------------------------------------------------------
# Matches MediaHive-1.2.3-win64.zip, MediaHive-1.2.3-macos-arm64.zip, etc.
# Matches MediaHive-1.2.3-win64.zip, MediaHive-1.2.3-macos-arm64.dmg, etc.
# Rejects dev/dirty versions like MediaHive-1.2.3.dev0+gabcd-win64.zip
_CLEAN_ZIP_RE = re.compile(r"^MediaHive-(\d+(?:\.\d+)*)-([A-Za-z0-9._-]+)\.zip$")
_CLEAN_ARTIFACT_RE = re.compile(r"^MediaHive-(\d+(?:\.\d+)*)-([A-Za-z0-9._-]+)\.(?:zip|dmg)$")
def find_releasable_zips() -> list[tuple[Path, str, str]]:
"""Return (path, version, platform_tag) for clean-versioned ZIPs in build/."""
def find_releasable_artifacts() -> list[tuple[Path, str, str]]:
"""Return (path, version, platform_tag) for clean-versioned ZIPs/DMGs in build/."""
build_dir = REPO_ROOT / "build"
results = []
for p in sorted(build_dir.glob("MediaHive-*.zip")):
m = _CLEAN_ZIP_RE.match(p.name)
for p in sorted(build_dir.glob("MediaHive-*")):
m = _CLEAN_ARTIFACT_RE.match(p.name)
if m:
results.append((p, m.group(1), m.group(2)))
return results
@@ -180,7 +180,10 @@ def upload_asset(
"""Upload a file to the release and return the download URL."""
url = f"{base_url}/api/v1/repos/{repo}/releases/{release_id}/assets"
size_mb = path.stat().st_size / (1024 * 1024)
mime = "application/zip" if path.suffix == ".zip" else "application/octet-stream"
mime = {
".zip": "application/zip",
".dmg": "application/x-apple-diskimage",
}.get(path.suffix, "application/octet-stream")
print(f"Uploading {path.name} ({size_mb:.1f} MB) ...")
with Path(path).open("rb") as fh:
resp = client.post(
@@ -222,10 +225,10 @@ def main() -> None:
cfg = load_gitea_config()
token = load_token()
zips = find_releasable_zips()
if not zips:
artifacts = find_releasable_artifacts()
if not artifacts:
print(
"No clean-versioned ZIPs found in build/.\n"
"No clean-versioned ZIPs/DMGs found in build/.\n"
"Run scripts/guibuild.py first.",
file=sys.stderr,
)
@@ -234,7 +237,7 @@ def main() -> None:
# Validate all dist files exist before touching Gitea
dist_files: dict[str, list[Path]] = {}
if not args.no_dist:
for _, version, _platform_tag in zips:
for _, version, _platform_tag in artifacts:
dist_files[version] = find_dist_files(version)
base_url = cfg["url"].rstrip("/")
@@ -242,7 +245,7 @@ def main() -> None:
with httpx.Client(headers=gitea_headers(token)) as client:
releases: dict[str, tuple[int, set[str]]] = {}
for zip_path, version, platform_tag in zips:
for artifact_path, version, platform_tag in artifacts:
print(f"\nReleasing {version} ...")
tag = f"v{version}"
if version not in releases:
@@ -257,11 +260,11 @@ def main() -> None:
upload_asset(client, base_url, repo, release_id, path)
release_id, uploaded = releases[version]
if zip_path.name in uploaded:
print(f"Skipping {zip_path.name}, already on the release.")
if artifact_path.name in uploaded:
print(f"Skipping {artifact_path.name}, already on the release.")
continue
print(f"Uploading platform artifact: {platform_tag}")
upload_asset(client, base_url, repo, release_id, zip_path)
upload_asset(client, base_url, repo, release_id, artifact_path)
print(f"{tag} published")
print("\nDone. To publish to PyPI, run:")