From bab6434c6263cdb8c1319791db6754116ac56476 Mon Sep 17 00:00:00 2001 From: Xare123 <57245242+Xare123@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:30:21 -0700 Subject: [PATCH 1/4] Fix the CargoKit hang introduced by the plugin-detection fix --- rust_builder/cargokit/gradle/plugin.gradle | 68 ++++++++++++++++------ 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/rust_builder/cargokit/gradle/plugin.gradle b/rust_builder/cargokit/gradle/plugin.gradle index 28636e9811..54e04d69f8 100644 --- a/rust_builder/cargokit/gradle/plugin.gradle +++ b/rust_builder/cargokit/gradle/plugin.gradle @@ -82,39 +82,71 @@ class CargoKitPlugin implements Plugin { static String file; - private Plugin findFlutterPlugin(Project rootProject) { - _findFlutterPlugin(rootProject.childProjects) + // Returns the Project that Flutter's Gradle plugin is applied to, not the + // plugin instance. + // + // CargoKit used to keep the plugin and reach through `plugin.project`. That + // worked against the Groovy plugin, where the field was reachable. Flutter + // 3.44.8's Kotlin rewrite declares `private var project: Project? = null` + // and dropped `getTargetPlatforms()` entirely, moving it to + // FlutterPluginUtils. Holding the plugin therefore buys nothing and every + // call through it throws once per variant. + private Project findFlutterProject(Project rootProject) { + _findFlutterProject(rootProject.childProjects) } - private Plugin _findFlutterPlugin(Map projects) { + private Project _findFlutterProject(Map projects) { for (project in projects) { for (plugin in project.value.getPlugins()) { - if (plugin.class.name == "FlutterPlugin") { - return plugin; + // Flutter's Groovy Gradle plugin lived in the default package. + // The Kotlin rewrite moved it to com.flutter.gradle, so match + // the class name itself rather than the fully qualified name. + // Getting this wrong silently skips the whole Rust build and + // ships a package with no native library in it. + def pluginClassName = plugin.class.name + if (pluginClassName == "FlutterPlugin" || + pluginClassName.endsWith(".FlutterPlugin")) { + return project.value; } } - def plugin = _findFlutterPlugin(project.value.childProjects); - if (plugin != null) { - return plugin; + def found = _findFlutterProject(project.value.childProjects); + if (found != null) { + return found; } } return null; } + // Mirrors FlutterPluginUtils.getTargetPlatforms, which is Kotlin-internal + // and not callable from Groovy. Flutter passes -Ptarget-platform on the + // command line; when it is absent this is the same default the Flutter + // plugin itself applies. + private static List flutterTargetPlatforms(Project flutterProject) { + if (flutterProject.hasProperty("target-platform")) { + return flutterProject + .property("target-platform") + .toString() + .split(",") + .collect { it.trim() } + .findAll { !it.isEmpty() } + } + return ["android-arm", "android-arm64", "android-x64"] + } + @Override void apply(Project project) { - def plugin = findFlutterPlugin(project.rootProject); + def flutterProject = findFlutterProject(project.rootProject); project.extensions.create("cargokit", CargoKitExtension) - if (plugin == null) { - print("Flutter plugin not found, CargoKit plugin will not be applied.") + if (flutterProject == null) { + println("Flutter plugin not found, CargoKit plugin will not be applied.") return; } def cargoBuildDir = "${project.buildDir}/build" - plugin.project.android.applicationVariants.all { variant -> + flutterProject.android.applicationVariants.all { variant -> final buildType = variant.buildType.name @@ -122,7 +154,7 @@ class CargoKitPlugin implements Plugin { def jniLibs = project.android.sourceSets.maybeCreate(buildType).jniLibs; jniLibs.srcDir(new File(cargoOutputDir)) - def platforms = plugin.getTargetPlatforms().collect() + def platforms = flutterTargetPlatforms(flutterProject).collect() // Same thing addFlutterDependencies does in flutter.gradle if (buildType == "debug") { @@ -139,7 +171,7 @@ class CargoKitPlugin implements Plugin { return } - if (plugin.project.android.ndkVersion == null) { + if (flutterProject.android.ndkVersion == null) { throw new GradleException("Please set 'android.ndkVersion' in 'app/build.gradle'.") } @@ -147,10 +179,10 @@ class CargoKitPlugin implements Plugin { buildMode = variant.buildType.name buildDir = cargoBuildDir outputDir = cargoOutputDir - ndkVersion = plugin.project.android.ndkVersion - sdkDirectory = plugin.project.android.sdkDirectory - minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int - compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int + ndkVersion = flutterProject.android.ndkVersion + sdkDirectory = flutterProject.android.sdkDirectory + minSdkVersion = flutterProject.android.defaultConfig.minSdkVersion.apiLevel as int + compileSdkVersion = flutterProject.android.compileSdkVersion.substring(8) as int targetPlatforms = platforms pluginFile = CargoKitPlugin.file } From 8256525f6cfa2650e8a095347a153d3b0b7e777e Mon Sep 17 00:00:00 2001 From: Xare123 <57245242+Xare123@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:30:54 -0700 Subject: [PATCH 2/4] Build the vendored CargoKit plugins so the app can start --- .github/workflows/build.yml | 12 ++- tooling/android/patch_pub_cache_cargokit.sh | 102 ++++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 tooling/android/patch_pub_cache_cargokit.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f118701ddf..19e615fb8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,12 +64,20 @@ jobs: cp rustpush/certs/legacy-fairplay/fairplay.crt rustpush/certs/fairplay/$name.crt done + # pub get restores the two vendored CargoKit copies, so patch them after + # dependency resolution and before the Android build. + - name: Patch vendored CargoKit for the Flutter Kotlin plugin + run: | + set -euo pipefail + flutter pub get + ./tooling/android/patch_pub_cache_cargokit.sh + # First run is expected to fail until ffmpeg_kit_flutter_new is fixed. - name: Run Build Script run: | flutter build apk --flavor alpha --debug --target-platform android-arm64 - + - uses: actions/upload-artifact@v4 with: name: Alpha Debug APK - path: build/app/outputs/flutter-apk/app-alpha-debug.apk \ No newline at end of file + path: build/app/outputs/flutter-apk/app-alpha-debug.apk diff --git a/tooling/android/patch_pub_cache_cargokit.sh b/tooling/android/patch_pub_cache_cargokit.sh new file mode 100644 index 0000000000..0edd088a6e --- /dev/null +++ b/tooling/android/patch_pub_cache_cargokit.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash +# +# Teach the CargoKit copies vendored inside pub packages how to work with +# Flutter 3.44.8's Gradle plugin. +# +# irondash_engine_context and super_native_extensions ship their own CargoKit. +# Those copies live in the pub cache, are refetched by `flutter pub get`, and +# cannot be fixed by a committed edit, so they are patched before Android +# builds. +# +# CargoKit first needs to recognise Flutter's Kotlin Gradle plugin. That alone +# is not sufficient: the Kotlin rewrite made the plugin project private and +# moved getTargetPlatforms() to FlutterPluginUtils. This patch resolves the +# Flutter Project directly and reads -Ptarget-platform instead. +# +# Idempotent. Safe to run repeatedly and on already-patched files. +set -euo pipefail + +patched=0 +skipped=0 +scanned=0 + +patch_file() { + local file="$1" + scanned=$((scanned + 1)) + + if grep -q "_findFlutterProject" "$file"; then + skipped=$((skipped + 1)) + return 0 + fi + + if ! grep -q "_findFlutterPlugin" "$file"; then + echo " ?? $file does not look like CargoKit's plugin.gradle; leaving it alone" >&2 + return 0 + fi + + # Rename the search so it yields the Project rather than the Plugin. The + # recursive call is rewritten as its own block first so the remaining bare + # `return plugin;` is unambiguously the one inside the plugin loop. + perl -0777 -pi -e ' + s/private Plugin findFlutterPlugin\(/private Project findFlutterProject\(/; + s/private Plugin _findFlutterPlugin\(/private Project _findFlutterProject\(/; + s/_findFlutterPlugin\(rootProject\.childProjects\)/_findFlutterProject(rootProject.childProjects)/; + s/def plugin = _findFlutterPlugin\(project\.value\.childProjects\);\s*\n(\s*)if \(plugin != null\) \{\s*\n\s*return plugin;\s*\n\s*\}/def found = _findFlutterProject(project.value.childProjects);\n$1if (found != null) {\n$1 return found;\n$1}/; + s/if \(plugin\.class\.name == "FlutterPlugin"\) \{\s*\n(\s*)return plugin;/if (plugin.class.name == "FlutterPlugin" ||\n$1 plugin.class.name.endsWith(".FlutterPlugin")) {\n$1 return project.value;/; + s/return plugin;/return project.value;/; + s/def plugin = findFlutterPlugin\(project\.rootProject\);/def flutterProject = findFlutterProject(project.rootProject);/; + s/if \(plugin == null\) \{/if (flutterProject == null) {/; + s/\bprint\("Flutter plugin not found/println("Flutter plugin not found/; + s/plugin\.getTargetPlatforms\(\)/flutterTargetPlatforms(flutterProject)/g; + s/\bplugin\.project\b/flutterProject/g; + ' "$file" + + # getTargetPlatforms moved into FlutterPluginUtils, which is Kotlin-internal + # and unreachable from Groovy. Flutter passes -Ptarget-platform; the + # fallback list matches the Flutter plugin's own default. + perl -0777 -pi -e ' + s/(\n \@Override\n void apply\(Project project\) \{)/\n private static List flutterTargetPlatforms(Project flutterProject) {\n if (flutterProject.hasProperty("target-platform")) {\n return flutterProject\n .property("target-platform")\n .toString()\n .split(",")\n .collect { it.trim() }\n .findAll { !it.isEmpty() }\n }\n return ["android-arm", "android-arm64", "android-x64"]\n }\n$1/; + ' "$file" + + if ! grep -q "flutterTargetPlatforms" "$file"; then + echo " !! $file: helper was not inserted; the file shape is unexpected" >&2 + return 1 + fi + + echo " patched $file" + patched=$((patched + 1)) +} + +roots=() +if [ "$#" -gt 0 ]; then + roots=("$@") +else + # Default pub cache locations, Windows first since that is the dev host. + for candidate in \ + "${PUB_CACHE:-}" \ + "$HOME/AppData/Local/Pub/Cache" \ + "$HOME/.pub-cache" + do + [ -n "$candidate" ] && [ -d "$candidate" ] && roots+=("$candidate") + done +fi + +if [ "${#roots[@]}" -eq 0 ]; then + echo "no pub cache found; nothing to patch" >&2 + exit 0 +fi + +for root in "${roots[@]}"; do + while IFS= read -r file; do + patch_file "$file" + done < <(find "$root" -path "*cargokit/gradle/plugin.gradle" -type f 2>/dev/null) +done + +echo "cargokit pub-cache patch: ${patched} patched, ${skipped} already current, ${scanned} scanned" + +if [ "$scanned" -eq 0 ]; then + echo "warning: no vendored CargoKit copies were found." >&2 + echo "If irondash_engine_context or super_native_extensions are still" >&2 + echo "dependencies, the Android build will produce an APK that hangs on" >&2 + echo "startup. Check that pub get has run." >&2 +fi From d66c2d924750145ea0df8476fb2fcdea51456e55 Mon Sep 17 00:00:00 2001 From: Xare123 <57245242+Xare123@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:31:32 -0700 Subject: [PATCH 3/4] Assert the complete set of CargoKit libraries, not the last one to crash --- .github/workflows/build.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19e615fb8b..00b9f86ef3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -77,6 +77,26 @@ jobs: run: | flutter build apk --flavor alpha --debug --target-platform android-arm64 + - name: Assert required native libraries are in the APK + run: | + set -euo pipefail + apk=build/app/outputs/flutter-apk/app-alpha-debug.apk + # A green Gradle build can still produce an APK that installs and + # then fails during plugin registration if CargoKit skipped a native + # library. Keep this list tied to the app's generated registrant and + # Rust bridge, not to unrelated libraries found in the pub cache. + for entry in \ + lib/arm64-v8a/libflutter.so \ + lib/arm64-v8a/librust_lib_bluebubbles.so \ + lib/arm64-v8a/libirondash_engine_context_native.so \ + lib/arm64-v8a/libsuper_native_extensions.so; do + if ! unzip -l "$apk" | grep -q "$entry"; then + echo "APK is missing $entry" >&2 + unzip -l "$apk" | grep 'lib/' >&2 || true + exit 1 + fi + done + - uses: actions/upload-artifact@v4 with: name: Alpha Debug APK From c9b7e98cf46a3a1a0634691f0d4e78a75425cbfd Mon Sep 17 00:00:00 2001 From: Xare123 <57245242+Xare123@users.noreply.github.com> Date: Sat, 15 Aug 2026 17:31:39 -0700 Subject: [PATCH 4/4] Make the CargoKit patch step runnable in CI --- .github/workflows/build.yml | 2 +- tooling/android/patch_pub_cache_cargokit.sh | 0 2 files changed, 1 insertion(+), 1 deletion(-) mode change 100644 => 100755 tooling/android/patch_pub_cache_cargokit.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00b9f86ef3..9a1fe54d8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -70,7 +70,7 @@ jobs: run: | set -euo pipefail flutter pub get - ./tooling/android/patch_pub_cache_cargokit.sh + bash ./tooling/android/patch_pub_cache_cargokit.sh # First run is expected to fail until ffmpeg_kit_flutter_new is fixed. - name: Run Build Script diff --git a/tooling/android/patch_pub_cache_cargokit.sh b/tooling/android/patch_pub_cache_cargokit.sh old mode 100644 new mode 100755