alef 0.62.10

Opinionated polyglot binding generator for Rust libraries
Documentation
use std::path::Path;

/// Opt-in env var gating FRB regeneration from this build script.
///
/// alef owns `flutter_rust_bridge_codegen` regeneration exclusively through its own
/// post-build step (`alef generate` / `alef build`), which applies the full post-processing
/// pipeline (sealed-variant renaming, native-library-loader import rewrite, excluded-function
/// filtering, injected text methods, bridge-coverage verification). This build script's own
/// invocation only ever replicated a partial subset of that pipeline, so a `cargo build` run
/// after `alef generate` (lint, `cargo test`, `cargo clippy`, or plain local iteration) would
/// silently regenerate the committed bridge with different content -- alef #140. Regeneration
/// here is now opt-in for local Flutter-only iteration (editing `lib.rs` without alef on
/// hand); the result bypasses alef's post-processing and must not be committed.
const ALEF_FRB_REGENERATE_ON_BUILD: &str = "ALEF_FRB_REGENERATE_ON_BUILD";

fn frb_regeneration_opted_in() -> bool {
    matches!(std::env::var(ALEF_FRB_REGENERATE_ON_BUILD).as_deref(), Ok("1") | Ok("true"))
}

fn main() {
    // Re-run whenever any Rust source changes, FRB config changes, or the opt-in gate flips.
    println!("cargo:rerun-if-changed=src");
    println!("cargo:rerun-if-changed=flutter_rust_bridge.yaml");
    println!("cargo:rerun-if-env-changed={ALEF_FRB_REGENERATE_ON_BUILD}");

    if !frb_regeneration_opted_in() {
        println!(
            "cargo:warning=flutter_rust_bridge_codegen regeneration skipped by default -- using the committed generated bridge. Run `alef generate` to regenerate it (with alef's full post-processing). Set {ALEF_FRB_REGENERATE_ON_BUILD}=1 to regenerate here instead for local-only iteration; the result bypasses alef's post-processing and must not be committed."
        );
        return;
    }

    match std::process::Command::new("flutter_rust_bridge_codegen")
        .args(["generate", "--config-file", "flutter_rust_bridge.yaml"])
        .status()
    {
        Ok(status) if status.success() => {
            // FRB v2.12+ emits `use` lists in an order rustfmt 2024 edition rewrites
            // (e.g. `{transform_result_dco, Lifetimeable, Lockable}` →
            // `{Lifetimeable, Lockable, transform_result_dco}`). Run rustfmt against
            // the generated file so committed output is fmt-clean and `cargo fmt --check`
            // stays green in CI.
            match std::process::Command::new("rustfmt")
                .args(["--edition", "2024", "src/frb_generated.rs"])
                .status()
            {
                Ok(s) if s.success() => {}
                Ok(s) => println!("cargo:warning=rustfmt on src/frb_generated.rs exited {s}"),
                Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
                    println!(
                        "cargo:warning=rustfmt not on PATH — skipping post-FRB format. Install rustfmt via rustup to keep generated bridge sources fmt-clean."
                    );
                }
                Err(err) => println!("cargo:warning=failed to spawn rustfmt: {err}"),
            }

            // Patch the generated Dart entrypoint so the published package resolves
            // its native library from its own installed location.
            patch_published_loader();

            // Rewrite FRB-generated handler.executeSync/handler.executeNormal calls
            // into direct handler invocations. FRB 2.x emits these calls assuming
            // `handler` is a BaseHandler field, but in service-API methods `handler`
            // is a user-supplied function parameter (FutureOr<R> Function(T)) which
            // does not expose those methods, so the generated Dart fails to compile.
            // The rewrite is idempotent (marker-gated) and runs after every FRB
            // invocation this build script performs — i.e. only under the opt-in
            // gate above, so it never contends with alef's own regeneration.
            fix_handler_executor_calls();

            // FRB is not feature-aware: it emits a wire wrapper and a dispatch arm for
            // every `pub fn` it can see, including ones behind `#[cfg(feature = ...)]`.
            // Under a reduced feature set (Android: --no-default-features --features
            // android-target) those functions are configured out and the glue fails with
            // E0425. alef injects the gates during `alef generate`, but the FRB run above
            // rewrites the file from scratch and drops them — same reversion the handler
            // rewrite above exists to survive, so this re-applies for the same reason.
            carry_frb_cfg_gates();
        }
        Ok(status) => panic!("flutter_rust_bridge_codegen generate failed (exit code: {status})"),
        Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
            println!(
                "cargo:warning=flutter_rust_bridge_codegen not on PATH — skipping codegen. Install via `cargo install flutter_rust_bridge_codegen --locked` to regenerate FRB artifacts at build time."
            );
        }
        Err(err) => panic!("failed to spawn flutter_rust_bridge_codegen: {err}"),
    }
}

{{ loader_patch }}
{{ cfg_gates_fn }}