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

[Help!] Dart/Flutter bindings #2878

Open
alfonsogarciacaro opened this issue May 11, 2022 · 28 comments
Open

[Help!] Dart/Flutter bindings #2878

alfonsogarciacaro opened this issue May 11, 2022 · 28 comments

Comments

@alfonsogarciacaro
Copy link
Member

I think we discussed it before but it'll be nice to have a semi-automatic way to generate Dart/Flutter bindings so users can start writing apps when the compiler is ready, so I'm opening this issue for discussion.

I've uploaded a dead-simple Elmish Flutter app here: https://github.com/alfonsogarciacaro/fable-flutterapp

I manually wrote a few bindings for the things I needed in this file. In general, bindings are very similar to the JS ones, there's some discussion about writing bindings in general for Snake Island in #2779. A couple of specifics for Dart:

  • NamedParams attribute: used to indicate the method has named params in Dart. If the named params start from the 2nd or 3rd argument you can pass fromIndex (zero based) as in [<NamedParams(fromIndex=1)>]
  • IsConst indicates the method (or constructor in the case of classes) should be called with const if all arguments are constants

Ideally the bindings should be extracted from this repo. In previous conversations @Nhowka commented there are introspection tools for Dart so maybe we could use those, or is it simpler to just write a simple custom parser? Pinging also @adelarsq who also has experience with Dart and @davedawkins because he's good at writing scripts for code generation as he did with the Feliz API 👼

@davedawkins
Copy link
Contributor

Let me take a look then

@davedawkins
Copy link
Contributor

The Mirror reflection API looks promising.

@davedawkins
Copy link
Contributor

Wow. F# app on macbook
image

@davedawkins
Copy link
Contributor

davedawkins commented May 12, 2022

A very quick summary of my findings so far:

  • Flutter is based upon dart. Flutter knows what dart is, dart doesn't know what flutter is

  • Flutter can create a MacOS app, so we can write output bindings direct to file system using reflection. However:

  • Dart has Mirrors for reflection, but Flutter does not support Mirrors => we can't reflect on MaterialApp, Scaffold etc from a flutter app. An attempt to use Mirrors in Flutter produces a compilation/runtime error (I can't recall), and research on Stackoverflow backs this up

  • Dart cannot (easily) load the flutter packages (eg 'package:flutter/material.dart', or even '../../flutter/lib/material.dart' eg) => this blocks the approach of using a Dart application to reflect on the flutter packages. Searches on "dart applications that use Flutter" result in "use flutter", understandably.

  • Flutter has a relection package named "reflectable" which looks promising, but a simple example didn't compile.

What to look at next:

  • Challenge any of the "cannot" and "does not" findings above - I've tried and failed but it doesn't mean someone else can't make it work.
  • Find an example of "reflectable" that compiles and works to understand it better
  • Reference parser for Dart: https://github.com/dart-lang/sdk/wiki/The-Dart-specification-parser. We may be able to use this to parse Material.dart etc and generate the bindings
  • Hand-made fuzzy parser. Once I understand the mapping that @alfonsogarciacaro made between material.dart and Flutter.Material.fs I'll have a feel for how well this approach might work

@Nhowka
Copy link
Contributor

Nhowka commented May 12, 2022

Dartdoc is the tool that generates this kind of documentation and the ones on pub.dev that already describes the public API for each library. Looking at the html it generates it could be feasible to parse them directly, but I still believe that the ideal solution could be reviving this issue so we could use the output to generate our bindings or forking/rewriting to our needs to generate them directly.

It even has an --auto-include-dependencies flag that generates documentation files for direct and indirect dependencies, so pretty powerful tool.

@adelarsq
Copy link
Contributor

Nice! I will help on this 🤗

@Nhowka
Copy link
Contributor

Nhowka commented May 14, 2022

When importing the dartdoc package, we can create a Dartdoc instance calling the constructor Dartdoc.withEmptyGenerator. There is a brittle generator field on it with a @visibleForTesting annotation that we could leverage. The Generator receives a PackageGraph that describes everything on the package and a FileWriter to write the result.

I'm doing some experiments with it to learn the layout to extract everything we need, but that way we probably can use the tool as is.

@adelarsq
Copy link
Contributor

A hand-made parser would make sense. So would be possible to extend ts2fable to support Dart and more languages?

@Nhowka
Copy link
Contributor

Nhowka commented May 15, 2022

I upload the test file I made in this repository. I started handling only the constructors for now and got this result for the flutter library. Function types, the generics on the arguments and the translation from core types are still not correctly handled, but it's already a start.

