Plezy consumed mpv through four unrelated supply chains: an MPVKit fork via SwiftPM for the Apple platforms, a libmpv-android fork's AAR for Android, an in-CI from-source build for Linux, and an unpinned sourceforge mpv-dev 7z for Windows. All four now consume the same per-commit, content-addressed binaries from https://github.com/edde746/mpv-build, pinned to one commit and built from one set of pinned sources (mpv v0.41.0 on Apple/Android/Windows, ffmpeg n8.0.1, our libass fork). - Apple: the SwiftPM package moves from edde746/MPVKit to edde746/mpv-build across the ios/macos/tvos projects; scripts/set_mpvkit_revision.sh becomes set_native_revision.sh, writes every pin site plus the new repo-root mpv-build.lock.json, and tvos/scripts/wire_mpv.rb derives the package repo from the locks. - Android: the mpv Kotlin API and JNI glue move in-app under android/libmpv (repackaged com.edde746.plezy.libmpv, exports renamed, shrinker rules covered), and the module downloads per-ABI native tarballs (lib/*.so incl. libc++_shared.so + include/) driven entirely by mpv-build.lock.json, with a PLEZY_LOCAL_MPV_DIR escape hatch for locally built artifacts. The fork AAR and its Maven coordinates are gone. - Linux: CI downloads the prebuilt self-relocating libmpv prefix (lock-driven, sha256-verified) instead of compiling mpv/ffmpeg/dav1d/ libplacebo/shaderc from source; linux/packaging/build-libmpv.sh and its test are deleted and native-inputs.json shrinks to the simdutf entry the CMake builds still fetch. - Windows: both arches FetchContent the mpv-build dev zips with URL_HASH enforcement, replacing the checksum-less sourceforge download and its ARM64 7-Zip special case; guard scripts updated. The lock plus the Apple pin sites all point at mpv-build commit d7c3d559, whose manifest was verified asset-by-asset against the published release digests (17/17 match). Verified locally: wire and pin-script suites green, runtime-input checks green, all four Android ABI tarballs downloaded/verified/extracted through the real Gradle tasks, the Windows FetchContent block exercised end to end through cmake, the Linux asset hash and layout checked against the workflow contract, and xcodebuild resolved the flipped SwiftPM graph with SwiftPM validating every binary checksum.
180 lines
6.8 KiB
Python
Executable File
180 lines
6.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Behavior tests for the privileged build-workflow guard."""
|
|
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
CHECKER = ROOT / "scripts/checks/check_build_workflow.py"
|
|
WORKFLOW = ROOT / ".github/workflows/build.yml"
|
|
SETUP_FLUTTER_GIT = ROOT / ".github/actions/setup-flutter-git/action.yml"
|
|
|
|
|
|
class BuildWorkflowGuardTest(unittest.TestCase):
|
|
def _run(self, workflow: str, action: str | None = None) -> subprocess.CompletedProcess[str]:
|
|
with tempfile.TemporaryDirectory(prefix="plezy-build-workflow-test-") as directory:
|
|
# The checker resolves the shared bootstrap beside the workflow, so
|
|
# the fixture has to mirror the real `.github` layout.
|
|
github = Path(directory) / ".github"
|
|
fixture = github / "workflows/build.yml"
|
|
fixture.parent.mkdir(parents=True)
|
|
fixture.write_text(workflow, encoding="utf-8")
|
|
bootstrap = github / "actions/setup-flutter-git/action.yml"
|
|
bootstrap.parent.mkdir(parents=True)
|
|
bootstrap.write_text(action if action is not None else self._action(), encoding="utf-8")
|
|
return subprocess.run(
|
|
[sys.executable, str(CHECKER), str(fixture)],
|
|
cwd=ROOT,
|
|
check=False,
|
|
capture_output=True,
|
|
text=True,
|
|
)
|
|
|
|
def _workflow(self) -> str:
|
|
return WORKFLOW.read_text(encoding="utf-8")
|
|
|
|
def _action(self) -> str:
|
|
return SETUP_FLUTTER_GIT.read_text(encoding="utf-8")
|
|
|
|
def test_locked_root_signer_passes(self) -> None:
|
|
result = self._run(self._workflow())
|
|
|
|
self.assertEqual(result.returncode, 0, result.stderr)
|
|
self.assertIn("architecture matrix checks passed", result.stdout)
|
|
|
|
def test_windows_arm_flutter_without_release_tag_is_rejected(self) -> None:
|
|
action = self._action().replace(
|
|
'git -C $root fetch --depth 1 origin "refs/tags/${version}:refs/tags/${version}"',
|
|
"git -C $root fetch --depth 1 origin 6655482ec06e547f90abf8ae7590466f4415978d",
|
|
1,
|
|
)
|
|
self.assertNotEqual(action, self._action(), "fixture mutation no longer matches the action")
|
|
|
|
result = self._run(self._workflow(), action)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("refs/tags/${version}", result.stderr)
|
|
|
|
def test_mutable_download_in_signing_step_is_rejected(self) -> None:
|
|
workflow = self._workflow().replace(
|
|
" try {\n",
|
|
" Invoke-WebRequest -Uri https://raw.githubusercontent.com/example/main/sign.dart -OutFile sign.dart\n"
|
|
" try {\n",
|
|
1,
|
|
)
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("mutable or ad-hoc input: raw.githubusercontent.com", result.stderr)
|
|
self.assertIn("mutable or ad-hoc input: invoke-webrequest", result.stderr)
|
|
|
|
def test_inline_dependency_resolution_in_signing_step_is_rejected(self) -> None:
|
|
workflow = self._workflow().replace(
|
|
" try {\n",
|
|
" Set-Content -Path pubspec.yaml -Value 'dependencies: {}'\n"
|
|
" dart pub get\n"
|
|
" try {\n",
|
|
1,
|
|
)
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("mutable or ad-hoc input: pubspec.yaml", result.stderr)
|
|
self.assertIn("mutable or ad-hoc input: dart pub get", result.stderr)
|
|
|
|
def test_downloaded_signer_execution_is_rejected(self) -> None:
|
|
workflow = self._workflow().replace(
|
|
"dart run auto_updater:sign_update plezy-windows-installer.exe $keyPath",
|
|
"dart run sign.dart plezy-windows-installer.exe $keyPath",
|
|
1,
|
|
)
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("must execute the locked auto_updater package", result.stderr)
|
|
|
|
def test_unlocked_install_is_rejected(self) -> None:
|
|
prefix, package_and_after = self._workflow().split(" package-windows:\n", 1)
|
|
workflow = prefix + " package-windows:\n" + package_and_after.replace(
|
|
"flutter pub get --enforce-lockfile --no-example",
|
|
"flutter pub get",
|
|
1,
|
|
)
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("enforced root dependency lock", result.stderr)
|
|
|
|
def test_missing_finally_cleanup_is_rejected(self) -> None:
|
|
workflow = self._workflow().replace(" } finally {\n", " }\n", 1)
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("cleanup must run from a finally block", result.stderr)
|
|
|
|
def test_libmpv_cache_without_lock_identity_is_rejected(self) -> None:
|
|
workflow = self._workflow().replace(
|
|
"hashFiles('mpv-build.lock.json')",
|
|
"hashFiles('linux/packaging/native-inputs.json')",
|
|
1,
|
|
)
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("mpv-build lock", result.stderr)
|
|
|
|
def test_draft_release_without_explicit_tag_is_rejected(self) -> None:
|
|
workflow = self._workflow().replace(
|
|
" && inputs.release_tag != '' }}",
|
|
" }}",
|
|
1,
|
|
)
|
|
self.assertNotEqual(workflow, self._workflow(), "fixture mutation no longer matches workflow")
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("must require an explicit release tag", result.stderr)
|
|
|
|
def test_release_run_without_tagged_name_is_rejected(self) -> None:
|
|
workflow = self._workflow().replace(
|
|
"run-name: ${{ inputs.release_tag != '' && format('Release {0}', inputs.release_tag) "
|
|
"|| format('Build {0}', github.sha) }}\n",
|
|
"",
|
|
1,
|
|
)
|
|
self.assertNotEqual(workflow, self._workflow(), "fixture mutation no longer matches workflow")
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("must expose their tag in the run name", result.stderr)
|
|
|
|
def test_release_tag_without_exact_target_is_rejected(self) -> None:
|
|
workflow = self._workflow().replace(
|
|
" target_commitish: ${{ github.sha }}\n",
|
|
"",
|
|
1,
|
|
)
|
|
self.assertNotEqual(workflow, self._workflow(), "fixture mutation no longer matches workflow")
|
|
|
|
result = self._run(workflow)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertIn("must target the exact build commit", result.stderr)
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|