findNativeSymbolsDirectory static method

Directory? findNativeSymbolsDirectory({
  1. required String mode,
  2. String? flavor,
  3. Directory? root,
})

Locates the merged native libraries directory produced by Gradle.

The layout cannot be hardcoded. Gradle names the intermediates directory after the variant, so a flavored build writes to prodRelease rather than release, and the task subdirectory (mergeProdReleaseNativeLibs) varies with the flavor and the Android Gradle Plugin version - older versions omit it entirely. Hardcoding release/mergeReleaseNativeLibs therefore silently missed every flavored build.

Searches <root>/build/app/intermediates/merged_native_libs for a variant directory matching mode (and flavor, when given), then for the out/lib directory beneath it. Returns null when nothing matches.

Implementation

static Directory? findNativeSymbolsDirectory({
  required String mode,
  String? flavor,
  Directory? root,
}) {
  final base = Directory(
    path.join(
      root?.path ?? '.',
      "build",
      "app",
      "intermediates",
      "merged_native_libs",
    ),
  );
  if (!base.existsSync()) return null;

  final normalizedMode = mode.toLowerCase();
  final normalizedFlavor = flavor?.toLowerCase();

  // Gradle names the directory `<flavor><Mode>`, or just `<mode>` when there
  // is no flavor. Substring matching got this wrong in both directions:
  // flavor `dev` also matched `devQaRelease`, and with no flavor at all a
  // leftover `debugRelease` matched `release` and won on name length — which
  // is how a debug variant's `.so` files could be shipped as the release
  // symbol archive.
  final exact = normalizedFlavor == null || normalizedFlavor.isEmpty
      ? normalizedMode
      : '$normalizedFlavor$normalizedMode';

  final all = base.listSync().whereType<Directory>().toList();
  final variants = all.where((directory) {
    return path.basename(directory.path).toLowerCase() == exact;
  }).toList();

  if (variants.isEmpty) {
    // Nothing matched exactly. Fall back to the old, looser match so an
    // unusual Gradle setup still finds something, but order by modification
    // time: the directory this build just wrote is the one that matters,
    // and the longest name is not evidence of anything.
    variants.addAll(
      all.where((directory) {
        final name = path.basename(directory.path).toLowerCase();
        if (!name.contains(normalizedMode)) return false;
        if (normalizedFlavor != null && normalizedFlavor.isNotEmpty) {
          return name.contains(normalizedFlavor);
        }
        return true;
      }),
    );
    variants.sort(
      (a, b) => b.statSync().modified.compareTo(a.statSync().modified),
    );
  }

  for (final variant in variants) {
    final libDirectory = _findOutLib(variant);
    if (libDirectory != null) return libDirectory;
  }
  return null;
}