Sdk: Move Platform (or similar) to dart:core

Created on 17 Feb 2019  Â·  57Comments  Â·  Source: dart-lang/sdk

While adding Web support to Flutter we'd like to minimize the amount of disruption for Flutter developers, and maximize their code portability. As part of that, we'd like to make as much of the standard library API as possible available to the Web compilation target.

Flutter uses Platform from dart:io to detect current OS. This function should be implementable on the Web via Window.navigator.

/cc @vsmenon @kevmoo

area-library closed-not-planned customer-flutter library-core

Most helpful comment

So, let me make an actual proposal for how to approach this issue. WDYT?

Proposal

dart:platform

We introduce a different, non-dart:io, way of accessing the relevant platform information in a platform independent way. More precisely, we introduce a new core (available on all platforms) platform library to the Dart SDK, dart:platform, containing getters for the properties that we can make available on all platforms.

library dart.platform;

/// A string representing the operating system or platform.
///
/// The *name* of an operating system. 
/// For web platforms, this may be the name of the browser or
/// it may just be "web" if the browser name is unknown.
external String get operatingSystem;

/// A string representing the version of the operating system or platform.
///
/// There is no fixed format for operating system versions, 
/// each operating system may use its own format.
/// For web platforms, this may be the browser's version string as
/// exposed in the `user-agent` property, if that value is available.
external String get operatingSystemVersion;

/// The version of the current Dart runtime.
///
/// The value is a [semantic versioning](http://semver.org)
/// string representing the version of the current Dart runtime,
/// possibly followed by whitespace and other version and
/// build details.
external String get dartVersion;

(Maybe also have a localeName getter if we think this can be supported on the web).

The dart:io library should change its Platform.operatingSystem, Platform.operatingSystemVersion and Platform.version getters to delegate to dart:platform.

The values are not constants. You cannot use them in constant expressions. The values must be provided by the actual run-time running the compiled Dart program.

package:platform

We will also introduce a new package, platform, which exports dart:platform and adds the isLinux, isWindows, etc. getters from dart:io's Platform library, as well as a pre-deprecated version getter (we can release a version 2.0.0 without version immediately as well if we want). This allows existing uses of dart:io to replace the import with import "package:platform/platform.dart" as Platform; and keep running, as long as they only use the compatible members of dart:io's platform.

library pkg.platform;

export "dart:platform";

/// Backwards compatible getter for the Dart SDK version.
///
/// If importing this library using `as Platform`, this value
/// mimics the `Platform.version` property from `dart:io`.
///
/// The [version] value will be removed in the next major version of 
/// this package.
@deprecated
String get version => dartVersion;

/// Whether the operating system is a version of
/// [Linux](https://en.wikipedia.org/wiki/Linux).
///
/// This value is `false` if the operating system is a specialized
/// version of Linux that identifies itself by a different name,
/// for example Android (see [isAndroid]).
bool get isLinux => operatingSystem == "linux";