While it's cool that it's using the metadata from the language and loading everything needed into the context so we could handle as we want, it makes it terribly slow. I ran it against my elmish-like lib with almost no dependencies and it takes around 2 minutes to reach the generator.

@davedawkins
Copy link
Contributor

Amazing. The speed issue makes it hard to test/debug, but once completed this thing only needs to be run occasionally (to keep bindings in sync), I would think?

@Nhowka
Copy link
Contributor

Nhowka commented May 15, 2022

Yes, we could plug some pre-analysis step to check for changes only start the generation if there is a new or different version package. Maybe we could create a project and save a copy of the pubspec.lock and compare them. If there is a change, we regenerate all files again as it is easier than detecting changes on a single package.

For the named constructors I mapped them as static methods that return an instance of the class. Is that the right way to map them?

@alfonsogarciacaro
Copy link
Member Author

Thanks everybody for your help! This is great @Nhowka, even if it requires some manual tweaking it's already a great help to make some demos/samples for the alpha release 🎉 As @davedawkins says speed probably it's not an issue if we only need to run the tool once in a while. For prototyping, is it faster if we work with a small code sample or most of the boot up time is taken by Dartdoc.

@adelarsq About ts2fable, probably the only part we could reuse would be printing and it has its own AST which may not have all the information we need (const constructors, named params) so it may be faster for now to do things in Dart directly as @Nhowka did 👍

@alfonsogarciacaro
Copy link
Member Author

About compiling named constructors as static members, this is fine as in Dart they're syntactically the same in the call site. You can also use the [<IsConst>] attribute in a static member if needed: https://github.com/alfonsogarciacaro/fable-flutterapp/blob/1a80389301c32f8a763bdac93f60c864ee07608d/src/Flutter.Material.fs#L65

@alfonsogarciacaro
Copy link
Member Author

@Nhowka I'm trying to edit your code a bit to get bindings for the constructors of the Material widgets. See this commit: alfonsogarciacaro/DartToFableBindings@b7728ac

I tried with a test Dart package and it seemed to work fine, however I downloaded the flutter repo, followed the instructions here and managed to run flutter analyze in packages/flutter dir without issues. However when I try to run the fsgen from that dir I don't get any output. This is the command I'm using:

dart run ../../../DartToFableBindings/bin/fsgen.dart \
  --exclude 'dart:async,dart:collection,dart:convert,dart:core,dart:developer,dart:io,dart:isolate,dart:math,dart:typed_data,dart,dart:ffi,dart:html,dart:js,dart:ui,dart:js_util' \
  --show-progress  \
  --no-auto-include-dependencies  \
  --no-validate-links  \
  --no-verbose-warnings  \
  --no-allow-non-local-warnings \
  --no-allow-tools

I only get many errors, this is the shortened log (only start and finish):

../../../../AppData/Local/Pub/Cache/hosted/pub.dartlang.org/dartdoc-5.0.1/lib/src/model/model_element.dart:706:14: Warning: Operand of null-aware operation '?.' has type 'LineInfo' which excludes null.
 - 'LineInfo' is from 'package:analyzer/source/line_info.dart' ('../../../../AppData/Local/Pub/Cache/hosted/pub.dartlang.org/analyzer-3.4.1/lib/source/line_info.dart').
      return lineInfo?.getLocation(nameOffset);
             ^
Documenting flutter...
  error: private API of package:Dart is reexported by libraries in other packages:
    from cupertino.Color: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/ui/painting.dart:94:7)
    referred to by cupertino: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/cupertino.dart:23:9)
    referred to by material: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/material.dart:21:9)
    referred to by painting: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/painting.dart:18:9)
    referred to by rendering: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/rendering.dart:22:9)
    referred to by widgets: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/widgets.dart:13:9)

...

  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:424:16)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.ByteData: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:428:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.view: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:459:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.sublistView: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:481:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
-
dartdoc 5.0.1 (/C:/Users/alfon/repos/DartToFableBindings/bin/fsgen.dart) failed: Command executables must exist. The file "c:\users\alfon\repos\flutter\bin\cache\dart-sdk\bin\dart" does not exist for tool snippet..

Did you manage to run the fsgen from the flutter repo? Can you help? 🙏

@Nhowka
Copy link
Contributor

Nhowka commented May 30, 2022

Flutter is somewhat unique. They generate a dummy pubspec to make it compatible with dartdoc. Maybe you can get some output by creating a dummy empty flutter project that depends only on flutter and generating all bindings using it as a base.

@alfonsogarciacaro
Copy link
Member Author

