fzyzcjy / flutter_rust_bridge

Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple.
https://fzyzcjy.github.io/flutter_rust_bridge/
MIT License
4.31k stars 302 forks source link

[Bug] Cannot integrate with Flutter plugin (the tutorial only discusses the case of Flutter application) #462

Closed KRTirtho closed 2 years ago

KRTirtho commented 2 years ago

Describe the bug

I'm using flutter_rust_bridge in a plugin & using that plugin in an Flutter Application (example application of the plugin). I guess I followed all the instructions of the docs but my application fails to load the dynamic library

Following are the error logs

Launching lib/main.dart on Linux in debug mode...
    Finished dev [unoptimized + debuginfo] target(s) in 0.15s
Building Linux application...                                           
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception: Invalid argument(s): Failed to load dynamic library 'librustee_rowdy.so': librustee_rowdy.so: cannot open shared object file: No such file or directory
#0      _open (dart:ffi-patch/ffi_dynamic_library_patch.dart:12:43)
#1      new DynamicLibrary.open (dart:ffi-patch/ffi_dynamic_library_patch.dart:23:12)
#2      loadDylib (package:rowdy/ffi.dart:22:24)
#3      Rowdy.initialize (package:rowdy/rowdy.dart:17:11)
#4      main (package:rowdy_example/main.dart:8:9)
#5      _runMainZoned.<anonymous closure>.<anonymous closure> (dart:ui/hooks.dart:130:25)
#6      _rootRun (dart:async/zone.dart:1426:13)
#7      _CustomZone.run (dart:async/zone.dart:1328:19)
#8      _runZoned (dart:async/zone.dart:1861:10)
#9      runZonedGuarded (dart:async/zone.dart:1849:12)
#10     _runMainZoned.<anonymous closure> (dart:ui/hooks.dart:126:5)
#11     _delayEntrypointInvocation.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:297:19)
#12     _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:192:12)

Codegen logs with RUST_LOG=debug env variable

[2022-05-17T12:34:24Z DEBUG lib_flutter_rust_bridge_codegen::commands] execute command: bin=sh args=["-c", "dart pub global list"] current_dir=None cmd="sh" "-c" "dart pub global list"
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::commands] command="sh" "-c" "dart pub global list" stdout=ffigen 4.1.3
    melos 2.2.0
    protoc_plugin 20.0.0
     stderr=
