alef 0.67.1

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}");

    // FRB is not feature-aware: it bakes a wire wrapper and a numeric dispatch arm for every
    // `pub fn` it can see, including ones behind `#[cfg(feature = "...")]`. Compiled under a
    // reduced feature set (Android: --no-default-features --features android-target) the gated
    // definition is configured out of `lib.rs` while the ungated caller in `frb_generated.rs`
    // survives, and the crate fails with E0425.
    //
    // This runs unconditionally, before the regeneration opt-in returns below, because the build
    // that needs it most is the one that regenerates nothing: a plain build of the *committed*
    // bridge, on a machine with no flutter_rust_bridge_codegen installed. Gating the repair on a
    // successful FRB run made it unreachable in exactly that configuration. It reads the gates
    // from `lib.rs` — the same file alef derives them from — so both sides of the wire always
    // agree, and it is idempotent, so re-applying costs nothing.
    carry_frb_cfg_gates();

    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();

            // The FRB run above rewrote `frb_generated.rs` from scratch and dropped the gates
            // applied at the top of `main`, so re-apply them to the file as it now stands.
            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 }}