Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wish for a more complete guide to using precompiled libraries in plugin_ffi template #1855

Closed
vongohren opened this issue Jan 2, 2025 · 15 comments

Comments

@vongohren
Copy link

vongohren commented Jan 2, 2025

Info

This is both a feature request, and a requets for help

I have seen issues similar to this, which tells me there is no clear guide on this:
dart-archive/ffi#184
#489

Feature request

I would like to see a better guide where one use the precompiled libraries for mac,ios and android as sources for the plugin.

android to use .so files
iOS to use .a or .dylibfiles. Preferebly both if there are any differences. And of course the .hfiles.

Im in a situation where I want to build a plugin but only have these files available.

Or even its own template that outputs the precompiled setup rather then a setup where the source is needed.

Request for help

What do I have to do to get this to work for these platforms and what are the pitfalls I have to think about.

Android

From the issues above, one have to add the .so files into the pluging and change the gradle build it seems. Is the 184 issue, on the right direction?

iOS

This is a bit more complicated, this issue talks about .dylibs. But can .a files be used just as easily? Does it exists any reference on the .a file direction?

I like the way that one primarily just add the files to podspec and that takes over the flow. Is this also possible for .a files?

Questions

Are there any pitfalls to this direction?
Is it recommended to go the .c source direction if possible?

@dcharkes
Copy link
Collaborator

dcharkes commented Jan 2, 2025

Hi @vongohren!

Im in a situation where I want to build a plugin but only have these files available.

We'd like to move away from plugin_ffi and instead move to package_ffi, precisely because of the reason that you're dealing with all the different build systems in Flutter.

With the native assets feature, you define a hook/build.dart and can compile the native code with package:native_toolchain_c or a similar helper package. To prebuilt libraries, you'd use the same logic to compile all your dylibs in tools/build.dart instead, and host them somewhere and then download them in hook/build.dart.

This is a bit more complicated, this issue talks about .dylibs. But can .a files be used just as easily? Does it exists any reference on the .a file direction?

Not yet. But the goal is that for flutter release builds we'd be able to use the native linker to tree-shake the native code to reduce the app size (#153). Whether after tree-shaking the native code is bundled as a dynamic library, or linked statically is somewhat less important. (But that is tracked here: dart-lang/sdk#49418, dart-lang/sdk#47718.)

So at this point we're not prioritizing improving the workflow with prebuilt libs in plugin_ffi. It does work if you know how the build systems work. I believe https://github.com/realm/realm-dart/tree/main/packages/realm is downloading prebuilt binaries if you want to have a place to look for inspiration.

@vongohren
Copy link
Author

@dcharkes thanks so much for a solid answer!

So for a long term perspective, I should focus on the package_ffi direction for native assets?
And according to the issue you mention, this works currently for all platforms, but using dylib for macOS and iOS?

I can also use dylib, it was just not what I had available immediatly.

Last question, are there any other docs on the package_ffi? I did not immedatly find its place?

@dcharkes
Copy link
Collaborator

dcharkes commented Jan 3, 2025

And according to the issue you mention, this works currently for all platforms, but using dylib for macOS and iOS?

Yes, but it only works with an experimental flag. So keep that in mind. (If you were to publish a package, it would only work in apps if the person building the app passes the experimental flag. And we might still break you while it is an experiment.)

Last question, are there any other docs on the package_ffi? I did not immedatly find its place?

precompiled libraries

Also, I'm in the process of addressing how to do prebuilt libraries:

@vongohren
Copy link
Author

Cool, thanks for the quick follow up! I will close this for now and I assume my requets are a given and will come when work come towards an end :D

What are your guyses timeline? We are not doing anything public but an app will go public at some point

@dcharkes
Copy link
Collaborator

dcharkes commented Jan 3, 2025

What are your guyses timeline?

As soon as possible, but only when we're satisfied with the overall quality of the feature. At this point it's hard to say, we're iterating until we've satisfied all our requirements and we're confident we don't have to break people later.

