const FRB_GENERATED_RUST: &str = "src/frb_generated.rs";
/// Collect `name -> #[cfg(...)]` for every column-0 `pub fn`/`pub async fn` in `lib.rs`
/// that sits directly under a column-0 `#[cfg(...)]` attribute.
///
/// Only top-level free functions matter here: those are the only items FRB turns into a
/// wire wrapper. A gate on an `impl` block is out of scope and currently unreachable.
fn cfg_gated_free_functions(lib_rs: &str) -> Vec<(String, String)> {
let lines: Vec<&str> = lib_rs.lines().collect();
let mut gated = Vec::new();
let mut index = 0;
while index < lines.len() {
if !lines[index].starts_with("#[cfg(") {
index += 1;
continue;
}
// Join a multi-line predicate (e.g. `any(\n feature = "a",\n ...)`) back together.
let mut attribute = String::from(lines[index]);
let mut cursor = index;
while !attribute.trim_end().ends_with(")]") && cursor + 1 < lines.len() {
cursor += 1;
attribute.push_str(lines[cursor].trim());
}
// Skip any further attributes stacked on the same item.
let mut item = cursor + 1;
while item < lines.len() && lines[item].starts_with('#') {
item += 1;
}
if let Some(rest) = lines
.get(item)
.and_then(|l| l.strip_prefix("pub async fn ").or_else(|| l.strip_prefix("pub fn ")))
&& let Some(name) = rest.split(['(', '<', ' ']).next()
&& !name.is_empty()
{
gated.push((name.to_string(), attribute));
}
index = cursor + 1;
}
gated
}
/// Re-apply `lib.rs`'s cfg gates to the FRB glue: the `wire__crate__<name>_impl`
/// definition and its numeric dispatch arm.
///
/// Dispatch arms are keyed by an explicit numeric literal fixed at generation time, not
/// by position, so removing one cannot renumber its siblings. Idempotent — a site that
/// already carries its gate is left alone.
fn carry_frb_cfg_gates() {
let Ok(lib_rs) = std::fs::read_to_string("src/lib.rs") else {
println!("cargo:warning=carry_frb_cfg_gates: src/lib.rs unreadable — skipping");
return;
};
let Ok(generated) = std::fs::read_to_string(FRB_GENERATED_RUST) else {
println!("cargo:warning=carry_frb_cfg_gates: {FRB_GENERATED_RUST} unreadable — skipping");
return;
};
let gated = cfg_gated_free_functions(&lib_rs);
if gated.is_empty() {
return;
}
let mut lines: Vec<String> = generated.lines().map(str::to_string).collect();
let mut applied = 0usize;
for (name, attribute) in &gated {
let definition = format!("fn wire__crate__{name}_impl(");
let dispatch = format!("=> wire__crate__{name}_impl(");
let mut index = 0;
while index < lines.len() {
let hit =
lines[index].trim_start().starts_with(definition.as_str()) || lines[index].contains(dispatch.as_str());
if hit && !lines[index.saturating_sub(1)].trim_start().starts_with("#[cfg(") {
let indent = " ".repeat(lines[index].len() - lines[index].trim_start().len());
lines.insert(index, format!("{indent}{attribute}"));
applied += 1;
index += 1;
}
index += 1;
}
}
if applied == 0 {
return;
}
let mut out = lines.join("\n");
out.push('\n');
if let Err(err) = std::fs::write(FRB_GENERATED_RUST, out) {
println!("cargo:warning=carry_frb_cfg_gates: failed to write {FRB_GENERATED_RUST}: {err}");
}
}