cbf-chrome-sys 148.0.0-alpha.1

Low-level Chromium bridge bindings for CBF.
Documentation
import argparse
import difflib
import subprocess
import tempfile
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[3]
CRATE_ROOT = Path(__file__).resolve().parents[1]
SRC_DIR = CRATE_ROOT / "src"
INCLUDE_DIR = CRATE_ROOT / "include"

FFI_WRAPPER_HEADER = INCLUDE_DIR / "cbf_bridge_ffi_bindgen_wrapper.h"
BRIDGE_WRAPPER_HEADER = INCLUDE_DIR / "cbf_bridge_bindgen_wrapper.h"

FFI_DATA_GENERATED = SRC_DIR / "ffi_data_generated.rs"
FFI_BRIDGE_GENERATED = SRC_DIR / "ffi_bridge_generated.rs"

FFI_ALLOW_ATTR = """#![allow(
    dead_code,
    non_camel_case_types,
    non_snake_case,
    non_upper_case_globals,
    improper_ctypes
)]"""

BRIDGE_API_ALLOW_ATTR = """#![allow(
    non_camel_case_types,
    unsafe_op_in_unsafe_fn,
    clippy::missing_safety_doc,
    clippy::too_many_arguments
)]"""


def _insert_inner_attr_after_first_line(text: str, attr: str) -> str:
    if attr in text:
        return text

    first_line, sep, rest = text.partition("\n")
    if not sep:
        return f"{first_line}\n\n{attr}\n"
    normalized_rest = rest.lstrip("\n")
    return f"{first_line}\n\n{attr}\n\n{normalized_rest}"


def _run_bindgen(args: list[str]) -> str:
    result = subprocess.run(
        args,
        cwd=REPO_ROOT,
        capture_output=True,
        text=True,
        check=False,
    )
    if result.returncode != 0:
        raise RuntimeError(result.stderr.strip() or "bindgen generation failed")
    return result.stdout


def generate_ffi_types(*, output_path: Path | None = None) -> Path:
    output = output_path or FFI_DATA_GENERATED
    text = _run_bindgen(
        [
            "bindgen",
            str(FFI_WRAPPER_HEADER),
            "--allowlist-type",
            "Cbf.*",
            "--allowlist-var",
            "k.*|Cbf.*",
            "--allowlist-function",
            "^$",
            "--with-derive-default",
            "--no-layout-tests",
            "--formatter",
            "rustfmt",
            "--",
            "-Ichromium/src",
        ]
    )
    text = _insert_inner_attr_after_first_line(text, FFI_ALLOW_ATTR)
    output.write_text(text)
    return output


def generate_bridge_api(*, output_path: Path | None = None) -> Path:
    output = output_path or FFI_BRIDGE_GENERATED
    text = _run_bindgen(
        [
            "bindgen",
            str(BRIDGE_WRAPPER_HEADER),
            "--dynamic-loading",
            "cbf_bridge",
            "--dynamic-link-require-all",
            "--allowlist-function",
            "cbf_bridge_.*",
            "--blocklist-type",
            "Cbf.*",
            "--raw-line",
            "use super::*;",
            "--no-layout-tests",
            "--formatter",
            "rustfmt",
            "--",
            "-Ichromium/src",
        ]
    )
    text = _insert_inner_attr_after_first_line(text, BRIDGE_API_ALLOW_ATTR)
    output.write_text(text)
    return output


def _verify_file(expected: Path, generated: Path) -> list[str]:
    expected_text = expected.read_text()
    generated_text = generated.read_text()
    if expected_text == generated_text:
        return []
    return list(
        difflib.unified_diff(
            expected_text.splitlines(),
            generated_text.splitlines(),
            fromfile=str(expected),
            tofile=str(generated),
            lineterm="",
        )
    )


def verify() -> int:
    with tempfile.TemporaryDirectory(prefix="cbf-ffi-") as temp_dir:
        temp_root = Path(temp_dir)
        ffi_temp = temp_root / FFI_DATA_GENERATED
        bridge_temp = temp_root / FFI_BRIDGE_GENERATED
        generate_ffi_types(output_path=ffi_temp)
        generate_bridge_api(output_path=bridge_temp)

        diffs = _verify_file(FFI_DATA_GENERATED, ffi_temp) + _verify_file(
            FFI_BRIDGE_GENERATED, bridge_temp
        )
        if diffs:
            print("\n".join(diffs))
            return 1
    print("FFI generated files are up to date.")
    return 0


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Generate cbf-chrome-sys FFI bindings."
    )
    subparsers = parser.add_subparsers(dest="command", required=True)

    subparsers.add_parser(
        "generate", help="Regenerate both generated Rust binding files."
    )
    subparsers.add_parser(
        "verify", help="Check that generated Rust binding files are up to date."
    )

    args = parser.parse_args()
    if args.command == "generate":
        generate_ffi_types()
        generate_bridge_api()
        print(f"Wrote {FFI_DATA_GENERATED.relative_to(REPO_ROOT)}")
        print(f"Wrote {FFI_BRIDGE_GENERATED.relative_to(REPO_ROOT)}")
        return 0
    if args.command == "verify":
        return verify()
    raise AssertionError(f"unexpected command: {args.command}")


if __name__ == "__main__":
    raise SystemExit(main())