[2022-05-17T12:34:25Z INFO  lib_flutter_rust_bridge_codegen] Picked config: Opts { rust_input_path: "/home/krtirtho/dev/rowdy/rustee_rowdy/src/api.rs", dart_output_path: "/home/krtirtho/dev/rowdy/lib/bridge_generated.dart", dart_decl_output_path: None, c_output_path: ["/home/krtirtho/dev/rowdy/linux/include/bridge_generated.h"], rust_crate_dir: "/home/krtirtho/dev/rowdy/rustee_rowdy", rust_output_path: "/home/krtirtho/dev/rowdy/rustee_rowdy/src/bridge_generated.rs", class_name: "RusteeRowdy", dart_format_line_length: 80, skip_add_mod_to_lib: false, llvm_path: ["/opt/homebrew/opt/llvm", "/usr/local/opt/llvm", "/usr/lib/llvm-9", "/usr/lib/llvm-10", "/usr/lib/llvm-11", "/usr/lib/llvm-12", "/usr/lib/llvm-13", "/usr/lib/llvm-14", "/usr/lib/", "/usr/lib64/", "C:/Program Files/llvm", "C:/Program Files/LLVM", "C:/msys64/mingw64"], llvm_compiler_opts: "", manifest_path: "/home/krtirtho/dev/rowdy/rustee_rowdy/Cargo.toml", dart_root: Some("/home/krtirtho/dev/rowdy"), build_runner: true }
[2022-05-17T12:34:25Z INFO  lib_flutter_rust_bridge_codegen] Phase: Parse source code to AST
[2022-05-17T12:34:25Z INFO  lib_flutter_rust_bridge_codegen] Phase: Parse AST to IR
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::source_graph] Trying to parse "/home/krtirtho/dev/rowdy/rustee_rowdy/src/bridge_generated.rs"
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::source_graph] Trying to parse "/home/krtirtho/dev/rowdy/rustee_rowdy/src/api.rs"
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::parser] parse_function function name: Ident(hello)
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen] parsed functions: IrFile { funcs: [IrFunc { name: "hello", inputs: [], output: Delegate(String), fallible: false, mode: Normal, comments: [] }], struct_pool: {}, enum_pool: {}, has_executor: false }
[2022-05-17T12:34:25Z INFO  lib_flutter_rust_bridge_codegen] Phase: Transform IR
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen] transformed functions: IrFile { funcs: [IrFunc { name: "hello", inputs: [], output: Delegate(String), fallible: false, mode: Normal, comments: [] }], struct_pool: {}, enum_pool: {}, has_executor: false }
[2022-05-17T12:34:25Z INFO  lib_flutter_rust_bridge_codegen] Phase: Generate Rust code
[2022-05-17T12:34:25Z INFO  lib_flutter_rust_bridge_codegen] Phase: Generate Dart code
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::generator::dart] distinct_input_types=[]
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::generator::dart] distinct_output_types=[Delegate(String), Primitive(U8), PrimitiveList(IrTypePrimitiveList { primitive: U8 })]
[2022-05-17T12:34:25Z INFO  lib_flutter_rust_bridge_codegen] Phase: Other things
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::commands] execute format_rust path=/home/krtirtho/dev/rowdy/rustee_rowdy/src/bridge_generated.rs
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::commands] execute command: bin=rustfmt args=["/home/krtirtho/dev/rowdy/rustee_rowdy/src/bridge_generated.rs"] current_dir=None cmd="rustfmt" "/home/krtirtho/dev/rowdy/rustee_rowdy/src/bridge_generated.rs"
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::commands] command="rustfmt" "/home/krtirtho/dev/rowdy/rustee_rowdy/src/bridge_generated.rs" stdout= stderr=
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::commands] execute cbindgen rust_crate_dir=/home/krtirtho/dev/rowdy/rustee_rowdy c_output_path=/tmp/.tmp0JjhsX.h
[2022-05-17T12:34:25Z DEBUG lib_flutter_rust_bridge_codegen::commands] cbindgen config: Config { header: None, includes: [], sys_includes: ["stdbool.h", "stdint.h", "stdlib.h"], after_includes: None, trailer: None, include_guard: None, pragma_once: false, no_includes: true, autogen_warning: None, include_version: false, namespace: None, namespaces: None, using_namespaces: None, braces: SameLine, line_length: 100, tab_width: 2, line_endings: LF, language: C, cpp_compat: false, style: Both, sort_by: None, usize_is_size_t: false, parse: ParseConfig { parse_deps: false, include: None, exclude: [], expand: ParseExpandConfig { crates: [], all_features: false, default_features: true, features: None, profile: Debug }, clean: false, extra_bindings: [] }, export: ExportConfig { include: [], exclude: [], rename: {}, pre_body: {}, body: {}, prefix: None, item_types: [], renaming_overrides_prefixing: false, mangle: MangleConfig { rename_types: None, remove_underscores: false } }, macro_expansion: MacroExpansionConfig { bitflags: false }, layout: LayoutConfig { packed: None, aligned_n: None }, function: FunctionConfig { prefix: None, postfix: None, must_use: None, args: Auto, rename_args: None, swift_name_macro: None, sort_by: None, no_return: None }, structure: StructConfig { rename_fields: None, derive_constructor: false, derive_eq: false, derive_neq: false, derive_lt: false, derive_lte: false, derive_gt: false, derive_gte: false, derive_ostream: false, associated_constants_in_body: false, must_use: None }, enumeration: EnumConfig { rename_variants: None, rename_variant_name_fields: SnakeCase, add_sentinel: false, prefix_with_name: false, derive_helper_methods: false, derive_const_casts: false, derive_mut_casts: false, cast_assert_name: None, must_use: None, derive_tagged_enum_destructor: false, derive_tagged_enum_copy_constructor: false, derive_tagged_enum_copy_assignment: false, derive_ostream: false, enum_class: true, private_default_tagged_enum_constructor: false }, constant: ConstantConfig { allow_static_const: true, allow_constexpr: true, sort_by: None }, defines: {}, documentation: true, documentation_style: Auto, documentation_length: Full, pointer: PtrConfig { non_null_attribute: None }, only_target_dependencies: false, cython: CythonConfig { header: None, cimports: {} } }
[2022-05-17T12:34:26Z DEBUG cbindgen::bindgen::parser] Parsing crate rustee_rowdy
[2022-05-17T12:34:26Z INFO  cbindgen::bindgen::parser] Take rustee_rowdy::wire_hello.
[2022-05-17T12:34:26Z INFO  cbindgen::bindgen::parser] Take rustee_rowdy::free_WireSyncReturnStruct.
[2022-05-17T12:34:26Z INFO  cbindgen::bindgen::parser] Take rustee_rowdy::DartPort.
[2022-05-17T12:34:26Z INFO  cbindgen::bindgen::parser] Take rustee_rowdy::DartPostCObjectFnType.
[2022-05-17T12:34:26Z INFO  cbindgen::bindgen::parser] Take rustee_rowdy::store_dart_post_cobject.
[2022-05-17T12:34:26Z INFO  cbindgen::bindgen::parser] Take rustee_rowdy::WireSyncReturnStruct.
[2022-05-17T12:34:26Z DEBUG lib_flutter_rust_bridge_codegen::commands] execute ffigen c_path=/tmp/.tmp0JjhsX.h dart_path=/tmp/.tmpcm9bfo llvm_path=["/opt/homebrew/opt/llvm", "/usr/local/opt/llvm", "/usr/lib/llvm-9", "/usr/lib/llvm-10", "/usr/lib/llvm-11", "/usr/lib/llvm-12", "/usr/lib/llvm-13", "/usr/lib/llvm-14", "/usr/lib/", "/usr/lib64/", "C:/Program Files/llvm", "C:/Program Files/LLVM", "C:/msys64/mingw64"]
[2022-05-17T12:34:26Z DEBUG lib_flutter_rust_bridge_codegen::commands] ffigen config: 
            output: '/tmp/.tmpcm9bfo'
            name: 'RusteeRowdyWire'
            description: 'generated by flutter_rust_bridge'
            headers:
              entry-points:
                - '/tmp/.tmp0JjhsX.h'
              include-directives:
                - '/tmp/.tmp0JjhsX.h'
            comments: false
            preamble: |
              // ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names

            llvm-path:
               - '/opt/homebrew/opt/llvm'
               - '/usr/local/opt/llvm'
               - '/usr/lib/llvm-9'
               - '/usr/lib/llvm-10'
               - '/usr/lib/llvm-11'
               - '/usr/lib/llvm-12'
               - '/usr/lib/llvm-13'
               - '/usr/lib/llvm-14'
               - '/usr/lib/'
               - '/usr/lib64/'
               - 'C:/Program Files/llvm'
               - 'C:/Program Files/LLVM'
               - 'C:/msys64/mingw64'

