// Round forty-three: the build nobody has made. Every APK in this port is // `assembleArm64Debug`, and the generated project turns R8 on for `release` // and only for `release` — while tauri finds a plugin class by string, files // every @Command under `commands[method.name]`, and lets Jackson fill every // @InvokeArg field by field. Minification breaks exactly that, and it breaks // it silently: the app builds, installs, launches, and then a tap answers // "No command pickFolder found for plugin ...". So: one consumer-rules.pro // per plugin module (registered by one Gradle line each, the shape // tauri-android and tauri-plugin-dialog already use here — opener ships the // file and never registers it, which is the failure mode), one app-wide // file for what belongs to no plugin — the runtime annotation attributes, a // -dontwarn pair for the two java.beans classes jackson-databind references // and Android has not, and the four @Command methods declared on // app.tauri.plugin.Plugin that no rule of tauri's own names — and a // `--release` flag on build.sh that is off by default, so the deploy pipeline // keeps the path it has run every twenty minutes for a month. No features, no // UI, no Rust, no Kotlin, and neither walk is touched: the release build is a // hand list, not a walk. const fs = require('fs'); const root = process.env.SS_ROOT || 'F:/Coding/SoulStream/'; const scratch = 'C:/Users/Kyle/AppData/Local/Temp/claude/F--Coding-SoulStream/5488514a-11e8-482a-b0a0-20cb511ee110/scratchpad/'; const ins = (name) => fs.readFileSync(scratch + name, 'utf8').replace(/\s+$/, ''); let p, s; const load = (file) => { p = file; s = fs.readFileSync(root + p, 'utf8'); }; const save = () => fs.writeFileSync(root + p, s); const once = (a) => { const i = s.indexOf(a); if (i < 0) throw new Error('missing in ' + p + ': ' + a.slice(0, 90)); if (s.indexOf(a, i + 1) >= 0) throw new Error('ambiguous in ' + p + ': ' + a.slice(0, 90)); return i; }; const rep = (a, b) => { once(a); s = s.replace(a, () => b); }; const NL = String.fromCharCode(10); /* Five of the writes below are whole new files, which succeed as happily the second time as the first, while the Gradle and build.sh `rep`s would throw somewhere in the middle and leave half a tree. Refuse a second run here, before a byte is touched: the app-wide rules file is what this round creates and nothing else in the repo makes it. */ if (fs.existsSync(root + 'src-tauri/android/proguard-soulstream.pro')) { throw new Error('src-tauri/android/proguard-soulstream.pro already exists — round 43 has already run against ' + root); } // ── the rules that belong to the app, kept where gen/ cannot lose them ─ /* first, so the guard above covers a run that dies anywhere below it */ fs.writeFileSync(root + 'src-tauri/android/proguard-soulstream.pro', ins('ins-r43-proguard-app.txt') + NL); // ── the rules that travel with each plugin ──────────────────────────── /* A consumer file is handed to whatever app links the module and does nothing at all unless that app minifies, so the debug APK is untouched; and because it lives in the module rather than in gen/android, a regenerated project still has it. One Gradle line each. The anchor is the whole `defaultConfig` block rather than its `minSdk` line, so it cannot match again once the line is in — a second pass over a half-applied tree refuses rather than writing the line twice. */ const plugins = [ ['storage', 'ins-r43-consumer-storage.txt'], ['media-session', 'ins-r43-consumer-media.txt'], ['share', 'ins-r43-consumer-share.txt'], ['transfer-notice', 'ins-r43-consumer-notice.txt'], ]; for (const [name, file] of plugins) { const dir = 'crates/tauri-plugin-' + name + '/android/'; fs.writeFileSync(root + dir + 'consumer-rules.pro', ins(file) + NL); load(dir + 'build.gradle.kts'); rep(` defaultConfig { minSdk = 26 }`, ` defaultConfig { minSdk = 26 // the plugin's own R8 keep rules, handed to whatever app links this // module — inert unless that app minifies, which only the release // build type does consumerProguardFiles("consumer-rules.pro") }`); save(); } // ── build.sh: a release flag, off by default ────────────────────────── load('src-tauri/android/build.sh'); /* the flag block sits between the toolchain and the cargo line, so a sibling round appending at the end or editing the cargo line does not meet it */ rep(`source src-tauri/android/env.sh`, `source src-tauri/android/env.sh` + NL + ins('ins-r43-build-flag.txt')); /* $CARGO_EXTRA is assigned on BOTH branches, so `set -u` is satisfied, and unquoted on purpose: empty expands to no words at all, which makes the default cargo line character for character what it is today. */ rep(`cargo build --lib -p app --target aarch64-linux-android --features tauri/custom-protocol 2>&1`, `cargo build --lib -p app --target aarch64-linux-android --features tauri/custom-protocol $CARGO_EXTRA 2>&1`); rep(`SO=target/aarch64-linux-android/debug/libapp_lib.so`, `SO=target/aarch64-linux-android/$PROFILE/libapp_lib.so`); rep(`./gradlew.bat assembleArm64Debug -x rustBuildArm64Debug -q`, `./gradlew.bat assemble$VARIANT -x rustBuild$VARIANT -q`); rep(`cd /f/Coding/SoulStream APK=src-tauri/gen/android/app/build/outputs/apk/arm64/debug/app-arm64-debug.apk ls -la "$APK"`, ins('ins-r43-build-tail.txt')); save(); // ── the doc ─────────────────────────────────────────────────────────── load('ANDROID.md'); /* the last sentence of the dev-flavour section, NOT the `## Verifying` heading below it: a sibling inserting a section of its own would reach for the heading, and this one still lands where it belongs either way */ rep(`harness and the app see one origin in both flavours.`, `harness and the app see one origin in both flavours.` + NL + NL + ins('ins-r43-doc-release.txt')); /* the last sentence of the file. Deliberately NOT the `## Not done yet` heading and NOT its intro — that sentence carries a ROUND COUNT, which is exactly what a sibling round bumps. No count anywhere in this file is touched, because this round adds no step to any walk. */ /* one newline, not two: the bullets in that list sit flush against each other, and a blank line between two of them turns the whole list loose */ rep(`which the tour deliberately refuses.`, `which the tour deliberately refuses.` + NL + ins('ins-r43-doc-notdone.txt')); save(); console.log('round 43 wired');