use crate::backends::dart::template_env;
use crate::core::backend::GeneratedFile;
use std::path::PathBuf;
pub(crate) fn emit_build_rs(rust_dir: &str, package_name: &str, module_name: &str, stem: &str) -> GeneratedFile {
let loader_patch = render_loader_patch_fn(package_name, module_name, stem);
let cfg_gates_fn = render_cfg_gates_fn();
let content = template_env::render(
"rust_build_rs.rs.jinja",
minijinja::context! {
loader_patch => loader_patch.as_str(),
cfg_gates_fn => cfg_gates_fn.as_str(),
},
);
GeneratedFile {
path: PathBuf::from(format!("{rust_dir}/build.rs")),
content,
generated_header: true,
}
}
fn render_loader_patch_fn(package_name: &str, module_name: &str, stem: &str) -> String {
let dart_replacement = dart_init_prologue_replacement(package_name, module_name, stem);
template_env::render(
"rust_loader_patch_fn.rs.jinja",
minijinja::context! {
module_name => module_name,
dart_replacement => dart_replacement.as_str(),
},
)
}
fn dart_init_prologue_replacement(package_name: &str, module_name: &str, stem: &str) -> String {
template_env::render(
"dart_init_prologue_replacement.jinja",
minijinja::context! {
package_name => package_name,
module_name => module_name,
stem => stem,
},
)
}
fn render_cfg_gates_fn() -> String {
template_env::render("rust_frb_cfg_gates_fn.rs.jinja", minijinja::context! {})
}
pub(crate) fn emit_frb_yaml(rust_dir: &str, module_name: &str) -> GeneratedFile {
let content = template_env::render(
"flutter_rust_bridge_yaml.jinja",
minijinja::context! {
module_name => module_name,
},
);
GeneratedFile {
path: PathBuf::from(format!("{rust_dir}/flutter_rust_bridge.yaml")),
content,
generated_header: true,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn emitted_build_rs_is_valid_rust() {
let file = emit_build_rs(
"packages/dart/rust",
"sample_router",
"sample_router",
"sample_router_dart",
);
syn::parse_file(&file.content).expect("generated build.rs must be valid Rust");
}
#[test]
fn emitted_build_rs_does_not_regenerate_frb_by_default() {
let file = emit_build_rs(
"packages/dart/rust",
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
file.content.contains("ALEF_FRB_REGENERATE_ON_BUILD"),
"build.rs must gate FRB regeneration behind an explicit opt-in env var; got:\n{}",
file.content
);
let frb_invocation = file
.content
.find(r#"Command::new("flutter_rust_bridge_codegen")"#)
.expect("build.rs must still be able to invoke flutter_rust_bridge_codegen for the opt-in path");
let gate_check = file
.content
.find("ALEF_FRB_REGENERATE_ON_BUILD")
.expect("gate check must exist");
assert!(
gate_check < frb_invocation,
"the opt-in env var must be checked before flutter_rust_bridge_codegen is invoked; got:\n{}",
file.content
);
assert!(
file.content.contains(r#""--no-deps-check""#),
"the opt-in path must tolerate valid prerelease Dart dependencies"
);
syn::parse_file(&file.content).expect("generated build.rs must be valid Rust");
}
#[test]
fn emitted_build_rs_does_not_mutate_sources_before_opt_in_gate() {
let file = emit_build_rs(
"packages/dart/rust",
"sample_router",
"sample_router",
"sample_router_dart",
);
let opt_in_gate = file
.content
.find("if !frb_regeneration_opted_in()")
.expect("build.rs must return early unless regeneration is explicitly enabled");
for mutation in [
"carry_frb_cfg_gates();",
"patch_published_loader();",
"fix_handler_executor_calls();",
] {
let mutation_call = file
.content
.find(mutation)
.unwrap_or_else(|| panic!("build.rs must retain the opt-in mutation `{mutation}`"));
assert!(
opt_in_gate < mutation_call,
"source mutation `{mutation}` must occur only after the regeneration opt-in gate; got:\n{}",
file.content
);
}
}
#[test]
fn emitted_build_rs_patches_published_loader_after_codegen() {
let file = emit_build_rs(
"packages/dart/rust",
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
file.content.contains("patch_published_loader();"),
"build.rs must invoke the loader patch after codegen"
);
assert!(
file.content.contains("fn patch_published_loader()"),
"build.rs must define the loader patch"
);
assert!(
file.content
.contains(r#"../lib/src/sample_router_bridge_generated/frb_generated.dart"#),
"build.rs must target the generated frb dart file"
);
assert!(
file.content
.contains("Isolate.resolvePackageUri(Uri.parse('package:sample_router/sample_router.dart'))"),
"build.rs replacement must resolve the package URI"
);
assert!(
file.content
.contains("externalLibrary ??= await _alefResolveExternalLibrary();"),
"build.rs replacement must prefer the package-relative library"
);
}
#[test]
fn emitted_build_rs_downloads_and_caches_library_on_cache_miss() {
let file = emit_build_rs(
"packages/dart/rust",
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
file.content.contains("await nativeDownloadAndCacheLibrary()"),
"build.rs replacement must call nativeDownloadAndCacheLibrary() on a cache miss, got:\n{}",
file.content
);
}
#[test]
fn emitted_build_rs_runs_dart_format_after_patch() {
let file = emit_build_rs(
"packages/dart/rust",
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
file.content.contains("Command::new(\"dart\")")
&& file.content.contains("\"format\"")
&& file.content.contains("FRB_GENERATED_DART"),
"build.rs must run `dart format` on the patched frb_generated.dart"
);
}
#[test]
fn emitted_build_rs_handles_loader_patch_write_error() {
let file = emit_build_rs(
"packages/dart/rust",
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
file.content
.contains("if let Err(err) = std::fs::write(path, &patched)")
&& file
.content
.contains("cargo:warning=failed to write published-loader patch: {err}")
&& file.content.contains("return;"),
"emitted build.rs must handle loader patch write errors"
);
}
#[test]
fn emitted_build_rs_carries_frb_cfg_gates_after_codegen() {
let file = emit_build_rs(
"packages/dart/rust",
"sample_router",
"sample_router",
"sample_router_dart",
);
assert!(
file.content.contains("carry_frb_cfg_gates();"),
"build.rs must invoke carry_frb_cfg_gates() after FRB codegen"
);
assert!(
file.content.contains("fn carry_frb_cfg_gates()"),
"build.rs must define carry_frb_cfg_gates()"
);
assert!(
file.content
.contains("fn cfg_gated_free_functions(lib_rs: &str) -> Vec<(String, String)>"),
"build.rs must define cfg_gated_free_functions() to scan lib.rs for gated pub fns"
);
assert!(
file.content
.contains(r#"const FRB_GENERATED_RUST: &str = "src/frb_generated.rs";"#),
"build.rs must target the generated frb rust file"
);
syn::parse_file(&file.content).expect("generated build.rs must be valid Rust");
}
#[test]
fn every_markable_dart_rust_crate_file_carries_a_provenance_marker_on_disk() {
use crate::cli::pipeline::generate::{ensure_generated_header, marker_comment_style};
use crate::core::config::ResolvedCrateConfig;
use crate::core::hash::content_has_alef_marker;
use crate::core::ir::{ApiSurface, FunctionDef};
let api = ApiSurface {
functions: vec![FunctionDef {
name: "count_tokens".to_string(),
rust_path: "sample_lib::text::count_tokens".to_string(),
cfg: Some(r#"feature = "text-metrics""#.to_string()),
..Default::default()
}],
..Default::default()
};
let config = ResolvedCrateConfig {
name: "sample-lib".to_string(),
..Default::default()
};
let files = crate::backends::dart::gen_rust_crate::emit(&api, &config).expect("dart backend generates files");
let lib_rs = files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("src/lib.rs"))
.expect("lib.rs is generated");
assert!(
lib_rs.content.contains("#[cfg(feature = \"text-metrics\")]"),
"control: the fixture must emit a cfg-gated bridge fn into lib.rs; got:\n{}",
lib_rs.content
);
let cargo_toml = files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("Cargo.toml"))
.expect("Cargo.toml is generated");
let parsed: toml::Value = toml::from_str(&cargo_toml.content).expect("generated Cargo.toml must be valid TOML");
assert!(
parsed["features"]
.as_table()
.expect("[features] is a table")
.contains_key("text-metrics"),
"control: the manifest must declare the gate's feature in memory; got:\n{}",
cargo_toml.content
);
let markable: Vec<&str> = files
.iter()
.filter(|f| marker_comment_style(&f.path).is_some())
.filter_map(|f| f.path.to_str())
.collect();
for name in ["Cargo.toml", "build.rs", "flutter_rust_bridge.yaml", "src/lib.rs"] {
assert!(
markable.iter().any(|path| path.ends_with(name)),
"control: {name} must be on the ownership predicate, else this test examines nothing; \
markable set was {markable:?}"
);
}
for file in files.iter().filter(|f| marker_comment_style(&f.path).is_some()) {
let on_disk = if file.generated_header {
ensure_generated_header(&file.path, &file.content)
} else {
file.content.clone()
};
assert!(
content_has_alef_marker(&on_disk),
"{} is written on a markable extension with no alef provenance marker, so \
`generate::write::write_files_report` refuses to overwrite it forever and its \
content freezes while lib.rs keeps regenerating; got:\n{}",
file.path.display(),
on_disk.lines().take(5).collect::<Vec<_>>().join("\n")
);
}
}
}