diff --git a/Cargo.lock b/Cargo.lock
index e6942ef72..5c4621f57 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1476,9 +1476,7 @@ dependencies = [
[[package]]
name = "flutter_rust_bridge_codegen"
-version = "1.30.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3209735fd687b06b8d770ec008874119b91f7f46b4a73d17226d5c337435bb74"
+version = "1.32.0"
dependencies = [
"anyhow",
"cargo_metadata",
diff --git a/Cargo.toml b/Cargo.toml
index 2b707a688..f046df244 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -105,7 +105,7 @@ jni = "0.19.0"
flutter_rust_bridge = "1.30.0"
[workspace]
-members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display"]
+members = ["libs/scrap", "libs/hbb_common", "libs/enigo", "libs/clipboard", "libs/virtual_display", "libs/flutter_rust_bridge_codegen"]
[package.metadata.winres]
LegalCopyright = "Copyright © 2022 Purslane, Inc."
@@ -119,7 +119,7 @@ winapi = { version = "0.3", features = [ "winnt" ] }
[build-dependencies]
cc = "1.0"
hbb_common = { path = "libs/hbb_common" }
-flutter_rust_bridge_codegen = "1.30.0"
+flutter_rust_bridge_codegen = { path = "libs/flutter_rust_bridge_codegen" }
[dev-dependencies]
hound = "3.4"
diff --git a/flutter/.gitignore b/flutter/.gitignore
index 7dc95a613..c8ff34feb 100644
--- a/flutter/.gitignore
+++ b/flutter/.gitignore
@@ -45,6 +45,7 @@ jniLibs
# flutter rust bridge
lib/generated_bridge.dart
+lib/generated_bridge.freezed.dart
# Flutter Generated Files
linux/flutter/generated_plugin_registrant.cc
diff --git a/flutter/pubspec.lock b/flutter/pubspec.lock
index 4eaa1c877..1ba610f19 100644
--- a/flutter/pubspec.lock
+++ b/flutter/pubspec.lock
@@ -1,6 +1,20 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ _fe_analyzer_shared:
+ dependency: transitive
+ description:
+ name: _fe_analyzer_shared
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "40.0.0"
+ analyzer:
+ dependency: transitive
+ description:
+ name: analyzer
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "4.1.0"
archive:
dependency: transitive
description:
@@ -64,6 +78,62 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
+ build:
+ dependency: transitive
+ description:
+ name: build
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.3.0"
+ build_config:
+ dependency: transitive
+ description:
+ name: build_config
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.0"
+ build_daemon:
+ dependency: transitive
+ description:
+ name: build_daemon
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.1.0"
+ build_resolvers:
+ dependency: transitive
+ description:
+ name: build_resolvers
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.9"
+ build_runner:
+ dependency: "direct dev"
+ description:
+ name: build_runner
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.1.11"
+ build_runner_core:
+ dependency: transitive
+ description:
+ name: build_runner_core
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "7.2.3"
+ built_collection:
+ dependency: transitive
+ description:
+ name: built_collection
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "5.1.1"
+ built_value:
+ dependency: transitive
+ description:
+ name: built_value
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "8.3.2"
characters:
dependency: transitive
description:
@@ -78,6 +148,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.1"
clock:
dependency: transitive
description:
@@ -85,6 +162,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
+ code_builder:
+ dependency: transitive
+ description:
+ name: code_builder
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "4.1.0"
collection:
dependency: transitive
description:
@@ -92,6 +176,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.16.0"
+ convert:
+ dependency: transitive
+ description:
+ name: convert
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.0.2"
cross_file:
dependency: transitive
description:
@@ -113,6 +204,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
+ dart_style:
+ dependency: transitive
+ description:
+ name: dart_style
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.2.3"
dash_chat:
dependency: "direct main"
description:
@@ -319,6 +417,41 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ freezed:
+ dependency: "direct dev"
+ description:
+ name: freezed
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.3+1"
+ freezed_annotation:
+ dependency: "direct main"
+ description:
+ name: freezed_annotation
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.3"
+ frontend_server_client:
+ dependency: transitive
+ description:
+ name: frontend_server_client
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.1.3"
+ glob:
+ dependency: transitive
+ description:
+ name: glob
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.2"
+ graphs:
+ dependency: transitive
+ description:
+ name: graphs
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.1.0"
http:
dependency: "direct main"
description:
@@ -326,6 +459,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.4"
+ http_multi_server:
+ dependency: transitive
+ description:
+ name: http_multi_server
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.2.0"
http_parser:
dependency: transitive
description:
@@ -382,6 +522,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
+ io:
+ dependency: transitive
+ description:
+ name: io
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.3"
js:
dependency: transitive
description:
@@ -389,6 +536,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.4"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "4.5.0"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.2"
matcher:
dependency: transitive
description:
@@ -410,6 +571,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.2"
nested:
dependency: transitive
description:
@@ -417,6 +585,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
+ package_config:
+ dependency: transitive
+ description:
+ name: package_config
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.2"
package_info_plus:
dependency: "direct main"
description:
@@ -543,6 +718,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
+ pool:
+ dependency: transitive
+ description:
+ name: pool
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.5.0"
process:
dependency: transitive
description:
@@ -557,6 +739,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
+ pub_semver:
+ dependency: transitive
+ description:
+ name: pub_semver
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.1.1"
+ pubspec_parse:
+ dependency: transitive
+ description:
+ name: pubspec_parse
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.2.0"
qr_code_scanner:
dependency: "direct main"
description:
@@ -636,11 +832,32 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
+ shelf:
+ dependency: transitive
+ description:
+ name: shelf
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.3.0"
+ shelf_web_socket:
+ dependency: transitive
+ description:
+ name: shelf_web_socket
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
+ source_gen:
+ dependency: transitive
+ description:
+ name: source_gen
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.2.2"
source_span:
dependency: transitive
description:
@@ -662,6 +879,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
+ stream_transform:
+ dependency: transitive
+ description:
+ name: stream_transform
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.0.0"
string_scanner:
dependency: transitive
description:
@@ -683,6 +907,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.9"
+ timing:
+ dependency: transitive
+ description:
+ name: timing
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.0"
toggle_switch:
dependency: "direct main"
description:
@@ -816,6 +1047,20 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
+ watcher:
+ dependency: transitive
+ description:
+ name: watcher
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.0.1"
+ web_socket_channel:
+ dependency: transitive
+ description:
+ name: web_socket_channel
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.2.0"
win32:
dependency: transitive
description:
diff --git a/flutter/pubspec.yaml b/flutter/pubspec.yaml
index 008d4ef9d..21b1857eb 100644
--- a/flutter/pubspec.yaml
+++ b/flutter/pubspec.yaml
@@ -3,7 +3,7 @@ description: Your Remote Desktop Software
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
-publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+publish_to: "none" # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
@@ -19,102 +19,102 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.1.10+27
environment:
- sdk: ">=2.16.1"
+ sdk: ">=2.16.1"
dependencies:
- flutter:
- sdk: flutter
+ flutter:
+ sdk: flutter
-
- # The following adds the Cupertino Icons font to your application.
- # Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^1.0.3
- ffi: ^1.1.2
- path_provider: ^2.0.2
- external_path: ^1.0.1
- provider: ^5.0.0
- tuple: ^2.0.0
- wakelock: ^0.5.2
- device_info_plus: ^3.2.3
- firebase_analytics: ^9.1.5
- package_info_plus: ^1.4.2
- url_launcher: ^6.0.9
- shared_preferences: ^2.0.6
- toggle_switch: ^1.4.0
- dash_chat: ^1.1.16
- draggable_float_widget: ^0.0.2
- settings_ui: ^2.0.2
- flutter_breadcrumb: ^1.0.1
- http: ^0.13.4
- qr_code_scanner:
- git:
- url: https://github.com/Heap-Hop/qr_code_scanner.git
- ref: fix_break_changes_platform
- zxing2: ^0.1.0
- image_picker: ^0.8.5
- image: ^3.1.3
- flutter_smart_dialog: ^4.3.1
- flutter_rust_bridge: ^1.30.0
- window_manager: ^0.2.3
- desktop_multi_window:
- git:
- url: https://github.com/Kingtous/rustdesk_desktop_multi_window
- ref: master
- bitsdojo_window: ^0.1.2
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.3
+ ffi: ^1.1.2
+ path_provider: ^2.0.2
+ external_path: ^1.0.1
+ provider: ^5.0.0
+ tuple: ^2.0.0
+ wakelock: ^0.5.2
+ device_info_plus: ^3.2.3
+ firebase_analytics: ^9.1.5
+ package_info_plus: ^1.4.2
+ url_launcher: ^6.0.9
+ shared_preferences: ^2.0.6
+ toggle_switch: ^1.4.0
+ dash_chat: ^1.1.16
+ draggable_float_widget: ^0.0.2
+ settings_ui: ^2.0.2
+ flutter_breadcrumb: ^1.0.1
+ http: ^0.13.4
+ qr_code_scanner:
+ git:
+ url: https://github.com/Heap-Hop/qr_code_scanner.git
+ ref: fix_break_changes_platform
+ zxing2: ^0.1.0
+ image_picker: ^0.8.5
+ image: ^3.1.3
+ flutter_smart_dialog: ^4.3.1
+ flutter_rust_bridge: ^1.30.0
+ window_manager: ^0.2.3
+ desktop_multi_window:
+ git:
+ url: https://github.com/Kingtous/rustdesk_desktop_multi_window
+ ref: master
+ bitsdojo_window: ^0.1.2
+ freezed_annotation: ^2.0.3
dev_dependencies:
- flutter_launcher_icons: ^0.9.1
- flutter_test:
- sdk: flutter
-
+ flutter_launcher_icons: ^0.9.1
+ flutter_test:
+ sdk: flutter
+ build_runner: ^2.1.11
+ freezed: ^2.0.3
# rerun: flutter pub run flutter_launcher_icons:main
flutter_icons:
- android: "ic_launcher"
- ios: true
- image_path: "../1024-rec.png"
+ android: "ic_launcher"
+ ios: true
+ image_path: "../1024-rec.png"
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # the material Icons class.
+ uses-material-design: true
- # The following line ensures that the Material Icons font is
- # included with your application, so that you can use the icons in
- # the material Icons class.
- uses-material-design: true
+ # To add assets to your application, add an assets section, like this:
+ assets:
+ - assets/
- # To add assets to your application, add an assets section, like this:
- assets:
- - assets/
+ fonts:
+ - family: GestureIcons
+ fonts:
+ - asset: assets/gestures.ttf
- fonts:
- - family: GestureIcons
- fonts:
- - asset: assets/gestures.ttf
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/assets-and-images/#resolution-aware.
- # An image asset can refer to one or more resolution-specific "variants", see
- # https://flutter.dev/assets-and-images/#resolution-aware.
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/assets-and-images/#from-packages
- # For details regarding adding assets from package dependencies, see
- # https://flutter.dev/assets-and-images/#from-packages
-
- # To add custom fonts to your application, add a fonts section here,
- # in this "flutter" section. Each entry in this list should have a
- # "family" key with the font family name, and a "fonts" key with a
- # list giving the asset and other descriptors for the font. For
- # example:
- # fonts:
- # - family: Schyler
- # fonts:
- # - asset: fonts/Schyler-Regular.ttf
- # - asset: fonts/Schyler-Italic.ttf
- # style: italic
- # - family: Trajan Pro
- # fonts:
- # - asset: fonts/TrajanPro.ttf
- # - asset: fonts/TrajanPro_Bold.ttf
- # weight: 700
- #
- # For details regarding fonts from package dependencies,
- # see https://flutter.dev/custom-fonts/#from-packages
+ # To add custom fonts to your application, add a fonts section here,
+ # in this "flutter" section. Each entry in this list should have a
+ # "family" key with the font family name, and a "fonts" key with a
+ # list giving the asset and other descriptors for the font. For
+ # example:
+ # fonts:
+ # - family: Schyler
+ # fonts:
+ # - asset: fonts/Schyler-Regular.ttf
+ # - asset: fonts/Schyler-Italic.ttf
+ # style: italic
+ # - family: Trajan Pro
+ # fonts:
+ # - asset: fonts/TrajanPro.ttf
+ # - asset: fonts/TrajanPro_Bold.ttf
+ # weight: 700
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/libs/flutter_rust_bridge_codegen/.gitignore b/libs/flutter_rust_bridge_codegen/.gitignore
new file mode 100644
index 000000000..6985cf1bd
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/.gitignore
@@ -0,0 +1,14 @@
+# Generated by Cargo
+# will have compiled files and executables
+debug/
+target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# MSVC Windows builds of rustc generate these, which store debugging information
+*.pdb
diff --git a/libs/flutter_rust_bridge_codegen/Cargo.toml b/libs/flutter_rust_bridge_codegen/Cargo.toml
new file mode 100644
index 000000000..dfd1556db
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/Cargo.toml
@@ -0,0 +1,37 @@
+[package]
+name = "flutter_rust_bridge_codegen"
+version = "1.32.0"
+edition = "2018"
+description = "High-level memory-safe bindgen for Dart/Flutter <-> Rust"
+license = "MIT"
+repository = "https://github.com/fzyzcjy/flutter_rust_bridge"
+keywords = ["flutter", "dart", "ffi", "code-generation", "bindings"]
+categories = ["development-tools::ffi"]
+
+[lib]
+name = "lib_flutter_rust_bridge_codegen"
+path = "src/lib.rs"
+
+[[bin]]
+name = "flutter_rust_bridge_codegen"
+path = "src/main.rs"
+
+[dependencies]
+syn = { version = "1.0.77", features = ["full", "extra-traits"] }
+quote = "1.0"
+regex = "1.5.4"
+lazy_static = "1.4.0"
+convert_case = "0.5.0"
+tempfile = "3.2.0"
+serde = { version = "1.0", features = ["derive"] }
+serde_yaml = "0.8"
+log = "0.4"
+env_logger = "0.9.0"
+structopt = "0.3"
+toml = "0.5.8"
+anyhow = "1.0.44"
+pathdiff = "0.2.1"
+cargo_metadata = "0.14.1"
+enum_dispatch = "0.3.8"
+thiserror = "1"
+cbindgen = "0.23"
\ No newline at end of file
diff --git a/libs/flutter_rust_bridge_codegen/README.md b/libs/flutter_rust_bridge_codegen/README.md
new file mode 100644
index 000000000..d9aa76531
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/README.md
@@ -0,0 +1,95 @@
+# [flutter_rust_bridge](https://github.com/fzyzcjy/flutter_rust_bridge): High-level memory-safe binding generator for Flutter/Dart <-> Rust
+
+[![Rust Package](https://img.shields.io/crates/v/flutter_rust_bridge.svg)](https://crates.io/crates/flutter_rust_bridge)
+[![Flutter Package](https://img.shields.io/pub/v/flutter_rust_bridge.svg)](https://pub.dev/packages/flutter_rust_bridge)
+[![Stars](https://img.shields.io/github/stars/fzyzcjy/flutter_rust_bridge)](https://github.com/fzyzcjy/flutter_rust_bridge)
+[![CI](https://github.com/fzyzcjy/flutter_rust_bridge/actions/workflows/ci.yaml/badge.svg)](https://github.com/fzyzcjy/flutter_rust_bridge/actions/workflows/ci.yaml)
+[![Example](https://github.com/fzyzcjy/flutter_rust_bridge/actions/workflows/post_release.yaml/badge.svg)](https://github.com/fzyzcjy/flutter_rust_bridge/actions/workflows/post_release.yaml)
+[![Codacy Badge](https://api.codacy.com/project/badge/Grade/6afbdad19e7245adbf9e9771777be3d7)](https://app.codacy.com/gh/fzyzcjy/flutter_rust_bridge?utm_source=github.com&utm_medium=referral&utm_content=fzyzcjy/flutter_rust_bridge&utm_campaign=Badge_Grade_Settings)
+
+![Logo](https://github.com/fzyzcjy/flutter_rust_bridge/raw/master/book/logo.png)
+
+Want to combine the best between [Flutter](https://flutter.dev/), a cross-platform hot-reload rapid-development UI toolkit, and [Rust](https://www.rust-lang.org/), a language empowering everyone to build reliable and efficient software? Here it comes!
+
+## 🚀 Advantages
+
+* **Memory-safe**: Never need to think about malloc/free.
+* **Feature-rich**: `enum`s with values, platform-optimized `Vec`, possibly recursive `struct`, zero-copy big arrays, `Stream` (iterator) abstraction, error (`Result`) handling, cancellable tasks, concurrency control, and more. See full features [here](https://fzyzcjy.github.io/flutter_rust_bridge/feature.html).
+* **Async programming**: Rust code will never block the Flutter. Call Rust naturally from Flutter's main isolate (thread).
+* **Lightweight**: This is not a huge framework that includes everything, so you are free to use your favorite Flutter and Rust libraries. For example, state-management with Flutter library (e.g. MobX) can be elegant and simple (contrary to implementing in Rust); implementing a photo manipulation algorithm in Rust will be fast and safe (countrary to implementing in Flutter).
+* **Cross-platform**: Android, iOS, Windows, Linux, MacOS ([Web](https://github.com/fzyzcjy/flutter_rust_bridge/issues/315) coming soon)
+* **Easy to code-review & convince yourself**: This package simply simulates how humans write boilerplate code. If you want to convince yourself (or your team) that it is safe, there is not much code to look at. No magic at all! ([More about](https://fzyzcjy.github.io/flutter_rust_bridge/safety.html) safety concerns.)
+* **Fast**: It is only a thin (though feature-rich) wrapper, without overhead such as protobuf serialization, thus performant. (More [benchmarks](https://github.com/fzyzcjy/flutter_rust_bridge/issues/318#issuecomment-1034536815) later) (Throw away components like thread-pool to make it even faster)
+* **Pure-Dart compatible:** Despite the name, this package is 100% compatible with [pure](https://github.com/fzyzcjy/flutter_rust_bridge/blob/master/frb_example/pure_dart/README.md) Dart.
+
+## 💡 User Guide
+
+Check out [the user guide](https://fzyzcjy.github.io/flutter_rust_bridge/) for [show-me-the-code](https://fzyzcjy.github.io/flutter_rust_bridge/quickstart.html), [tutorials](https://fzyzcjy.github.io/flutter_rust_bridge/tutorial_with_flutter.html), [features](https://fzyzcjy.github.io/flutter_rust_bridge/feature.html) and much more.
+
+## 📎 P.S. Convenient Flutter tests
+
+If you want to write and debug tests in Flutter conveniently, with action history, time travelling, screenshots, rapid re-execution, video recordings, interactive mode and more, here is my another open-source library: https://github.com/fzyzcjy/flutter_convenient_test.
+
+## ✨ Contributors
+
+
+[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg?style=flat-square)](#contributors-)
+
+
+Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key) following [all-contributors](https://github.com/all-contributors/all-contributors) specification):
+
+
+
+
+
+
+
+
+
+
+
+More specifically, thanks for all these contributions:
+
+* [Desdaemon](https://github.com/Desdaemon): Support not only simple enums but also enums with fields which gets translated to native enum or freezed class in Dart. Support the Option type as nullable types in Dart. Support Vec of Strings type. Support comments in code. Add marker attributes for future usage. Add Linux and Windows support for with-flutter example, and make CI works for that. Avoid parameter collision. Overhaul the documentation and add several chapters to demonstrate configuring a Flutter+Rust project in all five platforms. Refactor command module.
+* [SecondFlight](https://github.com/SecondFlight): Allow structs and enums to be imported from other files within the crate by creating source graph. Auto-create relavent dir.
+* [Unoqwy](https://github.com/Unoqwy): Add struct mirrors, such that types in the external crates can be imported and used without redefining and copying.
+* [antonok-edm](https://github.com/antonok-edm): Avoid converting syn types to strings before parsing to improve code and be more robust.
+* [sagudev](https://github.com/sagudev): Make code generator a `lib`. Add error types. Depend on `cbindgen`. Fix LLVM paths. Update deps. Fix CI errors.
+* [surban](https://github.com/surban): Support unit return type. Skip unresolvable modules. Ignore prefer_const_constructors. Non-final Dart fields.
+* [trobanga](https://github.com/trobanga): Add support for `[T;N]` structs. Add `usize` support. Add a cmd argument. Separate dart tests.
+* [AlienKevin](https://github.com/AlienKevin): Add flutter example for macOS. Add doc for Android NDK bug.
+* [alanlzhang](https://github.com/alanlzhang): Add generation for Dart metadata.
+* [efc-mw](https://github.com/efc-mw): Improve Windows encoding handling.
+* [valeth](https://github.com/valeth): Rename callFfi's port.
+* [Michael-F-Bryan](https://github.com/Michael-F-Bryan): Detect broken bindings.
+* [bus710](https://github.com/bus710): Add a case in troubleshooting.
+* [Syndim](https://github.com/Syndim): Add a bracket to box.
+* [feber](https://github.com/feber): Fix doc link.
+* [rustui](https://github.com/rustui): Fix a typo.
+* [eltociear](https://github.com/eltociear): Fix a typo.
+
diff --git a/libs/flutter_rust_bridge_codegen/src/commands.rs b/libs/flutter_rust_bridge_codegen/src/commands.rs
new file mode 100644
index 000000000..6838449d8
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/commands.rs
@@ -0,0 +1,267 @@
+use std::fmt::Write;
+use std::path::Path;
+use std::process::Command;
+use std::process::Output;
+
+use crate::error::{Error, Result};
+use log::{debug, info, warn};
+
+#[must_use]
+fn call_shell(cmd: &str) -> Output {
+ #[cfg(windows)]
+ return execute_command("powershell", &["-noprofile", "-c", cmd], None);
+
+ #[cfg(not(windows))]
+ execute_command("sh", &["-c", cmd], None)
+}
+
+pub fn ensure_tools_available() -> Result {
+ let output = call_shell("dart pub global list");
+ let output = String::from_utf8_lossy(&output.stdout);
+ if !output.contains("ffigen") {
+ return Err(Error::MissingExe(String::from("ffigen")));
+ }
+
+ Ok(())
+}
+
+pub fn bindgen_rust_to_dart(
+ rust_crate_dir: &str,
+ c_output_path: &str,
+ dart_output_path: &str,
+ dart_class_name: &str,
+ c_struct_names: Vec,
+ llvm_install_path: &[String],
+ llvm_compiler_opts: &str,
+) -> anyhow::Result<()> {
+ cbindgen(rust_crate_dir, c_output_path, c_struct_names)?;
+ ffigen(
+ c_output_path,
+ dart_output_path,
+ dart_class_name,
+ llvm_install_path,
+ llvm_compiler_opts,
+ )
+}
+
+#[must_use = "Error path must be handled."]
+fn execute_command(bin: &str, args: &[&str], current_dir: Option<&str>) -> Output {
+ let mut cmd = Command::new(bin);
+ cmd.args(args);
+
+ if let Some(current_dir) = current_dir {
+ cmd.current_dir(current_dir);
+ }
+
+ debug!(
+ "execute command: bin={} args={:?} current_dir={:?} cmd={:?}",
+ bin, args, current_dir, cmd
+ );
+
+ let result = cmd
+ .output()
+ .unwrap_or_else(|err| panic!("\"{}\" \"{}\" failed: {}", bin, args.join(" "), err));
+
+ let stdout = String::from_utf8_lossy(&result.stdout);
+ if result.status.success() {
+ debug!(
+ "command={:?} stdout={} stderr={}",
+ cmd,
+ stdout,
+ String::from_utf8_lossy(&result.stderr)
+ );
+ if stdout.contains("fatal error") {
+ warn!("See keywords such as `error` in command output. Maybe there is a problem? command={:?} output={:?}", cmd, result);
+ } else if args.contains(&"ffigen") && stdout.contains("[SEVERE]") {
+ // HACK: If ffigen can't find a header file it will generate broken
+ // bindings but still exit successfully. We can detect these broken
+ // bindings by looking for a "[SEVERE]" log message.
+ //
+ // It may emit SEVERE log messages for non-fatal errors though, so
+ // we don't want to error out completely.
+
+ warn!(
+ "The `ffigen` command emitted a SEVERE error. Maybe there is a problem? command={:?} output=\n{}",
+ cmd, String::from_utf8_lossy(&result.stdout)
+ );
+ }
+ } else {
+ warn!(
+ "command={:?} stdout={} stderr={}",
+ cmd,
+ stdout,
+ String::from_utf8_lossy(&result.stderr)
+ );
+ }
+ result
+}
+
+fn cbindgen(
+ rust_crate_dir: &str,
+ c_output_path: &str,
+ c_struct_names: Vec,
+) -> anyhow::Result<()> {
+ debug!(
+ "execute cbindgen rust_crate_dir={} c_output_path={}",
+ rust_crate_dir, c_output_path
+ );
+
+ let config = cbindgen::Config {
+ language: cbindgen::Language::C,
+ sys_includes: vec![
+ "stdbool.h".to_string(),
+ "stdint.h".to_string(),
+ "stdlib.h".to_string(),
+ ],
+ no_includes: true,
+ export: cbindgen::ExportConfig {
+ include: c_struct_names
+ .iter()
+ .map(|name| format!("\"{}\"", name))
+ .collect::>(),
+ ..Default::default()
+ },
+ ..Default::default()
+ };
+
+ debug!("cbindgen config: {:?}", config);
+
+ let canonical = Path::new(rust_crate_dir)
+ .canonicalize()
+ .expect("Could not canonicalize rust crate dir");
+ let mut path = canonical.to_str().unwrap();
+
+ // on windows get rid of the UNC path
+ if path.starts_with(r"\\?\") {
+ path = &path[r"\\?\".len()..];
+ }
+
+ if cbindgen::generate_with_config(path, config)?.write_to_file(c_output_path) {
+ Ok(())
+ } else {
+ Err(Error::str("cbindgen failed writing file").into())
+ }
+}
+
+fn ffigen(
+ c_path: &str,
+ dart_path: &str,
+ dart_class_name: &str,
+ llvm_path: &[String],
+ llvm_compiler_opts: &str,
+) -> anyhow::Result<()> {
+ debug!(
+ "execute ffigen c_path={} dart_path={} llvm_path={:?}",
+ c_path, dart_path, llvm_path
+ );
+ let mut config = format!(
+ "
+ output: '{}'
+ name: '{}'
+ description: 'generated by flutter_rust_bridge'
+ headers:
+ entry-points:
+ - '{}'
+ include-directives:
+ - '{}'
+ comments: false
+ preamble: |
+ // ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names
+ ",
+ dart_path, dart_class_name, c_path, c_path,
+ );
+ if !llvm_path.is_empty() {
+ write!(
+ &mut config,
+ "
+ llvm-path:\n"
+ )?;
+ for path in llvm_path {
+ writeln!(&mut config, " - '{}'", path)?;
+ }
+ }
+
+ if !llvm_compiler_opts.is_empty() {
+ config = format!(
+ "{}
+ compiler-opts:
+ - '{}'",
+ config, llvm_compiler_opts
+ );
+ }
+
+ debug!("ffigen config: {}", config);
+
+ let mut config_file = tempfile::NamedTempFile::new()?;
+ std::io::Write::write_all(&mut config_file, config.as_bytes())?;
+ debug!("ffigen config_file: {:?}", config_file);
+
+ // NOTE please install ffigen globally first: `dart pub global activate ffigen`
+ let res = call_shell(&format!(
+ "dart pub global run ffigen --config \"{}\"",
+ config_file.path().to_string_lossy()
+ ));
+ if !res.status.success() {
+ let err = String::from_utf8_lossy(&res.stderr);
+ let out = String::from_utf8_lossy(&res.stdout);
+ let pat = "Couldn't find dynamic library in default locations.";
+ if err.contains(pat) || out.contains(pat) {
+ return Err(Error::FfigenLlvm.into());
+ }
+ return Err(
+ Error::string(format!("ffigen failed:\nstderr: {}\nstdout: {}", err, out)).into(),
+ );
+ }
+ Ok(())
+}
+
+pub fn format_rust(path: &str) -> Result {
+ debug!("execute format_rust path={}", path);
+ let res = execute_command("rustfmt", &[path], None);
+ if !res.status.success() {
+ return Err(Error::Rustfmt(
+ String::from_utf8_lossy(&res.stderr).to_string(),
+ ));
+ }
+ Ok(())
+}
+
+pub fn format_dart(path: &str, line_length: i32) -> Result {
+ debug!(
+ "execute format_dart path={} line_length={}",
+ path, line_length
+ );
+ let res = call_shell(&format!(
+ "dart format {} --line-length {}",
+ path, line_length
+ ));
+ if !res.status.success() {
+ return Err(Error::Dartfmt(
+ String::from_utf8_lossy(&res.stderr).to_string(),
+ ));
+ }
+ Ok(())
+}
+
+pub fn build_runner(dart_root: &str) -> Result {
+ info!("Running build_runner at {}", dart_root);
+ let out = if cfg!(windows) {
+ call_shell(&format!(
+ "cd \"{}\"; flutter pub run build_runner build --delete-conflicting-outputs",
+ dart_root
+ ))
+ } else {
+ call_shell(&format!(
+ "cd \"{}\" && flutter pub run build_runner build --delete-conflicting-outputs",
+ dart_root
+ ))
+ };
+ if !out.status.success() {
+ return Err(Error::StringError(format!(
+ "Failed to run build_runner for {}: {}",
+ dart_root,
+ String::from_utf8_lossy(&out.stdout)
+ )));
+ }
+ Ok(())
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/config.rs b/libs/flutter_rust_bridge_codegen/src/config.rs
new file mode 100644
index 000000000..de77cd1b1
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/config.rs
@@ -0,0 +1,292 @@
+use std::env;
+use std::ffi::OsString;
+use std::fs;
+use std::path::Path;
+use std::path::PathBuf;
+use std::str::FromStr;
+
+use anyhow::{anyhow, Result};
+use convert_case::{Case, Casing};
+use serde::Deserialize;
+use structopt::clap::AppSettings;
+use structopt::StructOpt;
+use toml::Value;
+
+#[derive(StructOpt, Debug, PartialEq, Deserialize, Default)]
+#[structopt(setting(AppSettings::DeriveDisplayOrder))]
+pub struct RawOpts {
+ /// Path of input Rust code
+ #[structopt(short, long)]
+ pub rust_input: String,
+ /// Path of output generated Dart code
+ #[structopt(short, long)]
+ pub dart_output: String,
+ /// If provided, generated Dart declaration code to this separate file
+ #[structopt(long)]
+ pub dart_decl_output: Option,
+
+ /// Path of output generated C header
+ #[structopt(short, long)]
+ pub c_output: Option>,
+ /// Crate directory for your Rust project
+ #[structopt(long)]
+ pub rust_crate_dir: Option,
+ /// Path of output generated Rust code
+ #[structopt(long)]
+ pub rust_output: Option,
+ /// Generated class name
+ #[structopt(long)]
+ pub class_name: Option,
+ /// Line length for dart formatting
+ #[structopt(long)]
+ pub dart_format_line_length: Option,
+ /// Skip automatically adding `mod bridge_generated;` to `lib.rs`
+ #[structopt(long)]
+ pub skip_add_mod_to_lib: bool,
+ /// Path to the installed LLVM
+ #[structopt(long)]
+ pub llvm_path: Option>,
+ /// LLVM compiler opts
+ #[structopt(long)]
+ pub llvm_compiler_opts: Option,
+ /// Path to root of Dart project, otherwise inferred from --dart-output
+ #[structopt(long)]
+ pub dart_root: Option,
+ /// Skip running build_runner even when codegen-capable code is detected
+ #[structopt(long)]
+ pub no_build_runner: bool,
+ /// Show debug messages.
+ #[structopt(short, long)]
+ pub verbose: bool,
+}
+
+#[derive(Debug)]
+pub struct Opts {
+ pub rust_input_path: String,
+ pub dart_output_path: String,
+ pub dart_decl_output_path: Option,
+ pub c_output_path: Vec,
+ pub rust_crate_dir: String,
+ pub rust_output_path: String,
+ pub class_name: String,
+ pub dart_format_line_length: i32,
+ pub skip_add_mod_to_lib: bool,
+ pub llvm_path: Vec,
+ pub llvm_compiler_opts: String,
+ pub manifest_path: String,
+ pub dart_root: Option,
+ pub build_runner: bool,
+}
+
+pub fn parse(raw: RawOpts) -> Opts {
+ let rust_input_path = canon_path(&raw.rust_input);
+
+ let rust_crate_dir = canon_path(&raw.rust_crate_dir.unwrap_or_else(|| {
+ fallback_rust_crate_dir(&rust_input_path)
+ .unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("rust_crate_dir")))
+ }));
+ let manifest_path = {
+ let mut path = std::path::PathBuf::from_str(&rust_crate_dir).unwrap();
+ path.push("Cargo.toml");
+ path_to_string(path).unwrap()
+ };
+ let rust_output_path = canon_path(&raw.rust_output.unwrap_or_else(|| {
+ fallback_rust_output_path(&rust_input_path)
+ .unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("rust_output")))
+ }));
+ let class_name = raw.class_name.unwrap_or_else(|| {
+ fallback_class_name(&*rust_crate_dir)
+ .unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("class_name")))
+ });
+ let c_output_path = raw
+ .c_output
+ .map(|outputs| {
+ outputs
+ .iter()
+ .map(|output| canon_path(output))
+ .collect::>()
+ })
+ .unwrap_or_else(|| {
+ vec![fallback_c_output_path()
+ .unwrap_or_else(|_| panic!("{}", format_fail_to_guess_error("c_output")))]
+ });
+
+ let dart_root = {
+ let dart_output = &raw.dart_output;
+ raw.dart_root
+ .as_deref()
+ .map(canon_path)
+ .or_else(|| fallback_dart_root(dart_output).ok())
+ };
+
+ Opts {
+ rust_input_path,
+ dart_output_path: canon_path(&raw.dart_output),
+ dart_decl_output_path: raw
+ .dart_decl_output
+ .as_ref()
+ .map(|s| canon_path(s.as_str())),
+ c_output_path,
+ rust_crate_dir,
+ rust_output_path,
+ class_name,
+ dart_format_line_length: raw.dart_format_line_length.unwrap_or(80),
+ skip_add_mod_to_lib: raw.skip_add_mod_to_lib,
+ llvm_path: raw.llvm_path.unwrap_or_else(|| {
+ vec![
+ "/opt/homebrew/opt/llvm".to_owned(), // Homebrew root
+ "/usr/local/opt/llvm".to_owned(), // Homebrew x86-64 root
+ // Possible Linux LLVM roots
+ "/usr/lib/llvm-9".to_owned(),
+ "/usr/lib/llvm-10".to_owned(),
+ "/usr/lib/llvm-11".to_owned(),
+ "/usr/lib/llvm-12".to_owned(),
+ "/usr/lib/llvm-13".to_owned(),
+ "/usr/lib/llvm-14".to_owned(),
+ "/usr/lib/".to_owned(),
+ "/usr/lib64/".to_owned(),
+ "C:/Program Files/llvm".to_owned(), // Default on Windows
+ "C:/Program Files/LLVM".to_owned(),
+ "C:/msys64/mingw64".to_owned(), // https://packages.msys2.org/package/mingw-w64-x86_64-clang
+ ]
+ }),
+ llvm_compiler_opts: raw.llvm_compiler_opts.unwrap_or_else(|| "".to_string()),
+ manifest_path,
+ dart_root,
+ build_runner: !raw.no_build_runner,
+ }
+}
+
+fn format_fail_to_guess_error(name: &str) -> String {
+ format!(
+ "fail to guess {}, please specify it manually in command line arguments",
+ name
+ )
+}
+
+fn fallback_rust_crate_dir(rust_input_path: &str) -> Result {
+ let mut dir_curr = Path::new(rust_input_path)
+ .parent()
+ .ok_or_else(|| anyhow!(""))?;
+
+ loop {
+ let path_cargo_toml = dir_curr.join("Cargo.toml");
+
+ if path_cargo_toml.exists() {
+ return Ok(dir_curr
+ .as_os_str()
+ .to_str()
+ .ok_or_else(|| anyhow!(""))?
+ .to_string());
+ }
+
+ if let Some(next_parent) = dir_curr.parent() {
+ dir_curr = next_parent;
+ } else {
+ break;
+ }
+ }
+ Err(anyhow!(
+ "look at parent directories but none contains Cargo.toml"
+ ))
+}
+
+fn fallback_c_output_path() -> Result {
+ let named_temp_file = Box::leak(Box::new(tempfile::Builder::new().suffix(".h").tempfile()?));
+ Ok(named_temp_file
+ .path()
+ .to_str()
+ .ok_or_else(|| anyhow!(""))?
+ .to_string())
+}
+
+fn fallback_rust_output_path(rust_input_path: &str) -> Result {
+ Ok(Path::new(rust_input_path)
+ .parent()
+ .ok_or_else(|| anyhow!(""))?
+ .join("bridge_generated.rs")
+ .to_str()
+ .ok_or_else(|| anyhow!(""))?
+ .to_string())
+}
+
+fn fallback_dart_root(dart_output_path: &str) -> Result {
+ let mut res = canon_pathbuf(dart_output_path);
+ while res.pop() {
+ if res.join("pubspec.yaml").is_file() {
+ return res
+ .to_str()
+ .map(ToString::to_string)
+ .ok_or_else(|| anyhow!("Non-utf8 path"));
+ }
+ }
+ Err(anyhow!(
+ "Root of Dart library could not be inferred from Dart output"
+ ))
+}
+
+fn fallback_class_name(rust_crate_dir: &str) -> Result {
+ let cargo_toml_path = Path::new(rust_crate_dir).join("Cargo.toml");
+ let cargo_toml_content = fs::read_to_string(cargo_toml_path)?;
+
+ let cargo_toml_value = cargo_toml_content.parse::()?;
+ let package_name = cargo_toml_value
+ .get("package")
+ .ok_or_else(|| anyhow!("no `package` in Cargo.toml"))?
+ .get("name")
+ .ok_or_else(|| anyhow!("no `name` in Cargo.toml"))?
+ .as_str()
+ .ok_or_else(|| anyhow!(""))?;
+
+ Ok(package_name.to_case(Case::Pascal))
+}
+
+fn canon_path(sub_path: &str) -> String {
+ let path = canon_pathbuf(sub_path);
+ path_to_string(path).unwrap_or_else(|_| panic!("fail to parse path: {}", sub_path))
+}
+
+fn canon_pathbuf(sub_path: &str) -> PathBuf {
+ let mut path =
+ env::current_dir().unwrap_or_else(|_| panic!("fail to parse path: {}", sub_path));
+ path.push(sub_path);
+ path
+}
+
+fn path_to_string(path: PathBuf) -> Result {
+ path.into_os_string().into_string()
+}
+
+impl Opts {
+ pub fn dart_api_class_name(&self) -> String {
+ self.class_name.clone()
+ }
+
+ pub fn dart_api_impl_class_name(&self) -> String {
+ format!("{}Impl", self.class_name)
+ }
+
+ pub fn dart_wire_class_name(&self) -> String {
+ format!("{}Wire", self.class_name)
+ }
+
+ /// Returns None if the path terminates in "..", or not utf8.
+ pub fn dart_output_path_name(&self) -> Option<&str> {
+ let name = Path::new(&self.dart_output_path);
+ let root = name.file_name()?.to_str()?;
+ if let Some((name, _)) = root.rsplit_once('.') {
+ Some(name)
+ } else {
+ Some(root)
+ }
+ }
+
+ pub fn dart_output_freezed_path(&self) -> Option {
+ Some(
+ Path::new(&self.dart_output_path)
+ .with_extension("freezed.dart")
+ .to_str()?
+ .to_owned(),
+ )
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/error.rs b/libs/flutter_rust_bridge_codegen/src/error.rs
new file mode 100644
index 000000000..9a8607d37
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/error.rs
@@ -0,0 +1,32 @@
+use thiserror::Error;
+
+pub type Result = std::result::Result<(), Error>;
+
+#[derive(Error, Debug)]
+pub enum Error {
+ #[error("rustfmt failed: {0}")]
+ Rustfmt(String),
+ #[error("dart fmt failed: {0}")]
+ Dartfmt(String),
+ #[error(
+ "ffigen could not find LLVM.
+ Please supply --llvm-path to flutter_rust_bridge_codegen, e.g.:
+
+ flutter_rust_bridge_codegen .. --llvm-path "
+ )]
+ FfigenLlvm,
+ #[error("{0} is not a command, or not executable.")]
+ MissingExe(String),
+ #[error("{0}")]
+ StringError(String),
+}
+
+impl Error {
+ pub fn str(msg: &str) -> Self {
+ Self::StringError(msg.to_owned())
+ }
+
+ pub fn string(msg: String) -> Self {
+ Self::StringError(msg)
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/c/mod.rs b/libs/flutter_rust_bridge_codegen/src/generator/c/mod.rs
new file mode 100644
index 000000000..2a2410dbc
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/c/mod.rs
@@ -0,0 +1,14 @@
+pub fn generate_dummy(func_names: &[String]) -> String {
+ format!(
+ r#"static int64_t dummy_method_to_enforce_bundling(void) {{
+ int64_t dummy_var = 0;
+{}
+ return dummy_var;
+}}"#,
+ func_names
+ .iter()
+ .map(|func_name| { format!(" dummy_var ^= ((int64_t) (void*) {});", func_name) })
+ .collect::>()
+ .join("\n"),
+ )
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/mod.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/mod.rs
new file mode 100644
index 000000000..afe35527f
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/mod.rs
@@ -0,0 +1,393 @@
+mod ty;
+mod ty_boxed;
+mod ty_delegate;
+mod ty_enum;
+mod ty_general_list;
+mod ty_optional;
+mod ty_primitive;
+mod ty_primitive_list;
+mod ty_struct;
+
+use std::collections::HashSet;
+
+pub use ty::*;
+pub use ty_boxed::*;
+pub use ty_delegate::*;
+pub use ty_enum::*;
+pub use ty_general_list::*;
+pub use ty_optional::*;
+pub use ty_primitive::*;
+pub use ty_primitive_list::*;
+pub use ty_struct::*;
+
+use convert_case::{Case, Casing};
+use log::debug;
+
+use crate::ir::IrType::*;
+use crate::ir::*;
+use crate::others::*;
+
+pub struct Output {
+ pub file_prelude: DartBasicCode,
+ pub decl_code: DartBasicCode,
+ pub impl_code: DartBasicCode,
+}
+
+pub fn generate(
+ ir_file: &IrFile,
+ dart_api_class_name: &str,
+ dart_api_impl_class_name: &str,
+ dart_wire_class_name: &str,
+ dart_output_file_root: &str,
+) -> (Output, bool) {
+ let distinct_types = ir_file.distinct_types(true, true);
+ let distinct_input_types = ir_file.distinct_types(true, false);
+ let distinct_output_types = ir_file.distinct_types(false, true);
+ debug!("distinct_input_types={:?}", distinct_input_types);
+ debug!("distinct_output_types={:?}", distinct_output_types);
+
+ let dart_func_signatures_and_implementations = ir_file
+ .funcs
+ .iter()
+ .map(generate_api_func)
+ .collect::>();
+ let dart_structs = distinct_types
+ .iter()
+ .map(|ty| TypeDartGenerator::new(ty.clone(), ir_file).structs())
+ .collect::>();
+ let dart_api2wire_funcs = distinct_input_types
+ .iter()
+ .map(|ty| generate_api2wire_func(ty, ir_file))
+ .collect::>();
+ let dart_api_fill_to_wire_funcs = distinct_input_types
+ .iter()
+ .map(|ty| generate_api_fill_to_wire_func(ty, ir_file))
+ .collect::>();
+ let dart_wire2api_funcs = distinct_output_types
+ .iter()
+ .map(|ty| generate_wire2api_func(ty, ir_file))
+ .collect::>();
+
+ let needs_freezed = distinct_types.iter().any(|ty| match ty {
+ EnumRef(e) if e.is_struct => true,
+ StructRef(s) if s.freezed => true,
+ _ => false,
+ });
+ let freezed_header = if needs_freezed {
+ DartBasicCode {
+ import: "import 'package:freezed_annotation/freezed_annotation.dart';".to_string(),
+ part: format!("part '{}.freezed.dart';", dart_output_file_root),
+ body: "".to_string(),
+ }
+ } else {
+ DartBasicCode::default()
+ };
+
+ let imports = ir_file
+ .struct_pool
+ .values()
+ .flat_map(|s| s.dart_metadata.iter().flat_map(|it| &it.library))
+ .collect::>();
+
+ let import_header = if !imports.is_empty() {
+ DartBasicCode {
+ import: imports
+ .iter()
+ .map(|it| match &it.alias {
+ Some(alias) => format!("import '{}' as {};", it.uri, alias),
+ _ => format!("import '{}';", it.uri),
+ })
+ .collect::>()
+ .join("\n"),
+ part: "".to_string(),
+ body: "".to_string(),
+ }
+ } else {
+ DartBasicCode::default()
+ };
+
+ let common_header = DartBasicCode {
+ import: "import 'dart:convert';
+ import 'dart:typed_data';"
+ .to_string(),
+ part: "".to_string(),
+ body: "".to_string(),
+ };
+
+ let decl_body = format!(
+ "abstract class {} {{
+ {}
+ }}
+
+ {}
+ ",
+ dart_api_class_name,
+ dart_func_signatures_and_implementations
+ .iter()
+ .map(|(sig, _, comm)| format!("{}{}", comm, sig))
+ .collect::>()
+ .join("\n\n"),
+ dart_structs.join("\n\n"),
+ );
+
+ let impl_body = format!(
+ "class {dart_api_impl_class_name} extends FlutterRustBridgeBase<{dart_wire_class_name}> implements {dart_api_class_name} {{
+ factory {dart_api_impl_class_name}(ffi.DynamicLibrary dylib) => {dart_api_impl_class_name}.raw({dart_wire_class_name}(dylib));
+
+ {dart_api_impl_class_name}.raw({dart_wire_class_name} inner) : super(inner);
+
+ {}
+
+ // Section: api2wire
+ {}
+
+ // Section: api_fill_to_wire
+ {}
+ }}
+
+ // Section: wire2api
+ {}
+ ",
+ dart_func_signatures_and_implementations
+ .iter()
+ .map(|(_, imp, _)| imp.clone())
+ .collect::>()
+ .join("\n\n"),
+ dart_api2wire_funcs.join("\n\n"),
+ dart_api_fill_to_wire_funcs.join("\n\n"),
+ dart_wire2api_funcs.join("\n\n"),
+ dart_api_impl_class_name = dart_api_impl_class_name,
+ dart_wire_class_name = dart_wire_class_name,
+ dart_api_class_name = dart_api_class_name,
+ );
+
+ let decl_code = &common_header
+ + &freezed_header
+ + &import_header
+ + &DartBasicCode {
+ import: "".to_string(),
+ part: "".to_string(),
+ body: decl_body,
+ };
+
+ let impl_code = &common_header
+ + &DartBasicCode {
+ import: "import 'package:flutter_rust_bridge/flutter_rust_bridge.dart';".to_string(),
+ part: "".to_string(),
+ body: impl_body,
+ };
+
+ let file_prelude = DartBasicCode {
+ import: format!("{}
+
+ // ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, prefer_single_quotes, prefer_const_constructors
+ ",
+ CODE_HEADER
+ ),
+ part: "".to_string(),
+ body: "".to_string(),
+ };
+
+ (
+ Output {
+ file_prelude,
+ decl_code,
+ impl_code,
+ },
+ needs_freezed,
+ )
+}
+
+fn generate_api_func(func: &IrFunc) -> (String, String, String) {
+ let raw_func_param_list = func
+ .inputs
+ .iter()
+ .map(|input| {
+ format!(
+ "{}{} {}",
+ input.ty.dart_required_modifier(),
+ input.ty.dart_api_type(),
+ input.name.dart_style()
+ )
+ })
+ .collect::>();
+
+ let full_func_param_list = [raw_func_param_list, vec!["dynamic hint".to_string()]].concat();
+
+ let wire_param_list = [
+ if func.mode.has_port_argument() {
+ vec!["port_".to_string()]
+ } else {
+ vec![]
+ },
+ func.inputs
+ .iter()
+ .map(|input| {
+ // edge case: ffigen performs its own bool-to-int conversions
+ if let IrType::Primitive(IrTypePrimitive::Bool) = input.ty {
+ input.name.dart_style()
+ } else {
+ format!(
+ "_api2wire_{}({})",
+ &input.ty.safe_ident(),
+ &input.name.dart_style()
+ )
+ }
+ })
+ .collect::>(),
+ ]
+ .concat();
+
+ let partial = format!(
+ "{} {}({{ {} }})",
+ func.mode.dart_return_type(&func.output.dart_api_type()),
+ func.name.to_case(Case::Camel),
+ full_func_param_list.join(","),
+ );
+
+ let execute_func_name = match func.mode {
+ IrFuncMode::Normal => "executeNormal",
+ IrFuncMode::Sync => "executeSync",
+ IrFuncMode::Stream => "executeStream",
+ };
+
+ let signature = format!("{};", partial);
+
+ let comments = dart_comments(&func.comments);
+
+ let task_common_args = format!(
+ "
+ constMeta: const FlutterRustBridgeTaskConstMeta(
+ debugName: \"{}\",
+ argNames: [{}],
+ ),
+ argValues: [{}],
+ hint: hint,
+ ",
+ func.name,
+ func.inputs
+ .iter()
+ .map(|input| format!("\"{}\"", input.name.dart_style()))
+ .collect::>()
+ .join(", "),
+ func.inputs
+ .iter()
+ .map(|input| input.name.dart_style())
+ .collect::>()
+ .join(", "),
+ );
+
+ let implementation = match func.mode {
+ IrFuncMode::Sync => format!(
+ "{} => {}(FlutterRustBridgeSyncTask(
+ callFfi: () => inner.{}({}),
+ {}
+ ));",
+ partial,
+ execute_func_name,
+ func.wire_func_name(),
+ wire_param_list.join(", "),
+ task_common_args,
+ ),
+ _ => format!(
+ "{} => {}(FlutterRustBridgeTask(
+ callFfi: (port_) => inner.{}({}),
+ parseSuccessData: _wire2api_{},
+ {}
+ ));",
+ partial,
+ execute_func_name,
+ func.wire_func_name(),
+ wire_param_list.join(", "),
+ func.output.safe_ident(),
+ task_common_args,
+ ),
+ };
+
+ (signature, implementation, comments)
+}
+
+fn generate_api2wire_func(ty: &IrType, ir_file: &IrFile) -> String {
+ if let Some(body) = TypeDartGenerator::new(ty.clone(), ir_file).api2wire_body() {
+ format!(
+ "{} _api2wire_{}({} raw) {{
+ {}
+ }}
+ ",
+ ty.dart_wire_type(),
+ ty.safe_ident(),
+ ty.dart_api_type(),
+ body,
+ )
+ } else {
+ "".to_string()
+ }
+}
+
+fn generate_api_fill_to_wire_func(ty: &IrType, ir_file: &IrFile) -> String {
+ if let Some(body) = TypeDartGenerator::new(ty.clone(), ir_file).api_fill_to_wire_body() {
+ let target_wire_type = match ty {
+ Optional(inner) => &inner.inner,
+ it => it,
+ };
+
+ format!(
+ "void _api_fill_to_wire_{}({} apiObj, {} wireObj) {{
+ {}
+ }}",
+ ty.safe_ident(),
+ ty.dart_api_type(),
+ target_wire_type.dart_wire_type(),
+ body,
+ )
+ } else {
+ "".to_string()
+ }
+}
+
+fn generate_wire2api_func(ty: &IrType, ir_file: &IrFile) -> String {
+ let body = TypeDartGenerator::new(ty.clone(), ir_file).wire2api_body();
+
+ format!(
+ "{} _wire2api_{}(dynamic raw) {{
+ {}
+ }}
+ ",
+ ty.dart_api_type(),
+ ty.safe_ident(),
+ body,
+ )
+}
+
+fn gen_wire2api_simple_type_cast(s: &str) -> String {
+ format!("return raw as {};", s)
+}
+
+/// A trailing newline is included if comments is not empty.
+fn dart_comments(comments: &[IrComment]) -> String {
+ let mut comments = comments
+ .iter()
+ .map(IrComment::comment)
+ .collect::>()
+ .join("\n");
+ if !comments.is_empty() {
+ comments.push('\n');
+ }
+ comments
+}
+fn dart_metadata(metadata: &[IrDartAnnotation]) -> String {
+ let mut metadata = metadata
+ .iter()
+ .map(|it| match &it.library {
+ Some(IrDartImport {
+ alias: Some(alias), ..
+ }) => format!("@{}.{}", alias, it.content),
+ _ => format!("@{}", it.content),
+ })
+ .collect::>()
+ .join("\n");
+ if !metadata.is_empty() {
+ metadata.push('\n');
+ }
+ metadata
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty.rs
new file mode 100644
index 000000000..dd8004ed9
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty.rs
@@ -0,0 +1,64 @@
+use crate::generator::dart::*;
+use enum_dispatch::enum_dispatch;
+
+#[enum_dispatch]
+pub trait TypeDartGeneratorTrait {
+ fn api2wire_body(&self) -> Option;
+
+ fn api_fill_to_wire_body(&self) -> Option {
+ None
+ }
+
+ fn wire2api_body(&self) -> String {
+ "".to_string()
+ }
+
+ fn structs(&self) -> String {
+ "".to_string()
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct TypeGeneratorContext<'a> {
+ pub ir_file: &'a IrFile,
+}
+
+#[macro_export]
+macro_rules! type_dart_generator_struct {
+ ($cls:ident, $ir_cls:ty) => {
+ #[derive(Debug, Clone)]
+ pub struct $cls<'a> {
+ pub ir: $ir_cls,
+ pub context: TypeGeneratorContext<'a>,
+ }
+ };
+}
+
+#[enum_dispatch(TypeDartGeneratorTrait)]
+#[derive(Debug, Clone)]
+pub enum TypeDartGenerator<'a> {
+ Primitive(TypePrimitiveGenerator<'a>),
+ Delegate(TypeDelegateGenerator<'a>),
+ PrimitiveList(TypePrimitiveListGenerator<'a>),
+ Optional(TypeOptionalGenerator<'a>),
+ GeneralList(TypeGeneralListGenerator<'a>),
+ StructRef(TypeStructRefGenerator<'a>),
+ Boxed(TypeBoxedGenerator<'a>),
+ EnumRef(TypeEnumRefGenerator<'a>),
+}
+
+impl<'a> TypeDartGenerator<'a> {
+ pub fn new(ty: IrType, ir_file: &'a IrFile) -> Self {
+ let context = TypeGeneratorContext { ir_file };
+ match ty {
+ Primitive(ir) => TypePrimitiveGenerator { ir, context }.into(),
+ Delegate(ir) => TypeDelegateGenerator { ir, context }.into(),
+ PrimitiveList(ir) => TypePrimitiveListGenerator { ir, context }.into(),
+ Optional(ir) => TypeOptionalGenerator { ir, context }.into(),
+ GeneralList(ir) => TypeGeneralListGenerator { ir, context }.into(),
+ StructRef(ir) => TypeStructRefGenerator { ir, context }.into(),
+ Boxed(ir) => TypeBoxedGenerator { ir, context }.into(),
+ EnumRef(ir) => TypeEnumRefGenerator { ir, context }.into(),
+ }
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_boxed.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_boxed.rs
new file mode 100644
index 000000000..84c2b3675
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_boxed.rs
@@ -0,0 +1,45 @@
+use crate::generator::dart::gen_wire2api_simple_type_cast;
+use crate::generator::dart::ty::*;
+use crate::ir::IrType::{EnumRef, Primitive, StructRef};
+use crate::ir::*;
+use crate::type_dart_generator_struct;
+
+type_dart_generator_struct!(TypeBoxedGenerator, IrTypeBoxed);
+
+impl TypeDartGeneratorTrait for TypeBoxedGenerator<'_> {
+ fn api2wire_body(&self) -> Option {
+ Some(match &*self.ir.inner {
+ Primitive(_) => {
+ format!("return inner.new_{}(raw);", self.ir.safe_ident())
+ }
+ inner => {
+ format!(
+ "final ptr = inner.new_{}();
+ _api_fill_to_wire_{}(raw, ptr.ref);
+ return ptr;",
+ self.ir.safe_ident(),
+ inner.safe_ident(),
+ )
+ }
+ })
+ }
+
+ fn api_fill_to_wire_body(&self) -> Option {
+ if !matches!(*self.ir.inner, Primitive(_)) {
+ Some(format!(
+ " _api_fill_to_wire_{}(apiObj, wireObj.ref);",
+ self.ir.inner.safe_ident()
+ ))
+ } else {
+ None
+ }
+ }
+
+ fn wire2api_body(&self) -> String {
+ match &*self.ir.inner {
+ StructRef(inner) => format!("return _wire2api_{}(raw);", inner.safe_ident()),
+ EnumRef(inner) => format!("return _wire2api_{}(raw);", inner.safe_ident()),
+ _ => gen_wire2api_simple_type_cast(&self.ir.dart_api_type()),
+ }
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_delegate.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_delegate.rs
new file mode 100644
index 000000000..b585ff3f7
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_delegate.rs
@@ -0,0 +1,42 @@
+use crate::generator::dart::gen_wire2api_simple_type_cast;
+use crate::generator::dart::ty::*;
+use crate::ir::*;
+use crate::type_dart_generator_struct;
+
+type_dart_generator_struct!(TypeDelegateGenerator, IrTypeDelegate);
+
+impl TypeDartGeneratorTrait for TypeDelegateGenerator<'_> {
+ fn api2wire_body(&self) -> Option {
+ Some(match self.ir {
+ IrTypeDelegate::String => {
+ "return _api2wire_uint_8_list(utf8.encoder.convert(raw));".to_string()
+ }
+ IrTypeDelegate::SyncReturnVecU8 => "/*unsupported*/".to_string(),
+ IrTypeDelegate::ZeroCopyBufferVecPrimitive(_) => {
+ format!(
+ "return _api2wire_{}(raw);",
+ self.ir.get_delegate().safe_ident()
+ )
+ }
+ IrTypeDelegate::StringList => "final ans = inner.new_StringList(raw.length);
+ for (var i = 0; i < raw.length; i++) {
+ ans.ref.ptr[i] = _api2wire_String(raw[i]);
+ }
+ return ans;"
+ .to_owned(),
+ })
+ }
+
+ fn wire2api_body(&self) -> String {
+ match &self.ir {
+ IrTypeDelegate::String
+ | IrTypeDelegate::SyncReturnVecU8
+ | IrTypeDelegate::ZeroCopyBufferVecPrimitive(_) => {
+ gen_wire2api_simple_type_cast(&self.ir.dart_api_type())
+ }
+ IrTypeDelegate::StringList => {
+ "return (raw as List).cast();".to_owned()
+ }
+ }
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_enum.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_enum.rs
new file mode 100644
index 000000000..fc361b4c8
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_enum.rs
@@ -0,0 +1,207 @@
+use crate::generator::dart::dart_comments;
+use crate::generator::dart::ty::*;
+use crate::ir::*;
+use crate::type_dart_generator_struct;
+
+type_dart_generator_struct!(TypeEnumRefGenerator, IrTypeEnumRef);
+
+impl TypeDartGeneratorTrait for TypeEnumRefGenerator<'_> {
+ fn api2wire_body(&self) -> Option {
+ if !self.ir.is_struct {
+ Some("return raw.index;".to_owned())
+ } else {
+ None
+ }
+ }
+
+ fn api_fill_to_wire_body(&self) -> Option {
+ if self.ir.is_struct {
+ Some(
+ self.ir
+ .get(self.context.ir_file)
+ .variants()
+ .iter()
+ .enumerate()
+ .map(|(idx, variant)| {
+ if let IrVariantKind::Value = &variant.kind {
+ format!(
+ "if (apiObj is {}) {{ wireObj.tag = {}; return; }}",
+ variant.name, idx
+ )
+ } else {
+ let r = format!("wireObj.kind.ref.{}.ref", variant.name);
+ let body: Vec<_> = match &variant.kind {
+ IrVariantKind::Struct(st) => st
+ .fields
+ .iter()
+ .map(|field| {
+ format!(
+ "{}.{} = _api2wire_{}(apiObj.{});",
+ r,
+ field.name.rust_style(),
+ field.ty.safe_ident(),
+ field.name.dart_style()
+ )
+ })
+ .collect(),
+ _ => unreachable!(),
+ };
+ format!(
+ "if (apiObj is {0}) {{
+ wireObj.tag = {1};
+ wireObj.kind = inner.inflate_{2}_{0}();
+ {3}
+ }}",
+ variant.name,
+ idx,
+ self.ir.name,
+ body.join("\n")
+ )
+ }
+ })
+ .collect::>()
+ .join("\n"),
+ )
+ } else {
+ None
+ }
+ }
+
+ fn wire2api_body(&self) -> String {
+ if self.ir.is_struct {
+ let enu = self.ir.get(self.context.ir_file);
+ let variants = enu
+ .variants()
+ .iter()
+ .enumerate()
+ .map(|(idx, variant)| {
+ let args = match &variant.kind {
+ IrVariantKind::Value => "".to_owned(),
+ IrVariantKind::Struct(st) => st
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(idx, field)| {
+ let val = format!(
+ "_wire2api_{}(raw[{}]),",
+ field.ty.safe_ident(),
+ idx + 1
+ );
+ if st.is_fields_named {
+ format!("{}: {}", field.name.dart_style(), val)
+ } else {
+ val
+ }
+ })
+ .collect::>()
+ .join(""),
+ };
+ format!("case {}: return {}({});", idx, variant.name, args)
+ })
+ .collect::>();
+ format!(
+ "switch (raw[0]) {{
+ {}
+ default: throw Exception(\"unreachable\");
+ }}",
+ variants.join("\n"),
+ )
+ } else {
+ format!("return {}.values[raw];", self.ir.name)
+ }
+ }
+
+ fn structs(&self) -> String {
+ let src = self.ir.get(self.context.ir_file);
+
+ let comments = dart_comments(&src.comments);
+ if src.is_struct() {
+ let variants = src
+ .variants()
+ .iter()
+ .map(|variant| {
+ let args = match &variant.kind {
+ IrVariantKind::Value => "".to_owned(),
+ IrVariantKind::Struct(IrStruct {
+ is_fields_named: false,
+ fields,
+ ..
+ }) => {
+ let types = fields.iter().map(|field| &field.ty).collect::>();
+ let split = optional_boundary_index(&types);
+ let types = fields
+ .iter()
+ .map(|field| {
+ format!(
+ "{}{} {},",
+ dart_comments(&field.comments),
+ field.ty.dart_api_type(),
+ field.name.dart_style()
+ )
+ })
+ .collect::>();
+ if let Some(idx) = split {
+ let before = &types[..idx];
+ let after = &types[idx..];
+ format!("{}[{}]", before.join(""), after.join(""))
+ } else {
+ types.join("")
+ }
+ }
+ IrVariantKind::Struct(st) => {
+ let fields = st
+ .fields
+ .iter()
+ .map(|field| {
+ format!(
+ "{}{}{} {},",
+ dart_comments(&field.comments),
+ field.ty.dart_required_modifier(),
+ field.ty.dart_api_type(),
+ field.name.dart_style()
+ )
+ })
+ .collect::>();
+ format!("{{ {} }}", fields.join(""))
+ }
+ };
+ format!(
+ "{}const factory {}.{}({}) = {};",
+ dart_comments(&variant.comments),
+ self.ir.name,
+ variant.name.dart_style(),
+ args,
+ variant.name.rust_style(),
+ )
+ })
+ .collect::>();
+ format!(
+ "@freezed
+ class {0} with _${0} {{
+ {1}
+ }}",
+ self.ir.name,
+ variants.join("\n")
+ )
+ } else {
+ let variants = src
+ .variants()
+ .iter()
+ .map(|variant| {
+ format!(
+ "{}{},",
+ dart_comments(&variant.comments),
+ variant.name.rust_style()
+ )
+ })
+ .collect::>()
+ .join("\n");
+ format!(
+ "{}enum {} {{
+ {}
+ }}",
+ comments, self.ir.name, variants
+ )
+ }
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_general_list.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_general_list.rs
new file mode 100644
index 000000000..000f7288f
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_general_list.rs
@@ -0,0 +1,27 @@
+use crate::generator::dart::ty::*;
+use crate::ir::*;
+use crate::type_dart_generator_struct;
+
+type_dart_generator_struct!(TypeGeneralListGenerator, IrTypeGeneralList);
+
+impl TypeDartGeneratorTrait for TypeGeneralListGenerator<'_> {
+ fn api2wire_body(&self) -> Option {
+ // NOTE the memory strategy is same as PrimitiveList, see comments there.
+ Some(format!(
+ "final ans = inner.new_{}(raw.length);
+ for (var i = 0; i < raw.length; ++i) {{
+ _api_fill_to_wire_{}(raw[i], ans.ref.ptr[i]);
+ }}
+ return ans;",
+ self.ir.safe_ident(),
+ self.ir.inner.safe_ident()
+ ))
+ }
+
+ fn wire2api_body(&self) -> String {
+ format!(
+ "return (raw as List).map(_wire2api_{}).toList();",
+ self.ir.inner.safe_ident()
+ )
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_optional.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_optional.rs
new file mode 100644
index 000000000..5b7e60d27
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_optional.rs
@@ -0,0 +1,31 @@
+use crate::generator::dart::ty::*;
+use crate::ir::*;
+use crate::type_dart_generator_struct;
+
+type_dart_generator_struct!(TypeOptionalGenerator, IrTypeOptional);
+
+impl TypeDartGeneratorTrait for TypeOptionalGenerator<'_> {
+ fn api2wire_body(&self) -> Option {
+ Some(format!(
+ "return raw == null ? ffi.nullptr : _api2wire_{}(raw);",
+ self.ir.inner.safe_ident()
+ ))
+ }
+
+ fn api_fill_to_wire_body(&self) -> Option {
+ if !self.ir.needs_initialization() || self.ir.is_list() {
+ return None;
+ }
+ Some(format!(
+ "if (apiObj != null) _api_fill_to_wire_{}(apiObj, wireObj);",
+ self.ir.inner.safe_ident()
+ ))
+ }
+
+ fn wire2api_body(&self) -> String {
+ format!(
+ "return raw == null ? null : _wire2api_{}(raw);",
+ self.ir.inner.safe_ident()
+ )
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_primitive.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_primitive.rs
new file mode 100644
index 000000000..0ed9aa686
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_primitive.rs
@@ -0,0 +1,22 @@
+use crate::generator::dart::gen_wire2api_simple_type_cast;
+use crate::generator::dart::ty::*;
+use crate::ir::*;
+use crate::type_dart_generator_struct;
+
+type_dart_generator_struct!(TypePrimitiveGenerator, IrTypePrimitive);
+
+impl TypeDartGeneratorTrait for TypePrimitiveGenerator<'_> {
+ fn api2wire_body(&self) -> Option {
+ Some(match self.ir {
+ IrTypePrimitive::Bool => "return raw ? 1 : 0;".to_owned(),
+ _ => "return raw;".to_string(),
+ })
+ }
+
+ fn wire2api_body(&self) -> String {
+ match self.ir {
+ IrTypePrimitive::Unit => "return;".to_owned(),
+ _ => gen_wire2api_simple_type_cast(&self.ir.dart_api_type()),
+ }
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_primitive_list.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_primitive_list.rs
new file mode 100644
index 000000000..d07c24d6b
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_primitive_list.rs
@@ -0,0 +1,30 @@
+use crate::generator::dart::gen_wire2api_simple_type_cast;
+use crate::generator::dart::ty::*;
+use crate::ir::*;
+use crate::type_dart_generator_struct;
+
+type_dart_generator_struct!(TypePrimitiveListGenerator, IrTypePrimitiveList);
+
+impl TypeDartGeneratorTrait for TypePrimitiveListGenerator<'_> {
+ fn api2wire_body(&self) -> Option {
+ // NOTE Dart code *only* allocates memory. It never *release* memory by itself.
+ // Instead, Rust receives that pointer and now it is in control of Rust.
+ // Therefore, *never* continue to use this pointer after you have passed the pointer
+ // to Rust.
+ // NOTE WARN: Never use the [calloc] provided by Dart FFI to allocate any memory.
+ // Instead, ask Rust to allocate some memory and return raw pointers. Otherwise,
+ // memory will be allocated in one dylib (e.g. libflutter.so), and then be released
+ // by another dylib (e.g. my_rust_code.so), especially in Android platform. It can be
+ // undefined behavior.
+ Some(format!(
+ "final ans = inner.new_{}(raw.length);
+ ans.ref.ptr.asTypedList(raw.length).setAll(0, raw);
+ return ans;",
+ self.ir.safe_ident(),
+ ))
+ }
+
+ fn wire2api_body(&self) -> String {
+ gen_wire2api_simple_type_cast(&self.ir.dart_api_type())
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_struct.rs b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_struct.rs
new file mode 100644
index 000000000..fa67bd32a
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/dart/ty_struct.rs
@@ -0,0 +1,135 @@
+use crate::generator::dart::ty::*;
+use crate::generator::dart::{dart_comments, dart_metadata};
+use crate::ir::*;
+use crate::type_dart_generator_struct;
+
+type_dart_generator_struct!(TypeStructRefGenerator, IrTypeStructRef);
+
+impl TypeDartGeneratorTrait for TypeStructRefGenerator<'_> {
+ fn api2wire_body(&self) -> Option {
+ None
+ }
+
+ fn api_fill_to_wire_body(&self) -> Option {
+ let s = self.ir.get(self.context.ir_file);
+ Some(
+ s.fields
+ .iter()
+ .map(|field| {
+ format!(
+ "wireObj.{} = _api2wire_{}(apiObj.{});",
+ field.name.rust_style(),
+ field.ty.safe_ident(),
+ field.name.dart_style()
+ )
+ })
+ .collect::>()
+ .join("\n"),
+ )
+ }
+
+ fn wire2api_body(&self) -> String {
+ let s = self.ir.get(self.context.ir_file);
+ let inner = s
+ .fields
+ .iter()
+ .enumerate()
+ .map(|(idx, field)| {
+ format!(
+ "{}: _wire2api_{}(arr[{}]),",
+ field.name.dart_style(),
+ field.ty.safe_ident(),
+ idx
+ )
+ })
+ .collect::>()
+ .join("\n");
+
+ format!(
+ "final arr = raw as List;
+ if (arr.length != {}) throw Exception('unexpected arr length: expect {} but see ${{arr.length}}');
+ return {}({});",
+ s.fields.len(),
+ s.fields.len(),
+ s.name, inner,
+ )
+ }
+
+ fn structs(&self) -> String {
+ let src = self.ir.get(self.context.ir_file);
+ let comments = dart_comments(&src.comments);
+ let metadata = dart_metadata(&src.dart_metadata);
+
+ if src.using_freezed() {
+ let constructor_params = src
+ .fields
+ .iter()
+ .map(|f| {
+ format!(
+ "{} {} {},",
+ f.ty.dart_required_modifier(),
+ f.ty.dart_api_type(),
+ f.name.dart_style()
+ )
+ })
+ .collect::>()
+ .join("");
+
+ format!(
+ "{}{}class {} with _${} {{
+ const factory {}({{{}}}) = _{};
+ }}",
+ comments,
+ metadata,
+ self.ir.name,
+ self.ir.name,
+ self.ir.name,
+ constructor_params,
+ self.ir.name
+ )
+ } else {
+ let field_declarations = src
+ .fields
+ .iter()
+ .map(|f| {
+ let comments = dart_comments(&f.comments);
+ format!(
+ "{}{} {} {};",
+ comments,
+ if f.is_final { "final" } else { "" },
+ f.ty.dart_api_type(),
+ f.name.dart_style()
+ )
+ })
+ .collect::>()
+ .join("\n");
+
+ let constructor_params = src
+ .fields
+ .iter()
+ .map(|f| {
+ format!(
+ "{}this.{},",
+ f.ty.dart_required_modifier(),
+ f.name.dart_style()
+ )
+ })
+ .collect::>()
+ .join("");
+
+ format!(
+ "{}{}class {} {{
+ {}
+
+ {}({{{}}});
+ }}",
+ comments,
+ metadata,
+ self.ir.name,
+ field_declarations,
+ self.ir.name,
+ constructor_params
+ )
+ }
+ }
+}
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/mod.rs b/libs/flutter_rust_bridge_codegen/src/generator/mod.rs
new file mode 100644
index 000000000..3891c02e3
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/mod.rs
@@ -0,0 +1,3 @@
+pub mod c;
+pub mod dart;
+pub mod rust;
diff --git a/libs/flutter_rust_bridge_codegen/src/generator/rust/mod.rs b/libs/flutter_rust_bridge_codegen/src/generator/rust/mod.rs
new file mode 100644
index 000000000..0b0d1df88
--- /dev/null
+++ b/libs/flutter_rust_bridge_codegen/src/generator/rust/mod.rs
@@ -0,0 +1,481 @@
+mod ty;
+mod ty_boxed;
+mod ty_delegate;
+mod ty_enum;
+mod ty_general_list;
+mod ty_optional;
+mod ty_primitive;
+mod ty_primitive_list;
+mod ty_struct;
+
+pub use ty::*;
+pub use ty_boxed::*;
+pub use ty_delegate::*;
+pub use ty_enum::*;
+pub use ty_general_list::*;
+pub use ty_optional::*;
+pub use ty_primitive::*;
+pub use ty_primitive_list::*;
+pub use ty_struct::*;
+
+use std::collections::HashSet;
+
+use crate::ir::IrType::*;
+use crate::ir::*;
+use crate::others::*;
+
+pub const HANDLER_NAME: &str = "FLUTTER_RUST_BRIDGE_HANDLER";
+
+pub struct Output {
+ pub code: String,
+ pub extern_func_names: Vec,
+}
+
+pub fn generate(ir_file: &IrFile, rust_wire_mod: &str) -> Output {
+ let mut generator = Generator::new();
+ let code = generator.generate(ir_file, rust_wire_mod);
+
+ Output {
+ code,
+ extern_func_names: generator.extern_func_collector.names,
+ }
+}
+
+struct Generator {
+ extern_func_collector: ExternFuncCollector,
+}
+
+impl Generator {
+ fn new() -> Self {
+ Self {
+ extern_func_collector: ExternFuncCollector::new(),
+ }
+ }
+
+ fn generate(&mut self, ir_file: &IrFile, rust_wire_mod: &str) -> String {
+ let mut lines: Vec = vec![];
+
+ let distinct_input_types = ir_file.distinct_types(true, false);
+ let distinct_output_types = ir_file.distinct_types(false, true);
+
+ lines.push(r#"#![allow(non_camel_case_types, unused, clippy::redundant_closure, clippy::useless_conversion, clippy::unit_arg, clippy::double_parens, non_snake_case)]"#.to_string());
+ lines.push(CODE_HEADER.to_string());
+
+ lines.push(String::new());
+ lines.push(format!("use crate::{}::*;", rust_wire_mod));
+ lines.push("use flutter_rust_bridge::*;".to_string());
+ lines.push(String::new());
+
+ lines.push(self.section_header_comment("imports"));
+ lines.extend(self.generate_imports(
+ ir_file,
+ rust_wire_mod,
+ &distinct_input_types,
+ &distinct_output_types,
+ ));
+ lines.push(String::new());
+
+ lines.push(self.section_header_comment("wire functions"));
+ lines.extend(
+ ir_file
+ .funcs
+ .iter()
+ .map(|f| self.generate_wire_func(f, ir_file)),
+ );
+
+ lines.push(self.section_header_comment("wire structs"));
+ lines.extend(
+ distinct_input_types
+ .iter()
+ .map(|ty| self.generate_wire_struct(ty, ir_file)),
+ );
+ lines.extend(
+ distinct_input_types
+ .iter()
+ .map(|ty| TypeRustGenerator::new(ty.clone(), ir_file).structs()),
+ );
+
+ lines.push(self.section_header_comment("wrapper structs"));
+ lines.extend(
+ distinct_output_types
+ .iter()
+ .filter_map(|ty| self.generate_wrapper_struct(ty, ir_file)),
+ );
+ lines.push(self.section_header_comment("static checks"));
+ let static_checks: Vec<_> = distinct_output_types
+ .iter()
+ .filter_map(|ty| self.generate_static_checks(ty, ir_file))
+ .collect();
+ if !static_checks.is_empty() {
+ lines.push("const _: fn() = || {".to_owned());
+ lines.extend(static_checks);
+ lines.push("};".to_owned());
+ }
+
+ lines.push(self.section_header_comment("allocate functions"));
+ lines.extend(
+ distinct_input_types
+ .iter()
+ .map(|f| self.generate_allocate_funcs(f, ir_file)),
+ );
+
+ lines.push(self.section_header_comment("impl Wire2Api"));
+ lines.push(self.generate_wire2api_misc().to_string());
+ lines.extend(
+ distinct_input_types
+ .iter()
+ .map(|ty| self.generate_wire2api_func(ty, ir_file)),
+ );
+
+ lines.push(self.section_header_comment("impl NewWithNullPtr"));
+ lines.push(self.generate_new_with_nullptr_misc().to_string());
+ lines.extend(
+ distinct_input_types
+ .iter()
+ .map(|ty| self.generate_new_with_nullptr_func(ty, ir_file)),
+ );
+
+ lines.push(self.section_header_comment("impl IntoDart"));
+ lines.extend(
+ distinct_output_types
+ .iter()
+ .map(|ty| self.generate_impl_intodart(ty, ir_file)),
+ );
+
+ lines.push(self.section_header_comment("executor"));
+ lines.push(self.generate_executor(ir_file));
+
+ lines.push(self.section_header_comment("sync execution mode utility"));
+ lines.push(self.generate_sync_execution_mode_utility());
+
+ lines.join("\n")
+ }
+
+ fn section_header_comment(&self, section_name: &str) -> String {
+ format!("// Section: {}\n", section_name)
+ }
+
+ fn generate_imports(
+ &self,
+ ir_file: &IrFile,
+ rust_wire_mod: &str,
+ distinct_input_types: &[IrType],
+ distinct_output_types: &[IrType],
+ ) -> impl Iterator- {
+ let input_type_imports = distinct_input_types
+ .iter()
+ .map(|api_type| generate_import(api_type, ir_file));
+ let output_type_imports = distinct_output_types
+ .iter()
+ .map(|api_type| generate_import(api_type, ir_file));
+
+ input_type_imports
+ .chain(output_type_imports)
+ // Filter out `None` and unwrap
+ .flatten()
+ // Don't include imports from the API file
+ .filter(|import| !import.starts_with(&format!("use crate::{}::", rust_wire_mod)))
+ // de-duplicate
+ .collect::>()
+ .into_iter()
+ }
+
+ fn generate_executor(&mut self, ir_file: &IrFile) -> String {
+ if ir_file.has_executor {
+ "/* nothing since executor detected */".to_string()
+ } else {
+ format!(
+ "support::lazy_static! {{
+ pub static ref {}: support::DefaultHandler = Default::default();
+ }}
+ ",
+ HANDLER_NAME
+ )
+ }
+ }
+
+ fn generate_sync_execution_mode_utility(&mut self) -> String {
+ self.extern_func_collector.generate(
+ "free_WireSyncReturnStruct",
+ &["val: support::WireSyncReturnStruct"],
+ None,
+ "unsafe { let _ = support::vec_from_leak_ptr(val.ptr, val.len); }",
+ )
+ }
+
+ fn generate_wire_func(&mut self, func: &IrFunc, ir_file: &IrFile) -> String {
+ let params = [
+ if func.mode.has_port_argument() {
+ vec!["port_: i64".to_string()]
+ } else {
+ vec![]
+ },
+ func.inputs
+ .iter()
+ .map(|field| {
+ format!(
+ "{}: {}{}",
+ field.name.rust_style(),
+ field.ty.rust_wire_modifier(),
+ field.ty.rust_wire_type()
+ )
+ })
+ .collect::>(),
+ ]
+ .concat();
+
+ let inner_func_params = [
+ match func.mode {
+ IrFuncMode::Normal | IrFuncMode::Sync => vec![],
+ IrFuncMode::Stream => vec!["task_callback.stream_sink()".to_string()],
+ },
+ func.inputs
+ .iter()
+ .map(|field| format!("api_{}", field.name.rust_style()))
+ .collect::>(),
+ ]
+ .concat();
+
+ let wrap_info_obj = format!(
+ "WrapInfo{{ debug_name: \"{}\", port: {}, mode: FfiCallMode::{} }}",
+ func.name,
+ if func.mode.has_port_argument() {
+ "Some(port_)"
+ } else {
+ "None"
+ },
+ func.mode.ffi_call_mode(),
+ );
+
+ let code_wire2api = func
+ .inputs
+ .iter()
+ .map(|field| {
+ format!(
+ "let api_{} = {}.wire2api();",
+ field.name.rust_style(),
+ field.name.rust_style()
+ )
+ })
+ .collect::>()
+ .join("");
+
+ let code_call_inner_func = TypeRustGenerator::new(func.output.clone(), ir_file)
+ .wrap_obj(format!("{}({})", func.name, inner_func_params.join(", ")));
+ let code_call_inner_func_result = if func.fallible {
+ code_call_inner_func
+ } else {
+ format!("Ok({})", code_call_inner_func)
+ };
+
+ let (handler_func_name, return_type, code_closure) = match func.mode {
+ IrFuncMode::Sync => (
+ "wrap_sync",
+ Some("support::WireSyncReturnStruct"),
+ format!(
+ "{}
+ {}",
+ code_wire2api, code_call_inner_func_result,
+ ),
+ ),
+ IrFuncMode::Normal | IrFuncMode::Stream => (
+ "wrap",
+ None,
+ format!(
+ "{}
+ move |task_callback| {}
+ ",
+ code_wire2api, code_call_inner_func_result,
+ ),
+ ),
+ };
+
+ self.extern_func_collector.generate(
+ &func.wire_func_name(),
+ ¶ms
+ .iter()
+ .map(std::ops::Deref::deref)
+ .collect::>(),
+ return_type,
+ &format!(
+ "
+ {}.{}({}, move || {{
+ {}
+ }})
+ ",
+ HANDLER_NAME, handler_func_name, wrap_info_obj, code_closure,
+ ),
+ )
+ }
+
+ fn generate_wire_struct(&mut self, ty: &IrType, ir_file: &IrFile) -> String {
+ // println!("generate_wire_struct: {:?}", ty);
+ if let Some(fields) = TypeRustGenerator::new(ty.clone(), ir_file).wire_struct_fields() {
+ format!(
+ r###"
+ #[repr(C)]
+ #[derive(Clone)]
+ pub struct {} {{
+ {}
+ }}
+ "###,
+ ty.rust_wire_type(),
+ fields.join(",\n"),
+ )
+ } else {
+ "".to_string()
+ }
+ }
+
+ fn generate_allocate_funcs(&mut self, ty: &IrType, ir_file: &IrFile) -> String {
+ // println!("generate_allocate_funcs: {:?}", ty);
+ TypeRustGenerator::new(ty.clone(), ir_file).allocate_funcs(&mut self.extern_func_collector)
+ }
+
+ fn generate_wire2api_misc(&self) -> &'static str {
+ r"pub trait Wire2Api {
+ fn wire2api(self) -> T;
+ }
+
+ impl Wire2Api