linkBuildYamlSourcesExcludes function
Writes/patches android/consumer-rules.pro with the R8/ProGuard keep
rules every Kotlin-on-Android Nitro module needs.
The generated JNI bridge (nitro.<lib>_module.<Module>JniBridge) and the
hand-written impl (under the plugin's Android namespace) are reached
from C++ via FindClass + GetStaticMethodID by exact name AND
signature — R8 has no static-analysis visibility into that, so without
these rules it may rename, remove, or (in "full mode") mis-optimize them.
Uses includedescriptorclasses — not a stronger form of a plain -keep,
a DIFFERENT protection: plain -keep class X { *; } only protects a
member from removal/renaming, not the parameter/return TYPES referenced
in its signature. For methods JNI calls by exact signature (every
_call trampoline, several of which take many long/data-class params
for suspend methods), an altered parameter type produces a VerifyError
at the exact call site — a real, previously-hit crash
("VerifyError ... Long (Low Half)"), not a hypothetical one. This is
ProGuard's own documented remedy for native/JNI-called methods.
Idempotent and additive: only touches the marked block (creating it if
absent), never the rest of the file, so a plugin author's own rules for
unrelated dependencies survive every nitrogen link run. Re-derives the
block's content from the CURRENT module list each time, so e.g. adding a
second Nitro module to the plugin updates the keep rules automatically.
Ensures the plugin's build.yaml carries sources excludes that keep
build_runner's file-discovery walk out of the example app's platform
build output (issue #20).
Once example/ has been built, example/{ios,macos}/.symlinks/plugins/<name>
symlinks straight back to the plugin root; build_runner follows symlinks
with no cycle detection and hangs forever with no output. nitrogen generate deletes those dirs before every run — but a plain
dart run build_runner build/watch has no such guard. With the
excludes in place the walk never enters example/ at all, making direct
build_runner invocations safe too.
Behavior: creates build.yaml from the template when absent; inserts the
sources: block under $default: when the file exists without one;
NEVER touches a file that already declares sources: (the user owns
their customization — doctor reports if it looks insufficient).
Implementation
void linkBuildYamlSourcesExcludes({String baseDir = '.'}) {
final file = File(p.join(baseDir, 'build.yaml'));
if (!file.existsSync()) {
file.writeAsStringSync(sft.buildYamlTemplate());
stdout.writeln(' build.yaml: created with sources excludes (guards direct build_runner runs against the example symlink-cycle hang)');
return;
}
final content = file.readAsStringSync();
if (content.contains('sources:')) return; // user-owned customization
const sourcesBlock =
' sources:\n'
' include:\n'
' - lib/**\n'
' - \$package\$\n'
' - pubspec.yaml\n'
' exclude:\n'
' - example/**\n'
' - "**/.symlinks/**"\n'
' - "**/ephemeral/**"\n';
// Insert directly under the `$default:` target — the shape both the
// nitrogen template and flutter-created plugins use.
final anchor = RegExp(r'^(\s*)\$default:\s*$', multiLine: true).firstMatch(content);
if (anchor == null) {
stdout.writeln(' build.yaml: unrecognized shape — add sources excludes for example/**, **/.symlinks/**, **/ephemeral/** yourself (see nitrogen doctor)');
return;
}
final insertAt = content.indexOf('\n', anchor.end) + 1;
final updated = content.substring(0, insertAt) + sourcesBlock + content.substring(insertAt);
file.writeAsStringSync(updated);
stdout.writeln(' build.yaml: added sources excludes (guards direct build_runner runs against the example symlink-cycle hang)');
}