/// Whether the operating system is a version of
/// [macOS](https://en.wikipedia.org/wiki/MacOS).
bool get isMacOS> (_operatingSystem == "macos";

/// Whether the operating system is a version of
/// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows).
bool get isWindows => operatingSystem == "windows";

/// Whether the operating system is a version of
/// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29).
bool get isAndroid => operatingSystem == "android";

/// Whether the operating system is a version of
/// [iOS](https://en.wikipedia.org/wiki/IOS).
bool get isIOS => operatingSystem == "ios";

/// Whether the operating system is a version of
/// [Fuchsia](https://en.wikipedia.org/wiki/Google_Fuchsia).
bool get isFuchsia => operatingSystem == "fuchsia";

Constants

The getters in dart:platform are not constants. Unless we can definitely promise that all compilation knows, at compile-time, which target operating system it will run on, we cannot make the value constant.

We can, however, introduce best effort constant values for some of the properties. For example, we can promise that const String.fromEnvironment("dart.platform.operatingSystem") always evaluates to either the same value as operatingSystem from dart:platform, or to a recognizable default value (like "" or "unknown").

This will allow code which want to do compile-time constant tree-shaking or conditional imports to do so depending on the target platform if the platform is known at compile-time. If the platform is not known, the code will have to fall back on run-time evaluation.

With that, we can define, e.g., isLinux in package:platform/platform.dart as:

const _os = String.fromEnvironment("dart.platform.operatingSystem");
bool get isLinux => _os == "linux" || _os == "unknown" && operatingSystem == "linux";

That allows a smart compiler to recognize when the value of isLinux can be known at compile-time, and still fall back to the run-time getter when not. It does require inlining and constant-folding the expression, but that's a fairly low bar to set for a compiler.

I recommend making the operating system and Dart version available in this way, and perhaps also the operating system version. Both OS and Dart versions are hard to use as constants because they are strings, and you cannot do any useful comparison except equality at compile-time for strings.

New Properties

We can add more properties which do not exist on dart:io's `Platform' yet.

Perhaps architecture: Values may be ia32, x64, arm64 etc. Not sure if this is possible on the web, though. If not, it just belongs in dart:io instead.

All 57 comments

Are there any other parts of dart:io that you would like to use on the web?

@lrhn We talked a bit about the the Platform class before, any thoughts?

I was going to create issues for other parts separately, as I discover them, but to answer your question, I think we may also need the subset of the File/Directory API that deals with manipulating file paths (example).

I would very much recommend not making dart:io available on platforms that cannot support the majority of the API. The dart:io library is a hodge-podge of Posix OS functionality (filesystems, network sockets, HTTP client and server, etc.). Most of this makes absolutely no sense in a browser, and even if some of the concepts do, the chosen APIs will be incompatible in subtle ways.

It's far safer to just say no to dart:io if it isn't actually working. We have conditional imports which check if dart:io is available, and which might take a branch that tries to use dart:io's HttpClient over dart:html's HttpRequest, if it thinks dart:io is there. If you can import dart:io, but it doesn't work, you break this feature detection. (We may actually allow importing dart:io on the web because it allowed code predating conditional imports to compile, but we should move away from that hack now that we can). If the platform says that dart:io is available, the users should be able to import and use it, with its complete API, not just some very small subset. If it's not available, the user should not import it. The middle ground is very, very slippery for users.

If there is something non-platform specific or generalizable in dart:io, I'd recommend creating a new dart:something library with just that functionality (the name would depend on what it is). I don't expect there to be much, but dart:_http is shared between dart:io and dart:html, it's just not exposed by its own.

If the only functionality you need is the OS name, which is just one string, then I'd just make const String.fromEnvironment("dart.platform.os") always return the identifying string, and not bother with a library.

The isLinux, isMacOS, etc. getters are just simple wrappers that can be put in any package. They're literally:

  static String get operatingSystem => _operatingSystem;
  static final bool isLinux = (_operatingSystem == "linux");

Those booleans don't need to be in the platform libraries, and indeed it makes it harder to port Dart to a new OS when you have to add an isGoodieOS getter to the SDK libraries to properly support it. Operating system is not a closed enum, hardcoding it in source that users can't easily update is a bad idea.

Also, "the subset of the File/Directory API that deals with manipulating file paths" isn't that much, it's basically the one static function FileSystemEntity.parentOf, together with Platform.pathSeparator (which is again a simple string, so you could just do use String.fromEnvironment("dart.platform.pathSeparator"), and the dirname function from package:path does exactly the same thing as parentOf, so you don't actually need it.

@lrhn I think your solution would be fine if we had no existing Dart code in the wild. However, currently in Flutter dart:io is the only way to get platform information and existing code uses it. I don't see a practical way we can remove Platform from dart:io. Nor do I see a way to remove dart:io from Flutter. So if Platform is not supported by DDC/dart2js, then we will end up with the following:

  • Duplicate shared Platform functionality in another dart:* library.
  • Use conditional imports to route between dart:io and the other thing.
  • Not achieve the "no disruption" goal.

While I agree that having dart:io be only partially supported on the Web is not a clean solution, I'm not sure the complexity of duplicate functionality + conditional imports + disruption is any cleaner?

I would move the shared Platform functionality to another dart:* library.
Then unconditionally import that into dart:io and use it for the dart:io Platform class.
That keeps dart:io's Platform available, while also providing a new and direct access to the functionality.

Client libraries can directly and unconditionally import the other dart:* library instead of dart:io if all they need is platform information.
Client libraries that use any other dart:io functionality would still not work on non-IO-supporting platforms anyway.

It will require a migration for code that uses dart:io only for platform flags, in order for them to be compilable to platforms without dart:io. That was always the case, nothing new here. You can't depend on something which isn't there. Claiming it's there means that someone might use it, and that's a run-time failure instead of a compile-time failure.

No matter what we do, making dart:io available on a platform that doesn't support a majority of the functionality is actively harmful, and we should not do that. Making the right size of puzzle pieces and making them fit together is a design task that we can iterate on. That all comes down to which functionality we are talking about. How much is it? How general is it?
If the shared functionality is equivalent to the Platform class declaration, we can just move that in its entirety, and make dart:io export the declaration.

Just creating a fake dart:io is an anti-goal, even in the short run.

Agreed!

On Wed, Mar 6, 2019 at 3:48 AM Lasse R.H. Nielsen notifications@github.com
wrote:

I would move the shared Platform functionality to another dart:* library.
Then unconditionally import that into dart:io and use it for the dart:io
Platform class.
That keeps dart:io's Platform available, while also providing a new and
direct access to the functionality.

Client libraries can directly and unconditionally import the other dart:*
library instead of dart:io if all they need is platform information.
Client libraries that use any other dart:io functionality would still not
work on non-IO-supporting platforms anyway.

It will require a migration for code that uses dart:io only for
platform flags, in order for them to be compilable to platforms without
dart:io. That was always the case, nothing new here. You can't depend on
something which isn't there. Claiming it's there means that someone might
use it, and that's a run-time failure instead of a compile-time failure.

No matter what we do, making dart:io available on a platform that doesn't
support a majority of the functionality is actively harmful, and we should
not do that. Making the right size of puzzle pieces and making them fit
together is a design task that we can iterate on. That all comes down to which
functionality
we are talking about. How much is it? How general is it?
If the shared functionality is equivalent to the Platform class
declaration, we can just move that in its entirety, and make dart:io
export the declaration.

Just creating a fake dart:io is an anti-goal, even in the short run.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dart-lang/sdk/issues/35969#issuecomment-470078382,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AABCimR2BjBJmbpmjW2U6paw_4ZLcKJLks5vT6sggaJpZM4a_TIG
.

@lrhn

Ok, I think I'm sold on the idea that if we give access to a dart:* library then developers should be able to use most of it and not discover issues after they compile their code. Moving common functionality into a new dart:* library addresses the duplication issue and removes the need for conditional imports for newly written code. What feels wrong to me is that us adding another compilation target to the Flutter platform immediately requires that we refactor core libraries. It's like adding another compilation target to LLVM and immediately require that C++ refactors its standard library because some subset of it is not supported by the new target.

Perhaps we are looking at the problem from different angles. I think there is middle ground though.

The way I see it is we have Flutter that currently provides access to a bunch of dart:* libraries, and there's tons of Flutter code written in the wild. Now we want to add Web as a sibling to Android and iOS as another compiler option, i.e. flutter build web instead of flutter build apk. Now, I understand the limitations of the Web, and what we are realizing now is that with the Web in the mix we need to rethink what core libraries we should be making available in Flutter and we need to find ergonomic solutions for writing platform-specific code. However, Flutter Web is in a very early stage, and our current goal is to make it work on the Web as well as possible. Once we have that, we can also make it as ergonomic as possible. The two problems seem very much orthogonal to each other, and both require some work (e.g. according to your proposal there needs to be some code migration). So I think that going straight for the cleanest most ergonomic design is not the right choice in terms of sequencing work. It creates a dependency between these two aspect and slows down our progress.

From your angle, it seems the risk is messing up existing Dart ecosystem by introducing dart:io to Web code. If that is so, then I think there is a solution for that:

What if we make dart:io available _only_ to the flutter platform, and _not_ to existing Dart Web code (e.g. AngularDart apps)? This will let us make progress with Flutter Web while in parallel we can plan how to refactor dart:* libraries to make them more ergonomic to use in the presence of platforms that are dart:io-capable and those that are not.

Packages will have to do work to support the web anyway. Instead of adding special-case hacks, let's figure out a common package (or dart: lib, I guess). We can create lints/hints to help folks start migrating now.

I think it's a little early for a migration. There's a lot of value in learning from the current Flutter ecosystem in a non-disruptive way before we ask people to start adapting their code. This will give us time to design a solution and plan out migration steps. I just want to keep it independent from the goal of launching web support in Flutter.

What if we make dart:io available only to the flutter platform, and not to existing Dart Web code (e.g. AngularDart apps)?

Well, we can't stop you from doing that. The Flutter tools can add any dart:* library to their platform, like they already add dart:flutter. Adding dart:io to web compilation is just not something we'd want to encourage, or make changes in the SDK to enable.

You will risk that configurable imports might choose dart:io over dart:html for some features, and they then fail. And again, you can break protocol and make dart.library.io be false even though dart:io is available, which risks people doing configurable imports to import dart:io if dart.library.flutter.

Well, we can't stop you from doing that. The Flutter tools can add any dart:* library to their platform.

I'm not suggesting a mutiny, just trying to find an option that satisfies our constraints. My understanding was that dart2js/DDC implemented some compiler intrinsics around dart:io, so you can import it but the compiler crashes if it detects that you are actually using it. Is that not the case?

Adding it to the Flutter's build of the SDK makes sense. This way we won't be polluting existing Web code or the Dart SDK. It will also temporarily unblock us until we decide on the long-term solution.

@jonahwilliams How straightforward is it to add a custom dart:io implementation to the Flutter's build of the Dart SDK?

You will risk that configurable imports might choose dart:io over dart:html for some features.

As of now whether you use dart:io or dart:html the risk level is the same. We don't yet know how/if dart:html will be available to Flutter apps. We currently expose it but not because we decided that this is the best way to give apps access to the platform but because it was the easiest thing to do. However, of the two, I think the choice of dart:io is better because of backwards-compatibility.

@jonahwilliams How straightforward is it to add a custom dart:io implementation to the Flutter's build of the Dart SDK?

It's not straightforward at all really. Presumably we could only really support a subset of dart:io, so this would need to be done by supplying our own patch files, building our own dartdevc sdk, and so on. Then there are the code size concerns, et cetera.

Ultimately it would be a lot of duplicated work, and if we're only realistically going to support 1 or 2 types then I'm more in favor of using conditional imports. While there are plenty of existing flutter apps that make heavy use of dart:io, there are no existing flutter apps which compile to the web so I don't see this as breaking. Ultimately a user attempt to port their application to run on the web will need to make some adjustments regardless.

I think we should go ahead and move Platform from dart:io (and forward) as discussed above. Any objections?

@vsmenon Is there a concrete proposal for where exactly to move it?

@lrhn - going off of your suggestion above. Do you have a preference on where to move it?

Bumping for D25 consideration.

Ping - @lrhn

@leafpetersen @lrhn - can you please review whether we can go forward with this?

@lrhn will follow up on exactly what functionality needs to be made available.

If we introduce a shared platform library exposing a number of shared properties, we should

  • Not make it a class with static members, just make it top-level members.
  • Figure out exactly which properties actually make sense, and keep it at the minimum of those.

So, which properties do we actually want a dart:plaform library to have?

  • operatingSystem - what we are really asking for
  • operatingSystemVersion - can probably dream up something in a browser. Chrome version?
  • localeName - if available
  • script - might make sense for reference and debugging, even if it's a compile-time URI. I'd probably not include it.
  • version. Useful for debugging purposes.

Is that it?

I wouldn't include:

  • pathSeparator - there are no paths if there is no file-system.
  • numberOfProcessors. That makes on sense in a browser.
  • localHostName. That makes on sense in most cases (I'm not sure my stand-alone Windows machine has a name, and it's definitely not available in a browser).
  • environment. Not reasonable in a browser.
  • executable. It's a path. There is no executable.
  • resolvedExecutable. Ditto.
  • executableArguments. No executable.
  • packageRoot. Deprecated
  • packageConfig. Not really useful at run-time for AOT compiled programs

I would not include all the isOSName bools. Enumerating an open-ended set in the SDK code is a bad idea. I'd delegate that to a package instead, which can be maintained independently and add new operation systems as needed. It's not something the SDK should be in charge of. Just expose the operatingSystem string, and let others wrap it.

Is this adequate?

All the properties that I would include are strings.

Instead of introducing a very small and restricted library, I still think it would make more sense to expose those strings as Dart defines accessible through String.fromEnvironment. That would also make them constants, which is probably useful. (Can they be constant - that is, do we know the target operating system at constant evaluation time? Otherwise that would preclude using the environment, except if we start introducing non-const-only environment values, which would probably be weird).

Then the user-accessible package could just do:

const bool isFuchsia = "fuchsia" == String.fromEnvironment("dart.platform.os");

and not have to import any dart:platform library.

@ferhatb @yjbanov @jonahwilliams - thoughts here from a Flutter perspective?

If we're not just moving the class, I suggest we bump to D26.

This does not solve the original problem of giving developers a non-breaking path from Flutter for mobile to Flutter for web. It may be a good change otherwise, although I don't have a clear idea of what should be required of a hypothetical alternative to dart:io/Platform. So I'll abstain.

There is no completely non-breaking path which does not mean introducing dart:io on web platforms. I think that is a very bad idea, sacrificing long-term design for short-term convenience.

If we do not do that, people would at least have to import dart:platform instead.
If they have to change the import, they could also change it to package:platform and have that be a backwards compatbility library exposing a Platform class with static members taking values from dart:plaform. Then migration would still be only one import change.

+1, asking users to change their code to import dart:platform seems more
healthy option. Is it possible to gracefully deprecate
re-exporting dart:platform from dart:io ?

On Wed, Aug 21, 2019 at 2:53 AM Lasse R.H. Nielsen notifications@github.com
wrote:

There is no completely non-breaking path which does not mean introducing
dart:io on web platforms. I think that is a very bad idea, sacrificing
long-term design for short-term convenience.

If we do not do that, people would at least have to import dart:platform
instead.
If they have to change the import, they could also change it to
package:platform and have that be a backwards compatbility library
exposing a Platform class with static members taking values from
dart:plaform. Then migration would still be only one import change.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dart-lang/sdk/issues/35969?email_source=notifications&email_token=ABGTTKWSYPDY3DI2NSDGK63QFUGAFA5CNFSM4GX5GIDKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD4ZDZJY#issuecomment-523386023,
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABGTTKS6ZIMV7RZMDOO4P33QFUGAFANCNFSM4GX5GIDA
.

This does not solve the original problem of giving developers a non-breaking path from Flutter for mobile to Flutter for web.

@yjbanov

I'm a little unclear on what the request is, with respect to breaking-ness. There are two things I can imagine you wanting:

1) Enable writing code that works with Platform concepts and works on web and mobile.
a) Without breaking existing code that works with Platform via dart:io.
b) Or with breaking existing code that works with Platform via dart:io.
2) Enable existing code that works with Platform concepts by importing dart:io to work on web and mobile.

