linkAndroidConsumerRules function

void linkAndroidConsumerRules(
  1. List<Map<String, String>> kotlinModules, {
  2. String baseDir = '.',
})

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);
}