[2022-05-17T12:34:26Z DEBUG lib_flutter_rust_bridge_codegen::commands] ffigen config_file: NamedTempFile("/tmp/.tmpnWaZNZ")
[2022-05-17T12:34:26Z DEBUG lib_flutter_rust_bridge_codegen::commands] execute command: bin=sh args=["-c", "dart pub global run ffigen --config \"/tmp/.tmpnWaZNZ\""] current_dir=None cmd="sh" "-c" "dart pub global run ffigen --config \"/tmp/.tmpnWaZNZ\""
[2022-05-17T12:34:29Z DEBUG lib_flutter_rust_bridge_codegen::commands] command="sh" "-c" "dart pub global run ffigen --config \"/tmp/.tmpnWaZNZ\"" stdout=Running in Directory: '/home/krtirtho/dev/rowdy'
    Input Headers: [/tmp/.tmp0JjhsX.h]
    Finished, Bindings generated in /tmp/.tmpcm9bfo
     stderr=
[2022-05-17T12:34:29Z DEBUG lib_flutter_rust_bridge_codegen::commands] execute format_dart path=/home/krtirtho/dev/rowdy/lib/bridge_generated.dart line_length=80
[2022-05-17T12:34:29Z DEBUG lib_flutter_rust_bridge_codegen::commands] execute command: bin=sh args=["-c", "dart format /home/krtirtho/dev/rowdy/lib/bridge_generated.dart --line-length 80"] current_dir=None cmd="sh" "-c" "dart format /home/krtirtho/dev/rowdy/lib/bridge_generated.dart --line-length 80"
[2022-05-17T12:34:29Z DEBUG lib_flutter_rust_bridge_codegen::commands] command="sh" "-c" "dart format /home/krtirtho/dev/rowdy/lib/bridge_generated.dart --line-length 80" stdout=Formatted /home/krtirtho/dev/rowdy/lib/bridge_generated.dart
    Formatted 1 file (1 changed) in 0.34 seconds.
     stderr=
