diff --git a/.github/ISSUE_TEMPLATE/os_detect.md b/.github/ISSUE_TEMPLATE/os_detect.md new file mode 100644 index 000000000..4648d1540 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/os_detect.md @@ -0,0 +1,5 @@ +--- +name: "package:os_detect" +about: "Create a bug or file a feature request against package:os_detect." +labels: "package:os_detect" +--- \ No newline at end of file diff --git a/.github/labeler.yml b/.github/labeler.yml index 31e72c51a..7d8b8dc6a 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -27,3 +27,7 @@ "package:logging": - changed-files: - any-glob-to-any-file: 'pkgs/logging/**' + +"package:os_detect": + - changed-files: + - any-glob-to-any-file: 'pkgs/os_detect/**' diff --git a/.github/workflows/os_detect.yaml b/.github/workflows/os_detect.yaml new file mode 100644 index 000000000..be86b67e1 --- /dev/null +++ b/.github/workflows/os_detect.yaml @@ -0,0 +1,73 @@ +name: package:os_detect + +on: + # Run CI on pushes to the main branch, and on PRs against main. + push: + branches: [ main ] + paths: + - '.github/workflows/os_detect.yaml' + - 'pkgs/os_detect/**' + pull_request: + branches: [ main ] + paths: + - '.github/workflows/os_detect.yaml' + - 'pkgs/os_detect/**' + schedule: + - cron: "0 0 * * 0" +env: + PUB_ENVIRONMENT: bot.github + +defaults: + run: + working-directory: pkgs/os_detect/ + +jobs: + # Check code formatting and static analysis on a single OS (linux) + # against Dart dev. + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + sdk: [dev] + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Check formatting + run: dart format --output=none --set-exit-if-changed . + if: always() && steps.install.outcome == 'success' + - name: Analyze code + run: dart analyze --fatal-infos + if: always() && steps.install.outcome == 'success' + + # Run tests on a matrix consisting of two dimensions: + # 1. OS: ubuntu-latest, (macos-latest, windows-latest) + # 2. release channel: dev + test: + needs: analyze + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # Add macos-latest and/or windows-latest if relevant for this package. + os: [ubuntu-latest, windows-latest, macos-latest] + sdk: [3.0.0, dev] + steps: + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 + - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 + with: + sdk: ${{ matrix.sdk }} + - id: install + name: Install dependencies + run: dart pub get + - name: Run VM tests + run: dart test --platform vm + if: always() && steps.install.outcome == 'success' + - name: Run Chrome tests + run: dart test --platform chrome + if: always() && steps.install.outcome == 'success' diff --git a/README.md b/README.md index 7a6c0fdac..9da7f911d 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repository is home to various Dart packages under the [dart.dev](https://pu | [crypto](pkgs/crypto/) | Implementations of SHA, MD5, and HMAC cryptographic functions. | [![pub package](https://img.shields.io/pub/v/crypto.svg)](https://pub.dev/packages/crypto) | | [fixnum](pkgs/fixnum/) | Library for 32- and 64-bit signed fixed-width integers. | [![pub package](https://img.shields.io/pub/v/fixnum.svg)](https://pub.dev/packages/fixnum) | | [logging](pkgs/logging/) | Provides APIs for debugging and error logging. | [![pub package](https://img.shields.io/pub/v/logging.svg)](https://pub.dev/packages/logging) | +| [os_detect](pkgs/os_detect/) | Platform independent OS detection. | [![pub package](https://img.shields.io/pub/v/os_detect.svg)](https://pub.dev/packages/os_detect) | ## Publishing automation diff --git a/pkgs/os_detect/.gitignore b/pkgs/os_detect/.gitignore new file mode 100644 index 000000000..49ce72d76 --- /dev/null +++ b/pkgs/os_detect/.gitignore @@ -0,0 +1,3 @@ +.dart_tool/ +.packages +pubspec.lock diff --git a/pkgs/os_detect/AUTHORS b/pkgs/os_detect/AUTHORS new file mode 100644 index 000000000..846e4a156 --- /dev/null +++ b/pkgs/os_detect/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the Dart project. Names should be added to the list like so: +# +# Name/Organization + +Google LLC diff --git a/pkgs/os_detect/CHANGELOG.md b/pkgs/os_detect/CHANGELOG.md new file mode 100644 index 000000000..0dea3e5dc --- /dev/null +++ b/pkgs/os_detect/CHANGELOG.md @@ -0,0 +1,17 @@ +## 2.0.2 + +- Require Dart 3.0 +- Make work with VM's platform-constants. +- Move to `dart-lang/core` monorepo. + +## 2.0.1 + +- Populate the pubspec `repository` field. + +## 2.0.0 + +- Stable null safety release. + +## 1.0.0 + +- Initial release diff --git a/pkgs/os_detect/LICENSE b/pkgs/os_detect/LICENSE new file mode 100644 index 000000000..ed0a3506e --- /dev/null +++ b/pkgs/os_detect/LICENSE @@ -0,0 +1,27 @@ +Copyright 2020, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkgs/os_detect/README.md b/pkgs/os_detect/README.md new file mode 100644 index 000000000..cb931b15c --- /dev/null +++ b/pkgs/os_detect/README.md @@ -0,0 +1,42 @@ +[![Dart CI](https://github.com/dart-lang/core/actions/workflows/os_detect.yaml/badge.svg)](https://github.com/dart-lang/core/actions/workflows/os_detect.yaml) +[![pub package](https://img.shields.io/pub/v/os_detect.svg)](https://pub.dev/packages/os_detect) +[![package publisher](https://img.shields.io/pub/publisher/os_detect.svg)](https://pub.dev/packages/os_detect/publisher) + +Platform independent access to information about the current operating system. + +## Querying the current OS + +Exposes `operatingSystem` and `operatingSystemVersion` strings similar to those +of the `Platform` class in `dart:io`, but also works on the web. The +`operatingSystem` of a browser is the string "browser". Also exposes convenience +getters like `isLinux`, `isAndroid` and `isBrowser` based on the +`operatingSystem` string. + +To use this package instead of `dart:io`, replace the import of `dart:io` with: + +```dart +import 'package:os_detect/os_detect.dart' as os_detect; +``` + +That should keep the code working if the only functionality used from `dart:io` +is operating system detection. You should then use your IDE to rename the import +prefix from `Platform` to something lower-cased which follows the style guide +for import prefixes. + +Any new platform which supports neither `dart:io` nor `dart:html` can make +itself recognizable by configuring the `dart.os.name` and `dart.os.version` +environment settings, so that `const String.fromEnvironment` can access them. + +## Overriding the current OS string + +It's possible to override the current operating system string, as exposed by +`operatingSystem` and `operatingSystemVersion` in +`package:os_detect/os_detect.dart`. To do so, import the +`package:os_detect/override.dart` library and use the `overrideOperatingSystem` +function to run code in a zone where the operating system and version values are +set to whatever values are desired. + +The class `OperatingSystemID` can also be used directly to abstract over the +operating system name and version. The `OperatingSystemID.current` defaults to +the values provided by the platform when not overridden using +`overrideOperatingSystem`. diff --git a/pkgs/os_detect/analysis_options.yaml b/pkgs/os_detect/analysis_options.yaml new file mode 100644 index 000000000..f29baf1bf --- /dev/null +++ b/pkgs/os_detect/analysis_options.yaml @@ -0,0 +1,27 @@ +# https://dart.dev/tools/analysis#the-analysis-options-file +include: package:dart_flutter_team_lints/analysis_options.yaml + +analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + +linter: + rules: + - avoid_bool_literals_in_conditional_expressions + - avoid_classes_with_only_static_members + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_returning_this + - avoid_unused_constructor_parameters + - avoid_void_async + - literal_only_boolean_expressions + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_runtimeType_toString + - package_api_docs + - prefer_const_declarations + - use_raw_strings + + diff --git a/pkgs/os_detect/bin/os_detect.dart b/pkgs/os_detect/bin/os_detect.dart new file mode 100644 index 000000000..e9e6fc157 --- /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 new file mode 100644 index 000000000..4a159d867 --- /dev/null +++ b/pkgs/os_detect/example/example.dart @@ -0,0 +1,26 @@ +// 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. + +import 'package:os_detect/os_detect.dart' as os_detect; + +void main() { + print(''' + 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 000000000..987f3ddb0 --- /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 new file mode 100644 index 000000000..d323f63a8 --- /dev/null +++ b/pkgs/os_detect/lib/os_detect.dart @@ -0,0 +1,104 @@ +// 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. + +/// Information about the current operating system. +library pkg.os_detect; + +import 'src/os_override.dart'; + +/// Identification of the current operating system or platform. +/// +/// 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. +/// +/// May be empty if no version is known or available. +String get operatingSystemVersion => OperatingSystem.current.version; + +/// Whether the current operating system is a version of +/// [Linux](https://en.wikipedia.org/wiki/Linux). +/// +/// Identified by [operatingSystem] 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]), +/// 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. +/// +/// Identified by [operatingSystem] being the string `browser`. +/// +/// 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 new file mode 100644 index 000000000..cc3e91887 --- /dev/null +++ b/pkgs/os_detect/lib/override.dart @@ -0,0 +1,8 @@ +// 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; + +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 000000000..7f4ee8522 --- /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 000000000..6f31bfd4d --- /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 new file mode 100644 index 000000000..a5d896fb4 --- /dev/null +++ b/pkgs/os_detect/lib/src/osid_html.dart @@ -0,0 +1,13 @@ +// 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:html'; + +import 'os_kind.dart' show BrowserOS; +import 'os_override.dart'; + +String get _osVersion => window.navigator.appVersion; + +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 new file mode 100644 index 000000000..56b45eb91 --- /dev/null +++ b/pkgs/os_detect/lib/src/osid_io.dart @@ -0,0 +1,33 @@ +// 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:io'; + +import 'os_kind.dart'; +import 'os_override.dart'; + +// 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 = 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 new file mode 100644 index 000000000..2e6798ef3 --- /dev/null +++ b/pkgs/os_detect/lib/src/osid_unknown.dart @@ -0,0 +1,29 @@ +// 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 '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 = 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 new file mode 100644 index 000000000..fb927b4cd --- /dev/null +++ b/pkgs/os_detect/pubspec.yaml @@ -0,0 +1,14 @@ +name: os_detect +version: 2.0.2 +description: Platform independent OS detection. +repository: https://github.com/dart-lang/core/tree/main/pkgs/os_detect + +environment: + sdk: ^3.0.0 + +dependencies: + meta: ^1.9.0 + +dev_dependencies: + dart_flutter_team_lints: ^2.0.0 + test: ^1.24.0 diff --git a/pkgs/os_detect/test/osid_test.dart b/pkgs/os_detect/test/osid_test.dart new file mode 100644 index 000000000..862d9377d --- /dev/null +++ b/pkgs/os_detect/test/osid_test.dart @@ -0,0 +1,73 @@ +// 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'; + +import 'package:os_detect/os_detect.dart'; +import 'package:os_detect/override.dart'; +import 'package:test/test.dart'; + +void main() { + test('Exists and is consistent', () { + expect(operatingSystem, isNotNull); + expect(operatingSystemVersion, isNotNull); + + 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'; + final overrideOS = OperatingSystem(overrideName, overrideVersion); + Zone? overrideZone; + + final originalName = operatingSystem; + final originalVersion = operatingSystemVersion; + final originalID = OperatingSystem.current; + final originalZone = Zone.current; + expect(originalName, isNot(overrideName)); + expect(originalVersion, isNot(overrideVersion)); + + // Override OS ID. + overrideOperatingSystem(overrideOS, () { + overrideZone = Zone.current; + expect(operatingSystem, overrideName); + expect(operatingSystemVersion, overrideVersion); + expect(OperatingSystem.current, same(overrideOS)); + // Nested override. + overrideOperatingSystem(originalID, () { + expect(operatingSystem, originalName); + expect(operatingSystemVersion, originalVersion); + expect(OperatingSystem.current, same(originalID)); + }); + expect(operatingSystem, overrideName); + expect(operatingSystemVersion, overrideVersion); + expect(OperatingSystem.current, same(overrideOS)); + // Captured parent zone does not have override. + originalZone.run(() { + expect(operatingSystem, originalName); + expect(operatingSystemVersion, originalVersion); + }); + expect(operatingSystem, overrideName); + expect(operatingSystemVersion, overrideVersion); + expect(OperatingSystem.current, same(overrideOS)); + }); + + expect(operatingSystem, originalName); + expect(operatingSystemVersion, originalVersion); + + // A captured override zone retains the override. + overrideZone!.run(() { + expect(operatingSystem, overrideName); + expect(operatingSystemVersion, overrideVersion); + expect(OperatingSystem.current, same(overrideOS)); + }); + }); +}