Which is it it that you want? The former (1a or 1b) seem eminently solvable by any of a number of approaches. The latter (2) seems solvable by exactly one: making dart:io available on the web.

+1, asking users to change their code to import dart:platform seems more
healthy option. Is it possible to gracefully deprecate
re-exporting dart:platform from dart:io ?

The API of dart:platform will not be the same as the Platform class in dart:io. There are several members of that class which are not applicable to all platforms, and static-only classes are a bad design (and against our design guide too).
So, dart:io won't export dart:platform directly, but it might implement Platform.operatingSystem by forwarding to, say an operatingSystem getter, from dart:platform.

We can deprecate the dart:io Platform members which can also be accessed through dart:platform, but I'm not sure it's important. Any code importing dart:io should be considered incompatible with the web anyway.

@leafpetersen The request is that users are able to add Web support to their existing Flutter apps without a code migration.

It seems the only way to do that is to implement some minimal subset of the Flutter's API surface. This opens the dart:io question. This library is used in Flutter's own and in users' code. If we do not implement dart:io at all, our API surface will be incomplete, and adding Web support to an existing Flutter app becomes a migration. According to our analysis the most used API from dart:io is the Platform class. If we just add dart:io/Platform to the Web it becomes much less of a migration for our users.

However, as I explain in https://github.com/dart-lang/sdk/issues/35969#issuecomment-470357998 and https://github.com/dart-lang/sdk/issues/35969#issuecomment-471027084, I'm not against the idea of designing and migrating to a better API. I simply prefer that we keep the two issues separate and work on them on separate schedules. Otherwise, the design and migration to a new API would lie on the critical path for Flutter for web. We would likely have to rewrite our own code and ask our users to rewrite theirs using configurable imports (example), just to ask them to rewrite it again when the alternative API ships. Basically, for some time we'll have a deprecated API and not-yet-ready API (insert favorite mem here). If we added dart:io/Platform now, users wouldn't have to rewrite their code, and when the alternative API is ready we'll ask them to migrate once.