[2022-05-17T12:34:29Z INFO  lib_flutter_rust_bridge_codegen] Success!
[2022-05-17T12:34:29Z INFO  flutter_rust_bridge_codegen] Now go and use it :)

To Reproduce

Run following commands

Expected behavior

Dynamic Library should get loaded while using the plugin inside an Application & bindings should work

Generated binding code

// bridge_generated.h
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>

typedef struct WireSyncReturnStruct {
  uint8_t *ptr;
  int32_t len;
  bool success;
} WireSyncReturnStruct;

typedef int64_t DartPort;

typedef bool (*DartPostCObjectFnType)(DartPort port_id, void *message);

void wire_hello(int64_t port_);

void free_WireSyncReturnStruct(struct WireSyncReturnStruct val);

void store_dart_post_cobject(DartPostCObjectFnType ptr);

static int64_t dummy_method_to_enforce_bundling(void) {
    int64_t dummy_var = 0;
    dummy_var ^= ((int64_t) (void*) wire_hello);
    dummy_var ^= ((int64_t) (void*) free_WireSyncReturnStruct);
    dummy_var ^= ((int64_t) (void*) store_dart_post_cobject);
    return dummy_var;
}

// bridge_generated.dart
// AUTO GENERATED FILE, DO NOT EDIT.
// Generated by `flutter_rust_bridge`.

// 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

import 'dart:convert';
import 'dart:typed_data';

import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter_rust_bridge/flutter_rust_bridge.dart';
import 'dart:ffi' as ffi;

abstract class RusteeRowdy {
  Future<String> hello({dynamic hint});
}

class RusteeRowdyImpl extends FlutterRustBridgeBase<RusteeRowdyWire>
    implements RusteeRowdy {
  factory RusteeRowdyImpl(ffi.DynamicLibrary dylib) =>
      RusteeRowdyImpl.raw(RusteeRowdyWire(dylib));

  RusteeRowdyImpl.raw(RusteeRowdyWire inner) : super(inner);

  Future<String> hello({dynamic hint}) => executeNormal(FlutterRustBridgeTask(
        callFfi: (port_) => inner.wire_hello(port_),
        parseSuccessData: _wire2api_String,
        constMeta: const FlutterRustBridgeTaskConstMeta(
          debugName: "hello",
          argNames: [],
        ),
        argValues: [],
        hint: hint,
      ));

  // Section: api2wire

  // Section: api_fill_to_wire

}

// Section: wire2api
String _wire2api_String(dynamic raw) {
  return raw as String;
}

int _wire2api_u8(dynamic raw) {
  return raw as int;
}

Uint8List _wire2api_uint_8_list(dynamic raw) {
  return raw as Uint8List;
}

// ignore_for_file: camel_case_types, non_constant_identifier_names, avoid_positional_boolean_parameters, annotate_overrides, constant_identifier_names

// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.

/// generated by flutter_rust_bridge
class RusteeRowdyWire implements FlutterRustBridgeWireBase {
  /// Holds the symbol lookup function.
  final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
      _lookup;

  /// The symbols are looked up in [dynamicLibrary].
  RusteeRowdyWire(ffi.DynamicLibrary dynamicLibrary)
      : _lookup = dynamicLibrary.lookup;

  /// The symbols are looked up with [lookup].
  RusteeRowdyWire.fromLookup(
      ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
          lookup)
      : _lookup = lookup;

  void wire_hello(
    int port_,
  ) {
    return _wire_hello(
      port_,
    );
  }

  late final _wire_helloPtr =
      _lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int64)>>('wire_hello');
  late final _wire_hello = _wire_helloPtr.asFunction<void Function(int)>();

  void free_WireSyncReturnStruct(
    WireSyncReturnStruct val,
  ) {
    return _free_WireSyncReturnStruct(
      val,
    );
  }

  late final _free_WireSyncReturnStructPtr =
      _lookup<ffi.NativeFunction<ffi.Void Function(WireSyncReturnStruct)>>(
          'free_WireSyncReturnStruct');
  late final _free_WireSyncReturnStruct = _free_WireSyncReturnStructPtr
      .asFunction<void Function(WireSyncReturnStruct)>();

  void store_dart_post_cobject(
    DartPostCObjectFnType ptr,
  ) {
    return _store_dart_post_cobject(
      ptr,
    );
  }

  late final _store_dart_post_cobjectPtr =
      _lookup<ffi.NativeFunction<ffi.Void Function(DartPostCObjectFnType)>>(
          'store_dart_post_cobject');
  late final _store_dart_post_cobject = _store_dart_post_cobjectPtr
      .asFunction<void Function(DartPostCObjectFnType)>();
}

