Skip to content

Repository files navigation

copier-dart-frb-wrapper

GitHub release License

A Copier template for generating Flutter/Dart packages that wrap native Rust libraries using Flutter Rust Bridge (FRB) with full CI/CD support.

Features

  • Cross-platform native library support (Android, iOS, macOS, Linux, Windows, Web/WASM)
  • Flutter Rust Bridge integration for type-safe bindings
  • Automatic native library download via build hooks
  • SHA256 checksum verification for supply chain security
  • GitHub Actions workflows for CI/CD

Requirements

Installation

All platforms (pip):

pip install copier jinja2-strcase

macOS (Homebrew + pip):

brew install copier && pip install jinja2-strcase

Linux (pipx - recommended for isolation):

pipx install copier
pipx inject copier jinja2-strcase

Windows (pip or pipx):

pip install copier jinja2-strcase
# or with pipx
pipx install copier
pipx inject copier jinja2-strcase

Usage

Create a new project

mkdir my_package && cd my_package
copier copy https://github.com/djx-y-z/copier-dart-frb-wrapper .

Create a new project with Claude Code

If you use Claude Code, clone this template — it bundles a create-project skill that drives Copier for you:

git clone https://github.com/djx-y-z/copier-dart-frb-wrapper

Then open the cloned directory in Claude Code and run /create-project. Describe your package in plain language and the skill runs the Copier commands above.

Update an existing project

cd my_package
copier update

Example with all parameters

mkdir my_signal_lib && cd my_signal_lib
copier copy https://github.com/djx-y-z/copier-dart-frb-wrapper . \
  --data package_name=my_signal_lib \
  --data description="Dart bindings for Signal Protocol" \
  --data native_library_name=signal \
  --data github_repo=user/my_signal_lib \
  --data native_repo=signalapp/libsignal \
  --data crate_name=my_signal_lib_frb \
  --data rust_edition=2024 \
  --data rust_version=1.88 \
  --data frb_version="2.13.0" \
  --data upstream_crates=libsignal \
  --data upstream_version=v0.86.0 \
  --data enable_web=true \
  --data flutter_version=3.38.4 \
  --data dart_sdk_version="^3.10.0" \
  --data flutter_sdk_version=">=3.38.0" \
  --data android_min_sdk=21 \
  --data android_compile_sdk=34 \
  --data topics="cryptography,ffi,native" \
  --data license=MIT

Minimal example (uses defaults)

mkdir my_lib && cd my_lib
copier copy https://github.com/djx-y-z/copier-dart-frb-wrapper . \
  --trust \
  --defaults \
  --data package_name=my_lib \
  --data description="My Rust library wrapper" \
  --data native_library_name=mylib \
  --data github_repo=user/my_lib \
  --data native_repo=original/mylib

Variables

Required

Variable Description Example
package_name Dart package name (lowercase, underscores) my_signal_lib
description Package description for pubspec.yaml Dart bindings for Signal Protocol
native_library_name Name of the native library signal
github_repo GitHub repository of your wrapper user/my_signal_lib
native_repo GitHub repository of native library signalapp/libsignal

Rust Configuration

Variable Description Default Example
crate_name Rust crate name for FRB wrapper <package_name>_frb my_signal_lib_frb
rust_edition Rust edition (2021 or 2024) 2024 2021
rust_version Minimum Rust version (MSRV) 1.88 1.75
frb_version Flutter Rust Bridge version 2.13.0 2.10.0

Upstream Crate (optional)

Variable Description Default Example
upstream_crates Upstream Rust crates (comma-separated) `` libsignal-protocol or libsignal-protocol,libsignal-core
upstream_version Version/tag of upstream crate `` v0.86.0
version_tag_prefix Tag prefix for upstream version tags (used for version normalization) v release-v

Flutter/Dart Configuration

Variable Description Default Example
flutter_version Flutter version for FVM 3.38.4 3.35.0
dart_sdk_version Dart SDK version constraint ^3.10.0 ^3.8.0
flutter_sdk_version Flutter SDK version constraint >=3.38.0 >=3.35.0
enable_web Enable Web/WASM support true false
enable_protoc Enable Protocol Buffers support (install protoc in setup/CI) false true
enable_fuzzing Include a cargo-fuzz harness (rust/fuzz/, Fuzz CI, Makefile targets) true false
enable_claude Include Claude Code files (CLAUDE.md, .claude/skills/) true false

