From 754f048855ff214bf152c9f93257fec51c6aa28c Mon Sep 17 00:00:00 2001 From: "Lasse R.H. Nielsen" Date: Thu, 28 Sep 2023 08:28:26 +0200 Subject: [PATCH] Use the VM's "platform-const" feature to achieve tree-shaking. (dart-lang/os_detect#31) * Use the VM's "platform-const" feature to achieve tree-shaking. By using the VM's `@pragma("vm:platform-const")` annotation, and `Platform` members annotated with it, ensure that the `isLinux` and `operatingSystem` values are also known at compile-time, even if they cannot be annotated as platform-constants. This is done by having a class per known operating system, and making sure to only create instances of those classes guarded by platform-constants (or real constants), so that if nobody goes out of their way to create a custom instance, which they should only do for testing, all but one of those platform-specific classes can be tree-shaken. At that point, checks like `is MacOS` (one of the types) can be statically determined to be false if `MacOS` was tree-shaken, and `.id` can be statically resolved to a single final variable with a known value. * Update test matrix to start at 3.0.0 * Add example discussing tree-shaking. * Format * Address comments. * Don't refer to unimported name in docs. --- .../.github/workflows/test-package.yml | 2 +- pkgs/os_detect/CHANGELOG.md | 3 +- pkgs/os_detect/analysis_options.yaml | 35 ++-- pkgs/os_detect/bin/os_detect.dart | 40 ++++ pkgs/os_detect/example/example.dart | 20 +- pkgs/os_detect/example/tree_shaking.dart | 29 +++ pkgs/os_detect/lib/os_detect.dart | 44 ++++- pkgs/os_detect/lib/override.dart | 98 +--------- pkgs/os_detect/lib/src/os_kind.dart | 101 ++++++++++ pkgs/os_detect/lib/src/os_override.dart | 180 ++++++++++++++++++ pkgs/os_detect/lib/src/osid_html.dart | 7 +- pkgs/os_detect/lib/src/osid_io.dart | 29 ++- pkgs/os_detect/lib/src/osid_unknown.dart | 22 ++- pkgs/os_detect/pubspec.yaml | 7 +- pkgs/os_detect/test/osid_test.dart | 16 +- 15 files changed, 500 insertions(+), 133 deletions(-) create mode 100644 pkgs/os_detect/bin/os_detect.dart create mode 100644 pkgs/os_detect/example/tree_shaking.dart create mode 100644 pkgs/os_detect/lib/src/os_kind.dart create mode 100644 pkgs/os_detect/lib/src/os_override.dart diff --git a/pkgs/os_detect/.github/workflows/test-package.yml b/pkgs/os_detect/.github/workflows/test-package.yml index a010de99..fed0bdcd 100644 --- a/pkgs/os_detect/.github/workflows/test-package.yml +++ b/pkgs/os_detect/.github/workflows/test-package.yml @@ -47,7 +47,7 @@ jobs: matrix: # Add macos-latest and/or windows-latest if relevant for this package. os: [ubuntu-latest, windows-latest, macos-latest] - sdk: [2.18.0, dev] + sdk: [3.0.0, dev] steps: - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 - uses: dart-lang/setup-dart@d6a63dab3335f427404425de0fbfed4686d93c4f diff --git a/pkgs/os_detect/CHANGELOG.md b/pkgs/os_detect/CHANGELOG.md index 0328fbb9..47355ebe 100644 --- a/pkgs/os_detect/CHANGELOG.md +++ b/pkgs/os_detect/CHANGELOG.md @@ -1,6 +1,7 @@ ## 2.0.2-dev -- Require Dart 2.18 +- Require Dart 3.0 +- Make work with VM's platform-constants. ## 2.0.1 diff --git a/pkgs/os_detect/analysis_options.yaml b/pkgs/os_detect/analysis_options.yaml index d32a8aab..9d91fd3f 100644 --- a/pkgs/os_detect/analysis_options.yaml +++ b/pkgs/os_detect/analysis_options.yaml @@ -14,42 +14,55 @@ linter: - avoid_catching_errors - avoid_classes_with_only_static_members - avoid_dynamic_calls + - avoid_empty_else - avoid_private_typedef_functions - avoid_redundant_argument_values - - avoid_returning_null_for_future + - avoid_relative_lib_imports - avoid_returning_this + - avoid_shadowing_type_parameters + - avoid_types_as_parameter_names - avoid_unused_constructor_parameters - avoid_void_async - - cancel_subscriptions + - await_only_futures + - camel_case_extensions + - collection_methods_unrelated_type - comment_references + - curly_braces_in_flow_control_structures - directives_ordering - - join_return_with_assignment + - empty_catches + - file_names + - hash_and_equals - lines_longer_than_80_chars - literal_only_boolean_expressions - missing_whitespace_between_adjacent_strings - no_adjacent_strings_in_list + - no_duplicate_case_values - no_runtimeType_toString - omit_local_variable_types - only_throw_errors - package_api_docs - prefer_asserts_in_initializer_lists - - prefer_const_constructors - prefer_const_declarations - - prefer_expression_function_bodies - - prefer_final_locals + - prefer_generic_function_type_aliases + - prefer_is_empty + - prefer_is_not_empty + - prefer_iterable_whereType - prefer_relative_imports - prefer_single_quotes + - prefer_typing_uninitialized_variables + - provide_deprecation_message - sort_pub_dependencies - test_types_in_equals - throw_in_finally - type_annotate_public_apis - unawaited_futures - - unnecessary_await_in_return - unnecessary_lambdas + - unnecessary_overrides - unnecessary_parenthesis - - unnecessary_raw_strings - unnecessary_statements - - use_if_null_to_convert_nulls_to_bools + - unrelated_type_equality_checks + - use_is_even_rather_than_modulo + - void_checks - use_raw_strings - - use_string_buffers - - use_super_parameters + + diff --git a/pkgs/os_detect/bin/os_detect.dart b/pkgs/os_detect/bin/os_detect.dart new file mode 100644 index 00000000..e9e6fc15 --- /dev/null +++ b/pkgs/os_detect/bin/os_detect.dart @@ -0,0 +1,40 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Prints the operating system detected by the current compilation environment. +library pkg.os_detect.run; + +import 'package:os_detect/os_detect.dart' as os_detect; + +void main() { + final knownName = knownOSName(); + print('OS name : ${os_detect.operatingSystem} ' + '${knownName != null ? '($knownName)' : ''}'); + print('OS version : ${os_detect.operatingSystemVersion}'); +} + +String? knownOSName() { + if (os_detect.isAndroid) { + return 'Android'; + } + if (os_detect.isBrowser) { + return 'Browser'; + } + if (os_detect.isFuchsia) { + return 'Fuchsia'; + } + if (os_detect.isIOS) { + return 'iOS'; + } + if (os_detect.isLinux) { + return 'Linux'; + } + if (os_detect.isMacOS) { + return 'MacOS'; + } + if (os_detect.isWindows) { + return 'Windows'; + } + return null; +} diff --git a/pkgs/os_detect/example/example.dart b/pkgs/os_detect/example/example.dart index 331cf453..4a159d86 100644 --- a/pkgs/os_detect/example/example.dart +++ b/pkgs/os_detect/example/example.dart @@ -6,7 +6,21 @@ import 'package:os_detect/os_detect.dart' as os_detect; void main() { print(''' - OS: ${os_detect.operatingSystem} -OS Version: ${os_detect.operatingSystemVersion} -'''); + OS ID: ${os_detect.operatingSystem} +OS Version: ${os_detect.operatingSystemVersion}'''); + if (os_detect.isAndroid) { + print(' OS Type: Android'); + } else if (os_detect.isBrowser) { + print(' OS Type: Browser'); + } else if (os_detect.isFuchsia) { + print(' OS Type: Fuchsia'); + } else if (os_detect.isIOS) { + print(' OS Type: iOS'); + } else if (os_detect.isLinux) { + print(' OS Type: Linux'); + } else if (os_detect.isMacOS) { + print(' OS Type: MacOS'); + } else if (os_detect.isWindows) { + print(' OS Type: Windows'); + } } diff --git a/pkgs/os_detect/example/tree_shaking.dart b/pkgs/os_detect/example/tree_shaking.dart new file mode 100644 index 00000000..987f3ddb --- /dev/null +++ b/pkgs/os_detect/example/tree_shaking.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Try compiling this example with (if on Linux): +// +// dart compile exe --target-os=linux tree_shaking.dart +// +// then check that "SOMETHING ELSE" does not occur in the +// output `tree_shaking.exe` program, e.g.: +// +// strings tree_shaking.exe | grep SOMETHING +// +// which shows no matches. + +import 'package:os_detect/os_detect.dart' as platform; + +void main() { + if (platform.isLinux) { + print('Is Linux'); + } else { + print('SOMETHING ELSE'); + } + if (platform.operatingSystem == 'linux') { + print('Is Linux'); + } else { + print('SOMETHING ELSE'); + } +} diff --git a/pkgs/os_detect/lib/os_detect.dart b/pkgs/os_detect/lib/os_detect.dart index 2e252eb6..d323f63a 100644 --- a/pkgs/os_detect/lib/os_detect.dart +++ b/pkgs/os_detect/lib/os_detect.dart @@ -5,11 +5,19 @@ /// Information about the current operating system. library pkg.os_detect; -import 'override.dart'; +import 'src/os_override.dart'; /// Identification of the current operating system or platform. /// -/// Specific known operating systems are reported by a unique string. +/// Specific known operating systems are reported by a unique known string, +/// and all the `is` values are computed by comparing the +/// [operatingSystem] string against those known strings. +/// That means that *at most* one of those value can be `true`, +/// and usually precisely one will be `true`. +/// +/// **Notice:** Programs running in a browser will report their +/// operating system as `"browser"`, not the operating system +/// that browser is running on. See [isBrowser]. String get operatingSystem => OperatingSystem.current.id; /// Representation of the version of the current operating system or platform. @@ -24,37 +32,59 @@ String get operatingSystemVersion => OperatingSystem.current.version; /// /// 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]). +/// for example Android (see [isAndroid]), +/// or if the code is running inside a browser (see [isBrowser]). +@pragma('vm:prefer-inline') bool get isLinux => OperatingSystem.current.isLinux; /// Whether the current operating system is a version of /// [macOS](https://en.wikipedia.org/wiki/MacOS). /// /// Identified by [operatingSystem] being the string `macos`. +/// +/// The value is `false` if the code is running inside a browser, +/// even if that browser is running on MacOS (see [isBrowser]). +@pragma('vm:prefer-inline') bool get isMacOS => OperatingSystem.current.isMacOS; /// Whether the current operating system is a version of /// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows). /// /// Identified by [operatingSystem] being the string `windows`. +/// +/// The value is `false` if the code is running inside a browser, +/// even if that browser is running on Windows (see [isBrowser]). +@pragma('vm:prefer-inline') bool get isWindows => OperatingSystem.current.isWindows; /// Whether the current operating system is a version of /// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29). /// /// Identified by [operatingSystem] being the string `android`. +/// +/// The value is `false` if the code is running inside a browser, +/// even if that browser is running on Android (see [isBrowser]). +@pragma('vm:prefer-inline') bool get isAndroid => OperatingSystem.current.isAndroid; /// Whether the current operating system is a version of /// [iOS](https://en.wikipedia.org/wiki/IOS). /// /// Identified by [operatingSystem] being the string `ios`. +/// +/// The value is `false` if the code is running inside a browser, +/// even if that browser is running on iOS (see [isBrowser]). +@pragma('vm:prefer-inline') bool get isIOS => OperatingSystem.current.isIOS; /// Whether the current operating system is a version of /// [Fuchsia](https://en.wikipedia.org/wiki/Google_Fuchsia). /// /// Identified by [operatingSystem] being the string `fuchsia`. +/// +/// The value is `false` if the code is running inside a browser, +/// even if that browser is running on Fuchsia (see [isBrowser]). +@pragma('vm:prefer-inline') bool get isFuchsia => OperatingSystem.current.isFuchsia; /// Whether running in a web browser. @@ -63,4 +93,12 @@ bool get isFuchsia => OperatingSystem.current.isFuchsia; /// /// If so, the [operatingSystemVersion] is the string made available /// through `window.navigator.appVersion`. +/// +/// The value is `true` when the code is running inside a browser, +/// no matter which operating system the browser is itself running on. +/// No attempt is made to detect the underlying operating system. +/// That information *may* be derived from [operatingSystemVersion], +/// but browsers are able to lie in the app-version/user-agent +/// string. +@pragma('vm:prefer-inline') bool get isBrowser => OperatingSystem.current.isBrowser; diff --git a/pkgs/os_detect/lib/override.dart b/pkgs/os_detect/lib/override.dart index a3e64f3b..cc3e9188 100644 --- a/pkgs/os_detect/lib/override.dart +++ b/pkgs/os_detect/lib/override.dart @@ -1,100 +1,8 @@ -// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. /// Functionality to override information about the current platform. -library pkg.os_detect.override; +library; -import 'dart:async' show Zone, runZoned; - -import 'src/osid_unknown.dart' - if (dart.library.io) 'src/osid_io.dart' - if (dart.library.html) 'src/osid_html.dart'; - -/// The name and version of an operating system. -class OperatingSystem { - /// The current operating system ID. - /// - /// Defaults to what information is available - /// from known platform specific libraries, - /// but can be overridden using functionality from the - /// `osid_override.dart` library. - static OperatingSystem get current => - Zone.current[#_os] as OperatingSystem? ?? platformOS; - - /// A string representing the operating system or platform. - final String id; - - /// A string representing the version of the operating system or platform. - /// - /// May be empty if no version is known or available. - final String version; - - /// Creates an operating system ID with the provided values. - const OperatingSystem(this.id, this.version); -} - -/// Convenience operations on [OperatingSystem]. -/// -/// Implemented as extensions to allow users to *implement* [OperatingSystem] -/// without having to implement all of these getters. -extension OperatingSystemGetters on OperatingSystem { - /// Whether the operating system is a version of - /// [Linux](https://en.wikipedia.org/wiki/Linux). - /// - /// Identified by [id] being the string `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 => 'linux' == id; - - /// Whether the operating system is a version of - /// [macOS](https://en.wikipedia.org/wiki/MacOS). - /// - /// Identified by [id] being the string `macos`. - bool get isMacOS => 'macos' == id; - - /// Whether the operating system is a version of - /// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows). - /// - /// Identified by [id] being the string `windows`. - bool get isWindows => 'windows' == id; - - /// Whether the operating system is a version of - /// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29). - /// - /// Identified by [id] being the string `android`. - bool get isAndroid => 'android' == id; - - /// Whether the operating system is a version of - /// [iOS](https://en.wikipedia.org/wiki/IOS). - /// - /// Identified by [id] being the string `ios`. - bool get isIOS => 'ios' == id; - - /// Whether the operating system is a version of - /// [Fuchsia](https://en.wikipedia.org/wiki/Google_Fuchsia). - /// - /// Identified by [id] being the string `fuchsia`. - bool get isFuchsia => 'fuchsia' == id; - - /// Whether running in a web browser. - /// - /// Identified by [id] being the string `browser`. - /// - /// If so, the [version] is the string made available - /// through `window.navigator.appVersion`. - bool get isBrowser => 'browser' == id; -} - -/// Run [body] in a zone with platform overrides. -/// -/// Overrides [OperatingSystem.current] with the supplied [operatingSystem] -/// value while running in a new zone, and then runs [body] in that zone. -/// -/// This override affects the `operatingSystem` and `version` -/// exported by `package:osid/osid.dart`. -R overrideOperatingSystem( - OperatingSystem operatingSystem, R Function() body) => - runZoned(body, zoneValues: {#_os: operatingSystem}); +export 'src/os_override.dart' show OperatingSystem, overrideOperatingSystem; diff --git a/pkgs/os_detect/lib/src/os_kind.dart b/pkgs/os_detect/lib/src/os_kind.dart new file mode 100644 index 00000000..7f4ee852 --- /dev/null +++ b/pkgs/os_detect/lib/src/os_kind.dart @@ -0,0 +1,101 @@ +// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Shared constants and classes used to represent a recongized OS type +/// +/// Not exported in the public API, but used to communicate between +/// `override.dart` and the conditionally imported `osid_X.dart` files. +/// +/// When the platform is statically known, all but one of the subclasses +/// should be tree-shaken, so an `os is AndroidOS` can be resolved to +/// a constant true/false depending on whether the class is the retained one +/// or not. +library; + +/// Operating identity object. +/// +/// By only instantiating these subtypes guarded by target-OS guarded +/// checks, unless using the "for testing" `OperatingSystem` constructor, +/// all but one of the subclasses should be tree-shaken, +/// and, e.g., the `_isId is IOS` test above should become tree-shakable +/// on all other platforms. +sealed class RecognizedOS { + // The recognized OS identifier strings recognized. + static const androidId = 'android'; + static const browserId = 'browser'; + static const fuchsiaId = 'fuchsia'; + static const iOSId = 'ios'; + static const linuxId = 'linux'; + static const macOSId = 'macos'; + static const windowsId = 'windows'; + + abstract final String id; + const RecognizedOS(); +} + +/// Operations system object for Android. +class AndroidOS extends RecognizedOS { + @override + final String id = RecognizedOS.androidId; + const AndroidOS(); +} + +/// Operations system object for browsers. +class BrowserOS extends RecognizedOS { + @override + final String id = RecognizedOS.browserId; + const BrowserOS(); +} + +/// Operations system object for Fuchsia. +class FuchsiaOS extends RecognizedOS { + @override + final String id = RecognizedOS.fuchsiaId; + const FuchsiaOS(); +} + +/// Operations system object for iOS. +class IOS extends RecognizedOS { + @override + final String id = RecognizedOS.iOSId; + const IOS(); +} + +/// Operations system object for Linux. +class LinuxOS extends RecognizedOS { + @override + final String id = RecognizedOS.linuxId; + const LinuxOS(); +} + +/// Operations system object for MacOS. +class MacOS extends RecognizedOS { + @override + final String id = RecognizedOS.macOSId; + const MacOS(); +} + +/// Operations system object for Windows. +class WindowsOS extends RecognizedOS { + @override + final String id = RecognizedOS.windowsId; + const WindowsOS(); +} + +/// Fallback to represent unknown operating system. +/// +/// Do not use for one of the recognized operating +/// systems +class UnknownOS extends RecognizedOS { + @override + final String id; + const UnknownOS(this.id) + : assert(id != RecognizedOS.linuxId), + assert(id != RecognizedOS.macOSId), + assert(id != RecognizedOS.windowsId), + assert(id != RecognizedOS.androidId), + assert(id != RecognizedOS.iOSId), + assert(id != RecognizedOS.fuchsiaId), + assert(id != RecognizedOS.browserId); +} diff --git a/pkgs/os_detect/lib/src/os_override.dart b/pkgs/os_detect/lib/src/os_override.dart new file mode 100644 index 00000000..6f31bfd4 --- /dev/null +++ b/pkgs/os_detect/lib/src/os_override.dart @@ -0,0 +1,180 @@ +// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async' show Zone, runZoned; + +import 'package:meta/meta.dart'; + +import 'os_kind.dart'; +import 'osid_unknown.dart' + if (dart.library.io) 'osid_io.dart' + if (dart.library.html) 'osid_html.dart'; + +/// The name and version of an operating system. +final class OperatingSystem { + // The recognized OS identifier strings. + + /// The operating system ID string for Linux. + /// + /// Compare against [id] or the `operatingSystem` of `os_detect.dart`, + /// or use as argument to [OperatingSystem.new]. + static const androidId = RecognizedOS.androidId; + + /// The operating system ID string for browsers. + /// + /// Compare against [id] or the `operatingSystem` of `os_detect.dart`, + /// or use as argument to [OperatingSystem.new]. + static const browserId = RecognizedOS.browserId; + + /// The operating system ID string for Fuchsia. + /// + /// Compare against [id] or the `operatingSystem` of `os_detect.dart`, + /// or use as argument to [OperatingSystem.new]. + static const fuchsiaId = RecognizedOS.fuchsiaId; + + /// The operating system ID string for iOS. + /// + /// Compare against [id] or the `operatingSystem` of `os_detect.dart`, + /// or use as argument to [OperatingSystem.new]. + static const iOSId = RecognizedOS.iOSId; + + /// The operating system ID string for Linux. + /// + /// Compare against [id] or the `operatingSystem` of `os_detect.dart`, + /// or use as argument to [OperatingSystem.new]. + static const linuxId = RecognizedOS.linuxId; + + /// The operating system ID string for macOS. + /// + /// Compare against [id] or the `operatingSystem` of `os_detect.dart`, + /// or use as argument to [OperatingSystem.new]. + static const macOSId = RecognizedOS.macOSId; + + /// The operating system ID string for Windows. + /// + /// Compare against [id] or the `operatingSystem` of `os_detect.dart`, + /// or use as argument to [OperatingSystem.new]. + static const windowsId = RecognizedOS.windowsId; + + /// The current operating system ID. + /// + /// Defaults to what information is available + /// from known platform specific libraries, + /// but can be overridden using functionality from the + /// `osid_override.dart` library. + @pragma('vm:try-inline') + static OperatingSystem get current => + Zone.current[#_os] as OperatingSystem? ?? platformOS; + + /// A string representing the operating system or platform. + String get id => _osId.id; + + // Operating system ID object. + final RecognizedOS _osId; + + /// A string representing the version of the operating system or platform. + /// + /// May be empty if no version is known or available. + final String version; + + /// Creates a new operating system object for testing. + /// + /// Can be used with [overrideOperatingSystem] to selectively + /// change the value returned by [current]. + /// + /// **Notice:** Using this constructor may reduce the efficiency + /// of compilers recognizing code that isn't needed when compiling + /// for a particular platform (aka. "tree-shaking" of unreachable code). + // Uses chained conditionals to allow back-ends to constant fold when they + // know what `id` is, which they'd usually know for a specific operation. + // That can avoid retaining *all* the subclasses of `OS`. + @visibleForTesting + @pragma('vm:prefer-inline') + OperatingSystem(String id, String version) + : this._( + id == linuxId + ? const LinuxOS() + : id == macOSId + ? const MacOS() + : id == windowsId + ? const WindowsOS() + : id == androidId + ? const AndroidOS() + : id == iOSId + ? const IOS() + : id == fuchsiaId + ? const FuchsiaOS() + : id == browserId + ? const BrowserOS() + : UnknownOS(id), + version); + + /// Used by platforms which know the ID object. + const OperatingSystem._(this._osId, this.version); + + /// Whether the operating system is a version of + /// [Linux](https://en.wikipedia.org/wiki/Linux). + /// + /// Identified by [id] being the string `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 => _osId is LinuxOS; + + /// Whether the operating system is a version of + /// [macOS](https://en.wikipedia.org/wiki/MacOS). + /// + /// Identified by [id] being the string `macos`. + bool get isMacOS => _osId is MacOS; + + /// Whether the operating system is a version of + /// [Microsoft Windows](https://en.wikipedia.org/wiki/Microsoft_Windows). + /// + /// Identified by [id] being the string `windows`. + bool get isWindows => _osId is WindowsOS; + + /// Whether the operating system is a version of + /// [Android](https://en.wikipedia.org/wiki/Android_%28operating_system%29). + /// + /// Identified by [id] being the string `android`. + bool get isAndroid => _osId is AndroidOS; + + /// Whether the operating system is a version of + /// [iOS](https://en.wikipedia.org/wiki/IOS). + /// + /// Identified by [id] being the string `ios`. + bool get isIOS => _osId is IOS; + + /// Whether the operating system is a version of + /// [Fuchsia](https://en.wikipedia.org/wiki/Google_Fuchsia). + /// + /// Identified by [id] being the string `fuchsia`. + bool get isFuchsia => _osId is FuchsiaOS; + + /// Whether running in a web browser. + /// + /// Identified by [id] being the string `browser`. + /// + /// If so, the [version] is the string made available + /// through `window.navigator.appVersion`. + bool get isBrowser => _osId is BrowserOS; +} + +/// Run [body] in a zone with platform overrides. +/// +/// Overrides [OperatingSystem.current] with the supplied [operatingSystem] +/// value while running in a new zone, and then runs [body] in that zone. +/// +/// This override affects the `operatingSystem` and `version` +/// exported by `package:osid/osid.dart`. +R overrideOperatingSystem( + OperatingSystem operatingSystem, R Function() body) => + runZoned(body, zoneValues: {#_os: operatingSystem}); + +// Exposes the `OperatingSystem._` constructor to the conditionally imported +// libraries. Not exported by `../override.dart'. +final class OperatingSystemInternal extends OperatingSystem { + const OperatingSystemInternal(super.id, super.version) : super._(); +} diff --git a/pkgs/os_detect/lib/src/osid_html.dart b/pkgs/os_detect/lib/src/osid_html.dart index 2eead7c3..a5d896fb 100644 --- a/pkgs/os_detect/lib/src/osid_html.dart +++ b/pkgs/os_detect/lib/src/osid_html.dart @@ -4,9 +4,10 @@ import 'dart:html'; -import '../override.dart'; +import 'os_kind.dart' show BrowserOS; +import 'os_override.dart'; -const String _os = 'browser'; String get _osVersion => window.navigator.appVersion; -final OperatingSystem platformOS = OperatingSystem(_os, _osVersion); +final OperatingSystem platformOS = + OperatingSystemInternal(const BrowserOS(), _osVersion); diff --git a/pkgs/os_detect/lib/src/osid_io.dart b/pkgs/os_detect/lib/src/osid_io.dart index e2771f2d..56b45eb9 100644 --- a/pkgs/os_detect/lib/src/osid_io.dart +++ b/pkgs/os_detect/lib/src/osid_io.dart @@ -4,9 +4,30 @@ import 'dart:io'; -import '../override.dart'; +import 'os_kind.dart'; +import 'os_override.dart'; -String get _os => Platform.operatingSystem; -String get _osVersion => Platform.operatingSystemVersion; +// Uses VM platform-constant functionality to constant fold this expression +// when `Platform.operatingSystem` is known at compile-time. +// Uses a valid "potentially constant" expression for this, instead of, e.g., +// a `switch` expression. +@pragma('vm:platform-const') +final RecognizedOS? _osType = Platform.operatingSystem == RecognizedOS.linuxId + ? const LinuxOS() + : Platform.operatingSystem == RecognizedOS.macOSId + ? const MacOS() + : Platform.operatingSystem == RecognizedOS.windowsId + ? const WindowsOS() + : Platform.operatingSystem == RecognizedOS.androidId + ? const AndroidOS() + : Platform.operatingSystem == RecognizedOS.iOSId + ? const IOS() + : Platform.operatingSystem == RecognizedOS.fuchsiaId + ? const FuchsiaOS() + : Platform.operatingSystem == RecognizedOS.browserId + ? const BrowserOS() + : null; -final OperatingSystem platformOS = OperatingSystem(_os, _osVersion); +final OperatingSystem platformOS = OperatingSystemInternal( + _osType ?? UnknownOS(Platform.operatingSystem), + Platform.operatingSystemVersion); diff --git a/pkgs/os_detect/lib/src/osid_unknown.dart b/pkgs/os_detect/lib/src/osid_unknown.dart index fb33d52b..2e6798ef 100644 --- a/pkgs/os_detect/lib/src/osid_unknown.dart +++ b/pkgs/os_detect/lib/src/osid_unknown.dart @@ -2,10 +2,28 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import '../override.dart'; +import 'os_kind.dart'; +import 'os_override.dart'; +@pragma('vm:platform-const') const String _os = String.fromEnvironment('dart.os.name', defaultValue: 'unknown'); const String _osVersion = String.fromEnvironment('dart.os.version'); -const OperatingSystem platformOS = OperatingSystem(_os, _osVersion); +const OperatingSystem platformOS = OperatingSystemInternal( + _os == RecognizedOS.linuxId + ? LinuxOS() + : _os == RecognizedOS.macOSId + ? MacOS() + : _os == RecognizedOS.windowsId + ? WindowsOS() + : _os == RecognizedOS.androidId + ? AndroidOS() + : _os == RecognizedOS.iOSId + ? IOS() + : _os == RecognizedOS.fuchsiaId + ? FuchsiaOS() + : _os == RecognizedOS.browserId + ? BrowserOS() + : UnknownOS(_os), + _osVersion); diff --git a/pkgs/os_detect/pubspec.yaml b/pkgs/os_detect/pubspec.yaml index 586e69e8..d6435f9f 100644 --- a/pkgs/os_detect/pubspec.yaml +++ b/pkgs/os_detect/pubspec.yaml @@ -4,8 +4,11 @@ description: Platform independent OS detection. repository: https://github.com/dart-lang/os_detect environment: - sdk: '>=2.18.0 <3.0.0' + sdk: ^3.0.0 + +dependencies: + meta: ^1.9.0 dev_dependencies: lints: ^2.0.0 - test: ^1.16.8 + test: ^1.24.0 diff --git a/pkgs/os_detect/test/osid_test.dart b/pkgs/os_detect/test/osid_test.dart index a8d56814..862d9377 100644 --- a/pkgs/os_detect/test/osid_test.dart +++ b/pkgs/os_detect/test/osid_test.dart @@ -13,19 +13,19 @@ void main() { expect(operatingSystem, isNotNull); expect(operatingSystemVersion, isNotNull); - expect(isLinux, operatingSystem == 'linux'); - expect(isAndroid, operatingSystem == 'android'); - expect(isMacOS, operatingSystem == 'macos'); - expect(isWindows, operatingSystem == 'windows'); - expect(isIOS, operatingSystem == 'ios'); - expect(isFuchsia, operatingSystem == 'fuchsia'); - expect(isBrowser, operatingSystem == 'browser'); + expect(isLinux, operatingSystem == OperatingSystem.linuxId); + expect(isAndroid, operatingSystem == OperatingSystem.androidId); + expect(isMacOS, operatingSystem == OperatingSystem.macOSId); + expect(isWindows, operatingSystem == OperatingSystem.windowsId); + expect(isIOS, operatingSystem == OperatingSystem.iOSId); + expect(isFuchsia, operatingSystem == OperatingSystem.fuchsiaId); + expect(isBrowser, operatingSystem == OperatingSystem.browserId); }); test('Override', () { const overrideName = 'argle-bargle'; const overrideVersion = 'glop-glyf'; - const overrideOS = OperatingSystem(overrideName, overrideVersion); + final overrideOS = OperatingSystem(overrideName, overrideVersion); Zone? overrideZone; final originalName = operatingSystem;