There's another possibility. If the Dart team has high confidence that a new API can be designed and shipped quickly, we could wait until that lands and ask our users to please bear with us. The number of open questions about what this library should look like (see https://github.com/dart-lang/sdk/issues/35969#issuecomment-521656291) makes we a little worried though.

@leafpetersen I guess option 2, Enable existing code that works with Platform concepts by importing dart:io to work on web and mobile, is the closest to what I'm asking for. Notably, this does not include existing code that uses file or network features of dart:io.

Ok, trying again. After discussion, it's clear that @yjbanov has two desires:

  • Future flutter code can be written that uses the platform independent parts of dart:io and work on flutter web and flutter native
  • Existing flutter code that uses the platform independent parts of dart:io can be reused, unmodified on flutter web

The first is easy to satisfy, so it doesn't need more discussion. The second is satisfied only by making import dart:io available on flutter web.

My personal take is that this is a bad idea. It's the easy short term solution, at the cost of long term fragmentation. You now have packages that look like they work on the web, but don't (and may only fail on some code paths that you may not encounter in simple tests). You're stubbing out this large API surface with foot guns just so that you can get access to a tiny subset of the functionality that can be made work. This is a huge code smell to me.

If you do take this path I think it will be worse if we only expose dart:io on flutter web, and not on the regular web platform. Because in that scenario, you have triple fragmentation. A given package may:

  • Work on flutter VM
  • Compile on flutter Web, but crash on some code paths
  • Flat out fail to compile on Dart Web.

