findNativeSymbolsDirectory static method
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;
}