Skip to content

Commit

Permalink
Use the VM's "platform-const" feature to achieve tree-shaking. (dart-…
Browse files Browse the repository at this point in the history
…archive/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.
  • Loading branch information
lrhn authored Sep 28, 2023
1 parent b4f1d50 commit 754f048
Show file tree
Hide file tree
Showing 15 changed files with 500 additions and 133 deletions.
2 changes: 1 addition & 1 deletion pkgs/os_detect/.github/workflows/test-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion pkgs/os_detect/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
35 changes: 24 additions & 11 deletions pkgs/os_detect/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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


40 changes: 40 additions & 0 deletions pkgs/os_detect/bin/os_detect.dart
Original file line number Diff line number Diff line change
@@ -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;
}
20 changes: 17 additions & 3 deletions pkgs/os_detect/example/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
29 changes: 29 additions & 0 deletions pkgs/os_detect/example/tree_shaking.dart
Original file line number Diff line number Diff line change
@@ -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');
}
}
44 changes: 41 additions & 3 deletions pkgs/os_detect/lib/os_detect.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<Name>` 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.
Expand All @@ -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.
Expand All @@ -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;
98 changes: 3 additions & 95 deletions pkgs/os_detect/lib/override.dart
Original file line number Diff line number Diff line change
@@ -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<R>(
OperatingSystem operatingSystem, R Function() body) =>
runZoned(body, zoneValues: {#_os: operatingSystem});
export 'src/os_override.dart' show OperatingSystem, overrideOperatingSystem;
Loading

0 comments on commit 754f048

Please sign in to comment.