Skip to content

Conversation

@jonahwilliams
Copy link
Contributor

@jonahwilliams jonahwilliams commented Aug 18, 2019

Description

I'm pulling the thread on #34858. I've discovered that the hack I added to psuedo-support dart:io has lead to debug and release web builds resolving config specific imports differently. The fix is to remove dart:io usage from framework, which will allow the incremental web compiler to correctly select the right config import.

Background

Flutter for web and Flutter applications have access to different sets of dart:* libraries. dart:io and dart:isolate are not available* for the web and dart:html is not available for the vm. One tool we have to write code that works in both environments is config specific imports. These allow us to resolve import uri's based on the set of currently supported dart libraries. For example:

import 'a.dart'
  if (dart.library.bar) 'b.dart'
  if (dart.library.foo) 'c.dart';

In the above code, the analyzer will only see a.dart. When the code is built for a platform the CFE will check each condition in order to see if the library is supported. If none are, then the default import is selected. A default import is always required.

Supported usually means that there is no "supported:false" pair in the libraries.json for the particular platform. In the case of dartdevc/k the incremental web compiler, package:build uses the DartPlatform type instead, which lists all supported libraries.

Since the incremental compiler cannot compile unsupported libraries, I added """support""" for dart:io and dart:isolate to the platform we use. At the time I was not aware this would alter the config specific imports. Furthermore, since dart2js, the release compiler, only uses the libraries.json this leads to disagreements between the two. Specifically:

import 'a.dart'
  if (dart.library.html) 'b.dart'

The above would return b.dart in both dartdevc/k and dart2js

import 'a.dart'
  if (dart.library.io) 'b.dart'
  if (dart.library.html) 'c.dart'

The above would return 'b.dart' in dartdevc/k and 'c.dart' in dart2js.

Unfortunately a common pattern used by package:http is to have the first library be a "stub", while each real implementation is guarded below.

Overview

I think this can be done in a mostly non-breaking way. We have some usage of Platform.isFoo that could really be defaultTargetPlatform, and Platform.resolvedExecutable doesn't actually print useful information in release applications, so that can go.

There are a few places like FileImage where the type is unfortunately part of the public API. One solution would be to change the type to Object/dynamic, and then have the runtime implementation perform the type check. Another would be to define our own stub and substitute that using more config specific imports.

Then of course, there are breaking changes to fix it, like making FileImage take a String path instead.

Some specific changes here:

  • Replace Platform.isFoo with defaultTargetPlatform. This may not be necessary in the future due to Move Platform (or similar) to dart:core dart-lang/sdk#35969
  • Remove usage of platform.resolveExecutable. This doesn't return meaningful information in release mode.
  • Place NetworkAssetBundle behind a config specific import (like I did with network image)
  • Place FileImage behind a config specific import.

Related Issues

@fluttergithubbot
Copy link
Contributor

It looks like this pull request may not have tests. Please make sure to add tests before merging. While there are exceptions to this rule, if this patch modifies code it is probably not an exception.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@fluttergithubbot fluttergithubbot added the tool Affects the "flutter" command-line tool. See also t: labels. label Aug 18, 2019
@codecov
Copy link

codecov bot commented Aug 19, 2019

Codecov Report

Merging #38773 into master will decrease coverage by 0.11%.
The diff coverage is 0%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #38773      +/-   ##
==========================================
- Coverage   56.53%   56.41%   -0.12%     
==========================================
  Files         195      195              
  Lines       18422    18422              
