use regex::Regex;
use std::sync::OnceLock;
use super::imports_helpers::ensure_loader_imports;
const ALEF_LOADER_MARKER: &str = "_alefResolveExternalLibrary";
pub fn rewrite_frb_external_library_loader(source: &str, package_name: &str, module_name: &str, stem: &str) -> String {
let with_loader = if source.contains(ALEF_LOADER_MARKER) {
source.to_string()
} else {
let Some(prologue) = frb_init_prologue(source) else {
return source.to_string();
};
let replacement = frb_init_prologue_replacement(package_name, module_name, stem);
source.replacen(&prologue, &replacement, 1)
};
ensure_loader_imports(&with_loader)
}
fn frb_init_prologue(source: &str) -> Option<String> {
let re = init_prologue_regex();
re.find(source).map(|m| m.as_str().to_string())
}
fn init_prologue_regex() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| {
Regex::new(r"(?m)^\s*/// Initialize flutter_rust_bridge\n\s*static Future<void> init\((?s:.)*?\}\) async \{\n")
.expect("init prologue regex must compile")
})
}
pub(super) fn frb_init_prologue_replacement(package_name: &str, module_name: &str, stem: &str) -> String {
format!(
r#" /// Resolve the prebuilt native library from environment variable,
/// package-relative location, or defer to flutter_rust_bridge's default loader.
/// Returns `null` to defer to flutter_rust_bridge's default loader.
///
/// Checks in order:
/// 1. FRB_DART_LOAD_EXTERNAL_LIBRARY_NATIVE_LIB_DIR environment variable
/// (allows test harnesses to point to development build paths)
/// 2. Package-installed location with RID subdirectory (lib/src/native/<rid>/)
/// (for published pub.dev packages with platform-specific bundled native libraries)
/// 3. Package-installed location (lib/src/{module}_bridge_generated/)
/// (legacy fallback for development or packages without per-platform binaries)
/// 4. Returns null (flutter_rust_bridge falls back to its default loader)
static Future<ExternalLibrary?> {marker}() async {{
try {{
const candidates = <String>[
// macOS: framework bundle (preferred modern packaging)
'{stem}.framework',
// macOS: bare dylib fallback
'lib{stem}.dylib',
// Linux
'lib{stem}.so',
// Windows
'{stem}.dll',
];
// Helper to open a native library by absolute path.
// Normalizes path to absolute to avoid hardened-runtime "relative path rejected" errors.
ExternalLibrary? tryOpenAbsolute(String libPath) {{
try {{
final absPath = File(libPath).absolute.path;
return ExternalLibrary.open(absPath);
}} catch (_) {{
return null;
}}
}}
bool candidateExists(String libPath) {{
return File(libPath).existsSync() || Directory(libPath).existsSync();
}}
// Check FRB_DART_LOAD_EXTERNAL_LIBRARY_NATIVE_LIB_DIR env var first.
// This allows test harnesses to override library location for development.
final envDir = Platform.environment['FRB_DART_LOAD_EXTERNAL_LIBRARY_NATIVE_LIB_DIR'];
if (envDir != null && envDir.isNotEmpty) {{
final absEnvDir = Directory(envDir).absolute.path;
final libDir = Directory(absEnvDir);
if (libDir.existsSync()) {{
for (final candidate in candidates) {{
final libPath = '$absEnvDir/$candidate';
if (candidateExists(libPath)) {{
final result = tryOpenAbsolute(libPath);
if (result != null) return result;
}}
}}
}}
}}
// Compute RID (runtime identifier) from platform and architecture using Abi.current().
// This is more reliable than parsing Platform.version.
String? computeRid() {{
final abi = Abi.current();
final os = Platform.operatingSystem;
// Map from (os, Abi) to RID string.
String? ridFromAbi() {{
if (os == 'linux') {{
if (abi == Abi.linuxX64) return 'linux-x64';
if (abi == Abi.linuxArm64) return 'linux-arm64';
}} else if (os == 'macos') {{
if (abi == Abi.macosX64) return 'macos-x64';
if (abi == Abi.macosArm64) return 'macos-arm64';
}} else if (os == 'windows') {{
if (abi == Abi.windowsX64) return 'windows-x64';
if (abi == Abi.windowsArm64) return 'windows-arm64';
}}
return null;
}}
return ridFromAbi();
}}
final rid = computeRid();
if (rid != null) {{
final packageRoot =
await Isolate.resolvePackageUri(Uri.parse('package:{package}/{package}.dart'));
if (packageRoot != null) {{
final ridDir = packageRoot.resolve('src/native/$rid/');
for (final candidate in candidates) {{
final libPath = ridDir.resolve(candidate).toFilePath();
if (candidateExists(libPath)) {{
final result = tryOpenAbsolute(libPath);
if (result != null) return result;
}}
}}
}}
}}
// Check legacy package-installed location as fallback.
final packageRoot =
await Isolate.resolvePackageUri(Uri.parse('package:{package}/{package}.dart'));
if (packageRoot != null) {{
final libDir = packageRoot.resolve('src/{module}_bridge_generated/');
for (final candidate in candidates) {{
final libPath = libDir.resolve(candidate).toFilePath();
if (candidateExists(libPath)) {{
final result = tryOpenAbsolute(libPath);
if (result != null) return result;
}}
}}
}}
// As a last resort, resolve the running test/script's package root via
// `Platform.script` and search standard RID-relative locations there.
// Critical on macOS: `Directory.current` under hardened-runtime `dart` is
// the dart binary's own bin dir (relative-path dlopen rejected), whereas
// `Platform.script` resolves to the running .dart file's absolute URI,
// from which we can walk up to find the package root (the dir containing
// `pubspec.yaml`) and look for the bundled native library at standard
// paths. This handles the case where `Isolate.resolvePackageUri`
// resolution did not yield the actual staging location (e.g., a path
// dependency in local development, or a test_app whose host package
// contains the native lib directly rather than via the bridged package).
try {{
final scriptPath = Platform.script.toFilePath();
var dir = File(scriptPath).absolute.parent;
while (dir.parent.path != dir.path
&& !File('${{dir.path}}/pubspec.yaml').existsSync()) {{
dir = dir.parent;
}}
if (File('${{dir.path}}/pubspec.yaml').existsSync()) {{
final rid = computeRid();
final absRootPath = dir.absolute.path;
final searchRoots = <String>[
if (rid != null) '$absRootPath/lib/src/native/$rid',
'$absRootPath/lib',
absRootPath,
];
for (final root in searchRoots) {{
final absRoot = Directory(root).absolute.path;
for (final candidate in candidates) {{
final libPath = '$absRoot/$candidate';
if (candidateExists(libPath)) {{
final result = tryOpenAbsolute(libPath);
if (result != null) return result;
}}
}}
}}
}}
}} catch (_) {{
// fall through to default loader
}}
}} catch (_) {{
// Fall through to the default loader on any resolution failure.
}}
return null;
}}
/// Initialize flutter_rust_bridge
static Future<void> init({{
RustLibApi? api,
BaseHandler? handler,
ExternalLibrary? externalLibrary,
bool forceSameCodegenVersion = true,
}}) async {{
externalLibrary ??= await {marker}();
"#,
marker = ALEF_LOADER_MARKER,
package = package_name,
module = module_name,
stem = stem,
)
}
fn extract_loader_stem(source: &str) -> Option<String> {
let re = stem_regex();
re.captures(source).map(|c| c["stem"].to_string())
}
fn stem_regex() -> &'static Regex {
static RE: OnceLock<Regex> = OnceLock::new();
RE.get_or_init(|| Regex::new(r"stem:\s*'(?P<stem>[A-Za-z0-9_]+)'").expect("stem regex must compile"))
}
pub(super) fn apply_loader_fix_from_stem(source: &str) -> String {
let Some(stem) = extract_loader_stem(source) else {
return source.to_string();
};
let crate_base = stem.strip_suffix("_dart").unwrap_or(&stem);
let package_name = crate_base;
let module_name = crate_base;
rewrite_frb_external_library_loader(source, package_name, module_name, &stem)
}