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.
248 lines
10 KiB
Ruby
Executable File
248 lines
10 KiB
Ruby
Executable File
#!/usr/bin/env ruby
|
|
|
|
require 'fileutils'
|
|
require 'json'
|
|
require 'minitest/autorun'
|
|
require 'open3'
|
|
require 'rbconfig'
|
|
require 'tmpdir'
|
|
require 'xcodeproj'
|
|
|
|
class WireMpvTest < Minitest::Test
|
|
SOURCE_NAMES = %w[
|
|
MpvPlayerCoreBase.swift
|
|
MpvPlayerPluginShared.swift
|
|
MpvPlayerCore.swift
|
|
MpvPlayerPlugin.swift
|
|
MpvPipController.swift
|
|
MpvAudioPlayerCore.swift
|
|
MpvAudioPlayerPlugin.swift
|
|
].freeze
|
|
AFFECTED_NAMES = %w[
|
|
MpvAudioPlayerCore.swift
|
|
MpvAudioPlayerPlugin.swift
|
|
].freeze
|
|
# Without a lock the pin may sit on either side of the MPVKit -> mpv-build
|
|
# repo flip, as long as every site names the same repo; with a lock, the
|
|
# lock names the repo and every site must match it.
|
|
NATIVE_LOCATIONS = [
|
|
'https://github.com/edde746/MPVKit',
|
|
'https://github.com/edde746/mpv-build',
|
|
].freeze
|
|
NATIVE_REVISION = /\A[0-9a-f]{40}\z/.freeze
|
|
|
|
def setup
|
|
@temporary_root = Dir.mktmpdir('wire-mpv-test')
|
|
@tvos_root = File.join(@temporary_root, 'tvos')
|
|
FileUtils.mkdir_p(File.join(@tvos_root, 'scripts'))
|
|
FileUtils.cp_r(File.expand_path('../Runner.xcodeproj', __dir__), @tvos_root)
|
|
FileUtils.cp(File.expand_path('wire_mpv.rb', __dir__), File.join(@tvos_root, 'scripts'))
|
|
end
|
|
|
|
def teardown
|
|
FileUtils.remove_entry(@temporary_root)
|
|
end
|
|
|
|
def test_restores_missing_references_and_is_idempotent
|
|
edit_project do |_project, _runner, group|
|
|
AFFECTED_NAMES.each do |name|
|
|
group.files.find { |file| file.display_name == name }&.remove_from_project
|
|
end
|
|
end
|
|
|
|
run_wire_mpv
|
|
run_wire_mpv
|
|
assert_complete_source_graph
|
|
end
|
|
|
|
def test_restores_membership_when_references_remain
|
|
edit_project do |_project, runner, group|
|
|
affected = group.files.select { |file| AFFECTED_NAMES.include?(file.display_name) }
|
|
runner.source_build_phase.files.each do |build_file|
|
|
build_file.remove_from_project if affected.include?(build_file.file_ref)
|
|
end
|
|
end
|
|
|
|
run_wire_mpv
|
|
assert_complete_source_graph
|
|
end
|
|
|
|
def test_restores_missing_package_product_and_framework_edges
|
|
edit_project do |_project, runner, _group|
|
|
runner.package_product_dependencies
|
|
.select { |product| product.product_name == 'MPVKit' }
|
|
.each(&:remove_from_project)
|
|
end
|
|
|
|
run_wire_mpv
|
|
assert_complete_source_graph
|
|
end
|
|
|
|
# After scripts/set_native_revision.sh flips the repo, a Flutter-regenerated
|
|
# project still carries the old package reference; wire_mpv.rb must update it
|
|
# in place from the lock instead of hardcoding one repo or adding a second
|
|
# reference.
|
|
def test_wire_updates_the_package_reference_across_the_repo_flip
|
|
revision = flip_swiftpm_pin_to('edde746/mpv-build')
|
|
write_repo_lock('edde746/mpv-build', revision)
|
|
|
|
run_wire_mpv
|
|
|
|
project = Xcodeproj::Project.open(project_path)
|
|
references = project.root_object.package_references.select do |candidate|
|
|
(candidate.repositoryURL rescue nil)
|
|
end
|
|
assert_equal 1, references.count, 'expected exactly one remote package reference'
|
|
assert_equal 'https://github.com/edde746/mpv-build', references.first.repositoryURL
|
|
assert_equal({ 'kind' => 'revision', 'revision' => revision }, references.first.requirement)
|
|
assert_complete_source_graph
|
|
end
|
|
|
|
def test_wire_refuses_a_lock_that_disagrees_with_the_swiftpm_pin
|
|
# Whichever side of the flip the copied pin sits on, name the other one.
|
|
lock = JSON.parse(File.read(swiftpm_lock_path))
|
|
pin = lock.fetch('pins').find { |candidate| %w[mpvkit mpv-build].include?(candidate['identity']) }
|
|
refute_nil pin, 'copied lock has no native package pin'
|
|
other = pin['identity'] == 'mpvkit' ? 'edde746/mpv-build' : 'edde746/MPVKit'
|
|
write_repo_lock(other, 'f' * 40)
|
|
|
|
output = run_wire_mpv_failure
|
|
assert_match(/no #{Regexp.escape(File.basename(other).downcase)} pin/, output)
|
|
end
|
|
|
|
# The native package is pinned by commit so any upstream commit is
|
|
# consumable without a release. Assert the shape and that every pin site
|
|
# agrees, rather than a literal sha or repo: scripts/set_native_revision.sh
|
|
# is the only thing that writes one, and it must stay the only file to edit
|
|
# when the pin moves. The root mpv-build.lock.json is the same pin's
|
|
# non-Apple carrier; once it exists it is the tenth site: it names the repo
|
|
# every Apple site must point at and the commit they must all pin.
|
|
def test_all_apple_targets_pin_the_native_package_to_one_commit
|
|
repository_root = File.expand_path('../..', __dir__)
|
|
lock_path = File.join(repository_root, 'mpv-build.lock.json')
|
|
lock = File.exist?(lock_path) ? JSON.parse(File.read(lock_path)) : nil
|
|
|
|
expected_locations =
|
|
if lock
|
|
assert_equal 1, lock['formatVersion'], "#{lock_path} formatVersion"
|
|
assert_match %r{\A[\w.-]+/[\w.-]+\z}, lock['repo'].to_s, "#{lock_path} repo must name owner/name"
|
|
assert_match NATIVE_REVISION, lock['commit'].to_s, "#{lock_path} commit"
|
|
["https://github.com/#{lock['repo']}"]
|
|
else
|
|
NATIVE_LOCATIONS
|
|
end
|
|
|
|
locations = {}
|
|
revisions = {}
|
|
|
|
%w[ios macos tvos].each do |platform|
|
|
lock_paths = [
|
|
File.join(repository_root, platform, 'Runner.xcworkspace', 'xcshareddata', 'swiftpm', 'Package.resolved'),
|
|
File.join(
|
|
repository_root, platform, 'Runner.xcodeproj', 'project.xcworkspace',
|
|
'xcshareddata', 'swiftpm', 'Package.resolved'
|
|
),
|
|
]
|
|
lock_paths.each do |resolved_path|
|
|
resolved = JSON.parse(File.read(resolved_path))
|
|
pin = resolved.fetch('pins').find { |candidate| expected_locations.include?(candidate['location']) }
|
|
refute_nil pin, "#{resolved_path} must resolve the native package from #{expected_locations.join(' or ')}"
|
|
assert_equal 'remoteSourceControl', pin['kind'], "#{resolved_path} native pin kind"
|
|
assert_equal File.basename(pin['location']).downcase, pin['identity'],
|
|
"#{resolved_path} native pin identity must derive from its location"
|
|
state = pin.fetch('state')
|
|
assert_match NATIVE_REVISION, state['revision'].to_s, "#{resolved_path} native pin revision"
|
|
refute state.key?('version'), "#{resolved_path} pins the native package by version; it must pin a commit"
|
|
refute state.key?('branch'), "#{resolved_path} pins the native package by branch; it must pin a commit"
|
|
locations[resolved_path] = pin['location']
|
|
revisions[resolved_path] = state['revision']
|
|
end
|
|
|
|
project = Xcodeproj::Project.open(File.join(repository_root, platform, 'Runner.xcodeproj'))
|
|
package = project.root_object.package_references.find do |candidate|
|
|
expected_locations.include?((candidate.repositoryURL rescue nil))
|
|
end
|
|
refute_nil package, "#{platform} must reference the native package from #{expected_locations.join(' or ')}"
|
|
requirement = package.requirement
|
|
assert_equal 'revision', requirement['kind'], "#{platform} native requirement kind"
|
|
assert_match NATIVE_REVISION, requirement['revision'].to_s, "#{platform} native requirement revision"
|
|
locations[File.join(platform, 'Runner.xcodeproj')] = package.repositoryURL
|
|
revisions[File.join(platform, 'Runner.xcodeproj')] = requirement['revision']
|
|
end
|
|
|
|
revisions[lock_path] = lock['commit'] if lock
|
|
assert_equal 1, locations.values.uniq.count, "Apple targets disagree on the native repo: #{locations}"
|
|
assert_equal 1, revisions.values.uniq.count, "Apple targets disagree on the native commit: #{revisions}"
|
|
end
|
|
|
|
private
|
|
|
|
def project_path
|
|
File.join(@tvos_root, 'Runner.xcodeproj')
|
|
end
|
|
|
|
def edit_project
|
|
project = Xcodeproj::Project.open(project_path)
|
|
runner = project.targets.find { |target| target.name == 'Runner' }
|
|
group = project.main_group['Runner']['MpvPlayer']
|
|
yield project, runner, group
|
|
project.save
|
|
end
|
|
|
|
def run_wire_mpv
|
|
script = File.join(@tvos_root, 'scripts', 'wire_mpv.rb')
|
|
output, status = Open3.capture2e(RbConfig.ruby, script)
|
|
assert status.success?, output
|
|
end
|
|
|
|
def run_wire_mpv_failure
|
|
script = File.join(@tvos_root, 'scripts', 'wire_mpv.rb')
|
|
output, status = Open3.capture2e(RbConfig.ruby, script)
|
|
refute status.success?, "wire_mpv.rb unexpectedly succeeded:\n#{output}"
|
|
output
|
|
end
|
|
|
|
def swiftpm_lock_path
|
|
File.join(project_path, 'project.xcworkspace', 'xcshareddata', 'swiftpm', 'Package.resolved')
|
|
end
|
|
|
|
# Rewrites the copied project's SwiftPM pin to the given repo, returning the
|
|
# pin's revision. Mirrors what set_native_revision.sh does to the real lock.
|
|
def flip_swiftpm_pin_to(repo)
|
|
lock = JSON.parse(File.read(swiftpm_lock_path))
|
|
pin = lock.fetch('pins').find { |candidate| %w[mpvkit mpv-build].include?(candidate['identity']) }
|
|
refute_nil pin, 'copied lock has no native package pin'
|
|
pin['identity'] = File.basename(repo, '.git').downcase
|
|
pin['location'] = "https://github.com/#{repo}"
|
|
File.write(swiftpm_lock_path, JSON.pretty_generate(lock))
|
|
pin.fetch('state').fetch('revision')
|
|
end
|
|
|
|
# The temp root stands in for the repository root: wire_mpv.rb resolves
|
|
# mpv-build.lock.json two directories above itself.
|
|
def write_repo_lock(repo, commit)
|
|
File.write(
|
|
File.join(@temporary_root, 'mpv-build.lock.json'),
|
|
JSON.pretty_generate('formatVersion' => 1, 'repo' => repo, 'commit' => commit, 'artifacts' => {})
|
|
)
|
|
end
|
|
|
|
def assert_complete_source_graph
|
|
project = Xcodeproj::Project.open(project_path)
|
|
runner = project.targets.find { |target| target.name == 'Runner' }
|
|
group = project.main_group['Runner']['MpvPlayer']
|
|
|
|
SOURCE_NAMES.each do |name|
|
|
references = group.files.select { |file| file.display_name == name }
|
|
assert_equal 1, references.count, "expected one reference for #{name}"
|
|
memberships = runner.source_build_phase.files_references.count { |file| file == references.first }
|
|
assert_equal 1, memberships, "expected one Runner source membership for #{name}"
|
|
end
|
|
|
|
products = runner.package_product_dependencies.select { |product| product.product_name == 'MPVKit' }
|
|
assert_equal 1, products.count, 'expected one MPVKit product dependency'
|
|
framework_links = runner.frameworks_build_phase.files.count { |file| file.product_ref == products.first }
|
|
assert_equal 1, framework_links, 'expected one MPVKit framework link'
|
|
end
|
|
end
|