Skip to content

Commit

Permalink
Merge branch 'main' into merge-path-package
Browse files Browse the repository at this point in the history
mosuem authored Oct 17, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents bab3e3c + 1a3e04e commit ad17462
Showing 41 changed files with 2,538 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/args.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
name: "package:args"
about: "Create a bug or file a feature request against package:args."
labels: "package:args"
---
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/logging.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
name: "package:logging"
about: "Create a bug or file a feature request against package:logging."
labels: "package:logging"
---
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/os_detect.md
Original file line number Diff line number Diff line change
@@ -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"
---
8 changes: 8 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -24,6 +24,14 @@
- changed-files:
- any-glob-to-any-file: 'pkgs/fixnum/**'

"package:logging":
- changed-files:
- any-glob-to-any-file: 'pkgs/logging/**'

"package:os_detect":
- changed-files:
- any-glob-to-any-file: 'pkgs/os_detect/**'

"package:path":
- changed-files:
- any-glob-to-any-file: 'pkgs/path/**'
70 changes: 70 additions & 0 deletions .github/workflows/logging.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: package:logging

on:
# Run CI on pushes to the main branch, and on PRs against main.
push:
branches: [ main ]
paths:
- '.github/workflows/logging.yaml'
- 'pkgs/logging/**'
pull_request:
branches: [ main ]
paths:
- '.github/workflows/logging.yaml'
- 'pkgs/logging/**'
schedule:
- cron: "0 0 * * 0"
env:
PUB_ENVIRONMENT: bot.github

defaults:
run:
working-directory: pkgs/logging/

