linkAndroidConsumerRules function
Implementation
void linkAndroidConsumerRules(
List<Map<String, String>> kotlinModules, {
String baseDir = '.',
}) {
if (kotlinModules.isEmpty) return;
final androidDir = Directory(p.join(baseDir, 'android'));
if (!androidDir.existsSync()) return;
String? namespace;
for (final candidate in [
File(p.join(baseDir, 'android', 'build.gradle')),
File(p.join(baseDir, 'android', 'build.gradle.kts')),
]) {
if (!candidate.existsSync()) continue;
final m = RegExp(r'''namespace\s*=?\s*["']([^"']+)["']''').firstMatch(candidate.readAsStringSync());
if (m != null) namespace = m.group(1);
break;
}
final bridgePackages = kotlinModules.map((m) => 'nitro.${(m['lib'] ?? '').replaceAll('-', '_')}_module').toSet().toList()..sort();
final block = StringBuffer()
..writeln(_nitroConsumerRulesBeginMarker)
..writeln('# The generated Nitro JNI bridge and its hand-written impl are reached')
..writeln('# from C++ via FindClass + GetStaticMethodID by exact name and signature.')
..writeln('# includedescriptorclasses additionally keeps the parameter/return types')
..writeln('# referenced in those signatures — without it R8 full mode can still')
..writeln('# rename/merge them, producing a VerifyError at the JNI call site even')
..writeln('# though the method itself is "kept". See linkAndroidConsumerRules in')
..writeln('# nitrogen_cli for the full explanation.');
for (final pkg in bridgePackages) {
block.writeln('-keep,includedescriptorclasses class $pkg.** {');
block.writeln(' *;');
block.writeln('}');
}
if (namespace != null) {
block.writeln('-keep,includedescriptorclasses class $namespace.** {');
block.writeln(' *;');
block.writeln('}');
}
block
..writeln()
..writeln('# JNI resolves native methods (Kotlin `external fun`) by exact name AND')
..writeln('# signature — includedescriptorclasses protects their parameter/return')
..writeln('# types too, not just their names.')
..writeln('-keepclasseswithmembernames,includedescriptorclasses class * {')
..writeln(' native <methods>;')
..writeln('}')
..write(_nitroConsumerRulesEndMarker);
final rulesFile = File(p.join(androidDir.path, 'consumer-rules.pro'));
final existing = rulesFile.existsSync() ? rulesFile.readAsStringSync() : '';
final markerPattern = RegExp(
'${RegExp.escape(_nitroConsumerRulesBeginMarker)}.*?${RegExp.escape(_nitroConsumerRulesEndMarker)}',
dotAll: true,
);
final String updated;
if (markerPattern.hasMatch(existing)) {
updated = existing.replaceFirst(markerPattern, block.toString());
} else if (existing.trim().isEmpty) {
updated = '$block\n';
} else {
updated = '${existing.trimRight()}\n\n$block\n';
}
if (updated != existing) rulesFile.writeAsStringSync(updated);
}