Wrap platform (#4778)
* Initial PlatformInfo implementation
* Wrap Platform from dart:io
* Allow Platfrom in tool/
* Fix formatting
* Tests that Platform usage is not accidentally reintroduced
* Allow Platform from dart:io in tests
* Fixed missing methods
* Only run analyzer tests on linux
diff --git a/lib/src/authentication/credential.dart b/lib/src/authentication/credential.dart
index 9526ea6..91b3a2e 100644
--- a/lib/src/authentication/credential.dart
+++ b/lib/src/authentication/credential.dart
@@ -2,9 +2,8 @@
// 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 '../exceptions.dart';
+import '../platform_info.dart';
import '../source/hosted.dart';
import '../utils.dart';
@@ -134,7 +133,7 @@
final String tokenValue;
final environment = env;
if (environment != null) {
- final value = Platform.environment[environment];
+ final value = platform.environment[environment];
if (value == null) {
dataError(
'Saved credential for "$url" pub repository requires environment '
diff --git a/lib/src/authentication/token_store.dart b/lib/src/authentication/token_store.dart
index 4d87b65..f213cd3 100644
--- a/lib/src/authentication/token_store.dart
+++ b/lib/src/authentication/token_store.dart
@@ -3,12 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';
-import 'dart:io';
import '../exceptions.dart';
import '../io.dart';
import '../log.dart' as log;
import '../path.dart';
+import '../platform_info.dart';
import 'credential.dart';
/// Stores and manages authentication credentials.
@@ -96,7 +96,7 @@
}
Never missingConfigDir() {
- final variable = Platform.isWindows ? '%APPDATA%' : r'$HOME';
+ final variable = platform.isWindows ? '%APPDATA%' : r'$HOME';
throw DataException('No config dir found. Check that $variable is set');
}
diff --git a/lib/src/command.dart b/lib/src/command.dart
index 89e6628..d1a9d0b 100644
--- a/lib/src/command.dart
+++ b/lib/src/command.dart
@@ -20,6 +20,7 @@
import 'http.dart';
import 'log.dart' as log;
import 'path.dart';
+import 'platform_info.dart';
import 'pub_embeddable_command.dart';
import 'sdk.dart';
import 'solver.dart';
@@ -40,7 +41,7 @@
final lineLength = _lineLength();
int _lineLength() {
- final fromEnv = Platform.environment['_PUB_TEST_TERMINAL_COLUMNS'];
+ final fromEnv = platform.environment['_PUB_TEST_TERMINAL_COLUMNS'];
if (fromEnv != null) {
final parsed = int.tryParse(fromEnv);
if (parsed != null && parsed > 0) return parsed;
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index 62836ed..8166762 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -20,6 +20,7 @@
import '../log.dart' as log;
import '../oauth2.dart' as oauth2;
import '../path.dart';
+import '../platform_info.dart';
import '../pubspec.dart';
import '../solver/type.dart';
import '../source/hosted.dart' show validateAndNormalizeHostedUrl;
@@ -247,8 +248,8 @@
// explicitly have to define mock servers as official server to test
// publish command with oauth2 credentials.
if (runningFromTest &&
- Platform.environment.containsKey('_PUB_TEST_DEFAULT_HOSTED_URL'))
- Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'],
+ platform.environment.containsKey('_PUB_TEST_DEFAULT_HOSTED_URL'))
+ platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'],
};
// Using OAuth2 authentication client for the official pub servers
diff --git a/lib/src/command/token_add.dart b/lib/src/command/token_add.dart
index 9359e81..e913c88 100644
--- a/lib/src/command/token_add.dart
+++ b/lib/src/command/token_add.dart
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
-import 'dart:io';
import '../authentication/credential.dart';
import '../command.dart';
@@ -11,6 +10,7 @@
import '../exceptions.dart';
import '../io.dart';
import '../log.dart' as log;
+import '../platform_info.dart';
import '../source/hosted.dart';
import '../utils.dart';
@@ -141,7 +141,7 @@
'token stored in the environment variable "$envVar".',
);
- if (!Platform.environment.containsKey(envVar)) {
+ if (!platform.environment.containsKey(envVar)) {
// If environment variable doesn't exist when
// pub token add <hosted-url> --env-var <ENV_VAR> is called, we should
// print a warning.
diff --git a/lib/src/command/uploader.dart b/lib/src/command/uploader.dart
index d163c0b..d5361f0 100644
--- a/lib/src/command/uploader.dart
+++ b/lib/src/command/uploader.dart
@@ -3,9 +3,9 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
-import 'dart:io';
import '../command.dart';
+import '../platform_info.dart';
import '../utils.dart';
/// Handles the `uploader` pub command.
@@ -28,7 +28,7 @@
UploaderCommand() {
argParser.addOption(
'server',
- defaultsTo: Platform.environment['PUB_HOSTED_URL'] ?? 'https://pub.dev',
+ defaultsTo: platform.environment['PUB_HOSTED_URL'] ?? 'https://pub.dev',
help: 'The package server on which the package is hosted.\n',
hide: true,
);
diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart
index 35f0823..55af1be 100644
--- a/lib/src/command_runner.dart
+++ b/lib/src/command_runner.dart
@@ -3,7 +3,6 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
-import 'dart:io';
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
@@ -35,6 +34,7 @@
import 'log.dart' as log;
import 'log.dart';
import 'path.dart';
+import 'platform_info.dart';
import 'sdk.dart';
import 'utils.dart';
@@ -42,7 +42,7 @@
/// 'flutter' if we are running inside `flutter pub` 'dart' otherwise.
String topLevelProgram = _isRunningInsideFlutter ? 'flutter' : 'dart';
-bool _isRunningInsideFlutter = (Platform.environment['PUB_ENVIRONMENT'] ?? '')
+bool _isRunningInsideFlutter = (platform.environment['PUB_ENVIRONMENT'] ?? '')
.contains('flutter_cli');
class PubCommandRunner extends CommandRunner<int> implements PubTopLevel {
@@ -206,7 +206,7 @@
final depsRev = match[1];
String actualRev;
- final pubRoot = p.dirname(p.dirname(p.fromUri(Platform.script)));
+ final pubRoot = p.dirname(p.dirname(p.fromUri(platform.script)));
try {
actualRev =
git.runSync(['rev-parse', 'HEAD'], workingDir: pubRoot).trim();
diff --git a/lib/src/dart.dart b/lib/src/dart.dart
index 9a12e8d..78d8bac 100644
--- a/lib/src/dart.dart
+++ b/lib/src/dart.dart
@@ -19,6 +19,7 @@
import 'io.dart';
import 'log.dart' as log;
import 'path.dart';
+import 'platform_info.dart';
class AnalysisContextManager {
static final sessions = <String, AnalysisContextManager>{};
@@ -115,7 +116,7 @@
String? nativeAssets,
}) async {
const platformDill = 'lib/_internal/vm_platform_strong.dill';
- final sdkRoot = p.relative(p.dirname(p.dirname(Platform.resolvedExecutable)));
+ final sdkRoot = p.relative(p.dirname(p.dirname(platform.resolvedExecutable)));
String? tempDir;
FrontendServerClient? client;
try {
diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart
index 8e52054..314729a 100644
--- a/lib/src/entrypoint.dart
+++ b/lib/src/entrypoint.dart
@@ -27,6 +27,7 @@
import 'package_graph.dart';
import 'package_name.dart';
import 'path.dart';
+import 'platform_info.dart';
import 'pubspec.dart';
import 'pubspec_utils.dart';
import 'sdk.dart';
@@ -710,9 +711,9 @@
} else {
ensureDir(_snapshotPath);
}
- // Don't do more than `Platform.numberOfProcessors - 1` compilations
+ // Don't do more than `platform.numberOfProcessors - 1` compilations
// concurrently. Though at least one.
- final pool = Pool(max(Platform.numberOfProcessors - 1, 1));
+ final pool = Pool(max(platform.numberOfProcessors - 1, 1));
return waitAndPrintErrors(
executables.map((executable) async {
await pool.withResource(() async {
@@ -1417,7 +1418,7 @@
/// will result in [acquireDependencies] to only print a summary of the
/// results.
bool get _summaryOnlyEnvironment =>
- (Platform.environment['PUB_SUMMARY_ONLY'] ?? '0') != '0';
+ (platform.environment['PUB_SUMMARY_ONLY'] ?? '0') != '0';
/// Remove any `pubspec.lock` or `.dart_tool/package_config.json` files in
/// workspace packages that are not the root package.
diff --git a/lib/src/executable.dart b/lib/src/executable.dart
index 7d7a7dd..b0c2ccd 100644
--- a/lib/src/executable.dart
+++ b/lib/src/executable.dart
@@ -18,6 +18,7 @@
import 'log.dart';
import 'package_config.dart';
import 'path.dart';
+import 'platform_info.dart';
import 'sdk.dart';
import 'system_cache.dart';
import 'utils.dart';
@@ -104,7 +105,7 @@
}
// We use an absolute path here not because the VM insists but because it's
// helpful for the subprocess to be able to spawn Dart with
- // Platform.executableArguments and have that work regardless of the working
+ // platform.executableArguments and have that work regardless of the working
// directory.
final packageConfigAbsolute = p.absolute(entrypoint.packageConfigPath);
@@ -193,7 +194,7 @@
// semantics without `fork` for starting the subprocess.
// https://github.com/dart-lang/sdk/issues/41966.
final subscription = ProcessSignal.sigint.watch().listen((e) {});
- final process = await Process.start(Platform.resolvedExecutable, [
+ final process = await Process.start(platform.resolvedExecutable, [
'--packages=$packageConfig',
...vmArgs,
if (enableAsserts) '--enable-asserts',
@@ -446,7 +447,7 @@
bool _looksLikeFile(String candidate) {
return candidate.contains('/') ||
- (Platform.isWindows && candidate.contains(r'\')) ||
+ (platform.isWindows && candidate.contains(r'\')) ||
candidate.endsWith('.dart') ||
candidate.endsWith('.snapshot');
}
diff --git a/lib/src/flutter_releases.dart b/lib/src/flutter_releases.dart
index c048e1a..4810fca 100644
--- a/lib/src/flutter_releases.dart
+++ b/lib/src/flutter_releases.dart
@@ -2,7 +2,6 @@
// 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:convert';
-import 'dart:io';
import 'package:collection/collection.dart';
import 'package:http/http.dart';
@@ -10,9 +9,10 @@
import 'http.dart';
import 'log.dart';
+import 'platform_info.dart';
String get flutterReleasesUrl =>
- Platform.environment['_PUB_TEST_FLUTTER_RELEASES_URL'] ??
+ platform.environment['_PUB_TEST_FLUTTER_RELEASES_URL'] ??
'https://storage.googleapis.com/flutter_infra_release/releases/releases_linux.json';
// Retrieves all released versions of Flutter.
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index 1db8cc6..235c774 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -19,6 +19,7 @@
import 'package.dart';
import 'package_name.dart';
import 'path.dart';
+import 'platform_info.dart';
import 'pubspec.dart';
import 'sdk.dart';
import 'sdk/dart.dart';
@@ -949,7 +950,7 @@
required bool isRefreshingBinstub,
}) {
var binStubPath = p.join(_binStubDir, executable);
- if (Platform.isWindows) binStubPath += '.bat';
+ if (platform.isWindows) binStubPath += '.bat';
String? previousPackage;
if (!isRefreshingBinstub && fileExists(binStubPath)) {
@@ -964,12 +965,12 @@
// When running tests we want the binstub to invoke the current pub, not the
// one from the sdk.
final pubInvocation =
- runningFromTest ? Platform.script.toFilePath() : 'pub';
+ runningFromTest ? platform.script.toFilePath() : 'pub';
final runPubGlobal = '${package.name}:$script';
final String binstub;
- if (Platform.isWindows) {
+ if (platform.isWindows) {
final header = '''
@echo off
rem This file was created by pub v${sdk.version}.
@@ -1053,7 +1054,7 @@
// path names.
writeTextFile(tmpPath, binstub, encoding: const SystemEncoding());
- if (Platform.isLinux || Platform.isMacOS) {
+ if (platform.isLinux || platform.isMacOS) {
// Make it executable.
final result = Process.runSync('chmod', ['+x', tmpPath]);
if (result.exitCode != 0) {
@@ -1097,7 +1098,7 @@
/// [installed] should be the name of an installed executable that can be used
/// to test whether accessing it on the path works.
void _suggestIfNotOnPath(String installed) {
- if (Platform.isWindows) {
+ if (platform.isWindows) {
// See if the shell can find one of the binstubs.
// "\q" means return exit code 0 if found or 1 if not.
final result = runProcessSync('where', [r'\q', '$installed.bat']);
@@ -1122,14 +1123,14 @@
if (result.exitCode == 0) return;
var binDir = _binStubDir;
- if (binDir.startsWith(Platform.environment['HOME']!)) {
+ if (binDir.startsWith(platform.environment['HOME']!)) {
binDir = p.join(
r'$HOME',
- p.relative(binDir, from: Platform.environment['HOME']),
+ p.relative(binDir, from: platform.environment['HOME']),
);
}
final shellConfigFiles =
- Platform.isMacOS
+ platform.isMacOS
// zsh is default on mac - mention that first.
? '(.zshrc, .bashrc, .bash_profile, etc.)'
: '(.bashrc, .bash_profile, .zshrc etc.)';
diff --git a/lib/src/http.dart b/lib/src/http.dart
index ccecab0..a5b461a 100644
--- a/lib/src/http.dart
+++ b/lib/src/http.dart
@@ -15,6 +15,7 @@
import 'package:pool/pool.dart';
import 'log.dart' as log;
+import 'platform_info.dart';
import 'sdk.dart';
import 'utils.dart';
@@ -339,7 +340,7 @@
log.io('Attempt #$attemptNumber for $operation'),
maxAttempts: math.max(
1, // Having less than 1 attempt doesn't make sense.
- int.tryParse(Platform.environment['PUB_MAX_HTTP_RETRIES'] ?? '') ?? 7,
+ int.tryParse(platform.environment['PUB_MAX_HTTP_RETRIES'] ?? '') ?? 7,
),
);
}
diff --git a/lib/src/io.dart b/lib/src/io.dart
index 00ad322..30b8dbc 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -28,6 +28,7 @@
import 'gzip/gzip.dart';
import 'log.dart' as log;
import 'path.dart';
+import 'platform_info.dart';
import 'utils.dart';
export 'package:http/http.dart' show ByteStream;
@@ -427,7 +428,7 @@
}
if (pathInDir.contains('/.')) return false;
- if (!Platform.isWindows) return true;
+ if (!platform.isWindows) return true;
return !pathInDir.contains('\\.');
})
.map((entity) => entity.path)
@@ -455,7 +456,7 @@
void Function() operation, {
bool ignoreEmptyDir = false,
}) {
- if (!Platform.isWindows) {
+ if (!platform.isWindows) {
operation();
return;
}
@@ -594,11 +595,11 @@
// ```
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/errno-base.h#n21
// https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/asm-generic/errno.h#n20
- (Platform.isLinux && (errorCode == 39 || errorCode == 17)) ||
+ (platform.isLinux && (errorCode == 39 || errorCode == 17)) ||
// On Windows this may fail with ERROR_DIR_NOT_EMPTY or
// ERROR_ALREADY_EXISTS
// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
- (Platform.isWindows && (errorCode == 145 || errorCode == 183)) ||
+ (platform.isWindows && (errorCode == 145 || errorCode == 183)) ||
// On MacOS rename will fail with ENOTEMPTY if directory exists.
// We also catch EEXIST - perhaps that could also be thrown...
// ```
@@ -606,7 +607,7 @@
// #define EEXIST 17 /* File exists */
// ```
// https://github.com/apple-oss-distributions/xnu/blob/bb611c8fecc755a0d8e56e2fa51513527c5b7a0e/bsd/sys/errno.h#L190
- (Platform.isMacOS && (errorCode == 66 || errorCode == 17));
+ (platform.isMacOS && (errorCode == 66 || errorCode == 17));
}
/// Creates a new symlink at path [symlink] that points to [target].
@@ -623,7 +624,7 @@
// make sure we have a clean absolute path because it will interpret a
// relative path to be relative to the cwd, not the symlink, and will be
// confused by forward slashes.
- if (Platform.isWindows) {
+ if (platform.isWindows) {
target = p.normalize(p.absolute(target));
} else {
// If the directory where we're creating the symlink was itself reached
@@ -667,7 +668,7 @@
/// The "_PUB_TESTING" variable is automatically set for all the test code's
/// invocations of pub.
final bool runningFromTest =
- Platform.environment.containsKey('_PUB_TESTING') && _assertionsEnabled;
+ platform.environment.containsKey('_PUB_TESTING') && _assertionsEnabled;
final bool _assertionsEnabled = () {
try {
@@ -680,8 +681,8 @@
}();
final bool runningFromFlutter =
- Platform.environment.containsKey('PUB_ENVIRONMENT') &&
- (Platform.environment['PUB_ENVIRONMENT'] ?? '').contains('flutter_cli');
+ platform.environment.containsKey('PUB_ENVIRONMENT') &&
+ (platform.environment['PUB_ENVIRONMENT'] ?? '').contains('flutter_cli');
/// A regular expression to match the script path of a pub script running from
/// source in the Dart repo.
@@ -697,7 +698,7 @@
///
/// This can happen when running tests against the repo, as well as when
/// building Observatory.
-final bool runningFromDartRepo = Platform.script.path.contains(_dartRepoRegExp);
+final bool runningFromDartRepo = platform.script.path.contains(_dartRepoRegExp);
/// The path to the root of the Dart repo.
///
@@ -711,8 +712,8 @@
// Get the URL of the repo root in a way that works when either both
// running as a test or as a pub executable.
- final url = Platform.script.replace(
- path: Platform.script.path.replaceAll(_dartRepoRegExp, ''),
+ final url = platform.script.replace(
+ path: platform.script.path.replaceAll(_dartRepoRegExp, ''),
);
return p.fromUri(url);
})();
@@ -759,7 +760,7 @@
/// [EnvironmentKeys.forceTerminalOutput].
bool get terminalOutputForStdout {
final environmentValue =
- Platform.environment[EnvironmentKeys.forceTerminalOutput];
+ platform.environment[EnvironmentKeys.forceTerminalOutput];
if (environmentValue == null || environmentValue == '') {
return stdout.hasTerminal;
} else if (environmentValue == '0') {
@@ -1081,7 +1082,7 @@
// Spawning a process on Windows will not look for the executable in the
// system path. So, if executable looks like it needs that (i.e. it doesn't
// have any path separators in it), then spawn it through a shell.
- if (Platform.isWindows && !executable.contains('\\')) {
+ if (platform.isWindows && !executable.contains('\\')) {
args = ['/c', executable, ...args];
executable = 'cmd';
}
@@ -1205,7 +1206,7 @@
ensureDir(parentDirectory);
await createFileFromStream(entry.contents, filePath);
- if (Platform.isLinux || Platform.isMacOS) {
+ if (platform.isLinux || platform.isMacOS) {
// Apply executable bits from tar header, but don't change r/w bits
// from the default
final mode = _defaultMode | (entry.header.mode & _executableMask);
@@ -1334,8 +1335,8 @@
/// `null` if no config dir could be found.
final String? dartConfigDir = () {
if (runningFromTest &&
- Platform.environment.containsKey('_PUB_TEST_CONFIG_DIR')) {
- return p.join(Platform.environment['_PUB_TEST_CONFIG_DIR']!, 'dart');
+ platform.environment.containsKey('_PUB_TEST_CONFIG_DIR')) {
+ return p.join(platform.environment['_PUB_TEST_CONFIG_DIR']!, 'dart');
}
try {
return applicationConfigHome('dart');
diff --git a/lib/src/log.dart b/lib/src/log.dart
index 00fad85..1e98118 100644
--- a/lib/src/log.dart
+++ b/lib/src/log.dart
@@ -18,6 +18,7 @@
import 'exceptions.dart';
import 'io.dart';
import 'path.dart';
+import 'platform_info.dart';
import 'progress.dart';
import 'sdk.dart';
import 'transcript.dart';
@@ -372,11 +373,11 @@
Pub version: ${sdk.version}
Created: ${DateTime.now().toIso8601String()}
-FLUTTER_ROOT: ${Platform.environment['FLUTTER_ROOT'] ?? '<not set>'}
-PUB_HOSTED_URL: ${Platform.environment['PUB_HOSTED_URL'] ?? '<not set>'}
-PUB_CACHE: "${Platform.environment['PUB_CACHE'] ?? '<not set>'}"
+FLUTTER_ROOT: ${platform.environment['FLUTTER_ROOT'] ?? '<not set>'}
+PUB_HOSTED_URL: ${platform.environment['PUB_HOSTED_URL'] ?? '<not set>'}
+PUB_CACHE: "${platform.environment['PUB_CACHE'] ?? '<not set>'}"
Command: $command
-Platform: ${Platform.operatingSystem}
+Platform: ${platform.operatingSystem}
''');
if (entrypoint != null) {
diff --git a/lib/src/oauth2.dart b/lib/src/oauth2.dart
index 0cf841a..64d2b4e 100644
--- a/lib/src/oauth2.dart
+++ b/lib/src/oauth2.dart
@@ -4,7 +4,6 @@
import 'dart:async';
import 'dart:convert';
-import 'dart:io';
import 'dart:math';
import 'package:collection/collection.dart';
@@ -19,6 +18,7 @@
import 'io.dart';
import 'log.dart' as log;
import 'path.dart';
+import 'platform_info.dart';
import 'utils.dart';
/// The global HTTP client with basic retries. Used instead of retryForHttp for
@@ -65,7 +65,7 @@
/// This can be controlled externally by setting the `_PUB_TEST_TOKEN_ENDPOINT`
/// environment variable.
Uri get tokenEndpoint {
- final tokenEndpoint = Platform.environment['_PUB_TEST_TOKEN_ENDPOINT'];
+ final tokenEndpoint = platform.environment['_PUB_TEST_TOKEN_ENDPOINT'];
if (tokenEndpoint != null) {
return Uri.parse(tokenEndpoint);
} else {
diff --git a/lib/src/package.dart b/lib/src/package.dart
index 1fc04f4..2ccdfd9 100644
--- a/lib/src/package.dart
+++ b/lib/src/package.dart
@@ -16,6 +16,7 @@
import 'log.dart' as log;
import 'package_name.dart';
import 'path.dart';
+import 'platform_info.dart';
import 'pubspec.dart';
import 'utils.dart';
@@ -310,7 +311,7 @@
.path;
if (beneath == './') beneath = '.';
String resolve(String path) {
- if (Platform.isWindows) {
+ if (platform.isWindows) {
return p.joinAll([root, ...p.posix.split(path)]);
}
return p.join(root, path);
@@ -373,7 +374,7 @@
}
return contents.map((entity) {
final relative = p.relative(entity.path, from: root);
- if (Platform.isWindows) {
+ if (platform.isWindows) {
return p.posix.joinAll(p.split(relative));
}
return relative;
@@ -437,7 +438,7 @@
// [1]:
// https://git-scm.com/docs/git-config/2.14.6#Documentation/git-config.txt-coreignoreCase
// [2]: https://github.com/dart-lang/pub/issues/3003
- ignoreCase: Platform.isMacOS || Platform.isWindows,
+ ignoreCase: platform.isMacOS || platform.isWindows,
);
},
isDir: (dir) => dirExists(resolve(dir)),
@@ -584,7 +585,7 @@
bool _looksLikeGlob(String s) => Glob.quote(s) != s;
String _useBackSlashesOnWindows(String path) {
- if (Platform.isWindows) {
+ if (platform.isWindows) {
return p.joinAll(p.split(path));
}
return path;
diff --git a/lib/src/platform_info.dart b/lib/src/platform_info.dart
new file mode 100644
index 0000000..0a75feb
--- /dev/null
+++ b/lib/src/platform_info.dart
@@ -0,0 +1,207 @@
+// Copyright (c) 2026, 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:io';
+
+/// A proxy for [Platform] from `dart:io` which can be overridden.
+PlatformInfo get platform =>
+ Zone.current[_platformInfoKey] as PlatformInfo? ??
+ PlatformInfo.nativePlatform();
+
+/// The key for the [platform] in the current [Zone].
+final _platformInfoKey = Object();
+
+/// Runs [callback] in a [Zone] where `platform` is overridden by [platform].
+Future<T> withPlatform<T>(
+ FutureOr<T> Function() callback, {
+ required PlatformInfo platform,
+}) {
+ return runZoned(() async {
+ return await callback();
+ }, zoneValues: {_platformInfoKey: platform});
+}
+
+abstract final class PlatformInfo {
+ const PlatformInfo._();
+
+ factory PlatformInfo.nativePlatform() = _NativePlatformInfo;
+
+ factory PlatformInfo.override({
+ required Map<String, String> environment,
+ required String executable,
+ required bool isAndroid,
+ required bool isFuchsia,
+ required bool isIOS,
+ required bool isLinux,
+ required bool isMacOS,
+ required bool isWindows,
+ required String lineTerminator,
+ required String operatingSystem,
+ required String pathSeparator,
+ required String resolvedExecutable,
+ required String version,
+ required int numberOfProcessors,
+ required Uri script,
+ }) = _PlatformInfoOverride;
+
+ /// Returns [Platform.environment].
+ Map<String, String> get environment;
+
+ /// Returns [Platform.executable].
+ String get executable;
+
+ /// Returns [Platform.isAndroid].
+ bool get isAndroid;
+
+ /// Returns [Platform.isFuchsia].
+ bool get isFuchsia;
+
+ /// Returns [Platform.isIOS].
+ bool get isIOS;
+
+ /// Returns [Platform.isLinux].
+ bool get isLinux;
+
+ /// Returns [Platform.isMacOS].
+ bool get isMacOS;
+
+ /// Returns [Platform.isWindows].
+ bool get isWindows;
+
+ /// Returns [Platform.lineTerminator].
+ String get lineTerminator;
+
+ /// Returns [Platform.operatingSystem].
+ String get operatingSystem;
+
+ /// Returns [Platform.pathSeparator].
+ String get pathSeparator;
+
+ /// Returns [Platform.resolvedExecutable].
+ String get resolvedExecutable;
+
+ /// Returns [Platform.numberOfProcessors].
+ int get numberOfProcessors;
+
+ /// Returns [Platform.script].
+ Uri get script;
+
+ /// Returns [Platform.version] from 'dart:io'.
+ String get version;
+}
+
+final class _NativePlatformInfo extends PlatformInfo {
+ const _NativePlatformInfo() : super._();
+
+ @override
+ Map<String, String> get environment => Platform.environment;
+
+ @override
+ String get executable => Platform.executable;
+
+ @override
+ bool get isAndroid => Platform.isAndroid;
+
+ @override
+ bool get isFuchsia => Platform.isFuchsia;
+
+ @override
+ bool get isIOS => Platform.isIOS;
+
+ @override
+ bool get isLinux => Platform.isLinux;
+
+ @override
+ bool get isMacOS => Platform.isMacOS;
+
+ @override
+ bool get isWindows => Platform.isWindows;
+
+ @override
+ String get lineTerminator => Platform.lineTerminator;
+
+ @override
+ String get operatingSystem => Platform.operatingSystem;
+
+ @override
+ String get pathSeparator => Platform.pathSeparator;
+
+ @override
+ String get resolvedExecutable => Platform.resolvedExecutable;
+
+ @override
+ String get version => Platform.version;
+
+ @override
+ int get numberOfProcessors => Platform.numberOfProcessors;
+
+ @override
+ Uri get script => Platform.script;
+}
+
+final class _PlatformInfoOverride extends PlatformInfo {
+ const _PlatformInfoOverride({
+ required this.environment,
+ required this.executable,
+ required this.isAndroid,
+ required this.isFuchsia,
+ required this.isIOS,
+ required this.isLinux,
+ required this.isMacOS,
+ required this.isWindows,
+ required this.lineTerminator,
+ required this.operatingSystem,
+ required this.pathSeparator,
+ required this.resolvedExecutable,
+ required this.version,
+ required this.numberOfProcessors,
+ required this.script,
+ }) : super._();
+
+ @override
+ final Map<String, String> environment;
+
+ @override
+ final String executable;
+
+ @override
+ final bool isAndroid;
+
+ @override
+ final bool isFuchsia;
+
+ @override
+ final bool isIOS;
+
+ @override
+ final bool isLinux;
+
+ @override
+ final bool isMacOS;
+
+ @override
+ final bool isWindows;
+
+ @override
+ final String lineTerminator;
+
+ @override
+ final String operatingSystem;
+
+ @override
+ final String pathSeparator;
+
+ @override
+ final String resolvedExecutable;
+
+ @override
+ final String version;
+
+ @override
+ final int numberOfProcessors;
+
+ @override
+ final Uri script;
+}
diff --git a/lib/src/sdk/dart.dart b/lib/src/sdk/dart.dart
index a67f751..3b1578f 100644
--- a/lib/src/sdk/dart.dart
+++ b/lib/src/sdk/dart.dart
@@ -2,13 +2,12 @@
// 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 'package:pub_semver/pub_semver.dart';
import 'package:yaml/yaml.dart';
import '../io.dart';
import '../path.dart';
+import '../platform_info.dart';
import '../sdk.dart';
import 'sdk_package_config.dart';
@@ -27,7 +26,7 @@
static final String _rootDirectory = () {
// If DART_ROOT is specified, then this always points to the Dart SDK
- if (Platform.environment['DART_ROOT'] case final root?) {
+ if (platform.environment['DART_ROOT'] case final root?) {
return root;
}
@@ -35,7 +34,7 @@
// The Dart executable is in "/path/to/sdk/bin/dart", so two levels up is
// "/path/to/sdk".
- final aboveExecutable = p.dirname(p.dirname(Platform.resolvedExecutable));
+ final aboveExecutable = p.dirname(p.dirname(platform.resolvedExecutable));
assert(fileExists(p.join(aboveExecutable, 'version')));
return aboveExecutable;
}();
@@ -64,8 +63,8 @@
// tests on the bots are not run from a built SDK so this lets us avoid
// parsing the missing version file.
final sdkVersion =
- Platform.environment['_PUB_TEST_SDK_VERSION'] ??
- Platform.version.split(' ').first;
+ platform.environment['_PUB_TEST_SDK_VERSION'] ??
+ platform.version.split(' ').first;
return Version.parse(sdkVersion);
}();
diff --git a/lib/src/sdk/flutter.dart b/lib/src/sdk/flutter.dart
index 4fb494a..9a81989 100644
--- a/lib/src/sdk/flutter.dart
+++ b/lib/src/sdk/flutter.dart
@@ -10,6 +10,7 @@
import '../io.dart';
import '../log.dart';
import '../path.dart';
+import '../platform_info.dart';
import '../sdk.dart';
class FlutterSdk extends Sdk {
@@ -28,8 +29,8 @@
late final bool isAvailable = rootDirectory != null && version != null;
late final String? rootDirectory = () {
// If FLUTTER_ROOT is specified, then this always points to the Flutter SDK
- if (Platform.environment.containsKey('FLUTTER_ROOT')) {
- return Platform.environment['FLUTTER_ROOT'];
+ if (platform.environment.containsKey('FLUTTER_ROOT')) {
+ return platform.environment['FLUTTER_ROOT'];
}
// We can try to find the Flutter SDK relative to the Dart SDK.
diff --git a/lib/src/sdk/fuchsia.dart b/lib/src/sdk/fuchsia.dart
index bb3a78d..20de1d4 100644
--- a/lib/src/sdk/fuchsia.dart
+++ b/lib/src/sdk/fuchsia.dart
@@ -2,12 +2,11 @@
// 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 'package:pub_semver/pub_semver.dart';
import '../io.dart';
import '../path.dart';
+import '../platform_info.dart';
import '../sdk.dart';
class FuchsiaSdk extends Sdk {
@@ -20,7 +19,7 @@
static final bool _isAvailable = _rootDirectory != null;
static final String? _rootDirectory =
- Platform.environment['FUCHSIA_DART_SDK_ROOT'];
+ platform.environment['FUCHSIA_DART_SDK_ROOT'];
@override
String get installMessage =>
diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 238cd1e..1ba043e 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -16,6 +16,7 @@
import '../package.dart';
import '../package_name.dart';
import '../path.dart';
+import '../platform_info.dart';
import '../pubspec.dart';
import '../source.dart';
import '../system_cache.dart';
@@ -340,7 +341,7 @@
);
// Git doesn't recognize backslashes in paths, even on Windows.
- if (Platform.isWindows) pathInCache = pathInCache.replaceAll('\\', '/');
+ if (platform.isWindows) pathInCache = pathInCache.replaceAll('\\', '/');
final repoPath = _repoCachePath(description, cache);
final revision = resolvedDescription.resolvedRef;
@@ -1178,7 +1179,7 @@
String _gitDirArg(String path) {
path = p.absolute(path);
final forwardSlashPath =
- Platform.isWindows ? path.replaceAll('\\', '/') : path;
+ platform.isWindows ? path.replaceAll('\\', '/') : path;
return '--git-dir=$forwardSlashPath';
}
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 9f04928..b149d68 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -26,6 +26,7 @@
import '../package.dart';
import '../package_name.dart';
import '../path.dart';
+import '../platform_info.dart';
import '../pubspec.dart';
import '../rate_limited_scheduler.dart';
import '../source.dart';
@@ -109,8 +110,8 @@
}
if (runningFromTest &&
u == Uri.parse('https://pub.dev') &&
- Platform.environment.containsKey('_PUB_TEST_DEFAULT_HOSTED_URL')) {
- u = Uri.parse(Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL']!);
+ platform.environment.containsKey('_PUB_TEST_DEFAULT_HOSTED_URL')) {
+ u = Uri.parse(platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL']!);
}
return u;
}
@@ -141,8 +142,8 @@
final origin = parsedUrl.origin;
// Allow the defaultHostedUrl to be overriden when running from tests
if (runningFromTest &&
- io.Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'] != null) {
- return origin == io.Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'];
+ platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'] != null) {
+ return origin == platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'];
}
return origin == pubDevUrl || origin == pubDartlangUrl;
}
@@ -171,11 +172,11 @@
// Allow the defaultHostedUrl to be overriden when running from tests
if (runningFromTest) {
defaultHostedUrl =
- io.Platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'] ??
+ platform.environment['_PUB_TEST_DEFAULT_HOSTED_URL'] ??
defaultHostedUrl;
}
return validateAndNormalizeHostedUrl(
- io.Platform.environment['PUB_HOSTED_URL'] ?? defaultHostedUrl,
+ platform.environment['PUB_HOSTED_URL'] ?? defaultHostedUrl,
).toString();
} on FormatException catch (e) {
throw ConfigException(
diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart
index 274d495..3eacc07 100644
--- a/lib/src/system_cache.dart
+++ b/lib/src/system_cache.dart
@@ -16,6 +16,7 @@
import 'package.dart';
import 'package_name.dart';
import 'path.dart';
+import 'platform_info.dart';
import 'pubspec.dart';
import 'source.dart';
import 'source/cached.dart';
@@ -42,14 +43,14 @@
static String defaultDir =
(() {
- final envCache = Platform.environment['PUB_CACHE'];
+ final envCache = platform.environment['PUB_CACHE'];
if (envCache != null) {
return envCache;
- } else if (Platform.isWindows) {
+ } else if (platform.isWindows) {
// %LOCALAPPDATA% is used as the cache location over %APPDATA%,
// because the latter is synchronised between devices when the user
// roams between them, whereas the former is not.
- final localAppData = Platform.environment['LOCALAPPDATA'];
+ final localAppData = platform.environment['LOCALAPPDATA'];
if (localAppData == null) {
dataError('''
Could not find the pub cache. No `LOCALAPPDATA` environment variable exists.
@@ -58,7 +59,7 @@
}
return p.join(localAppData, 'Pub', 'Cache');
} else {
- final home = Platform.environment['HOME'];
+ final home = platform.environment['HOME'];
if (home == null) {
dataError('''
Could not find the pub cache. No `HOME` environment variable exists.
@@ -335,9 +336,9 @@
//
// Thus, we migrated to storing the pub-cache in `%LOCALAPPDATA%`. And
// finished the migration in Dart 3 to keep things simple.
- if (!Platform.isWindows) return;
+ if (!platform.isWindows) return;
- final appData = Platform.environment['APPDATA'];
+ final appData = platform.environment['APPDATA'];
if (appData == null) return;
final legacyCacheLocation = p.join(appData, 'Pub', 'Cache');
final legacyCacheDeprecatedFile = p.join(
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index a769cf6..3b37458 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -22,6 +22,7 @@
import 'exit_codes.dart' as exit_codes;
import 'io.dart';
import 'log.dart' as log;
+import 'platform_info.dart';
import 'pubspec_parse.dart';
/// A regular expression matching a Dart identifier.
@@ -267,7 +268,7 @@
Set<String> createFileFilter(Iterable<String> files) {
return files.expand<String>((file) {
final result = ['/$file'];
- if (Platform.isWindows) result.add('\\$file');
+ if (platform.isWindows) result.add('\\$file');
return result;
}).toSet();
}
@@ -280,7 +281,7 @@
Set<String> createDirectoryFilter(Iterable<String> dirs) {
return dirs.expand<String>((dir) {
final result = ['/$dir/'];
- if (Platform.isWindows) {
+ if (platform.isWindows) {
result
..add('/$dir\\')
..add('\\$dir/')
@@ -444,7 +445,7 @@
case ForceColorOption.never:
return false;
case ForceColorOption.auto:
- return (!Platform.environment.containsKey('NO_COLOR')) &&
+ return (!platform.environment.containsKey('NO_COLOR')) &&
terminalOutputForStdout &&
stdout.supportsAnsiEscapes;
}
@@ -467,8 +468,8 @@
runningFromTest ||
// When not outputting to terminal we can also use unicode.
!terminalOutputForStdout ||
- !Platform.isWindows ||
- Platform.environment.containsKey('WT_SESSION');
+ !platform.isWindows ||
+ platform.environment.containsKey('WT_SESSION');
/// Prepends each line in [text] with [prefix].
///
diff --git a/lib/src/validator/analyze.dart b/lib/src/validator/analyze.dart
index f3e48a8..542bea7 100644
--- a/lib/src/validator/analyze.dart
+++ b/lib/src/validator/analyze.dart
@@ -3,11 +3,11 @@
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
-import 'dart:io';
import '../io.dart';
import '../log.dart';
import '../path.dart';
+import '../platform_info.dart';
import '../validator.dart';
/// Runs `dart analyze` and gives a warning if it returns non-zero.
@@ -25,7 +25,7 @@
final entries = _entriesToAnalyze
.map((dir) => p.join(package.dir, dir))
.where(entryExists);
- final result = await runProcess(Platform.resolvedExecutable, [
+ final result = await runProcess(platform.resolvedExecutable, [
'analyze',
...entries,
p.join(package.dir, 'pubspec.yaml'),
diff --git a/lib/src/validator/gitignore.dart b/lib/src/validator/gitignore.dart
index 1d9fe3b..1718d04 100644
--- a/lib/src/validator/gitignore.dart
+++ b/lib/src/validator/gitignore.dart
@@ -12,6 +12,7 @@
import '../io.dart';
import '../log.dart' as log;
import '../path.dart';
+import '../platform_info.dart';
import '../utils.dart';
import '../validator.dart';
@@ -59,7 +60,7 @@
beneath = '';
}
String resolve(String path) {
- if (Platform.isWindows) {
+ if (platform.isWindows) {
return p.joinAll([root, ...p.posix.split(path)]);
}
return p.join(root, path);
@@ -91,7 +92,7 @@
},
).map((file) {
final relative = p.relative(resolve(file), from: package.dir);
- return Platform.isWindows
+ return platform.isWindows
? p.posix.joinAll(p.split(relative))
: relative;
}).toSet();
diff --git a/lib/src/validator/leak_detection.dart b/lib/src/validator/leak_detection.dart
index e65000f..78d3f07 100644
--- a/lib/src/validator/leak_detection.dart
+++ b/lib/src/validator/leak_detection.dart
@@ -15,6 +15,7 @@
import '../ignore.dart';
import '../path.dart';
+import '../platform_info.dart';
import '../validator.dart';
/// All recognized secrets fit in ASCII (first seven bits). So for speed we
@@ -32,7 +33,7 @@
// Load `false_secrets` from `pubspec.yaml`.
final falseSecrets = Ignore(
package.pubspec.falseSecrets,
- ignoreCase: Platform.isWindows || Platform.isMacOS,
+ ignoreCase: platform.isWindows || platform.isMacOS,
);
final pool = Pool(20); // don't read more than 20 files concurrently!
diff --git a/test/platform_info_test.dart b/test/platform_info_test.dart
new file mode 100644
index 0000000..dfb2f53
--- /dev/null
+++ b/test/platform_info_test.dart
@@ -0,0 +1,143 @@
+// Copyright (c) 2026, 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:analyzer/dart/analysis/analysis_context_collection.dart';
+import 'package:analyzer/dart/analysis/results.dart';
+import 'package:analyzer/dart/ast/ast.dart';
+import 'package:analyzer/dart/ast/visitor.dart';
+import 'package:path/path.dart' as p;
+import 'package:pub/src/platform_info.dart';
+import 'package:source_span/source_span.dart';
+import 'package:test/test.dart';
+
+void main() {
+ test('overriding works', () async {
+ final originalOS = platform.operatingSystem;
+ final fakePlatform = PlatformInfo.override(
+ environment: {'FOO': 'BAR'},
+ executable: 'dart_fake',
+ isAndroid: false,
+ isFuchsia: false,
+ isIOS: false,
+ isLinux: false,
+ isMacOS: false,
+ isWindows: true,
+ lineTerminator: '\r\n',
+ operatingSystem: 'windows',
+ pathSeparator: '\\',
+ resolvedExecutable: 'C:\\bin\\dart_fake',
+ version: '3.0.0-fake',
+ script: Uri.file('C:\\bin\\dart_fake.dart'),
+ numberOfProcessors: 2,
+ );
+
+ await withPlatform(() async {
+ expect(platform.operatingSystem, 'windows');
+ expect(platform.isWindows, isTrue);
+ expect(platform.isLinux, isFalse);
+ expect(platform.environment['FOO'], 'BAR');
+ expect(platform.executable, 'dart_fake');
+ expect(platform.pathSeparator, '\\');
+ }, platform: fakePlatform);
+
+ expect(platform.operatingSystem, originalOS);
+ }, testOn: 'vm');
+
+ test('overriding works (also in browser)', () async {
+ final fakePlatform = PlatformInfo.override(
+ environment: {'FOO': 'BAR'},
+ executable: 'dart_fake',
+ isAndroid: false,
+ isFuchsia: false,
+ isIOS: false,
+ isLinux: false,
+ isMacOS: false,
+ isWindows: true,
+ lineTerminator: '\r\n',
+ operatingSystem: 'windows',
+ pathSeparator: '\\',
+ resolvedExecutable: 'C:\\bin\\dart_fake',
+ version: '3.0.0-fake',
+ script: Uri.file('C:\\bin\\dart_fake.dart'),
+ numberOfProcessors: 2,
+ );
+
+ await withPlatform(() async {
+ expect(platform.operatingSystem, 'windows');
+ expect(platform.isWindows, isTrue);
+ expect(platform.isLinux, isFalse);
+ expect(platform.environment['FOO'], 'BAR');
+ expect(platform.executable, 'dart_fake');
+ expect(platform.pathSeparator, '\\');
+ }, platform: fakePlatform);
+ });
+
+ test('Platform is not used outside platform_info.dart', () async {
+ // This test exists to ensure that we don't use Platform from dart:io
+ // unintentionally. We only want to use it in lib/src/platform_info.dart!
+ // Everywhere else we should rely on `platform` from here.
+ // This way, we can overrride the platform when we need to.
+ final allowListedFiles = [
+ 'lib/src/platform_info.dart',
+ 'test/platform_info_test.dart',
+ ];
+
+ final root = p.normalize(p.absolute('.'));
+ final collection = AnalysisContextCollection(
+ includedPaths: [p.join(root, 'lib')],
+ );
+
+ for (final context in collection.contexts) {
+ for (final filePath in context.contextRoot.analyzedFiles()) {
+ if (!filePath.endsWith('.dart')) continue;
+
+ // Skip allow listed files
+ if (allowListedFiles.contains(p.relative(filePath, from: root))) {
+ continue;
+ }
+
+ final result = await context.currentSession.getResolvedUnit(filePath);
+ if (result is ResolvedUnitResult) {
+ SourceSpan? first;
+ result.unit.accept(
+ ForEachIdentifier((element) {
+ if (first == null &&
+ element.element?.name == 'Platform' &&
+ element.element?.library?.name == 'dart.io') {
+ first = SourceFile.fromString(
+ result.content,
+ url: filePath,
+ ).span(element.offset, element.end);
+ }
+ }),
+ );
+ if (first != null) {
+ fail(
+ first!.message(
+ 'Found Platform usage from dart:io, '
+ 'use lib/src/platform_info.dart instead.',
+ ),
+ );
+ }
+ }
+ }
+ }
+ }, testOn: 'vm && linux');
+}
+
+final class ForEachIdentifier extends GeneralizingAstVisitor<void> {
+ final void Function(Identifier element) _visitIdentifier;
+ ForEachIdentifier(this._visitIdentifier);
+
+ @override
+ void visitComment(Comment node) {
+ // Do not walk into comments! They are allowed to reference Platform!
+ }
+
+ @override
+ void visitIdentifier(Identifier element) {
+ _visitIdentifier(element);
+ super.visitIdentifier(element);
+ }
+}
diff --git a/tool/test.dart b/tool/test.dart
index f767263..700a2b6 100755
--- a/tool/test.dart
+++ b/tool/test.dart
@@ -14,9 +14,9 @@
import 'dart:io';
+import 'package:path/path.dart' as p;
import 'package:pub/src/dart.dart';
import 'package:pub/src/exceptions.dart';
-import 'package:pub/src/path.dart';
Future<void> main(List<String> args) async {
if (Platform.environment['FLUTTER_ROOT'] != null) {