I don't think pub will successfully sort this all out for you. So it may be less bad simply to expose it on all platforms, so at least you reduce it to the first two failure modes.

The multiple failure mode situation is not new in Flutter. We already have plugins that work only on Android or only on iOS. Those plugins _may_ work on the desktop, but there's no guarantee until you try and see what happens. This is despite the fact that you have full implementation of dart:io on all platforms. Issues you hit are all runtime issues:

  • Certain OSes like to lock files
  • Certain OSes use \ path separator, others use /
  • Different OSes use different file system organization
  • Numerous Process-related issues
  • Permissions work differently across platforms

OTOH, you can have a plugin that does work on all platforms, including the Web. You just avoid dart:io via configurable imports. So having dart:io imports in your code is not an indicator of incompatibility with the Web.

This is something that pub indeed should sort out, and when it does, it should consider Flutter for web as well. Plugin developers should list platforms that their plugins are built for. Otherwise, there's no guarantee with or without dart:io.

The problem I'm trying to solve is the barrier to entry for existing Flutter code onto the Flutter for web. It is for developers who want to add web support to their existing plugins. I don't want to ask them to break their APIs.

I would also treat Flutter for web as something completely different from the classic Dart for web. I do not expect much of the Flutter for web code to work in the classic mode simply because a lot of use-cases will reach for dart:ui (directly or transitively via package:flutter) fairly quickly. So I would encourage that we draw a clear line between the two. Otherwise, indeed, there could be much confusion. Perhaps we should treat "Flutter" and "Web" as top-level categories, and Flutter for web a subcategory of Flutter.

I would like to also point out that I've seen several documents floating around which propose removing or drastically reducing the scope of conditional imports/exports. This would be more or less impossible if they were the only solution to writing code that worked across flutter's supported platforms

You just avoid dart:io via configurable imports. So having dart:io imports in your code is not an indicator of incompatibility with the Web.

These two sentences are in direct contradiction. You avoid dart:io via configurable imports because having dart:io in your code is an indicator of incompatibility with the web. The fact that you avoid it by using "#ifdef include" doesn't change anything.