We are not doing anything public but an app will go public at some point

As long as you're publishing an app to the store (and not publishing packages), using the experiment should be fine. We'll try to break you as little as possible while adding more functionality.

@vongohren
Copy link
Author

Great thanks for that update.
@dcharkes there seem to be an issue with turning on the flag, and the running with package_ffi template.

I seem to be able to turn on the flag, but get this error when running

$ flutter create --template=package_ffi app_sdk_flutter_package
"package_ffi" is not an allowed value for option "template".

Run 'flutter -h' (or 'flutter <command> -h') for available flutter commands and
options.

@dcharkes
Copy link
Collaborator

dcharkes commented Jan 3, 2025

You need to be on the master channel of flutter to use experiments.

@vongohren
Copy link
Author

Some follow up after some work

In short, this is a replacement for plugin_ffi?
To use packaged binaries, they are downloaded at build and app package time or at app load time?

@dcharkes
Copy link
Collaborator

dcharkes commented Jan 6, 2025

In short, this is a replacement for plugin_ffi?

Correct. 👍

To use packaged binaries, they are downloaded at build and app package time or at app load time?

The build hooks run during the Flutter build and the produced dylibs are bundled in the app bundle.

(We haven't looked yet on how these dynamic libraries interact with deferred loading. Presumably we should be able to defer loading of them with Flutters deferred components. #1092 That could postpone downloading dylibs until the deferred component is needed while the app is already running potentially.)

@vongohren
Copy link
Author

vongohren commented Jan 6, 2025

Thanks for the swift replies. I have one more question. Which seems undocumented?
And sorry for lots of questions, just new to dart and flutter and got this task at hand 😅

I have created the package. And out of the box I can do flutter run in the example folder, and it works

However, I dont see any where how to build the package itself?

I dont get any feedback when adding print("BUILD FILE") in the build.dart?
I deleted the gen file to force a build, did not work, just tests and run failing.

I run dart run ffigen --config ffigen.yaml --enable-experiment=native-assets as stated in the readme, but getting message Package(s) app_sdk_flutter_package require the native assets feature to be enabled. Enable native assets with '--enable-experiment=native-assets'..

I have turned on the flutter channel and config, but dont see a way to do this for dart? If this is nessecary?

@dcharkes
Copy link
Collaborator

dcharkes commented Jan 6, 2025

Thanks for the swift replies. I have one more question. Which seems undocumented? And sorry for lots of questions, just new to dart and flutter and got this task at hand 😅

I have created the package. And out of the box I can do flutter run in the example folder, and it works

However, I dont see any where how to build the package itself?

You don't build the package itself, all transitive packages with native assets have their native assets build on dart and flutter commands

I dont get any feedback when adding print("BUILD FILE") in the build.dart? I deleted the gen file to force a build, did not work, just tests and run failing.

Try adding --verbose to Flutter commands.

I run dart run ffigen --config ffigen.yaml --enable-experiment=native-assets as stated in the readme, but getting message Package(s) app_sdk_flutter_package require the native assets feature to be enabled. Enable native assets with '--enable-experiment=native-assets'..

I have turned on the flutter channel and config, but dont see a way to do this for dart? If this is nessecary?

Dart has a different experiments logic than Flutter. Dart is per invocation. But the flag shoud be in front.

dart --enable-experiment=native-assets run ffigen --config ffigen.yaml

@vongohren
Copy link
Author

vongohren commented Jan 6, 2025

@dcharkes thanks!

Important questions

Try adding --verbose to Flutter commands.

flutter run --verbose | tee output.log 2>&1

I still dont get anything. I get a verbose logfile, but no mention of the print methods I have in this image.
Screenshot 2025-01-06 at 10 46 14.

Would be very helpful to get some output for lowering the roundtrip times of developing the build.dart file 😅
Also would be great with a confirmation that the build file runs.

flutter clean solves the build deletion issues mentioned earlier

ffi gen combined with native assets

I see a direct link between the linking of the generated files and source when doing the from source solution. But how is the link when doing f.eks dylib or so? Its still just through the .h file and since its compiled together, it hopefully works :D?

Replies

You don't build the package itself, all transitive packages with native assets have their native assets build on dart and flutter commands

Cool cool. This means that when I publish the package, it is published "unbuilt" and people add it to their setup and when building the app its built? Interesting approach!

Dart has a different experiments logic than Flutter. Dart is per invocation. But the flag shoud be in front.

Thanks, did the trick!

@vongohren
Copy link
Author

Concern

Im sorry if this is sub optimal flow, as the issue is closed and all. What other options do you propose for a better back and forth?

I think atleast im a prime candidate for the new flow, as im new to this and can really glide out the quirks of the documentation, as I need assistance to get to a certain level.

Update

Right now I tried to follow a build style from your latest PRs

https://github.com/dart-lang/native/blob/81961d74d7a6d04ea21fad3dc89d56103e915d07/pkgs/native_assets_cli/example/build/download_asset/hook/build.dart

I decided not to download for now as I just want confirmation that it all works properly and Im attached to the process in the right way. I decided to load all files locally like this and run the setup. See the code below. Questions I do have though:

Questions

What connection does the name in CodeAsset have to everything working?
I did a package create with the name test_app_sdk_flutter_package and have tried to change as little as possible.

How can I verify that the files are loading to the right place? Or is this the right direction too look into?

Build

import 'package:native_assets_cli/code_assets_builder.dart';
import 'package:native_assets_cli/native_assets_cli.dart';
import "dart:io";

void main(List<String> args) async {
  const localBuild = false;

  await build(args, (config, output) async {
    final targetOS = config.codeConfig.targetOS;

    final androidFile = File('src/file.so');
    final androidFile2 = File('src/file2.so');

    final iosFile = File('src/file.dylib');
    final iosFile2 = File('src/file2.dylib');

    final androidFiles = [
      androidFile,
      androidFile2,
    ];

    final iosFiles = [
      iosFile,
      iosFile2,
    ];

    if (targetOS == OS.android) {
      for (final file in androidFiles) {
        output = addAssetsToOutput(config, file.uri, output);
      }
    } else if (targetOS == OS.iOS) {
      for (final file in iosFiles) {
        output = addAssetsToOutput(config, file.uri, output);
      }
    }

  });
}


BuildOutputBuilder addAssetsToOutput(config, Uri uri, BuildOutputBuilder output) {
  final targetOS = config.codeConfig.targetOS;
  final targetArchitecture = config.codeConfig.targetArchitecture;

  output.codeAssets.add(CodeAsset(
    package: config.packageName,
    name: 'test_app_sdk_flutter_package.dart',
    linkMode: DynamicLoadingBundled(),
    os: targetOS,
    architecture: targetArchitecture,
    file: uri,
  ));

  return output;
}

Outcome

But im getting the following error. With trying to only expose small parts of the code.
Both binding calls return an int and the one is a function the other is a variable.

Code

import 'test_app_sdk_flutter_package_bindings_generated.dart' as bindings;

int work(int a, int b) => bindings.Close();
int variable() => bindings.CLOSE_ERROR;

Error

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY
╞═══════════════════════════════════════════════════════════
The following ArgumentError was thrown building _FocusInheritedScope:
Invalid argument(s): Couldn't resolve native function 'Close' in
'package:test_app_sdk_flutter_package/test_app_sdk_flutter_package_bindings
_generated.dart' : No
asset with id
'package:test_app_sdk_flutter_package/test_app_sdk_flutter_package_bindings
_generated.dart' found.
No available native assets. Attempted to fallback to process lookup.
dlsym(RTLD_DEFAULT,
Close): symbol not found.

When the exception was thrown, this was the stack:
#0      Native._ffi_resolver.#ffiClosure0 (dart:ffi-patch/ffi_patch.dart)
#1      Native._ffi_resolver_function
(dart:ffi-patch/ffi_patch.dart:1569:20)
#2      Close
(package:test_app_sdk_flutter_package/test_app_sdk_flutter_package_bindings
_generated.dart)
#3      work
(package:test_app_sdk_flutter_package/test_app_sdk_flutter_package.dart:11:
36)
#4      _MyAppState.initState
(package:test_app_sdk_flutter_package_example/main.dart:25:22)
#5      StatefulElement._firstBuild
(package:flutter/src/widgets/framework.dart:5860:55)
#6      ComponentElement.mount
(package:flutter/src/widgets/framework.dart:5709:5)
...     Normal element mounting (63 frames)
#69     Element.inflateWidget
(package:flutter/src/widgets/framework.dart:4555:16)
#70     Element.updateChild
(package:flutter/src/widgets/framework.dart:4020:18)
#71     _RawViewElement._updateChild
(package:flutter/src/widgets/view.dart:481:16)
#72     _RawViewElement.mount (package:flutter/src/widgets/view.dart:505:5)
...     Normal element mounting (15 frames)
#87     Element.inflateWidget
(package:flutter/src/widgets/framework.dart:4555:16)
#88     Element.updateChild
(package:flutter/src/widgets/framework.dart:4020:18)
#89     RootElement._rebuild
(package:flutter/src/widgets/binding.dart:1687:16)
#90     RootElement.mount (package:flutter/src/widgets/binding.dart:1656:5)
#91     RootWidget.attach.<anonymous closure>
(package:flutter/src/widgets/binding.dart:1609:18)
#92     BuildOwner.buildScope
(package:flutter/src/widgets/framework.dart:3056:19)
#93     RootWidget.attach
(package:flutter/src/widgets/binding.dart:1608:13)
#94     WidgetsBinding.attachToBuildOwner
(package:flutter/src/widgets/binding.dart:1346:27)
#95     WidgetsBinding.attachRootWidget
(package:flutter/src/widgets/binding.dart:1331:5)
#96     WidgetsBinding.scheduleAttachRootWidget.<anonymous closure>
(package:flutter/src/widgets/binding.dart:1317:7)
#100    _RawReceivePort._handleMessage
(dart:isolate-patch/isolate_patch.dart:194:12)
(elided 3 frames from class _Timer and dart:async-patch)

═══════════════════════════════════════════════════════════════════════════
═════════════════════════

@vongohren
Copy link
Author

A couple more question

  1. So im gradually understanding more and more. The goal for this system you are making is to avoid having to be aware of android and iOS needs to load the native assets into the system?

  2. Meaning one does not have to add the files into xcode framework files, and neither have to setup things via gradle and move to JNI folders?

  3. I think I can load things directly into the app, but it would be nice with a package/plugin to seperate the complexity from the app itself. But then currently I only partly understand the plugin direction. Meaning that one can add the files into the podspec for ios, and the android setup in the plugin, and then the example app builds this together as it depends on it. Is this the "plugin" way of doing it?

  4. Package way is going to remove any platform specific folder and primarily use the build.dart hook to solve this?

  5. Does this solution also avoid the need for the DynamicLibrary.open/.process usage?

 AudioPlayerUtils audioPlayerLibrary = AudioPlayerUtils(DynamicLibrary.open(
     'lib/libaudioplayer.dylib'));    

@dcharkes
Copy link
Collaborator

dcharkes commented Jan 7, 2025

5. Does this solution also avoid the need for the DynamicLibrary.open/.process usage?

Yes, it uses @Native() external functions, DynamicLibrary.open is done by the native assets system which knows what the dylib name/location is in Dart and Flutter app bundles. You can see how it works in flutter create --template=package_ffi my_package.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants