diff --git a/scripts/guibuild.py b/scripts/guibuild.py index 4ce041a..b25d989 100755 --- a/scripts/guibuild.py +++ b/scripts/guibuild.py @@ -29,6 +29,7 @@ import sys import urllib.request import zipfile from pathlib import Path +from typing import NamedTuple import setuptools_scm from platformdirs import user_cache_path @@ -63,14 +64,32 @@ _VPK_URL = ( _VPK_STAGING = _build_cache_dir() / f"vpk-{_VPK_VERSION}" -def _platform_zip_suffix() -> str: - # Only Windows keeps an arch marker (win64); macOS is arm64-only and we - # build just one Linux flavor, so -macos / -linux suffice. +class _Platform(NamedTuple): + """Per-platform naming/packaging constants. + + tag is the release artifact suffix. Only Windows keeps an arch marker + (win64); macOS builds are arm64-only and we ship one Linux flavor. + """ + + tag: str # win64 / macos / linux + rid: str # Velopack runtime id + dist_dir: str # PyInstaller output dir under build/ + icon: str # file in mediahive/assets + main_exe: str + setup_ext: str + + +def _platform() -> _Platform: if sys.platform == "win32": - return "win64" + return _Platform("win64", "win-x64", "MediaHive", "mediahive.ico", "MediaHive.exe", ".exe") if sys.platform == "darwin": - return "macos" - return "linux" + return _Platform("macos", "osx-arm64", "MediaHive.app", "mediahive.icns", "MediaHive", ".pkg") + return _Platform("linux", "linux-x64", "MediaHive", "mediahive.png", "MediaHive", ".AppImage") + + +def setup_artifact_name(version: str) -> str: + p = _platform() + return f"MediaHive-{version}-{p.tag}-setup{p.setup_ext}" def fetch_ffmpeg() -> Path: @@ -310,24 +329,8 @@ def build_velopack(version: str) -> Path: the .NET CLR loads pythonnet/pywebview assemblies that it refuses from a downloaded ZIP. """ - if sys.platform == "darwin": - dist_folder = _REPO_ROOT / "build" / "MediaHive.app" - icon = _ASSETS_DIR / "mediahive.icns" - rid = "osx-arm64" - main_exe = "MediaHive" - artifact_ext = ".pkg" - elif sys.platform == "win32": - dist_folder = _REPO_ROOT / "build" / "MediaHive" - icon = _ASSETS_DIR / "mediahive.ico" - rid = "win-x64" - main_exe = "MediaHive.exe" - artifact_ext = ".exe" - else: - dist_folder = _REPO_ROOT / "build" / "MediaHive" - icon = _ASSETS_DIR / "mediahive.png" - rid = "linux-x64" - main_exe = "MediaHive" - artifact_ext = ".AppImage" + plat = _platform() + dist_folder = _REPO_ROOT / "build" / plat.dist_dir if not dist_folder.exists(): raise FileNotFoundError(f"Distribution folder not found: {dist_folder}") @@ -345,15 +348,15 @@ def build_velopack(version: str) -> Path: "--packDir", str(dist_folder), "--mainExe", - main_exe, + plat.main_exe, "--packAuthors", "MediaHive", "--packTitle", "MediaHive", "--icon", - str(icon), + str(_ASSETS_DIR / plat.icon), "--runtime", - rid, + plat.rid, "--outputDir", str(releases_dir), ] @@ -370,18 +373,14 @@ def build_velopack(version: str) -> Path: f"stdout:\n{result.stdout}\nstderr:\n{result.stderr}" ) - setup = next(iter(sorted(releases_dir.glob(f"*Setup*{artifact_ext}"))), None) + setup = next(iter(sorted(releases_dir.glob(f"*Setup*{plat.setup_ext}"))), None) if setup is None: - setup = next(iter(sorted(releases_dir.glob(f"*{artifact_ext}"))), None) + setup = next(iter(sorted(releases_dir.glob(f"*{plat.setup_ext}"))), None) if setup is None: - raise RuntimeError(f"vpk produced no *{artifact_ext} in {releases_dir}") + raise RuntimeError(f"vpk produced no *{plat.setup_ext} in {releases_dir}") if sys.platform == "darwin": force_macos_user_install(setup) - artifact = ( - _REPO_ROOT - / "build" - / f"MediaHive-{version}-{_platform_zip_suffix()}-setup{artifact_ext}" - ) + artifact = _REPO_ROOT / "build" / setup_artifact_name(version) artifact.unlink(missing_ok=True) setup.rename(artifact) rename_feed_packages(releases_dir) @@ -487,21 +486,18 @@ def build_executable() -> None: raise RuntimeError(f"PyInstaller failed with exit code {result.returncode}") -def create_zip(version: str) -> Path: - """Create a version-numbered ZIP file of the build/MediaHive folder.""" - repo_root = _REPO_ROOT - dist_folder = repo_root / "build" / "MediaHive" +def create_portable_zip(version: str) -> Path: + """Create the Windows portable ZIP of the build/MediaHive folder. + Velopack-less plain-folder distribution for users who cannot or do not + want to run Setup.exe. No auto-updates; the app strips Mark-of-the-Web + from bundled DLLs at first run instead. + """ + dist_folder = _REPO_ROOT / "build" / "MediaHive" if not dist_folder.exists(): raise FileNotFoundError(f"Distribution folder not found: {dist_folder}") - zip_name = f"MediaHive-{version}-{_platform_zip_suffix()}.zip" - if sys.platform == "win32": - # Distinguish from the Velopack installer (MediaHive-*-win64-setup.exe) - zip_name = zip_name.replace("-win64.zip", "-win64-portable.zip") - zip_path = repo_root / "build" / zip_name - zip_path.parent.mkdir(parents=True, exist_ok=True) - + zip_path = _REPO_ROOT / "build" / f"MediaHive-{version}-win64-portable.zip" print(f"Creating {zip_path}...") shutil.make_archive( str(zip_path.with_suffix("")), # removes .zip so make_archive can add it @@ -534,8 +530,7 @@ def main() -> None: artifacts = [build_velopack(version)] if sys.platform == "win32": - # Velopack-less plain-folder distribution (with MOTW strip) - artifacts.append(create_zip(version)) + artifacts.append(create_portable_zip(version)) for artifact_path in artifacts: print(f"✓ Built successfully: {artifact_path}") diff --git a/scripts/release.py b/scripts/release.py index 39f0e14..875750c 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -9,8 +9,8 @@ Reads from [project.urls] Repository in pyproject.toml. Token: GITEA_TOKEN environment variable Steps: - 1. Find clean-versioned ZIPs in build/ and matching dist/ wheels/sdists - 2. Abort if any dist files are missing for a found ZIP version + 1. Find clean-versioned platform artifacts in build/ and matching dist/ wheels/sdists + 2. Abort if any dist files are missing for a found artifact version 3. Create a Gitea release for each version (or reuse the existing one for the tag, skipping already-uploaded assets) and upload all assets 4. Remind the user to run: uv publish @@ -76,7 +76,7 @@ _CLEAN_ARTIFACT_RE = re.compile( def find_releasable_artifacts() -> list[tuple[Path, str, str]]: - """Return (path, version, platform_tag) for clean-versioned ZIPs/DMGs/EXEs in build/.""" + """Return (path, version, platform_tag) for clean-versioned artifacts in build/.""" build_dir = REPO_ROOT / "build" results = [] for p in sorted(build_dir.glob("MediaHive-*")): @@ -246,7 +246,7 @@ def main() -> None: artifacts = find_releasable_artifacts() if not artifacts: print( - "No clean-versioned ZIPs/DMGs/EXEs found in build/.\n" + "No clean-versioned platform artifacts found in build/.\n" "Run scripts/guibuild.py first.", file=sys.stderr, )