I don't want to ask them to break their APIs.

I don't understand where the question of API breakage comes up? Changing import dart:io show Platform to import dart:core show Platform doesn't change my API.

I would like to also point out that I've seen several documents floating around which propose removing or drastically reducing the scope of conditional imports/exports. This would be more or less impossible if they were the only solution to writing code that worked across flutter's supported platforms

@jonahwilliams I don't understand where you're going with this. As far as I know, nothing being discussed uses configurable imports. The question is:

  • Do we move Platform out of dart:io?
  • Or do we make dart:io "work" on the web.

Either solution eliminates the need for configurable imports to access Platform.

I am strongly against making dart:io "work" on the web. It's a dirty hack which will introduce technical debt, not a proper long-term solution.

Exposing the existing Platform class, with its static getters, in a separate library is still bad because it contains more than what is reasonably supported by all platforms, just as the dart:io library contains more than reasonable.

The units to move somewhere else are the individual members which are generally useful.
Then Platform can be rewritten to delegate to those, keeping existing dart:io using libraries functioning, while also providing a more direct access to the things that can be used cross-platform.

That requires rewrite of code which unconditionally imports dart:io. That's not surprising, that library was never intended to be compatible with web compilation. It doesn't have to break any API, but code compiled against dart:io, and which uses only features that are also meaningful on the web, will need to be compiled against something else providing those features.

Even if we introduce dart:platform with top-level getters, it's easy to write a package:platform (or dart:flutter-platform) which has essentially the same API as part of dart:io, and then the Flutter code can just import that instead of dart:io and keep running. That's the simplest migration strategy available: Change one import.

The only thing simpler is to make dart:io available directly, and I do not believe that to be a reasonable design choice.

+100 to Lasse. We shouldn't have dart:io

On Wed, Sep 4, 2019 at 2:15 AM Lasse R.H. Nielsen notifications@github.com
wrote:

I am strongly against making dart:io "work" on the web. It's a dirty hack
which will introduce technical debt, not a proper long-term solution.

Exposing the existing Platform class, with its static getters, in a
separate library is still bad because it contains more than what is
reasonably supported by all platforms, just as the dart:io library
contains more than reasonable.

The units to move somewhere else are the individual members which are
generally useful.
Then Platform can be rewritten to delegate to those, keeping existing
dart:io using libraries functioning, while also providing a more direct
access to the things that can be used cross-platform.

That requires rewrite of code which unconditionally imports dart:io.
That's not surprising, that library was never intended to be compatible
with web compilation. It doesn't have to break any API, but code compiled
against dart:io, and which uses only features that are also meaningful
on the web, will need to be compiled against something else providing those
features.

Even if we introduce dart:platform with top-level getters, it's easy to
write a package:platform (or dart:flutter-platform) which has essentially
the same API as part of dart:io, and then the Flutter code can just
import that instead of dart:io and keep running. That's the simplest
migration strategy available: Change one import.

The only thing simpler is to make dart:io available directly, and I do
not believe that to be a reasonable design choice.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/dart-lang/sdk/issues/35969?email_source=notifications&email_token=AAAEFCRMUQQFXGOCRBI6EQTQH54DNA5CNFSM4GX5GIDKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD525N7I#issuecomment-527816445,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAAEFCSVIGGWKLVNNMGTO4TQH54DNANCNFSM4GX5GIDA
.

So, let me make an actual proposal for how to approach this issue. WDYT?

Proposal

dart:platform

We introduce a different, non-dart:io, way of accessing the relevant platform information in a platform independent way. More precisely, we introduce a new core (available on all platforms) platform library to the Dart SDK, dart:platform, containing getters for the properties that we can make available on all platforms.

library dart.platform;

/// A string representing the operating system or platform.
///
/// The *name* of an operating system. 
/// For web platforms, this may be the name of the browser or
/// it may just be "web" if the browser name is unknown.
external String get operatingSystem;

/// A string representing the version of the operating system or platform.
///
/// There is no fixed format for operating system versions, 
/// each operating system may use its own format.
/// For web platforms, this may be the browser's version string as
/// exposed in the `user-agent` property, if that value is available.
external String get operatingSystemVersion;

/// The version of the current Dart runtime.
///
/// The value is a [semantic versioning](http://semver.org)
/// string representing the version of the current Dart runtime,
/// possibly followed by whitespace and other version and
/// build details.
external String get dartVersion;

(Maybe also have a localeName getter if we think this can be supported on the web).

The dart:io library should change its Platform.operatingSystem, Platform.operatingSystemVersion and Platform.version getters to delegate to dart:platform.

The values are not constants. You cannot use them in constant expressions. The values must be provided by the actual run-time running the compiled Dart program.

package:platform

We will also introduce a new package, platform, which exports dart:platform and adds the isLinux, isWindows, etc. getters from dart:io's Platform library, as well as a pre-deprecated version getter (we can release a version 2.0.0 without version immediately as well if we want). This allows existing uses of dart:io to replace the import with import "package:platform/platform.dart" as Platform; and keep running, as long as they only use the compatible members of dart:io's platform.