==========================================
- Hits        10414    10392      -22     
- Misses       8008     8030      +22
Flag Coverage Δ
#flutter_tool 56.41% <0%> (-0.12%) ⬇️
Impacted Files Coverage Δ
...ges/flutter_tools/lib/src/build_runner/web_fs.dart 26.22% <ø> (-0.22%) ⬇️
.../flutter_tools/lib/src/android/android_device.dart 34% <ø> (ø) ⬆️
...utter_tools/lib/src/build_runner/build_script.dart 0% <0%> (ø) ⬆️
...er_tools/lib/src/build_system/targets/windows.dart 6.25% <0%> (-87.5%) ⬇️
packages/flutter_tools/lib/src/compile.dart 64.21% <0%> (-12.71%) ⬇️
packages/flutter_tools/lib/src/version.dart 90.9% <0%> (-1.92%) ⬇️
packages/flutter_tools/lib/src/cache.dart 43.02% <0%> (-0.73%) ⬇️
packages/flutter_tools/lib/src/base/logger.dart 82.06% <0%> (-0.39%) ⬇️
packages/flutter_tools/lib/src/artifacts.dart 68.75% <0%> (-0.35%) ⬇️
packages/flutter_tools/lib/src/base/process.dart 82.94% <0%> (+0.77%) ⬆️
... and 1 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e31d88f...566f1d4. Read the comment docs.

@jonahwilliams jonahwilliams changed the title Allow dart2js and dartdevk to disagree on what libraries are supported. Remove dart:io/dart:isolate from transitive closure of web applications Aug 20, 2019
@jonahwilliams
Copy link
Contributor Author

cc @goderbauer @Piinks

