#!/usr/bin/env python3 """Run deterministic real Kotlin controls tests and optional Android SDK compilation. Uses cached Kotlin jars, Android 36 and LibVLC 3.7.7; no downloads or Gradle. All compiler invocations share the repository tooling lock. """ import argparse import fcntl import os from pathlib import Path import subprocess import tempfile ROOT = Path(__file__).resolve().parents[1] SRC = ROOT / 'android/app/src/main/kotlin/org/reelbox/reelbox' TEST = ROOT / 'android/app/src/test/kotlin/org/reelbox/reelbox' CACHE = Path.home() / '.gradle/caches/modules-2/files-2.1' SCRATCH = Path('/root/.hermes/cache/scratch') def compiler_jars(): jars = [] for artifact, version in [ ('org.jetbrains.kotlin/kotlin-compiler-embeddable', '2.4.0'), ('org.jetbrains.kotlin/kotlin-stdlib', '2.4.0'), ('org.jetbrains.kotlin/kotlin-script-runtime', '2.4.0'), ('org.jetbrains.kotlin/kotlin-reflect', '*'), ('org.jetbrains.intellij.deps/trove4j', '*'), ('org.jetbrains.kotlinx/kotlinx-coroutines-core-jvm', '*'), ('org.jetbrains/annotations', '*'), ]: matches = sorted((CACHE / artifact).glob(version + '/*/*.jar')) if matches: jars.append(str(matches[-1])) elif version != '*': raise RuntimeError(f'Missing cached compiler dependency: {artifact}:{version}') return jars def compile_sources(jars, sources, classpath, out): subprocess.run([ 'java', '-Xmx320m', '-XX:MaxMetaspaceSize=192m', '-XX:+UseSerialGC', f'-Djava.io.tmpdir={SCRATCH}', '-cp', os.pathsep.join(jars), 'org.jetbrains.kotlin.cli.jvm.K2JVMCompiler', '-jvm-target', '17', '-no-stdlib', '-no-reflect', '-classpath', os.pathsep.join(classpath), '-d', str(out), *map(str, sources), ], cwd=ROOT, check=True, timeout=180) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--native', action='store_true', help='Also compile actual native player sources') args = parser.parse_args() print('Waiting for repository tooling lock', flush=True) with (ROOT / '.tooling.lock').open('a') as lock: fcntl.flock(lock, fcntl.LOCK_EX) print('Repository tooling lock acquired', flush=True) jars = compiler_jars() with tempfile.TemporaryDirectory(prefix='player-controls-', dir=SCRATCH) as temp: out = Path(temp) / 'state' compile_sources(jars, [SRC / 'PlayerControlsState.kt', TEST / 'PlayerControlsStateTest.kt'], jars, out) subprocess.run(['java', '-Xmx96m', '-cp', os.pathsep.join([str(out), *jars]), 'org.reelbox.reelbox.tests.PlayerControlsStateTestKt'], check=True, cwd=ROOT, timeout=30) if args.native: deps = [ '/root/developer-tools/android/platforms/android-36/android.jar', '/root/.hermes/cache/scratch/reelbox-vlc-api/classes.jar', '/root/developer-tools/flutter/bin/cache/artifacts/engine/android-arm64-release/flutter.jar', str(ROOT / 'build/app/tmp/kotlin-classes/release'), ] deps += map(str, (Path.home() / '.gradle/caches/9.3.1/transforms').glob('*/transformed/core-1.16.0-api.jar')) deps += map(str, (CACHE / 'androidx.lifecycle/lifecycle-common-jvm').glob('*/*/*.jar')) sources = [SRC / name for name in [ 'NativeUrlPolicy.kt', 'NativePlayerPolicy.kt', 'NativeFileIntents.kt', 'PlayerControlsState.kt', 'PlayerOverlayView.kt', 'VlcPlayerActivity.kt', 'MainActivity.kt', ]] compile_sources(jars, sources, [*jars, *deps], Path(temp) / 'native') print('Native player compilation PASS: actual Android 36 / LibVLC 3.7.7 SDKs', flush=True) if __name__ == '__main__': main()