You're right, that worked! Now I've a ton of bindings... I only need to make them compile 😅 Getting excited now!

@bentok
Copy link

bentok commented Jul 25, 2022

I'm gonna take a stab at getting that code generator to produce translations for function types. I'm using @alfonsogarciacaro's latest changes to the generator and it seems like functions are still not being translated. Correct me if I'm wrong on that, and I'm open to suggestions if anyone already has ideas.

@alfonsogarciacaro
Copy link
Member Author

Thanks a lot for your help @bentok! I think my latest changes are in this branch: https://github.com/alfonsogarciacaro/DartToFableBindings/tree/material-widgets

I was making several attempts and also did some manual changes. I think this code can output the module functions, but in any case the generator is a bit dirty at the moment, so some cleanup would be much appreciated. See the comment from @Nhowka about the dummy project necessary to make the doc generator work 👍 https://github.com/alfonsogarciacaro/DartToFableBindings/blob/f001c9621d018a082a37cd593745d7549d33f72c/lib/fsgen.dart#L358-L361

@bentok
Copy link

bentok commented Aug 8, 2022

Flutter is somewhat unique. They generate a dummy pubspec to make it compatible with dartdoc. Maybe you can get some output by creating a dummy empty flutter project that depends only on flutter and generating all bindings using it as a base.

@Nhowka do you think generating bindings for 3rd party Flutter libraries would require the same approach? I have made some modifications to both your code and @alfonsogarciacaro's fork (https://github.com/alfonsogarciacaro/DartToFableBindings/tree/material-widgets) and am getting close, but can't quite get it right.

For example, if I generate bindings for flutter_secure_storage, it will generate dozens of files - some of which have only a few bindings and others that have more. However it doesn't seem like any of the files have all of the bindings.

@Nhowka
Copy link
Contributor

Nhowka commented Aug 8, 2022

@bentok, I believe that for typical packages with standard pubspec files, the same surface outputted on their dartdoc documentation on pub.dev should be available to us. Could there be some filter pruning some of those before they reach the code generation stage?

If I'm not mistaken, we could generate bindings for them without using a dummy project, but it could be amazing if we could create bindings for all packages an actual project is referencing on the fly. We could have a cache, so we skip generating for known versions, but as dart has the advantage that the libraries are packaged with raw code that dartdoc could read, I believe it is an achievable goal.

@johnstorey
Copy link

Hey, l've come late to this thread, but @bentok and I have connected to start working on this again. I'm able to reproduce everything up to @alfonsogarciacaro getting errors on his first try to run this. But I have not yet worked out what to do once the empty flutter project is set up to get the bindings. I know it's been more than a year, but if someone remember and can give the exact step that would speed up the work a bit.

@octaviordz
Copy link

Hi everyone, I got the source code from https://github.com/alfonsogarciacaro/fable-flutterapp and after updating fable it compiled and ran correctly, but the auto/hot-reload does not work. Is there something that can be done to make this work?

I'm using vscode under Windows 10.
Thank you.

@MangelMaxime
Copy link
Member

Hello,

in the readme of the repo there is a mention that you have to trigger hot reload manually.

Did you give it a try ?

I never used Flutter so I don’t know if there is some requirements like in JavaScript for it to work.

@octaviordz
Copy link

Hi, I had not tried the "Ctr+F5" that works, I was just manually updating the .dart generated file to trigger the hot reload. But my question was more in regard to what is the missing/link/configuration the 'build.cmd' is running fable in watch mode so when you update the source code it creates the .dart file(s) and vscode does load the updated .dart file(s) and like I said I was manually updating the .dart file to manual trigger hot reload hence my question is why if vscode 'knows' the file has changed the hot reload is not triggered.

Thank you.

@MangelMaxime
Copy link
Member

Hello @octaviordz,

I am not sure I understood everything because it was a really long sentence 😅

But, the first thing I was check is what is the behaviour of hot reload in a pure Dart project because if the behaviour is the same then there is nothing that can be improve.

If not, then someone with Dart/Flutter knowledge will need to have a look at it.

@octaviordz
Copy link

Hi @MangelMaxime,
Hot reload works in a pure Dart project as expected. Thank you for your response.

@endeavour
Copy link

Has this been abandoned? I just forked the repo and updated the dependencies but it looks like there is still a lot of work to get this into a usable state. Did you get anywhere with generating the class members?

@MangelMaxime
Copy link
Member

@endeavour The main developer for Dart target is not active anymore.

If someone want to step in to contribute to it, we welcome contributors.

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

No branches or pull requests

9 participants