library pkg.platform;

export "dart:platform";

/// Backwards compatible getter for the Dart SDK version.
///
/// If importing this library using `as Platform`, this value
/// mimics the `Platform.version` property from `dart:io`.
///
/// The [version] value will be removed in the next major version of 
/// this package.
@deprecated
String get version => dartVersion;

/// Whether the operating system is a version of
/// [Linux](https://en.wikipedia.org/wiki/Linux).
///
/// This value is `false` if the operating system is a specialized
/// version of Linux that identifies itself by a different name,
/// for example Android (see [isAndroid]).
bool get isLinux => operatingSystem == "linux";

/// Whether the operating system is a version of
/// [macOS](https://en.wikipedia.org/wiki/MacOS).
bool get isMacOS> (_operatingSystem == "macos";

/// Whether the operating system is a version of
/// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows).
bool get isWindows => operatingSystem == "windows";

/// Whether the operating system is a version of
/// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29).
bool get isAndroid => operatingSystem == "android";

/// Whether the operating system is a version of
/// [iOS](https://en.wikipedia.org/wiki/IOS).
bool get isIOS => operatingSystem == "ios";

/// Whether the operating system is a version of
/// [Fuchsia](https://en.wikipedia.org/wiki/Google_Fuchsia).
bool get isFuchsia => operatingSystem == "fuchsia";

Constants

The getters in dart:platform are not constants. Unless we can definitely promise that all compilation knows, at compile-time, which target operating system it will run on, we cannot make the value constant.

We can, however, introduce best effort constant values for some of the properties. For example, we can promise that const String.fromEnvironment("dart.platform.operatingSystem") always evaluates to either the same value as operatingSystem from dart:platform, or to a recognizable default value (like "" or "unknown").

This will allow code which want to do compile-time constant tree-shaking or conditional imports to do so depending on the target platform if the platform is known at compile-time. If the platform is not known, the code will have to fall back on run-time evaluation.

With that, we can define, e.g., isLinux in package:platform/platform.dart as:

const _os = String.fromEnvironment("dart.platform.operatingSystem");
bool get isLinux => _os == "linux" || _os == "unknown" && operatingSystem == "linux";

That allows a smart compiler to recognize when the value of isLinux can be known at compile-time, and still fall back to the run-time getter when not. It does require inlining and constant-folding the expression, but that's a fairly low bar to set for a compiler.

I recommend making the operating system and Dart version available in this way, and perhaps also the operating system version. Both OS and Dart versions are hard to use as constants because they are strings, and you cannot do any useful comparison except equality at compile-time for strings.

New Properties

We can add more properties which do not exist on dart:io's `Platform' yet.

Perhaps architecture: Values may be ia32, x64, arm64 etc. Not sure if this is possible on the web, though. If not, it just belongs in dart:io instead.

The proposed design looks good to me. I propose we solve #35705 at the same time by providing a mechanism for detecting the operating system at compile time. This will be useful for tree shaking, OS-dependent constants, conditional imports, etc.

I propose we use String.fromEnvironment to implement it, e.g.:

String.fromEnvironment("dart.platform.operating-system")

It will return 'unknown' if not known at compile time (which I imagine could happen in some agnostic bytecode). Although this information will be available in the the major modes used by our customers (VM JIT knows the OS, VM AOT also knows the OS, dart2js will return 'web').

This could be exposed as a constant in dart:platform or package:dart:

const String constOperatingSystem =
    String.fromEnvironment("dart.platform.operating-system");

Code can take advantage of the constant whenever it is available for efficient tree shaking, and fall back to the runtime check. For instance package:platform can provide:

bool get isLinux => constOperatingSystem == "linux" ||
    (constOperatingSystem == "unknown" && operatingSystem == "linux");

The same can be done for:

  • The Dart SDK version.
  • The CPU architecture if known (ia32, x64, arm, arm64, ...) (arguably useful as there is no other way to get this information, though it probably belongs in dart:io or just via String.fromEnvironment).

Another option, which should really be the default option as @munificent pointed out, is to not introduce a dart:platform at all, and simply introduce the desired data into dart:html.

We can then still use a package:platform package to provide access to the information which is available on both platforms using conditional imports of libraries using either dart:io or dart:html.
That package can still add all the extra trimmings it wants to (mocking for testing, which would encourage people to use it, even for single-platform development.
Example: https://github.com/lrhn/platform

This scales to other things that are available on different platforms, but with different feature selection. We can create a "lowest common denominator" implementation package that provides access to all the features that are available everywhere.
(It doesn't scale indefinitely because the lowest common denominator can become very small).

Another option, which should really be the default option as @munificent pointed out, is to not introduce a dart:platform at all, and simply introduce the desired data into dart:html.

We can then still use a package:platform package to provide access to the information which is available on both platforms using conditional imports of libraries using either dart:io or dart:html.

I like this idea!

@lrhn

but with different feature selection.

Do you have an idea for what this feature selection would look like? The only thing that comes to mind is this.

Do you have an idea for what this feature selection would look like? The only thing that comes to mind is this.

I believe that @lrhn is referring to things for which a subset of the full features are available on some platforms. So the File interface is a concrete one which has been brought up - the async APIs might be reasonable to support on the web, but not the sync APIs. So the package would want to offer up only the things which are in the intersection of the features available on the individual platforms.

Oh, I totally misread that, sorry. I thought there would be one library that allows dynamically checking which features are supported. But _could_ we support dynamic feature selection? This way we would only need one library that scales to all platforms.

If we have different implementations depending on the supported library, dart:html or dart:io, then that implementation can also add features which only work on one of the platforms, and ways to query the supported feature set.

It's usually a bad idea. Users won't check individual features, they'll just check which platform they are on, and use whatever features they know work on that platform. Or forget to check, and only work on one platform. Or wait for someone else to build an abstraction library on top which only exposes the features that are available everywhere.

We could make the "GeneralFile" class expose the dart:io File object indirectly, allowing people to break the abstraction if they need to, and know what they are doing. Again, that code will only work if you import dart:io, and unless you are also using conditional imports, you won't be needing the general library then.

Have we reached agreement here to move forward with the option described in https://github.com/dart-lang/sdk/issues/35969#issuecomment-530670012?

@lrhn

It's usually a bad idea. Users won't check individual features, they'll just check which platform they are on, and use whatever features they know work on that platform. Or forget to check, and only work on one platform. Or wait for someone else to build an abstraction library on top which only exposes the features that are available everywhere.

I might have a solution for that: https://github.com/dart-lang/language/issues/416

Have we reached agreement here to move forward with the option described in #35969 (comment)?

I'm still worried that we'll go into all this trouble carefully refactoring the core libraries and telling our users to migrate, and in the end we'll end up with platforms that can only implement these libraries partially leaving us exactly where we started. We can only predict the future so far. Any rigid structure we come up with today is bound to get outdated.

I think there might be a way to have something more malleable that changes its shape as we onboard more platforms. However, it will require some design work, perhaps new language features. We might be better off doing something that feels hacky short-term and invest our energy into something that lasts.

I'm still worried that we'll go into all this trouble carefully refactoring the core libraries and telling our users to migrate, and in the end we'll end up with platforms that can only implement these libraries partially leaving us exactly where we started. We can only predict the future so far. Any rigid structure we come up with today is bound to get outdated.

The proposal in that comment isn't to refactor the core libraries, it's to expose the platform independent parts in a package. One of the key benefits of this is that packages are versioned, so there's a story for changing the functionality provided by the package if and when we add platforms.

Is this a D26 issue ?

Not D26, but I believe we need this no later than D27.

Pushing from D27 to next as we haven't nailed down what / requirements yet.

litle case:
dart import 'player_base.dart' if (dart.platform.html) 'web_player.dart' if (Platform.isWindows) 'winmm_player.dart' if (Platform.isLinux) 'alsa_player.dart' if (Platform.isMacOS) 'audiotoolbox_player.dart' if (Platform.isAndroid) 'audiotrack_player.dart' // ... ;

This case is interesting because it requires environment declarations in order to drive configurable imports.
That is an option too for the platform/OS value: Define a set of environment keys, like dart.platform.os which any implementation can set to match their run-time.

import 'player_base.dart'
   if (dart.library.html) 'web_player.dart'  // Already works!
   if (dart.platform.os == "windows") 'winmm_player.dart'
   if (dart.platform.os == "linux") 'also_player.dart'
   // ...
   ;

That comes with some complications too.

It means that platform agnostic compilation is impossible when you have to specify the target platform at compile-time, ... which you do because it affects which libraries are imported and compiled.

On the other hand, any new platform (like the sweet SugarOS platform) can introduce itself by simply setting the platform OS variable to, fx, "sugaros", and then code can slowly adapt to that also existing).

It's only an approach which works for fairly static things, and not something we can do for, say, supporting File operations.

OS specific imports are not something we want to support on the build system side. We will be doubling our "platforms" for compilation with NNBD, multiplying that again by the number of operating systems is not feasible.

cc @jakemac53

Just want to chime in and mention that if this value was const like kIsWeb or kReleaseMode that would seem to mean that code could be optimized out when building for different platforms, reducing the amount of code included but not used in the final binary/build.

Note that configurable imports also enable tree-shaking, but actually in a much more powerful sense. Only the matching import ever exists in the program _at all_. The other code never has to be tree shaken by the compiler because it is never imported. This includes all the transitive deps of each platform specific import.

@lrhn - should we close this in favor of a different issue re package:platform?

I believe we should, @lrhn ?

The current plan is to provide a package which supports the behavior of Platform.operatingSystem, but which also works in a browser. See http://github.com/dart-lang/os_detect.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

DartBot picture DartBot  Â·  3Comments

bergwerf picture bergwerf  Â·  3Comments

xster picture xster  Â·  3Comments

DartBot picture DartBot  Â·  3Comments

ranquild picture ranquild  Â·  3Comments