jobs:
# Check code formatting and static analysis.
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 of platforms and sdk versions.
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]
sdk: [3.4, 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'
73 changes: 73 additions & 0 deletions .github/workflows/os_detect.yaml
Original file line number Diff line number Diff line change
@@ -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'
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -38,7 +38,7 @@ new file please also add this. The year should be a single number stating the
year the file was created (don't use a range like "2011-2012"). Additionally, if
you edit an existing file, you shouldn't update the year.

// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// Copyright (c) 2024, 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.

@@ -48,4 +48,4 @@ This project follows
[Google's Open Source Community Guidelines](https://opensource.google/conduct/).

We pledge to maintain an open and welcoming environment. For details, see our
[code of conduct](https://dart.dev/code-of-conduct).
[code of conduct](https://dart.dev/code-of-conduct).
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@ This repository is home to various Dart packages under the [dart.dev](https://pu
| [convert](pkgs/convert/) | Utilities for converting between data representations. | [![pub package](https://img.shields.io/pub/v/convert.svg)](https://pub.dev/packages/convert) |
| [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) |
| [path](pkgs/path/) | A string-based path manipulation library for all of the path operations you know and love. | [![pub package](https://img.shields.io/pub/v/path.svg)](https://pub.dev/packages/path) |

## Publishing automation
4 changes: 3 additions & 1 deletion pkgs/args/example/arg_parser/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -6,8 +6,10 @@ name: arg_parser_example
version: 1.0.0
description: An example of using ArgParser
publish_to: 'none'

environment:
sdk: '>=2.14.0 <3.0.0'
sdk: ^3.0.0

dependencies:
args:
path: ../..
4 changes: 3 additions & 1 deletion pkgs/args/example/command_runner/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -6,8 +6,10 @@ name: command_runner_example
version: 1.0.0
description: An example of using CommandRunner
publish_to: 'none'

environment:
sdk: '>=2.14.0 <3.0.0'
sdk: ^3.0.0

dependencies:
args:
path: ../..
3 changes: 3 additions & 0 deletions pkgs/logging/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dart_tool
.packages
pubspec.lock
10 changes: 10 additions & 0 deletions pkgs/logging/AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Names should be added to this file with this pattern:
#
# For individuals:
# Name <email address>
#
# For organizations:
# Organization <fnmatch pattern>
#
Google Inc. <*@google.com>
Anton Astashov <[email protected]>
92 changes: 92 additions & 0 deletions pkgs/logging/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
## 1.3.0

* Override empty stack traces for trace level events.
* Require Dart 3.4
* Move to `dart-lang/core` monorepo.

## 1.2.0

* Add notification when the log level is changed. Logger `onLevelChanged` broadcasts a stream of level values.
* Require Dart 2.19.

## 1.1.1

* Add a check that throws if a logger name ends with '.'.
* Require Dart 2.18

## 1.1.0

* Add `Logger.attachedLoggers` which exposes all loggers created with the
default constructor.
* Enable the `avoid_dynamic_calls` lint.

## 1.0.2

* Update description.
* Add example.

## 1.0.1

* List log levels in README.

## 1.0.0

* Stable null safety release.

## 1.0.0-nullsafety.0

* Migrate to null safety.
* Removed the deprecated `LoggerHandler` typedef.

## 0.11.4

* Add top level `defaultLevel`.
* Require Dart `>=2.0.0`.
* Make detached loggers work regardless of `hierarchicalLoggingEnabled`.

## 0.11.3+2

* Set max SDK version to `<3.0.0`, and adjust other dependencies.

## 0.11.3+1

* Fixed several documentation comments.

## 0.11.3

* Added optional `LogRecord.object` field.

* `Logger.log` sets `LogRecord.object` if the message is not a string or a
function that returns a string. So that a handler can access the original
object instead of just its `toString()`.

## 0.11.2

* Added `Logger.detached` - a convenience factory to obtain a logger that is not
attached to this library's logger hierarchy.

## 0.11.1+1

* Include default error with the auto-generated stack traces.

## 0.11.1

* Add support for automatically logging the stack trace on error messages. Note
this can be expensive, so it is off by default.

## 0.11.0

* Revert change in `0.10.0`. `stackTrace` must be an instance of `StackTrace`.
Use the `Trace` class from the [stack_trace package][] to convert strings.

[stack_trace package]: https://pub.dev/packages/stack_trace

## 0.10.0

* Change type of `stackTrace` from `StackTrace` to `Object`.

## 0.9.3

* Added optional `LogRecord.zone` field.

* Record current zone (or user specified zone) when creating new `LogRecord`s.
27 changes: 27 additions & 0 deletions pkgs/logging/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Copyright 2013, 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.
141 changes: 141 additions & 0 deletions pkgs/logging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
[![Dart CI](https://github.com/dart-lang/core/actions/workflows/logging.yaml/badge.svg)](https://github.com/dart-lang/core/actions/workflows/logging.yaml)
[![Pub](https://img.shields.io/pub/v/logging.svg)](https://pub.dev/packages/logging)
[![package publisher](https://img.shields.io/pub/publisher/logging.svg)](https://pub.dev/packages/logging/publisher)

## Initializing

By default, the logging package does not do anything useful with the log
messages. You must configure the logging level and add a handler for the log
messages.

Here is a simple logging configuration that logs all messages via `print`.

```dart
Logger.root.level = Level.ALL; // defaults to Level.INFO
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
```

First, set the root `Level`. All messages at or above the current level are sent to the
`onRecord` stream. Available levels are:

+ `Level.OFF`
+ `Level.SHOUT`
+ `Level.SEVERE`
+ `Level.WARNING`
+ `Level.INFO`
+ `Level.CONFIG`
+ `Level.FINE`
+ `Level.FINER`
+ `Level.FINEST`

Then, listen on the `onRecord` stream for `LogRecord` events. The `LogRecord`
class has various properties for the message, error, logger name, and more.

To listen for changed level notifications use:

```dart
Logger.root.onLevelChanged.listen((level) {
print('The new log level is $level');
});
```

## Logging messages

Create a `Logger` with a unique name to easily identify the source of the log
messages.

```dart
final log = Logger('MyClassName');
```

Here is an example of logging a debug message and an error:

```dart
var future = doSomethingAsync().then((result) {
log.fine('Got the result: $result');
processResult(result);
}).catchError((e, stackTrace) => log.severe('Oh noes!', e, stackTrace));
```

When logging more complex messages, you can pass a closure instead that will be
evaluated only if the message is actually logged:

```dart
log.fine(() => [1, 2, 3, 4, 5].map((e) => e * 4).join("-"));
```

Available logging methods are:

+ `log.shout(logged_content);`
+ `log.severe(logged_content);`
+ `log.warning(logged_content);`
+ `log.info(logged_content);`
+ `log.config(logged_content);`
+ `log.fine(logged_content);`
+ `log.finer(logged_content);`
+ `log.finest(logged_content);`

## Configuration

Loggers can be individually configured and listened to. When an individual logger has no
specific configuration, it uses the configuration and any listeners found at `Logger.root`.

To begin, set the global boolean `hierarchicalLoggingEnabled` to `true`.

Then, create unique loggers and configure their `level` attributes and assign any listeners to
their `onRecord` streams.


```dart
hierarchicalLoggingEnabled = true;
Logger.root.level = Level.WARNING;
Logger.root.onRecord.listen((record) {
print('[ROOT][WARNING+] ${record.message}');
});
final log1 = Logger('FINE+');
log1.level = Level.FINE;
log1.onRecord.listen((record) {
print('[LOG1][FINE+] ${record.message}');
});
// log2 inherits LEVEL value of WARNING from `Logger.root`
final log2 = Logger('WARNING+');
log2.onRecord.listen((record) {
print('[LOG2][WARNING+] ${record.message}');
});
// Will NOT print because FINER is too low level for `Logger.root`.
log1.finer('LOG_01 FINER (X)');
// Will print twice ([LOG1] & [ROOT])
log1.fine('LOG_01 FINE (√√)');
// Will print ONCE because `log1` only uses root listener.
log1.warning('LOG_01 WARNING (√)');
// Will never print because FINE is too low level.
log2.fine('LOG_02 FINE (X)');
// Will print twice ([LOG2] & [ROOT]) because warning is sufficient for all
// loggers' levels.
log2.warning('LOG_02 WARNING (√√)');
// Will never print because `info` is filtered by `Logger.root.level` of
// `Level.WARNING`.
log2.info('INFO (X)');
```

Results in:

```
[LOG1][FINE+] LOG_01 FINE (√√)
[ROOT][WARNING+] LOG_01 FINE (√√)
[LOG1][FINE+] LOG_01 WARNING (√)
[ROOT][WARNING+] LOG_01 WARNING (√)
[LOG2][WARNING+] LOG_02 WARNING (√√)
[ROOT][WARNING+] LOG_02 WARNING (√√)
```
31 changes: 31 additions & 0 deletions pkgs/logging/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# https://dart.dev/guides/language/analysis-options
include: package:dart_flutter_team_lints/analysis_options.yaml

analyzer:
language:
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
- cancel_subscriptions
- join_return_with_assignment
- literal_only_boolean_expressions
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_runtimeType_toString
- package_api_docs
- prefer_const_declarations
- prefer_expression_function_bodies
- prefer_final_locals
- unnecessary_await_in_return
- unnecessary_raw_strings
- use_if_null_to_convert_nulls_to_bools
- use_raw_strings
- use_string_buffers
41 changes: 41 additions & 0 deletions pkgs/logging/example/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) 2024, 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:logging/logging.dart';

final log = Logger('ExampleLogger');

/// Example of configuring a logger to print to stdout.
///
/// This example will print:
///
/// INFO: 2021-09-13 15:35:10.703401: recursion: n = 4
/// INFO: 2021-09-13 15:35:10.707974: recursion: n = 3
/// Fibonacci(4) is: 3
/// Fibonacci(5) is: 5
/// SHOUT: 2021-09-13 15:35:10.708087: Unexpected negative n: -42
/// Fibonacci(-42) is: 1
void main() {
Logger.root.level = Level.ALL; // defaults to Level.INFO
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});

print('Fibonacci(4) is: ${fibonacci(4)}');

Logger.root.level = Level.SEVERE; // skip logs less then severe.
print('Fibonacci(5) is: ${fibonacci(5)}');

print('Fibonacci(-42) is: ${fibonacci(-42)}');
}

int fibonacci(int n) {
if (n <= 2) {
if (n < 0) log.shout('Unexpected negative n: $n');
return 1;
} else {
log.info('recursion: n = $n');
return fibonacci(n - 2) + fibonacci(n - 1);
}
}
7 changes: 7 additions & 0 deletions pkgs/logging/lib/logging.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) 2012, 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.

export 'src/level.dart';
export 'src/log_record.dart';
export 'src/logger.dart';
88 changes: 88 additions & 0 deletions pkgs/logging/lib/src/level.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2019, 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.

// ignore_for_file: constant_identifier_names

/// [Level]s to control logging output. Logging can be enabled to include all
/// levels above certain [Level]. [Level]s are ordered using an integer
/// value [Level.value]. The predefined [Level] constants below are sorted as
/// follows (in descending order): [Level.SHOUT], [Level.SEVERE],
/// [Level.WARNING], [Level.INFO], [Level.CONFIG], [Level.FINE], [Level.FINER],
/// [Level.FINEST], and [Level.ALL].
///
/// We recommend using one of the predefined logging levels. If you define your
/// own level, make sure you use a value between those used in [Level.ALL] and
/// [Level.OFF].
class Level implements Comparable<Level> {
final String name;

/// Unique value for this level. Used to order levels, so filtering can
/// exclude messages whose level is under certain value.
final int value;

const Level(this.name, this.value);

/// Special key to turn on logging for all levels ([value] = 0).
static const Level ALL = Level('ALL', 0);

/// Special key to turn off all logging ([value] = 2000).
static const Level OFF = Level('OFF', 2000);

/// Key for highly detailed tracing ([value] = 300).
static const Level FINEST = Level('FINEST', 300);

/// Key for fairly detailed tracing ([value] = 400).
static const Level FINER = Level('FINER', 400);

/// Key for tracing information ([value] = 500).
static const Level FINE = Level('FINE', 500);

/// Key for static configuration messages ([value] = 700).
static const Level CONFIG = Level('CONFIG', 700);

/// Key for informational messages ([value] = 800).
static const Level INFO = Level('INFO', 800);

/// Key for potential problems ([value] = 900).
static const Level WARNING = Level('WARNING', 900);

/// Key for serious failures ([value] = 1000).
static const Level SEVERE = Level('SEVERE', 1000);

/// Key for extra debugging loudness ([value] = 1200).
static const Level SHOUT = Level('SHOUT', 1200);

static const List<Level> LEVELS = [
ALL,
FINEST,
FINER,
FINE,
CONFIG,
INFO,
WARNING,
SEVERE,
SHOUT,
OFF
];

@override
bool operator ==(Object other) => other is Level && value == other.value;

bool operator <(Level other) => value < other.value;

bool operator <=(Level other) => value <= other.value;

bool operator >(Level other) => value > other.value;

bool operator >=(Level other) => value >= other.value;

@override
int compareTo(Level other) => value - other.value;

@override
int get hashCode => value;

@override
String toString() => name;
}
46 changes: 46 additions & 0 deletions pkgs/logging/lib/src/log_record.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2019, 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 'level.dart';
import 'logger.dart';

/// A log entry representation used to propagate information from [Logger] to
/// individual handlers.
class LogRecord {
final Level level;
final String message;

/// Non-string message passed to Logger.
final Object? object;

/// Logger where this record is stored.
final String loggerName;

/// Time when this record was created.
final DateTime time;

/// Unique sequence number greater than all log records created before it.
final int sequenceNumber;

static int _nextNumber = 0;

/// Associated error (if any) when recording errors messages.
final Object? error;

/// Associated stackTrace (if any) when recording errors messages.
final StackTrace? stackTrace;

/// Zone of the calling code which resulted in this LogRecord.
final Zone? zone;

LogRecord(this.level, this.message, this.loggerName,
[this.error, this.stackTrace, this.zone, this.object])
: time = DateTime.now(),
sequenceNumber = LogRecord._nextNumber++;

@override
String toString() => '[${level.name}] $loggerName: $message';
}
326 changes: 326 additions & 0 deletions pkgs/logging/lib/src/logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
// Copyright (c) 2019, 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 'dart:collection';

import 'level.dart';
import 'log_record.dart';

/// Whether to allow fine-grain logging and configuration of loggers in a
/// hierarchy.
///
/// When false, all hierarchical logging instead is merged in the root logger.
bool hierarchicalLoggingEnabled = false;

/// Automatically record stack traces for any message of this level or above.
///
/// Because this is expensive, this is off by default.
Level recordStackTraceAtLevel = Level.OFF;

/// The default [Level].
const defaultLevel = Level.INFO;

/// Use a [Logger] to log debug messages.
///
/// [Logger]s are named using a hierarchical dot-separated name convention.
class Logger {
/// Simple name of this logger.
final String name;

/// The full name of this logger, which includes the parent's full name.
String get fullName =>
parent?.name.isNotEmpty ?? false ? '${parent!.fullName}.$name' : name;

/// Parent of this logger in the hierarchy of loggers.
final Logger? parent;

/// Logging [Level] used for entries generated on this logger.
///
/// Only the root logger is guaranteed to have a non-null [Level].
Level? _level;

/// Private modifiable map of child loggers, indexed by their simple names.
final Map<String, Logger> _children;

/// Children in the hierarchy of loggers, indexed by their simple names.
///
/// This is an unmodifiable map.
final Map<String, Logger> children;

/// Controller used to notify when log entries are added to this logger.
///
/// If hierarchical logging is disabled then this is `null` for all but the
/// root [Logger].
StreamController<LogRecord>? _controller;

/// Controller used to notify when the log level of this logger is changed.
StreamController<Level?>? _levelChangedController;

/// Create or find a Logger by name.
///
/// Calling `Logger(name)` will return the same instance whenever it is called
/// with the same string name. Loggers created with this constructor are
/// retained indefinitely and available through [attachedLoggers].
factory Logger(String name) =>
_loggers.putIfAbsent(name, () => Logger._named(name));

/// Creates a new detached [Logger].
///
/// Returns a new [Logger] instance (unlike `new Logger`, which returns a
/// [Logger] singleton), which doesn't have any parent or children,
/// and is not a part of the global hierarchical loggers structure.
///
/// It can be useful when you just need a local short-living logger,
/// which you'd like to be garbage-collected later.
factory Logger.detached(String name) =>
Logger._internal(name, null, <String, Logger>{});

factory Logger._named(String name) {
if (name.startsWith('.')) {
throw ArgumentError("name shouldn't start with a '.'");
}
if (name.endsWith('.')) {
throw ArgumentError("name shouldn't end with a '.'");
}

// Split hierarchical names (separated with '.').
final dot = name.lastIndexOf('.');
Logger? parent;
String thisName;
if (dot == -1) {
if (name != '') parent = Logger('');
thisName = name;
} else {
parent = Logger(name.substring(0, dot));
thisName = name.substring(dot + 1);
}
return Logger._internal(thisName, parent, <String, Logger>{});
}

Logger._internal(this.name, this.parent, Map<String, Logger> children)
: _children = children,
children = UnmodifiableMapView(children) {
if (parent == null) {
_level = defaultLevel;
} else {
parent!._children[name] = this;
}
}

/// Effective level considering the levels established in this logger's
/// parents (when [hierarchicalLoggingEnabled] is true).
Level get level {
Level effectiveLevel;

if (parent == null) {
// We're either the root logger or a detached logger. Return our own
// level.
effectiveLevel = _level!;
} else if (!hierarchicalLoggingEnabled) {
effectiveLevel = root._level!;
} else {
effectiveLevel = _level ?? parent!.level;
}

// ignore: unnecessary_null_comparison
assert(effectiveLevel != null);
return effectiveLevel;
}

/// Override the level for this particular [Logger] and its children.
///
/// Setting this to `null` makes it inherit the [parent]s level.
set level(Level? value) {
if (!hierarchicalLoggingEnabled && parent != null) {
throw UnsupportedError(
'Please set "hierarchicalLoggingEnabled" to true if you want to '
'change the level on a non-root logger.');
}
if (parent == null && value == null) {
throw UnsupportedError(
'Cannot set the level to `null` on a logger with no parent.');
}
final isLevelChanged = _level != value;
_level = value;
if (isLevelChanged) {
_levelChangedController?.add(value);
}
}

/// Returns a stream of level values set to this [Logger].
///
/// You can listen for set levels using the standard stream APIs,
/// for instance:
///
/// ```dart
/// logger.onLevelChanged.listen((level) { ... });
/// ```
/// A state error will be thrown if the level is changed
/// inside the callback.
Stream<Level?> get onLevelChanged {
_levelChangedController ??= StreamController<Level?>.broadcast(sync: true);
return _levelChangedController!.stream;
}

/// Returns a stream of messages added to this [Logger].
///
/// You can listen for messages using the standard stream APIs, for instance:
///
/// ```dart
/// logger.onRecord.listen((record) { ... });
/// ```
Stream<LogRecord> get onRecord => _getStream();

void clearListeners() {
if (hierarchicalLoggingEnabled || parent == null) {
_controller?.close();
_controller = null;
} else {
root.clearListeners();
}
}

/// Whether a message for [value]'s level is loggable in this logger.
bool isLoggable(Level value) => value >= level;

/// Adds a log record for a [message] at a particular [logLevel] if
/// `isLoggable(logLevel)` is true.
///
/// Use this method to create log entries for user-defined levels. To record a
/// message at a predefined level (e.g. [Level.INFO], [Level.WARNING], etc)
/// you can use their specialized methods instead (e.g. [info], [warning],
/// etc).
///
/// If [message] is a [Function], it will be lazy evaluated. Additionally, if
/// [message] or its evaluated value is not a [String], then 'toString()' will
/// be called on the object and the result will be logged. The log record will
/// contain a field holding the original object.
///
/// The log record will also contain a field for the zone in which this call
/// was made. This can be advantageous if a log listener wants to handler
/// records of different zones differently (e.g. group log records by HTTP
/// request if each HTTP request handler runs in it's own zone).
///
/// If this record is logged at a level equal to or higher than
/// [recordStackTraceAtLevel] and [stackTrace] is `null` or [StackTrace.empty]
/// it will be defaulted to the current stack trace for this call.
void log(Level logLevel, Object? message,
[Object? error, StackTrace? stackTrace, Zone? zone]) {
Object? object;
if (isLoggable(logLevel)) {
if (message is Function) {
message = (message as Object? Function())();
}

String msg;
if (message is String) {
msg = message;
} else {
msg = message.toString();
object = message;
}

if ((stackTrace == null || stackTrace == StackTrace.empty) &&
logLevel >= recordStackTraceAtLevel) {
stackTrace = StackTrace.current;
error ??= 'autogenerated stack trace for $logLevel $msg';
}
zone ??= Zone.current;

final record =
LogRecord(logLevel, msg, fullName, error, stackTrace, zone, object);

if (parent == null) {
_publish(record);
} else if (!hierarchicalLoggingEnabled) {
root._publish(record);
} else {
Logger? target = this;
while (target != null) {
target._publish(record);
target = target.parent;
}
}
}
}

/// Log message at level [Level.FINEST].
///
/// See [log] for information on how non-String [message] arguments are
/// handled.
void finest(Object? message, [Object? error, StackTrace? stackTrace]) =>
log(Level.FINEST, message, error, stackTrace);

/// Log message at level [Level.FINER].
///
/// See [log] for information on how non-String [message] arguments are
/// handled.
void finer(Object? message, [Object? error, StackTrace? stackTrace]) =>
log(Level.FINER, message, error, stackTrace);

/// Log message at level [Level.FINE].
///
/// See [log] for information on how non-String [message] arguments are
/// handled.
void fine(Object? message, [Object? error, StackTrace? stackTrace]) =>
log(Level.FINE, message, error, stackTrace);

/// Log message at level [Level.CONFIG].
///
/// See [log] for information on how non-String [message] arguments are
/// handled.
void config(Object? message, [Object? error, StackTrace? stackTrace]) =>
log(Level.CONFIG, message, error, stackTrace);

/// Log message at level [Level.INFO].
///
/// See [log] for information on how non-String [message] arguments are
/// handled.
void info(Object? message, [Object? error, StackTrace? stackTrace]) =>
log(Level.INFO, message, error, stackTrace);

/// Log message at level [Level.WARNING].
///
/// See [log] for information on how non-String [message] arguments are
/// handled.
void warning(Object? message, [Object? error, StackTrace? stackTrace]) =>
log(Level.WARNING, message, error, stackTrace);

/// Log message at level [Level.SEVERE].
///
/// See [log] for information on how non-String [message] arguments are
/// handled.
void severe(Object? message, [Object? error, StackTrace? stackTrace]) =>
log(Level.SEVERE, message, error, stackTrace);

/// Log message at level [Level.SHOUT].
///
/// See [log] for information on how non-String [message] arguments are
/// handled.
void shout(Object? message, [Object? error, StackTrace? stackTrace]) =>
log(Level.SHOUT, message, error, stackTrace);

Stream<LogRecord> _getStream() {
if (hierarchicalLoggingEnabled || parent == null) {
return (_controller ??= StreamController<LogRecord>.broadcast(sync: true))
.stream;
} else {
return root._getStream();
}
}

void _publish(LogRecord record) => _controller?.add(record);

/// Top-level root [Logger].
static final Logger root = Logger('');

/// All attached [Logger]s in the system.
static final Map<String, Logger> _loggers = <String, Logger>{};

/// All attached [Logger]s in the system.
///
/// Loggers created with [Logger.detached] are not included.
static Iterable<Logger> get attachedLoggers => _loggers.values;
}
17 changes: 17 additions & 0 deletions pkgs/logging/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: logging
version: 1.3.0
description: >-
Provides APIs for debugging and error logging, similar to loggers in other
languages, such as the Closure JS Logger and java.util.logging.Logger.
repository: https://github.com/dart-lang/core/tree/main/pkgs/logging

topics:
- logging
- debugging

environment:
sdk: ^3.4.0

dev_dependencies:
dart_flutter_team_lints: ^3.0.0
test: ^1.16.0
761 changes: 761 additions & 0 deletions pkgs/logging/test/logging_test.dart

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions pkgs/os_detect/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.dart_tool/
.packages
pubspec.lock
6 changes: 6 additions & 0 deletions pkgs/os_detect/AUTHORS
Original file line number Diff line number Diff line change
@@ -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 <email address>

Google LLC
17 changes: 17 additions & 0 deletions pkgs/os_detect/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions pkgs/os_detect/LICENSE
Original file line number Diff line number Diff line change
@@ -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.
42 changes: 42 additions & 0 deletions pkgs/os_detect/README.md
Original file line number Diff line number Diff line change
@@ -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`.
27 changes: 27 additions & 0 deletions pkgs/os_detect/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -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


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;
}
26 changes: 26 additions & 0 deletions pkgs/os_detect/example/example.dart
Original file line number Diff line number Diff line change
@@ -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');
}
}
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');
}
}
104 changes: 104 additions & 0 deletions pkgs/os_detect/lib/os_detect.dart
Original file line number Diff line number Diff line change
@@ -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<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.
///
/// 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;
8 changes: 8 additions & 0 deletions pkgs/os_detect/lib/override.dart
Original file line number Diff line number Diff line change
@@ -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;
101 changes: 101 additions & 0 deletions pkgs/os_detect/lib/src/os_kind.dart
Original file line number Diff line number Diff line change
@@ -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);
}
180 changes: 180 additions & 0 deletions pkgs/os_detect/lib/src/os_override.dart
Original file line number Diff line number Diff line change
@@ -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<R>(
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._();
}
13 changes: 13 additions & 0 deletions pkgs/os_detect/lib/src/osid_html.dart
Original file line number Diff line number Diff line change
@@ -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);
33 changes: 33 additions & 0 deletions pkgs/os_detect/lib/src/osid_io.dart
Original file line number Diff line number Diff line change
@@ -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);
29 changes: 29 additions & 0 deletions pkgs/os_detect/lib/src/osid_unknown.dart
Original file line number Diff line number Diff line change
@@ -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);
14 changes: 14 additions & 0 deletions pkgs/os_detect/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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
73 changes: 73 additions & 0 deletions pkgs/os_detect/test/osid_test.dart
Original file line number Diff line number Diff line change
@@ -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));
});
});
}

0 comments on commit ad17462

Please sign in to comment.