typedef DartPostCObjectFnType = ffi.Pointer<
    ffi.NativeFunction<ffi.Uint8 Function(DartPort, ffi.Pointer<ffi.Void>)>>;
typedef DartPort = ffi.Int64;

OS

Endeavour OS (Gnome Edition) Linux

Version of flutter_rust_bridge_codegen

1.30.0

Flutter info

[✓] Flutter (Channel stable, 2.10.5, on EndeavourOS 5.17.6-zen1-1-zen, locale en_US.UTF-8)
    • Flutter version 2.10.5 at /opt/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 5464c5bac7 (4 weeks ago), 2022-04-18 09:55:37 -0700
    • Engine revision 57d3bac3dd
    • Dart version 2.16.2
    • DevTools version 2.9.2

[!] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
    • Android SDK at /opt/android-sdk
    • Platform android-31, build-tools 30.0.3
    • ANDROID_HOME = /opt/android-sdk
    • ANDROID_SDK_ROOT = /opt/android-sdk
    • Java binary at: /usr/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_332-b09)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses

[✗] Chrome - develop for the web (Cannot find Chrome executable at google-chrome)
    ! Cannot find Chrome. Try setting CHROME_EXECUTABLE to a Chrome executable.

[✓] Linux toolchain - develop for Linux desktop
    • clang version 13.0.1
    • cmake version 3.23.1
    • ninja version 1.10.2
    • pkg-config version 1.8.0

[!] Android Studio (not installed)
    • Android Studio not found; download from https://developer.android.com/studio/index.html
      (or visit https://flutter.dev/docs/get-started/install/linux#android-setup for detailed instructions).

[✓] Connected device (1 available)
    • Linux (desktop) • linux • linux-x64 • EndeavourOS 5.17.6-zen1-1-zen

[✓] HTTP Host Availability
    • All required HTTP hosts are available

! Doctor found issues in 3 categories.

Version of clang++

13.0.1

Version of ffigen

4.1.3

Additional context

https://github.com/fzyzcjy/flutter_rust_bridge/discussions/460

welcome[bot] commented 2 years ago

Hi! Thanks for opening your first issue here! :smile:

fzyzcjy commented 2 years ago

Context

https://github.com/fzyzcjy/flutter_rust_bridge/discussions/460

KRTirtho commented 2 years ago

I guess an example with plugin is much more needed as people provide native implementation with plugins to their Applications. IMO, a working example using Flutter plugin using FRB will be perfect to clear all the confusions for all the plugin developers

fzyzcjy commented 2 years ago

@KRTirtho Agree. Currently I do not have much time for this tutorial, but I may find some time later if there is much need.

KRTirtho commented 2 years ago

Of course do it when ever you're free. But for the moment, can give me any clue/suggestion about this problem? It'd be really helpful

fzyzcjy commented 2 years ago

Just search: how to integrate a rust code into a flutter code?

or, more generally: how to integrate an arbitrary command (e.g. "cargo build", but it can also be "gcc compile-something" or "your-fancy-command run-something") into a flutter plugin when flutter compiles?

and, since your bug is about "cannot load the .so file", just search: how to integrate a .so file into flutter plugin? you know, rust .so is no special. it is just the same as a .so file you get by compiling C or C++ or sth.

KRTirtho commented 2 years ago

Well, I managed to load the .so or .dll file used in the plugin in an application. The way is one have to do the all the CMake related configuration on the application & point the corrosion manifest to the flutter/ephemeral/.plugin_symlinks/<plugin-name>/<crate-name>/Cargo.toml. That means the rust.cmake also needs to be created on each application's linux or windows directory. Its usable but the end user have to manually do this cumbersome configuration

mouEsam commented 2 years ago

Bundling a prebuilt library with your plugin is the solution.

Please take a look at this repo I created as an example for a flutter plugin with rust.

It supports iOS, Android, MacOS, Windows, and Linux. Although I haven't tried the Linux version yet and its library was cross compiled on my windows machine.

The example folder is a stock example that flutter creates for the plugin except that the function it calls is changed.

Hope this helps.

stale[bot] commented 2 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] commented 2 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new issue.