@Piinks Piinks added the framework flutter/packages/flutter repository. See also f: labels. label Aug 21, 2019
@jonahwilliams jonahwilliams marked this pull request as ready for review August 21, 2019 00:14
///
/// This implementation is unsupported and throws when invoked.
Future<Uint8List> consolidateHttpClientResponseBytes(
Object response, {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type can't be HttpClient since that comes from dart:io

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make it be a Stream<List<int>>?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what that would accomplish. You won't get a Stream<List> from an XmlHttpRequest, so if you're calling this method at all you've done something wrong

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't.

// can provide an explicit applicationName to the widgets defined in this
// file, instead of relying on the default.
final Title ancestorTitle = context.ancestorWidgetOfExactType(Title);
return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns /bin/system-something on release mode android

return true;
}
if (Platform.isIOS) {
if (defaultTargetPlatform == TargetPlatform.iOS) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we have a lot of tests that rely on this being false for both :(

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these asserts were essentially never tested. Maybe we should remove them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tester would resolve this to the host platform right?

We should fix the test. I believe the asserts work as intended even if they're not correctly tested. And this should be a switch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is definitely not default Target Platform fodder. Changing the UI style to Android doesn't magically make it support iOS actions.


Future<bool> isIOSSimulator() async {
return Platform.isIOS && !(await deviceInfoPlugin.iosInfo).isPhysicalDevice;
return defaultTargetPlatform == TargetPlatform.iOS && !(await deviceInfoPlugin.iosInfo).isPhysicalDevice;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we switch to TargetPlatform, this should be a switch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

default Target Platform is wrong here. It does not reliably say if you're on the iOS simulator: it just says what your preferred UI style is. you can toggle it by pressing "o" at the console in flutter run, for instance.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh yes. But we could still expose something like TargetPlatform currentPlatform somewhere and have that delegate to Platform when io is available


/// The file to decode into an image.
final File file;
final Object file;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we come up with a foundation type for this instead of Object? Maybe for a future PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think anything else (besides dynamic) is actually a breaking change. If the type implements File, then dart:io is still a dependency of the API. If it doesn't, then it breaks callsites.

If we decide to make a breaking change, it would be easier to maintain this API if it took a path String instead.

// This only affects the initial value: the ongoing value is updated as soon
// as any input events are received.
_lastInteractionWasTouch ??= Platform.isAndroid || Platform.isIOS || !WidgetsBinding.instance.mouseTracker.mouseIsConnected;
_lastInteractionWasTouch ??= defaultTargetPlatform == TargetPlatform.android ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

switch

/// parameter to false.
Future<Uint8List> consolidateHttpClientResponseBytes(
HttpClientResponse response, {
Object response, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is ok. we lose type safety too.

final Title ancestorTitle = context.ancestorWidgetOfExactType(Title);
return ancestorTitle?.title ?? Platform.resolvedExecutable.split(Platform.pathSeparator).last;
return ancestorTitle?.title ?? 'Flutter';
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flutter is definitely not correct here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to use Platform.resolved executable on mobile/desktop and "browser" on the web

/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
Image.file(
File file, {
Object file, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems highly undesireable

return true;
}
if (Platform.isIOS) {
if (defaultTargetPlatform == TargetPlatform.iOS) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is definitely not default Target Platform fodder. Changing the UI style to Android doesn't magically make it support iOS actions.

/// The platform that the application is currently running on.
///
/// The [currentHostPlatform] getter returns the current platform.
enum HostPlatform {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not use target platform for this since I think I need an enum for each Platform.is*, regardless of whether it is supported.

@jonahwilliams jonahwilliams requested review from Hixie and dnfield August 26, 2019 22:43
@jonahwilliams
Copy link
Contributor Author

I've updated this PR with some changes to make the types less bad.

  • Introduce a "HostPlatform/currentHostPlatform", really just a targetPlatform that can't be overriden. Might be able to use TargetPlatform enum.
  • Update image.dart to conditional export a child class that has constructors implemented. This requires duplication, only to remove the File type from the API. There is no other way I could think to do this that wasn't breaking or not type safe.
  • Update consolidateHttpResponse to conditional export and exclude HttpClient from the web api


/// The dart:html implementation of [platform.currentHostPlatform].
platform.HostPlatform get currentHostPlatform {
return platform.HostPlatform.browser;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this resolve to specific browsers? E.g. Safari vs Chrome?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we could return the user agent?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That feels more like a plugin of some sort to me.

HostPlatform.browser feels something more like HostPlatform.mobileApplication instead of HostPlatform.androidor HostPlatform.ios. I'm not really sure what the plans for browser support are, but it seems like it would fit better to have HostPlatform.chrome and HostPlatform.safari here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is some slight need for browser detection features, but that is handled entirely in the engine - similarly to how the android/ios shells handle certain platform specific features.

I don't really know that we'd ever need to know safari vs chrome, not to mention with user agent spoofing there are no guarantees that our guess is accurate.

///
/// This is a no-op, as web applications do not have sufficient permissions to
/// exit their own browser.
void exit() { }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this throw, or at least print an error log?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure, we don't call this today otherwise we would crash on exit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a user I might find it confusing if I found this, called it, and had nothing happen. I'd probably go file a bug before reading the documentation.

/// Unlike [TargetPlatform], this value is not intended to be overriden
/// for theming or physics. Instead, it is only for guarding features
/// which are highly platform specific, such as accessibility adaptions.
HostPlatform get currentHostPlatform => _platform.currentHostPlatform;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this a compile time constant so it works with tree shaking?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, Platform.is* is not a constant so the overall getter can't be const. I'm also pretty sure that redirecting a getter through a const won't tree shake for the case of the browser impl

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for another PR then, but it seems like we should be able to just define these as const .fromEnvironments or something.

return parser(await loadString(key));
}

// TODO(ianh): Once the underlying network logic learns about caching, we
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this here.

class NetworkAssetBundle extends asset_bundle.AssetBundle implements asset_bundle.NetworkAssetBundle {
/// Creates an network asset bundle that resolves asset keys as URLs relative
/// to the given base URL.
NetworkAssetBundle(this._baseUrl);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we throw here for a better stack trace?

Can we give them some suggestion of what they should use instead? Just a NetworkImage?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it should just be the same as NetworkImage anyway, but it's not clear to me how it's supposed to be different. Either way, I think we should just suggest using NetworkImage which should be portable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated this to throw. I don't feel great making suggestions here because I don't even know when NetworkAssetBundle would be useful

this.filterQuality = FilterQuality.low,
}) : loadingBuilder = null,
image = null {
throw UnsupportedError('Image.file is not supported on the web.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if we provided a migration suggestion here, such as Use Image.memory instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Iterable<FocusNode> get traversalChildren {
return children.where(
(FocusNode node) => !node.skipTraversal && node.canRequestFocus,
(FocusNode node) => !node.skipTraversal && node.canRequestFocus,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: spacing in this file seems all off. Probably can be reverted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. not sure why these changed. will revert

@jonahwilliams
Copy link
Contributor Author

Updated goldens in flutter_test to conditionally not depend on package:image, which does not support the web

///
/// Used by [debugNetworkImageHttpClientProvider].
typedef HttpClientProvider = HttpClient Function();
typedef HttpClientProvider = Object Function();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is for debug, can we just break this and have it return an interface that implements io when it's available?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to export API

Iterable<FocusNode> get traversalChildren {
return children.where(
(FocusNode node) => !node.skipTraversal && node.canRequestFocus,
(FocusNode node) => !node.skipTraversal && node.canRequestFocus,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still seems off?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Contributor

@Piinks Piinks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Golden changes LGTM. Thanks for doing this. :)
There are some doc changes in the related golden classes that need to be merged.

if (errors.length == 1) {
print('${bold}An error was detected when looking at dart:* libraries within the Flutter package:$reset\n');
} else {
print('${bold}Multiple errors were detected when looking at dart:* librarie within the Flutter package:$reset\n');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo nit:

Suggested change
print('${bold}Multiple errors were detected when looking at dart:* librarie within the Flutter package:$reset\n');
print('${bold}Multiple errors were detected when looking at dart:* libraries within the Flutter package:$reset\n');

if (dart.library.html) '../demo/video_demo_web.dart';
import 'icons.dart';


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: extra line

///
/// The future returned will forward any error emitted by `response`.
///
///
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: extra line


/// The `dart:io` implementation of [asset_bundle.NetworkAssetBundle].
class NetworkAssetBundle extends asset_bundle.AssetBundle implements asset_bundle.NetworkAssetBundle {
/// Creates an network asset bundle that resolves asset keys as URLs relative
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo nit:

Suggested change
/// Creates an network asset bundle that resolves asset keys as URLs relative
/// Creates a network asset bundle that resolves asset keys as URLs relative


import 'goldens.dart';

/// The default [GoldenFileComparator] implementation for `flutter test`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are doc updates that landed in #39082 for GoldenFileComparator and LocalFileComparator.

const String jsEntrypointSourceMapExtension = '.dart.js.map';
const String jsEntrypointArchiveExtension = '.dart.js.tar.gz';
const String digestsEntrypointExtension = '.digests';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: extra line

'--define', 'flutter_tools:entrypoint=release=$release',
'--define', 'flutter_tools:entrypoint=profile=$profile',
'--define', 'flutter_tools:shell=flutterWebSdk=$flutterWebSdk',
'--define', 'flutter_tools:ddc_modules=release=$release'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:

Suggested change
'--define', 'flutter_tools:ddc_modules=release=$release'
'--define', 'flutter_tools:ddc_modules=release=$release',

Size get size {
assert(
context != null,
context != null,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits: It looks like the indentation is off for the changes in this file.

@Piinks
Copy link
Contributor

Piinks commented Sep 3, 2019

FYI: I realize there are already merge conflicts to resolve here, but I noticed #39589 adds dart:io to packages/flutter/lib/src/widgets/focus_manager.dart 👍

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM once the nits are fixed

@jonahwilliams
Copy link
Contributor Author

I'm going to break this PR up so it is landable into cleanups of the various separate libraries, starting with flutter_test.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

framework flutter/packages/flutter repository. See also f: labels. tool Affects the "flutter" command-line tool. See also t: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Web] Dartev compile Error http module

6 participants