iOS / macOS Configuration

Variable Description Default Example
ios_min_version iOS minimum deployment target 13.0 15.0
macos_min_version macOS minimum deployment target 10.15 11.0

Android Configuration

Variable Description Default Example
android_min_sdk Android minimum SDK version 24 21
android_compile_sdk Android compile SDK version 36 35
android_ndk_version Android NDK version for Rust (r28+) 28.2.13676358 28.1.13356709
android_gradle_version Android Gradle plugin version 8.11.1 8.7.0
android_java_version Java version for Android compilation (11, 17 or 21) 17 21

Package Metadata

Variable Description Default Example
license Package license MIT Apache-2.0
copyright_year Year of first publication for the LICENSE copyright line (rendered as <year>-present; a stored answer, so it does not move on re-render) 2026 2025
topics pub.dev topics (comma-separated) ffi,native,rust cryptography,ffi,native

Post-Generation Setup

After generating the project, follow these steps:

1. Initial Setup

cd <package_name>

# Install dependencies and set up the project
make setup

# Generate Flutter Rust Bridge bindings
make codegen

# Build native libraries for current platform
make build

# Generate the third-party notice inventory (needs the Cargo.lock from `make build`)
# CI verifies the committed file against the dependency graph, so the first CI
# run fails until this is generated and committed.
make third-party-notices

# Verify everything works
make analyze          # Dart static analysis (should be clean)
make rust-check       # Rust type check
make test             # Run tests (should pass)

2. GitHub Repository Setup

  1. Create a new repository on GitHub matching github_repo
  2. Push the generated code:
    git remote add origin https://github.com/<github_repo>.git
    git push -u origin main

3. GitHub Actions Configuration

Configure secrets and variables in your repository settings (Settings → Secrets and variables → Actions).

Secrets (Settings → Secrets and variables → Actions → Secrets)

Secret Description Required For
APP_PRIVATE_KEY GitHub App private key (PEM format) Update checker workflow
ANTHROPIC_API_KEY Anthropic API key AI changelog generation (anthropic/… entries)
GEMINI_API_KEY Google AI Studio API key AI changelog generation (google/… entries)
OPENROUTER_API_KEY OpenRouter API key AI changelog generation (openrouter/… entries)
GIST_TOKEN Personal Access Token with gist scope Coverage badge

Variables (Settings → Secrets and variables → Actions → Variables)

Variable Description Required For
APP_CLIENT_ID GitHub App Client ID (not the numeric App ID) Update checker workflow
AI_MODELS Ordered provider/model list, e.g. anthropic/claude-opus-5,google/gemini-3.5-flash-lite. No default — unset means no model is called AI changelog generation
AI_EFFORT low | medium (default) | high | xhigh | max AI changelog generation
COVERAGE_GIST_ID Gist ID for coverage badge JSON Coverage badge

GitHub App Setup (for the update checkers)

Both check-*-updates.yml (new upstream release) and check-template-updates.yml (new template release) use the App to open Pull Requests with signed commits. This ensures commits are verified and associated with a bot account rather than a personal account.

  1. Go to Settings → Developer settings → GitHub Apps → New GitHub App
  2. Fill in:
    • Name: my-package-bot (must be unique on GitHub)
    • Homepage URL: Your repository URL
    • Webhook: Uncheck "Active" (not needed)
  3. Set Repository permissions:
    • Contents: Read & Write
    • Pull requests: Read & Write
    • Workflows: Read & Write — see below
    • Metadata: Read-only
  4. Click Create GitHub App
  5. Copy the App ID (shown at the top) → add as APP_ID variable
  6. Scroll down to Private keys → Generate a private key
  7. A .pem file will download → copy its contents as APP_PRIVATE_KEY secret
  8. Go to Install App (left sidebar) → Install on your repository

