diff --git a/.github/workflows/spotube-release-binary.yml b/.github/workflows/spotube-release-binary.yml index bfd66bb2..b132e670 100644 --- a/.github/workflows/spotube-release-binary.yml +++ b/.github/workflows/spotube-release-binary.yml @@ -27,8 +27,8 @@ concurrency: cancel-in-progress: true env: - COMPOSE_WEBVIEW_REPO: kdroidFilter/ComposeNativeWebview - COMPOSE_WEBVIEW_COMMIT: 5aeb268a2c37ff4dbfb76924573344a7c4f763ce + COMPOSE_WEBVIEW_REPO: KRTirtho/ComposeNativeWebview + COMPOSE_WEBVIEW_COMMIT: cbd36f88927298a28a2d64c6acf7ec3b2def6f9d GRADLE_PLUGIN_REPO: team-spotube/gradle-plugin VLCJ_BUNDLER_REPO: team-spotube/vlcj-bundler-gradle-plugin @@ -181,7 +181,7 @@ jobs: - name: Install packaging tools run: | sudo apt-get update - sudo apt-get install -y rpm fuse + sudo apt-get install -y rpm fuse build-essential zlib1g-dev patchelf - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -198,13 +198,19 @@ jobs: - name: Make gradlew executable run: chmod +x gradlew + - name: Cache GraalVM toolchain + uses: actions/cache@v4 + with: + path: ~/.gradle/nucleus/graalvm + key: graalvm-${{ runner.os }}-${{ runner.arch }} + - name: Package Linux - run: ./gradlew :composeApp:packageReleaseDeb :composeApp:packageReleaseRpm -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }} + run: ./gradlew :composeApp:packageGraalvmDeb :composeApp:packageGraalvmRpm -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }} - name: Rename Linux artifacts run: | - DEB_FILE=$(find composeApp/build -name "*.deb" -type f | head -1) - RPM_FILE=$(find composeApp/build -name "*.rpm" -type f | head -1) + DEB_FILE=$(find composeApp/build/compose/binaries/main/graalvm-deb -maxdepth 1 -name "*.deb" -type f | head -1) + RPM_FILE=$(find composeApp/build/compose/binaries/main/graalvm-rpm -maxdepth 1 -name "*.rpm" -type f | head -1) cp "$DEB_FILE" Spotube-linux-x86_64.deb cp "$RPM_FILE" Spotube-linux-x86_64.rpm @@ -242,7 +248,7 @@ jobs: - name: Install packaging tools run: | sudo apt-get update - sudo apt-get install -y rpm fuse + sudo apt-get install -y rpm fuse build-essential zlib1g-dev patchelf - name: Set up JDK 21 uses: actions/setup-java@v4 @@ -259,13 +265,19 @@ jobs: - name: Make gradlew executable run: chmod +x gradlew + - name: Cache GraalVM toolchain + uses: actions/cache@v4 + with: + path: ~/.gradle/nucleus/graalvm + key: graalvm-${{ runner.os }}-${{ runner.arch }} + - name: Package Linux - run: ./gradlew :composeApp:packageReleaseDeb :composeApp:packageReleaseRpm -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }} + run: ./gradlew :composeApp:packageGraalvmDeb :composeApp:packageGraalvmRpm -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }} - name: Rename Linux artifacts run: | - DEB_FILE=$(find composeApp/build -name "*.deb" -type f | head -1) - RPM_FILE=$(find composeApp/build -name "*.rpm" -type f | head -1) + DEB_FILE=$(find composeApp/build/compose/binaries/main/graalvm-deb -maxdepth 1 -name "*.deb" -type f | head -1) + RPM_FILE=$(find composeApp/build/compose/binaries/main/graalvm-rpm -maxdepth 1 -name "*.rpm" -type f | head -1) cp "$DEB_FILE" Spotube-linux-aarch64.deb cp "$RPM_FILE" Spotube-linux-aarch64.rpm @@ -312,13 +324,25 @@ jobs: - name: Set up Gradle uses: gradle/actions/setup-gradle@v4 + # native-image links with MSVC + - name: Set up MSVC + uses: ilammy/msvc-dev-cmd@v1 + + - name: Cache GraalVM toolchain + uses: actions/cache@v4 + with: + path: ~/.gradle/nucleus/graalvm + key: graalvm-${{ runner.os }}-${{ runner.arch }} + - name: Package EXE - run: ./gradlew :composeApp:packageReleaseExe -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }} + run: ./gradlew :composeApp:packageGraalvmExe -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }} - name: Rename Windows artifacts shell: pwsh run: | - $EXE_FILE = Get-ChildItem -Path composeApp/build -Filter "*.exe" -Recurse | Select-Object -First 1 + # Top level only: subfolders hold the unpacked app, and __* are electron-builder helpers + $EXE_FILE = Get-ChildItem -Path composeApp/build/compose/binaries/main/graalvm-exe -Filter "*.exe" | + Where-Object { $_.Name -notlike '__*' } | Select-Object -First 1 Copy-Item $EXE_FILE.FullName Spotube-windows-x86_64-setup.exe - name: Upload EXE @@ -361,12 +385,18 @@ jobs: - name: Make gradlew executable run: chmod +x gradlew + - name: Cache GraalVM toolchain + uses: actions/cache@v4 + with: + path: ~/.gradle/nucleus/graalvm + key: graalvm-${{ runner.os }}-${{ runner.arch }} + - name: Package DMG - run: ./gradlew :composeApp:packageReleaseDmg -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }} + run: ./gradlew :composeApp:packageGraalvmDmg -Pnightly=${{ inputs.release_type == 'nightly' }} -PbuildNumber=${{ github.run_number }} - name: Rename macOS artifact run: | - DMG_FILE=$(find composeApp/build -name "*.dmg" -type f | head -1) + DMG_FILE=$(find composeApp/build/compose/binaries/main/graalvm-dmg -maxdepth 1 -name "*.dmg" -type f | head -1) cp "$DMG_FILE" Spotube-macos-universal.dmg - name: Upload DMG diff --git a/AGENTS.md b/AGENTS.md index 44b1662f..6de06c88 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,6 +22,15 @@ - Kotlin: `2.3.0`, JVM target: `11` (compile/target compatibility in both `composeApp/build.gradle.kts` and `plugin_interfaces/build.gradle.kts`). - **compose-webview (desktop) dispatcher hijack:** `dev.nucleusframework:composewebview` transitively pulls `nucleus.decorated-window-tao`, which registers a `TaoMainDispatcherFactory` (`loadPriority = 100`) via `META-INF/services` that hijacks `Dispatchers.Main` away from the Swing EDT. This breaks lifecycle's `enforceMainThreadIfNeeded` (window fails with "Method addObserver must be called on the main thread"). The app overrides it with `SwingMainDispatcherFactory` (`composeApp/src/jvmMain/.../core/coroutines/`, `loadPriority = Int.MAX_VALUE`) which pins `Dispatchers.Main` back to the Swing EDT. Keep that factory + its `META-INF/services/kotlinx.coroutines.internal.MainDispatcherFactory` resource; the long-term fix is in the webview (make the Tao dispatcher opt-in or lower its priority). +## GraalVM native image (desktop) +- Enabled via `graalvm { }` inside `nucleus.application` in `composeApp/build.gradle.kts`. Nucleus downloads GraalVM CE itself (or uses `$GRAALVM_HOME`). Linux needs `gcc` + `patchelf`. +- Build: `:composeApp:createGraalvmNativeDistributable` (output `composeApp/build/compose/binaries/main/graalvm-app/`), installers via `packageGraalvm{Deb,Rpm,AppImage,Dmg,Msi,Exe}`. A build takes ~10 min and uses all cores and ~15 GB RAM. +- Hand-maintained + agent-recorded metadata lives in `composeApp/graalvm/reachability-metadata.json`. After adding features that use reflection/JNI/JNA (vlcj, Zipline, plugins), run `:composeApp:runWithNativeAgent`, exercise the feature, then close the window normally (the merge only happens on a clean exit). +- `uk.co.caprica.vlcj.binding.lib.LibVlc` must stay registered (`allDeclaredMethods` + `jniAccessible`): JNA direct mapping finds its `native` methods by reflection. +- The `*UberJarForCurrentOS` task excludes other OSes'/CPUs' native libs and stale CEF binaries, because native-image embeds every registered resource into the binary. On Linux, `copyGraalvmJvmSo` is disabled and `copyGraalvmAwtSoLibs` prefers native-image's own libs (10 KB `libjvm.so`/`libjava.so` shims instead of the JDK's 28 MB `libjvm.so`). +- `isProductionBuild` (top of `composeApp/build.gradle.kts`) is true for `package*`/`create*`/`notarize*`/`nativeImageCompile` tasks, or with `-Pproduction=true|false`. It gates the Rust release profile (Gobley `jvmVariant`) so `:composeApp:run` keeps fast debug Rust builds. Put new slow, ship-only optimizations behind it. +- Full user-facing build docs are in `README.md` ("Building from source"). + ## Codegen and plugin packaging - JS plugin bundles: `:js_plugin_example:packageDevelopmentPlugin` and `:js_plugin_example:packageProductionPlugin`. Output is `.smplug` files in `js_plugin_example/build/distributions/`. - Zipline plugin entrypoint: `mainFunction = "org.spotube.js_plugin_example.main"` in `js_plugin_example/build.gradle.kts`. Plugin metadata from `js_plugin_example/plugin.json`. @@ -53,7 +62,7 @@ - `prepare-deps` job checks out & publishes to `mavenLocal`: - `kdroidFilter/ComposeNativeWebview` (compose-webview) - `team-spotube/gradle-plugin` (spotubeGradle + vlcjBundler plugins) -- Build jobs per platform (Android, Linux, Windows, macOS), all `needs: prepare-deps`. +- Build jobs per platform (Android, Linux, Windows, macOS), all `needs: prepare-deps`. Desktop jobs ship GraalVM native installers (`packageGraalvm{Deb,Rpm,Exe,Dmg}`, output in `composeApp/build/compose/binaries/main/graalvm-/`), not jpackage/JVM ones. - **Stable**: reads `versionName` from `composeApp/build.gradle.kts`, tag = `v{version}`, draft release. - **Nightly**: builds with `-PversionName=nightly`, tag = `nightly` (updates existing), prerelease. - **Android signing**: decodes `secrets.KEYSTORE` (base64) → `composeApp/upload-keystore.jks`, writes signing config into `local.properties` from secrets. diff --git a/README.md b/README.md index 610016b1..6ae62b54 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,104 @@ Grab the latest nightly builds of Spotube [from the Offical Website](https://spo GitHub Workflow Status -You can compile Spotube's source code by [following these instructions](CONTRIBUTION.md#your-first-code-contribution). +Spotube is a Kotlin Multiplatform + Compose Multiplatform app. The desktop app is built with the [Nucleus](https://nucleusframework.dev) Gradle plugin and released as a **GraalVM native image**: an ahead-of-time compiled binary that needs no JVM installed. + +### Prerequisites + +- **JDK 21** to run Gradle. The Gradle daemon JDK and the GraalVM toolchain are provisioned automatically. +- **Rust** (`rustup` + `cargo`). The `composeApp` Rust library is built through [Gobley](https://gobley.dev). +- **Android SDK**, only for Android builds. +- **Unpublished dependencies.** Three dependencies aren't on Maven Central yet. Clone each one and run `./gradlew publishToMavenLocal` in it: + - [kdroidFilter/ComposeNativeWebview](https://github.com/kdroidFilter/ComposeNativeWebview), at the commit pinned in [`spotube-release-binary.yml`](.github/workflows/spotube-release-binary.yml) (`COMPOSE_WEBVIEW_COMMIT`) + - [team-spotube/gradle-plugin](https://github.com/team-spotube/gradle-plugin) + - [team-spotube/vlcj-bundler-gradle-plugin](https://github.com/team-spotube/vlcj-bundler-gradle-plugin) +- **Linux runtime:** `webkit2gtk-4.1` for the embedded webview. + +Always use the Gradle wrapper (`./gradlew`, or `gradlew.bat` on Windows). + +### Development + +```bash +./gradlew :composeApp:run # desktop (JVM), fastest edit/run loop +./gradlew :composeApp:assembleStableDebug # Android debug APK (or assembleNightlyDebug) +``` + +Development builds stay fast. The release-only optimizations (the Rust release profile with LTO and symbol stripping, the native image, native-library pruning) are only applied when you build something to ship. See [Production vs development builds](#production-vs-development-builds). + +### Building the desktop app (GraalVM native image) + +#### Native toolchain + +[Nucleus downloads and caches GraalVM CE](https://nucleusframework.dev/en/docs/performance/graalvm/) under `~/.gradle/nucleus/graalvm` on the first native build. Set `GRAALVM_HOME` to use your own GraalVM instead. Each OS also needs a native C toolchain: + +| OS | Requirements | +|---|---| +| Linux | `gcc`, `zlib` headers, `patchelf`; plus `rpm` for `.rpm` packages. Debian/Ubuntu: `sudo apt-get install build-essential zlib1g-dev patchelf rpm`. Fedora: `sudo dnf install gcc zlib-devel patchelf rpm-build` | +| macOS | Xcode Command Line Tools (`xcode-select --install`) | +| Windows | Visual Studio Build Tools with the "Desktop development with C++" workload. Build from an "x64 Native Tools" prompt if `native-image` can't find MSVC. | + +A native build is a whole-program compile. It takes **about 10 minutes**, uses **every CPU core**, and peaks at roughly **15 GB of RAM** (it gets slower, not broken, with less). That only happens for the native tasks below, never for `run`. + +#### Build and run + +```bash +# App folder: binary + its native libs, in composeApp/build/compose/binaries/main/graalvm-app/ +./gradlew :composeApp:createGraalvmNativeDistributable + +# Run that folder's binary directly +./composeApp/build/compose/binaries/main/graalvm-app/org.spotube.spotube/spotube + +# Or build and launch in one step +./gradlew :composeApp:runGraalvmNativeDistributable +``` + +Keep the `spotube` binary next to the `.so`/`.dll`/`lib/` files it was built with; it loads them from its own folder. + +#### Installers + +Each OS can only build its own installers: + +| OS | Command | Output | +|---|---|---| +| Linux | `./gradlew :composeApp:packageGraalvmDeb :composeApp:packageGraalvmRpm` (also `packageGraalvmAppImage`) | `composeApp/build/compose/binaries/main/graalvm-{deb,rpm}/` | +| Windows | `./gradlew :composeApp:packageGraalvmExe` (or `packageGraalvmMsi`) | `composeApp/build/compose/binaries/main/graalvm-exe/` | +| macOS | `./gradlew :composeApp:packageGraalvmDmg` | `composeApp/build/compose/binaries/main/graalvm-dmg/` | + +Add `-Pnightly=true` for the nightly channel (separate icons and name). + +#### Reflection metadata (when a feature breaks only in the native build) + +A native image is a closed world: anything the app reaches through reflection, JNI or JNA (vlcj, the Zipline/QuickJS plugin engine, NewPipe, ktor, datastore, …) must be declared at build time. Nucleus generates most of this automatically. The rest is in [`composeApp/graalvm/reachability-metadata.json`](composeApp/graalvm/reachability-metadata.json). + +If something works in `:composeApp:run` but throws `ClassNotFoundException`, `NoSuchMethodException`, `MissingReflectionRegistrationError` or similar in the native binary, record what's missing: + +```bash +./gradlew :composeApp:runWithNativeAgent +``` + +The app starts on the JVM with GraalVM's tracing agent attached. Use the broken feature (and anything near it), then **close the window normally**: the recorded entries are merged into `composeApp/graalvm/` only on a clean exit, not with Ctrl+C. Commit the updated JSON and rebuild the native image. + +`./gradlew :composeApp:runGraalvmNative` gives a faster debugging loop: a quick-build (`-Ob`) native image that reports exactly which registration is missing. + +Keep the hand-written `uk.co.caprica.vlcj.binding.lib.LibVlc` entry. JNA's direct mapping looks up its `native` methods through reflection, and without the entry, playback fails to initialize. + +### Production vs development builds + +Production mode is switched on automatically when you ask Gradle for a shippable artifact: any `package*`, `create*`, `notarize*` or `nativeImageCompile` task. You can force it either way with `-Pproduction=true` or `-Pproduction=false`. + +| | Development (`run`, debug builds) | Production (installers, native image) | +|---|---|---| +| Rust library (desktop) | `debug` profile, fast incremental compile | `release` profile with LTO, single codegen unit, stripped (31 MB → 1.8 MB) | +| Desktop app | JVM, hot-reloadable | GraalVM native image, `-Os` (size-optimized) | +| Native libraries | everything on the classpath | only the host OS/CPU's natives; other OSes' and CPUs' libraries are pruned from the uber JAR the image is compiled from | +| Skia (Skiko) | extracted from the JAR | loaded from `lib/` next to the binary, not embedded a second time | +| JDK runtime libs (Linux) | n/a | native-image's ~10 KB `libjvm.so`/`libjava.so` shims instead of the JDK's (28 MB `libjvm.so`) | + +Android picks the Rust profile from its build type (`debug` or `release`) on its own. + +### Releases + +Releases are built by [`spotube-release-binary.yml`](.github/workflows/spotube-release-binary.yml), run manually (`workflow_dispatch`) as `stable` or `nightly`. Each desktop platform (Linux x86_64 and aarch64, Windows, macOS) builds its native installers on its own runner. Android builds a signed APK. ## 👥 The Spotube team diff --git a/composeApp/Cargo.toml b/composeApp/Cargo.toml index 8a06d501..85f1f4dd 100644 --- a/composeApp/Cargo.toml +++ b/composeApp/Cargo.toml @@ -14,4 +14,9 @@ tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } [lib] crate-type = ["cdylib", "staticlib"] -path = "src/commonMain/rust/lib.rs" \ No newline at end of file +path = "src/commonMain/rust/lib.rs" + +[profile.release] +strip = true +lto = true +codegen-units = 1 diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 3a242623..5fd468bf 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -16,7 +16,9 @@ */ import gobley.gradle.GobleyHost +import gobley.gradle.Variant import gobley.gradle.cargo.dsl.jvm +import dev.nucleusframework.desktop.application.dsl.NativeImageOptimization import dev.nucleusframework.desktop.application.dsl.TargetFormat import org.jetbrains.compose.reload.gradle.ComposeHotRun import org.jetbrains.kotlin.gradle.dsl.JvmTarget @@ -50,6 +52,17 @@ val desktopIconDir = if (isNightly) "icons/nightly" else "icons/stable" val nightlyBuildNumber = providers.gradleProperty("buildNumber").orNull?.toIntOrNull() ?: System.getenv("GITHUB_RUN_NUMBER")?.toIntOrNull() ?: buildNumber +// Production = shipping artifacts (installers, distributables, native images). Size/speed +// optimizations that slow builds down (Rust release + LTO) only apply then, never to `run`/dev. +// Override with -Pproduction=true|false. +val isProductionBuild = providers.gradleProperty("production").map { it.toBoolean() }.getOrElse( + gradle.startParameter.taskNames.any { task -> + task.substringAfterLast(':').let { name -> + name.startsWith("package") || name.startsWith("create") || + name.startsWith("notarize") || name == "nativeImageCompile" + } + } +) val targetName = "Spotube" val homePageUrl = "https://spotube.org" val repositoryUrl = "https://github.com/team-spotube/spotube" @@ -431,6 +444,84 @@ nucleus.application { buildTypes.release.proguard { configurationFiles.from(project.file("proguard-rules.pro")) } + + // AOT-compiled native binary (no bundled JVM). Build with `packageGraalvmNative`, try it with + // `runGraalvmNative`. Nucleus downloads GraalVM CE on first use (or uses $GRAALVM_HOME). + // Reflection/JNI metadata the automatic passes miss (vlcj, Zipline, NewPipe/Rhino, uniffi) + // lives in `graalvm/` and is (re)collected by exercising the app under `runWithNativeAgent`. + graalvm { + isEnabled = true + imageName = "spotube" + nativeImageConfigBaseDir.set(project.layout.projectDirectory.dir("graalvm")) + // ProGuard is not used for native images; -Os is the size equivalent + optimization = NativeImageOptimization.SIZE + // Same ceiling as the JVM distribution's -Xmx + maxHeapSize = "384m" + // Skiko is loaded from lib/ next to the binary (Nucleus copies it there); don't embed + // a second ~30 MB copy as a resource + buildArgs.add("-H:+UnlockExperimentalVMOptions") + buildArgs.add("-H:ExcludeResources=.*skiko-[^/]*\\.(so|dylib|dll)") + } +} + +// The uber JAR is what the GraalVM build compiles from, and native-image embeds every registered +// resource in the binary. Libraries ship natives for every OS/CPU, so keep only the host's +// (the uber JAR is per-OS/arch already; it isn't used by `run`). +tasks.withType().matching { it.name.endsWith("UberJarForCurrentOS") }.configureEach { + // Everything the exclude closure uses is local: the configuration cache can't serialize + // references to the build script object. + val hostOs = System.getProperty("os.name").lowercase() + val foreignExtensions = when { + hostOs.contains("win") -> setOf("so", "dylib", "jnilib", "a") + hostOs.contains("mac") -> setOf("so", "dll", "a") + else -> setOf("dll", "dylib", "jnilib", "a") + } + val nativeExtensions = setOf("so", "dll", "dylib", "jnilib", "a") + // CPU names as libraries spell them in their native folders (linux-x86-64, jni/amd64, darwin-aarch64, …) + val cpuDirToken = Regex( + "(?:^|[-_])(x86[-_]64|amd64|x64|aarch64|arm64|armv6|armv7|armel|arm|x86|i[36]86|ppc64le|ppc64|ppc" + + "|s390x|riscv64|loongarch64|mips64el|sparcv9|sparc)(?=$|[-_])" + ) + val hostCpuTokens = when (System.getProperty("os.arch").lowercase()) { + "aarch64", "arm64" -> setOf("aarch64", "arm64") + else -> setOf("x86-64", "x86_64", "amd64", "x64") + } + // OS flavours we never ship desktop builds for (sqlite-jdbc and JNA bundle these) + val foreignOsDirToken = Regex("android|musl|freebsd|openbsd|dragonflybsd|aix|sunos") + exclude { element -> + val segments = element.path.lowercase().split('/') + val extension = segments.last().substringAfterLast('.', "") + !element.isDirectory && extension in nativeExtensions && ( + extension in foreignExtensions || + segments.dropLast(1).any { dir -> + foreignOsDirToken.containsMatchIn(dir) || + cpuDirToken.findAll(dir).any { it.groupValues[1] !in hostCpuTokens } + } + ) + } + // The locally built compose-webview SNAPSHOT carries stale CEF binaries (~1.3 GB unstripped + // libcef.so) that no code loads (Linux uses WebKitGTK). + exclude( + "nucleus/native/*/libcef.*", + "nucleus/native/*/libcompose_cef_*", + "nucleus/native/*/cef_subprocess*", + "nucleus/native/*/*.pak", + "nucleus/native/*/icudtl.dat", + "nucleus/native/*/v8_context_snapshot.bin", + ) +} + +// Linux: native-image emits 13 KB libjvm.so/libjava.so shims (the image itself provides the JVM +// symbols), but Nucleus copies the JDK's real ones next to the binary — libjvm.so alone is 28 MB. +// Ship native-image's own outputs instead: skip the libjvm copy, and in the AWT copy prefer +// nativeCompile's version of any library native-image produced over the JDK's. +tasks.matching { it.name == "copyGraalvmJvmSo" }.configureEach { enabled = false } +tasks.withType().matching { it.name == "copyGraalvmAwtSoLibs" }.configureEach { + val nativeCompileDir = layout.buildDirectory.dir("compose/tmp/main/graalvm/nativeCompile").get().asFile + from(nativeCompileDir) { include("*.so") } + eachFile { + if (file.parentFile != nativeCompileDir && nativeCompileDir.resolve(name).exists()) exclude() + } } // Issue with compose plugin @@ -446,6 +537,8 @@ tasks.withType().configureEach { } cargo { + // Gobley defaults desktop to the debug profile, which ships an unstripped ~31 MB library + if (isProductionBuild) jvmVariant = Variant.Release builds.jvm { embedRustLibrary = (rustTarget == GobleyHost.current.rustTarget) } diff --git a/composeApp/graalvm/reachability-metadata.json b/composeApp/graalvm/reachability-metadata.json new file mode 100644 index 00000000..6d8dfc7c --- /dev/null +++ b/composeApp/graalvm/reachability-metadata.json @@ -0,0 +1,1332 @@ +{ + "reflection": [ + { + "type": "uk.co.caprica.vlcj.binding.lib.LibVlc", + "jniAccessible": true, + "allDeclaredMethods": true, + "allDeclaredFields": true + }, + { + "type": "androidx.compose.ui.platform.GlobalSnapshotManager$Registration" + }, + { + "type": "androidx.datastore.preferences.PreferencesProto$PreferenceMap", + "fields": [ + { + "name": "preferences_" + } + ] + }, + { + "type": "androidx.datastore.preferences.PreferencesProto$Value", + "fields": [ + { + "name": "valueCase_" + }, + { + "name": "value_" + } + ] + }, + { + "type": "app.cash.zipline.InterruptHandler", + "jniAccessible": true, + "methods": [ + { + "name": "poll", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "app.cash.zipline.QuickJsException", + "jniAccessible": true, + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.String", + "java.lang.String" + ] + } + ] + }, + { + "type": "app.cash.zipline.internal.bridge.CallChannel", + "jniAccessible": true, + "methods": [ + { + "name": "call", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "disconnect", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "type": "com.sun.crypto.provider.HKDFKeyDerivation$HKDFSHA384", + "methods": [ + { + "name": "", + "parameterTypes": [ + "javax.crypto.KDFParameters" + ] + } + ] + }, + { + "type": "com.sun.crypto.provider.HmacCore$HmacSHA384", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "com.sun.jna.CallbackReference$AttachOptions", + "jniAccessible": true, + "fields": [ + { + "name": "daemon" + }, + { + "name": "detach" + }, + { + "name": "name" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "com.sun.jna.Union" + }, + { + "type": "dev.nucleusframework.window.tao.event.TaoSyntheticKeyKt$SyntheticAwtKeyEventSource$1" + }, + { + "type": "dev.nucleusframework.window.tao.event.TaoSyntheticMouseWheelEvent$source$1" + }, + { + "type": "io.ktor.client.plugins.DefaultRequest" + }, + { + "type": "io.ktor.client.plugins.HttpTimeoutConfig" + }, + { + "type": "io.ktor.client.plugins.websocket.WebSockets" + }, + { + "type": "io.ktor.server.application.PluginInstance" + }, + { + "type": "io.ktor.server.plugins.MutableOriginConnectionPoint" + }, + { + "type": "io.ktor.utils.io.ByteReadChannel" + }, + { + "type": "java.lang.Boolean", + "jniAccessible": true, + "fields": [ + { + "name": "TYPE" + }, + { + "name": "value" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "boolean" + ] + }, + { + "name": "booleanValue", + "parameterTypes": [ + + ] + }, + { + "name": "getBoolean", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "valueOf", + "parameterTypes": [ + "boolean" + ] + } + ] + }, + { + "type": "java.lang.Double", + "jniAccessible": true, + "fields": [ + { + "name": "TYPE" + }, + { + "name": "value" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "double" + ] + }, + { + "name": "valueOf", + "parameterTypes": [ + "double" + ] + } + ] + }, + { + "type": "java.lang.Integer", + "jniAccessible": true, + "fields": [ + { + "name": "TYPE" + }, + { + "name": "value" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "int" + ] + }, + { + "name": "valueOf", + "parameterTypes": [ + "int" + ] + } + ] + }, + { + "type": "java.lang.Module", + "methods": [ + { + "name": "getDescriptor", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "java.lang.StackWalker", + "methods": [ + { + "name": "getInstance", + "parameterTypes": [ + "java.lang.StackWalker$Option" + ] + }, + { + "name": "walk", + "parameterTypes": [ + "java.util.function.Function" + ] + } + ] + }, + { + "type": "java.lang.StackWalker$StackFrame", + "methods": [ + { + "name": "getDeclaringClass", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "kotlin.Any" + }, + { + "type": "kotlin.Boolean" + }, + { + "type": "kotlin.Cloneable" + }, + { + "type": "kotlin.Function0" + }, + { + "type": "kotlin.Function2" + }, + { + "type": "kotlin.Function3" + }, + { + "type": "kotlin.Int" + }, + { + "type": "kotlin.Long" + }, + { + "type": "kotlin.String" + }, + { + "type": "kotlin.Throwable" + }, + { + "type": "kotlin.collections.List" + }, + { + "type": "kotlin.collections.Map" + }, + { + "type": "kotlinx.atomicfu.AtomicBoolean" + }, + { + "type": "kotlinx.io.Source" + }, + { + "type": "org.freedesktop.dbus.connections.impl.DBusConnection", + "methods": [ + { + "name": "addGenericSigHandler", + "parameterTypes": [ + "org.freedesktop.dbus.matchrules.DBusMatchRule", + "org.freedesktop.dbus.interfaces.DBusSigHandler" + ] + } + ] + }, + { + "type": "org.robolectric.Robolectric" + }, + { + "type": "org.spotube.spotube.core.server.StreamProxy", + "methods": [ + { + "name": "respondWithUpstream", + "parameterTypes": [ + "io.ktor.server.application.ApplicationCall", + "io.ktor.client.statement.HttpResponse", + "java.lang.String", + "io.ktor.http.HttpMethod", + "kotlin.coroutines.Continuation" + ] + } + ] + }, + { + "type": "org.spotube.spotube.core.server.StreamProxy$respondWithUpstream$5" + }, + { + "type": "org.spotube.spotube.core.zipline.ZiplinePluginService$use$2", + "fields": [ + { + "name": "label" + } + ] + }, + { + "type": "sun.net.www.protocol.http.Handler", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "sun.net.www.protocol.https.Handler", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "sun.security.rsa.RSASignature$SHA384withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "sun.text.resources.cldr.FormatData_en" + }, + { + "type": "sun.text.resources.cldr.FormatData_en_US" + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_callback_t", + "jniAccessible": true, + "methods": [ + { + "name": "callback", + "parameterTypes": [ + "uk.co.caprica.vlcj.binding.internal.libvlc_event_t", + "com.sun.jna.Pointer" + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_dialog_id", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_equalizer_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_event_manager_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_event_t", + "fields": [ + { + "name": "obj" + }, + { + "name": "type" + }, + { + "name": "u" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_event_u", + "fields": [ + { + "name": "media_duration_changed" + }, + { + "name": "media_freed" + }, + { + "name": "media_list_item_added" + }, + { + "name": "media_list_item_deleted" + }, + { + "name": "media_list_player_next_item_set" + }, + { + "name": "media_list_will_add_item" + }, + { + "name": "media_list_will_delete_item" + }, + { + "name": "media_meta_changed" + }, + { + "name": "media_parsed_changed" + }, + { + "name": "media_player_audio_device" + }, + { + "name": "media_player_audio_volume" + }, + { + "name": "media_player_buffering" + }, + { + "name": "media_player_chapter_changed" + }, + { + "name": "media_player_es_changed" + }, + { + "name": "media_player_length_changed" + }, + { + "name": "media_player_media_changed" + }, + { + "name": "media_player_pausable_changed" + }, + { + "name": "media_player_position_changed" + }, + { + "name": "media_player_scrambled_changed" + }, + { + "name": "media_player_seekable_changed" + }, + { + "name": "media_player_snapshot_taken" + }, + { + "name": "media_player_time_changed" + }, + { + "name": "media_player_title_changed" + }, + { + "name": "media_player_vout" + }, + { + "name": "media_state_changed" + }, + { + "name": "media_subitem_added" + }, + { + "name": "media_subitemtree_added" + }, + { + "name": "media_thumbnail_generated" + }, + { + "name": "renderer_discoverer_item_added" + }, + { + "name": "renderer_discoverer_item_deleted" + }, + { + "name": "vlm_media_event" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_instance_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_log_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_media_discoverer_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_media_list_player_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_media_list_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_media_player_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_media_stats_t", + "fields": [ + { + "name": "f_demux_bitrate" + }, + { + "name": "f_input_bitrate" + }, + { + "name": "f_send_bitrate" + }, + { + "name": "i_decoded_audio" + }, + { + "name": "i_decoded_video" + }, + { + "name": "i_demux_corrupted" + }, + { + "name": "i_demux_discontinuity" + }, + { + "name": "i_demux_read_bytes" + }, + { + "name": "i_displayed_pictures" + }, + { + "name": "i_lost_abuffers" + }, + { + "name": "i_lost_pictures" + }, + { + "name": "i_played_abuffers" + }, + { + "name": "i_read_bytes" + }, + { + "name": "i_sent_bytes" + }, + { + "name": "i_sent_packets" + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_media_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_picture_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_renderer_discoverer_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.libvlc_renderer_item_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_duration_changed", + "fields": [ + { + "name": "new_duration" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_freed", + "fields": [ + { + "name": "md" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_list_item_added", + "fields": [ + { + "name": "index" + }, + { + "name": "item" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_list_item_deleted", + "fields": [ + { + "name": "index" + }, + { + "name": "item" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_list_player_next_item_set", + "fields": [ + { + "name": "item" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_list_will_add_item", + "fields": [ + { + "name": "index" + }, + { + "name": "item" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_list_will_delete_item", + "fields": [ + { + "name": "index" + }, + { + "name": "item" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_meta_changed", + "fields": [ + { + "name": "meta_type" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_parsed_changed", + "fields": [ + { + "name": "new_status" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_audio_device", + "fields": [ + { + "name": "device" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_audio_volume", + "fields": [ + { + "name": "volume" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_buffering", + "fields": [ + { + "name": "new_cache" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_chapter_changed", + "fields": [ + { + "name": "new_chapter" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_es_changed", + "fields": [ + { + "name": "i_id" + }, + { + "name": "i_type" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_length_changed", + "fields": [ + { + "name": "new_length" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_media_changed", + "fields": [ + { + "name": "md" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_pausable_changed", + "fields": [ + { + "name": "new_pausable" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_position_changed", + "fields": [ + { + "name": "new_position" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_scrambled_changed", + "fields": [ + { + "name": "new_scrambled" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_seekable_changed", + "fields": [ + { + "name": "new_seekable" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_snapshot_taken", + "fields": [ + { + "name": "filename" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_time_changed", + "fields": [ + { + "name": "new_time" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_title_changed", + "fields": [ + { + "name": "new_title" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_player_vout", + "fields": [ + { + "name": "new_count" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_state_changed", + "fields": [ + { + "name": "new_state" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_subitem_added", + "fields": [ + { + "name": "new_child" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_subitemtree_added", + "fields": [ + { + "name": "item" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.media_thumbnail_generated", + "fields": [ + { + "name": "p_thumbnail" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.renderer_discoverer_item_added", + "fields": [ + { + "name": "item" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.renderer_discoverer_item_deleted", + "fields": [ + { + "name": "item" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.internal.vlm_media_event", + "fields": [ + { + "name": "psz_instance_name" + }, + { + "name": "psz_media_name" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.binding.support.types.size_t", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uk.co.caprica.vlcj.support.eventmanager.NativeEventManager$EventCallback" + }, + { + "type": "uniffi.compose_app.ForeignBytesStruct", + "fields": [ + { + "name": "data" + }, + { + "name": "len" + } + ] + }, + { + "type": "uniffi.compose_app.ForeignBytesStruct$ByValue", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uniffi.compose_app.IntegrityCheckingUniffiLib" + }, + { + "type": "uniffi.compose_app.RustBufferStruct", + "fields": [ + { + "name": "capacity" + }, + { + "name": "data" + }, + { + "name": "len" + } + ] + }, + { + "type": "uniffi.compose_app.RustBufferStruct$ByValue", + "methods": [ + { + "name": "", + "parameterTypes": [ + + ] + } + ] + }, + { + "type": "uniffi.compose_app.UniffiLib" + }, + { + "type": "uniffi.compose_app.UniffiRustCallStatusStruct", + "fields": [ + { + "name": "code" + }, + { + "name": "errorBuf" + } + ] + }, + { + "type": "uniffi.compose_app.UniffiRustCallStatusStruct$ByReference" + } + ], + "resources": [ + { + "glob": "okhttp3/internal/publicsuffix/PublicSuffixDatabase.list" + } + ] +}