Why Workflows. A template update is an ordinary commit over whatever the template owns, and it owns .github/workflows/**. GitHub refuses to let an App write a workflow file without this permission, and the refusal is easy to misread: create-pull-request commits through the Git Data API, so it arrives as Resource not accessible by integration on POST /git/trees — after every blob has been created, which is itself a Contents: write operation and therefore proves that permission is fine. It also stays invisible until the first update that happens to touch a workflow; every PR the bot opened before that one goes through untouched.

Two traps when adding it to an App that already exists. Actions is not Workflows: Actions governs workflow runs, Workflows governs the workflow files, and only the second unblocks this. And a permission added to an App does not reach the tokens it issues until every installation accepts the change — Settings → Applications → Installed GitHub Apps → your app → Review request. Until that is accepted the App's own settings page shows the new permission while the workflow keeps failing exactly as before.

AI Changelog Setup

The check-*-updates.yml and check-template-updates.yml workflows write the CHANGELOG entry with an AI model. Which model is configuration rather than code: AI_MODELS holds an ordered, comma-separated list of provider/model entries and the first one that has a key and answers wins, so changing provider is a repository-variable edit instead of a template release rolled out across every generated project. (That cost is why it works this way: the previous provider, GitHub Models, was retired on 2026-07-30 and had been hard-coded.)

There is no default list. With AI_MODELS unset nothing is called: the workflows still open the pull request, record the entry as not written, and label it changelog-needed for a human. That is also how a project says "no AI here" without a provider sitting there waiting for a key to appear.

  1. Get an API key from the provider you want — Anthropic, Google AI Studio or OpenRouter.
  2. Add it as a repository secret: ANTHROPIC_API_KEY, GEMINI_API_KEY or OPENROUTER_API_KEY.
  3. Add the repository variable AI_MODELS, naming the models to try in order — e.g. anthropic/claude-opus-5,google/gemini-3.5-flash-lite. An entry whose key is unset is skipped; a malformed one is warned about loudly, because it is a typo rather than a choice.
  4. Optionally add the variable AI_EFFORT (low | medium | high | xhigh | max, default medium).

openrouter is an aggregator, so its model half carries its own slash: openrouter/anthropic/claude-opus-5. The pull-request body names the model that actually wrote the entry, so a first entry that has quietly started failing is visible immediately rather than months later as a drift in house style.

Each generated project also gets .github/agent-prompts/changelog-scope.md — its own statement of what it binds and exposes, which every entry is classified against. It is written once and never overwritten by a template update, so edit it to match the project.

pub.dev Publishing Setup

The publish.yml workflow uses OIDC authentication to publish to pub.dev without tokens. This requires a one-time setup on both pub.dev and GitHub.

On pub.dev:

  1. Go to https://pub.dev and sign in
  2. Navigate to your publisher page (or create a publisher)
  3. Go to Admin → Automated publishing
  4. Click Enable automated publishing
  5. Add your GitHub repository (e.g., user/my_package)
  6. Set Publishing from: GitHub Actions with tag → tag pattern: v*

See dart.dev/tools/pub/automated-publishing for details.

On GitHub (create environment):

  1. Go to your repository → Settings → Environments
  2. Click New environment → name it exactly pub.dev
  3. Under Deployment protection rules:
    • Check Required reviewers → add yourself (and/or your team) as reviewer
    • Uncheck Allow administrators to bypass configured protection rules
  4. Click Save protection rules

Why? The pub.dev environment is required by the publish workflow. Protection rules ensure that every publish to pub.dev requires manual approval, preventing accidental releases.

Coverage Badge Setup

  1. Go to https://gist.github.com and create a new secret gist
  2. Name the file coverage.json with content: {"schemaVersion":1,"label":"coverage","message":"0%","color":"red"}
  3. Click Create secret gist
  4. Copy the Gist ID from URL (e.g., https://gist.github.com/user/abc123def456 → abc123def456)
  5. Add the Gist ID as COVERAGE_GIST_ID variable (not secret)
  6. Create a Personal Access Token:
    • Go to Settings → Developer settings → Personal access tokens → Tokens (classic)
    • Click Generate new token (classic)
    • Name: gist-coverage
    • Scopes: check only gist
    • Click Generate token → copy and add as GIST_TOKEN secret

4. Repository Protection

Rulesets and environments live on GitHub, not in the repository, so nothing in the generated tree applies them for you. Once the repository exists and the first push has landed, run — as a repo admin, with gh authenticated:

make setup-repo-protections

It creates every ruleset committed under .github/rulesets/ (protected main, required commit signing, protected release tags, no branch deletion) and the native-build environment with you as its required reviewer. Until it runs, the native binary that every consumer downloads at build time can be published by anyone with write and no review, and the release tags that trigger that build are unprotected. See .github/rulesets/README.md for what each ruleset does, how to adjust bypass actors, and how to re-apply after editing one.

5. Repository Topics

Add these topics to your GitHub repository for discoverability:

  • dart
  • flutter
  • rust
  • ffi
  • flutter-rust-bridge
  • Your native library name

6. Update SECURITY.md

Review and update SECURITY.md with your security policy and contact information.

Development Commands

The generated project includes a Makefile. Run make help to see all available commands:

# Setup
make setup              # Full setup (FVM + Rust tools + dependencies)
make setup-fvm          # Install FVM and project Flutter version only
make setup-rust-tools   # Install Rust tools (cargo-audit, frb codegen)
make setup-android      # Install Android build tools (cargo-ndk)
make setup-web          # Install web build tools (wasm-pack) - if enable_web=true

# Build & Codegen
make codegen            # Generate Dart bindings from Rust code
make build              # Build Rust library for current platform
make build-android      # Build for Android (all ABIs)
make build-web          # Build WASM for web platform - if enable_web=true

# Development
make check-upstream     # Check for native library updates
make version            # Show current crate version

# Rust Quality
make rust-check         # Check Rust code compiles
make rust-doc           # Rustdoc GATE: intra-doc links, -D warnings
make rust-geiger        # Unsafe-expression census (diagnostic, not gated)
make rust-audit         # Audit Rust dependencies for vulnerabilities
make test-web           # Browser tests in headless Chrome - if enable_web=true

# Dart Quality
make test               # Run tests
make coverage           # Run tests with coverage report
make analyze            # Run static analysis
make format             # Format Dart code
make format-check       # Check Dart code formatting
make doc                # Generate API docs (GATE: fails on unresolved refs)

# Utilities
make get                # Get dependencies
make clean              # Clean build artifacts
make publish-dry-run    # Validate package before publishing

Troubleshooting

FRB codegen fails

Ensure you have the correct Rust toolchain and cross-compilation targets installed.

Note: These are target architectures (what you build for), not your development machine's architecture. You can add targets on any OS, but some targets have platform requirements.

rustup update

# Desktop - Linux (any OS, but native builds require Linux)
rustup target add x86_64-unknown-linux-gnu   # Linux x86_64
rustup target add aarch64-unknown-linux-gnu  # Linux ARM64

# Desktop - macOS (macOS only - requires Xcode)
rustup target add aarch64-apple-darwin       # macOS Apple Silicon
rustup target add x86_64-apple-darwin        # macOS Intel

# Desktop - Windows (any OS, but native builds require Windows + MSVC)
rustup target add x86_64-pc-windows-msvc     # Windows x86_64

# Mobile - iOS (macOS only - requires Xcode)
rustup target add aarch64-apple-ios          # iOS devices (ARM64)
rustup target add aarch64-apple-ios-sim      # iOS Simulator on Apple Silicon
rustup target add x86_64-apple-ios           # iOS Simulator on Intel Mac

# Mobile - Android (any OS with Android NDK)
rustup target add aarch64-linux-android      # ARM64 devices (most modern phones)
rustup target add armv7-linux-androideabi    # ARMv7 devices (older phones)
rustup target add x86_64-linux-android       # x86_64 emulator

# Web/WASM (any OS)
rustup target add wasm32-unknown-unknown

Native library download fails

  1. Check that native_repo is correct and the repository has releases
  2. Verify the release assets follow the expected naming convention
  3. Check your network connection and GitHub API rate limits

Build hook not found

Ensure you're using Dart 3.10+ with native assets support:

dart --version
flutter --version

WASM build issues

For Web/WASM support:

# Install wasm-pack
cargo install wasm-pack

# Build WASM
make build-web

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

This template is licensed under the MIT License. See LICENSE for details.

Generated projects inherit this license by default, but you can choose a different license during generation.

About

A Copier template for generating Flutter/Dart packages that wrap native Rust libraries using Flutter Rust Bridge (FRB) with full CI/CD support

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Contributors

Languages