| // Copyright 2014 The Flutter Authors. 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' as io show Directory, File, Link, Process, ProcessException, ProcessResult, ProcessSignal, ProcessStartMode, systemEncoding; |
| import 'dart:typed_data'; |
| |
| import 'package:file/file.dart'; |
| import 'package:meta/meta.dart'; |
| import 'package:path/path.dart' as p; // flutter_ignore: package_path_import |
| import 'package:process/process.dart'; |
| |
| import 'common.dart' show throwToolExit; |
| import 'platform.dart'; |
| |
| // The Flutter tool hits file system and process errors that only the end-user can address. |
| // We would like these errors to not hit crash logging. In these cases, we |
| // should exit gracefully and provide potentially useful advice. For example, if |
| // a write fails because the target device is full, we can explain that with a |
| // ToolExit and a message that is more clear than the FileSystemException by |
| // itself. |
| |
| /// On windows this is error code 2: ERROR_FILE_NOT_FOUND, and on |
| /// macOS/Linux it is error code 2/ENOENT: No such file or directory. |
| const int kSystemCannotFindFile = 2; |
| |
| /// A [FileSystem] that throws a [ToolExit] on certain errors. |
| /// |
| /// If a [FileSystem] error is not caused by the Flutter tool, and can only be |
| /// addressed by the user, it should be caught by this [FileSystem] and thrown |
| /// as a [ToolExit] using [throwToolExit]. |
| /// |
| /// Cf. If there is some hope that the tool can continue when an operation fails |
| /// with an error, then that error/operation should not be handled here. For |
| /// example, the tool should generally be able to continue executing even if it |
| /// fails to delete a file. |
| class ErrorHandlingFileSystem extends ForwardingFileSystem { |
| ErrorHandlingFileSystem({ |
| required FileSystem delegate, |
| required Platform platform, |
| }) : |
| assert(delegate != null), |
| assert(platform != null), |
| _platform = platform, |
| super(delegate); |
| |
| @visibleForTesting |
| FileSystem get fileSystem => delegate; |
| |
| final Platform _platform; |
| |
| /// Allow any file system operations executed within the closure to fail with any |
| /// operating system error, rethrowing an [Exception] instead of a [ToolExit]. |
| /// |
| /// This should not be used with async file system operation. |
| /// |
| /// This can be used to bypass the [ErrorHandlingFileSystem] permission exit |
| /// checks for situations where failure is acceptable, such as the flutter |
| /// persistent settings cache. |
| static void noExitOnFailure(void Function() operation) { |
| final bool previousValue = ErrorHandlingFileSystem._noExitOnFailure; |
| try { |
| ErrorHandlingFileSystem._noExitOnFailure = true; |
| operation(); |
| } finally { |
| ErrorHandlingFileSystem._noExitOnFailure = previousValue; |
| } |
| } |
| |
| /// Delete the file or directory and return true if it exists, take no |
| /// action and return false if it does not. |
| /// |
| /// This method should be preferred to checking if it exists and |
| /// then deleting, because it handles the edge case where the file or directory |
| /// is deleted by a different program between the two calls. |
| static bool deleteIfExists(FileSystemEntity file, {bool recursive = false}) { |
| if (!file.existsSync()) { |
| return false; |
| } |
| try { |
| file.deleteSync(recursive: recursive); |
| } on FileSystemException catch (err) { |
| // Certain error codes indicate the file could not be found. It could have |
| // been deleted by a different program while the tool was running. |
| // if it still exists, the file likely exists on a read-only volume. |
| if (err.osError?.errorCode != kSystemCannotFindFile || _noExitOnFailure) { |
| rethrow; |
| } |
| if (file.existsSync()) { |
| throwToolExit( |
| 'The Flutter tool tried to delete the file or directory ${file.path} but was ' |
| "unable to. This may be due to the file and/or project's location on a read-only " |
| 'volume. Consider relocating the project and trying again', |
| ); |
| } |
| } |
| return true; |
| } |
| |
| static bool _noExitOnFailure = false; |
| |
| @override |
| Directory get currentDirectory { |
| try { |
| return _runSync(() => directory(delegate.currentDirectory), platform: _platform); |
| } on FileSystemException catch (err) { |
| // Special handling for OS error 2 for current directory only. |
| if (err.osError?.errorCode == kSystemCannotFindFile) { |
| throwToolExit( |
| 'Unable to read current working directory. This can happen if the directory the ' |
| 'Flutter tool was run from was moved or deleted.' |
| ); |
| } |
| rethrow; |
| } |
| } |
| |
| @override |
| File file(dynamic path) => ErrorHandlingFile( |
| platform: _platform, |
| fileSystem: this, |
| delegate: delegate.file(path), |
| ); |
| |
| @override |
| Directory directory(dynamic path) => ErrorHandlingDirectory( |
| platform: _platform, |
| fileSystem: this, |
| delegate: delegate.directory(path), |
| ); |
| |
| @override |
| Link link(dynamic path) => ErrorHandlingLink( |
| platform: _platform, |
| fileSystem: this, |
| delegate: delegate.link(path), |
| ); |
| |
| // Caching the path context here and clearing when the currentDirectory setter |
| // is updated works since the flutter tool restricts usage of dart:io directly |
| // via the forbidden import tests. Otherwise, the path context's current |
| // working directory might get out of sync, leading to unexpected results from |
| // methods like `path.relative`. |
| @override |
| p.Context get path => _cachedPath ??= delegate.path; |
| p.Context? _cachedPath; |
| |
| @override |
| set currentDirectory(dynamic path) { |
| _cachedPath = null; |
| delegate.currentDirectory = path; |
| } |
| |
| @override |
| String toString() => delegate.toString(); |
| } |
| |
| class ErrorHandlingFile |
| extends ForwardingFileSystemEntity<File, io.File> |
| with ForwardingFile { |
| ErrorHandlingFile({ |
| required Platform platform, |
| required this.fileSystem, |
| required this.delegate, |
| }) : |
| assert(platform != null), |
| assert(fileSystem != null), |
| assert(delegate != null), |
| _platform = platform; |
| |
| @override |
| final io.File delegate; |
| |
| @override |
| final ErrorHandlingFileSystem fileSystem; |
| |
| final Platform _platform; |
| |
| @override |
| File wrapFile(io.File delegate) => ErrorHandlingFile( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| Directory wrapDirectory(io.Directory delegate) => ErrorHandlingDirectory( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| Link wrapLink(io.Link delegate) => ErrorHandlingLink( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| Future<File> writeAsBytes( |
| List<int> bytes, { |
| FileMode mode = FileMode.write, |
| bool flush = false, |
| }) async { |
| return _run<File>( |
| () async => wrap(await delegate.writeAsBytes( |
| bytes, |
| mode: mode, |
| flush: flush, |
| )), |
| platform: _platform, |
| failureMessage: 'Flutter failed to write to a file at "${delegate.path}"', |
| posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
| ); |
| } |
| |
| @override |
| String readAsStringSync({Encoding encoding = utf8}) { |
| return _runSync<String>( |
| () => delegate.readAsStringSync(), |
| platform: _platform, |
| failureMessage: 'Flutter failed to read a file at "${delegate.path}"', |
| posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
| ); |
| } |
| |
| @override |
| void writeAsBytesSync( |
| List<int> bytes, { |
| FileMode mode = FileMode.write, |
| bool flush = false, |
| }) { |
| _runSync<void>( |
| () => delegate.writeAsBytesSync(bytes, mode: mode, flush: flush), |
| platform: _platform, |
| failureMessage: 'Flutter failed to write to a file at "${delegate.path}"', |
| posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
| ); |
| } |
| |
| @override |
| Future<File> writeAsString( |
| String contents, { |
| FileMode mode = FileMode.write, |
| Encoding encoding = utf8, |
| bool flush = false, |
| }) async { |
| return _run<File>( |
| () async => wrap(await delegate.writeAsString( |
| contents, |
| mode: mode, |
| encoding: encoding, |
| flush: flush, |
| )), |
| platform: _platform, |
| failureMessage: 'Flutter failed to write to a file at "${delegate.path}"', |
| posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
| ); |
| } |
| |
| @override |
| void writeAsStringSync( |
| String contents, { |
| FileMode mode = FileMode.write, |
| Encoding encoding = utf8, |
| bool flush = false, |
| }) { |
| _runSync<void>( |
| () => delegate.writeAsStringSync( |
| contents, |
| mode: mode, |
| encoding: encoding, |
| flush: flush, |
| ), |
| platform: _platform, |
| failureMessage: 'Flutter failed to write to a file at "${delegate.path}"', |
| posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
| ); |
| } |
| |
| // TODO(aam): Pass `exclusive` through after dartbug.com/49647 lands. |
| @override |
| void createSync({bool recursive = false, bool exclusive = false}) { |
| _runSync<void>( |
| () => delegate.createSync( |
| recursive: recursive, |
| ), |
| platform: _platform, |
| failureMessage: 'Flutter failed to create file at "${delegate.path}"', |
| posixPermissionSuggestion: recursive ? null : _posixPermissionSuggestion(<String>[delegate.parent.path]), |
| ); |
| } |
| |
| @override |
| RandomAccessFile openSync({FileMode mode = FileMode.read}) { |
| return _runSync<RandomAccessFile>( |
| () => delegate.openSync( |
| mode: mode, |
| ), |
| platform: _platform, |
| failureMessage: 'Flutter failed to open a file at "${delegate.path}"', |
| posixPermissionSuggestion: _posixPermissionSuggestion(<String>[delegate.path]), |
| ); |
| } |
| |
| /// This copy method attempts to handle file system errors from both reading |
| /// and writing the copied file. |
| @override |
| File copySync(String newPath) { |
| final File resultFile = fileSystem.file(newPath); |
| // First check if the source file can be read. If not, bail through error |
| // handling. |
| _runSync<void>( |
| () => delegate.openSync().closeSync(), |
| platform: _platform, |
| failureMessage: 'Flutter failed to copy $path to $newPath due to source location error', |
| posixPermissionSuggestion: _posixPermissionSuggestion(<String>[path]), |
| ); |
| // Next check if the destination file can be written. If not, bail through |
| // error handling. |
| _runSync<void>( |
| () => resultFile.createSync(recursive: true), |
| platform: _platform, |
| failureMessage: 'Flutter failed to copy $path to $newPath due to destination location error' |
| ); |
| // If both of the above checks passed, attempt to copy the file and catch |
| // any thrown errors. |
| try { |
| return wrapFile(delegate.copySync(newPath)); |
| } on FileSystemException { |
| // Proceed below |
| } |
| // If the copy failed but both of the above checks passed, copy the bytes |
| // directly. |
| _runSync(() { |
| RandomAccessFile? source; |
| RandomAccessFile? sink; |
| try { |
| source = delegate.openSync(); |
| sink = resultFile.openSync(mode: FileMode.writeOnly); |
| // 64k is the same sized buffer used by dart:io for `File.openRead`. |
| final Uint8List buffer = Uint8List(64 * 1024); |
| final int totalBytes = source.lengthSync(); |
| int bytes = 0; |
| while (bytes < totalBytes) { |
| final int chunkLength = source.readIntoSync(buffer); |
| sink.writeFromSync(buffer, 0, chunkLength); |
| bytes += chunkLength; |
| } |
| } catch (err) { // ignore: avoid_catches_without_on_clauses, rethrows |
| ErrorHandlingFileSystem.deleteIfExists(resultFile, recursive: true); |
| rethrow; |
| } finally { |
| source?.closeSync(); |
| sink?.closeSync(); |
| } |
| }, platform: _platform, |
| failureMessage: 'Flutter failed to copy $path to $newPath due to unknown error', |
| posixPermissionSuggestion: _posixPermissionSuggestion(<String>[path, resultFile.parent.path]), |
| ); |
| // The original copy failed, but the manual copy worked. |
| return wrapFile(resultFile); |
| } |
| |
| String _posixPermissionSuggestion(List<String> paths) => 'Try running:\n' |
| ' sudo chown -R \$(whoami) ${paths.map(fileSystem.path.absolute).join(' ')}'; |
| |
| @override |
| String toString() => delegate.toString(); |
| } |
| |
| class ErrorHandlingDirectory |
| extends ForwardingFileSystemEntity<Directory, io.Directory> |
| with ForwardingDirectory<Directory> { |
| ErrorHandlingDirectory({ |
| required Platform platform, |
| required this.fileSystem, |
| required this.delegate, |
| }) : |
| assert(platform != null), |
| assert(fileSystem != null), |
| assert(delegate != null), |
| _platform = platform; |
| |
| @override |
| final io.Directory delegate; |
| |
| @override |
| final ErrorHandlingFileSystem fileSystem; |
| |
| final Platform _platform; |
| |
| @override |
| File wrapFile(io.File delegate) => ErrorHandlingFile( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| Directory wrapDirectory(io.Directory delegate) => ErrorHandlingDirectory( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| Link wrapLink(io.Link delegate) => ErrorHandlingLink( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| Directory childDirectory(String basename) { |
| return fileSystem.directory(fileSystem.path.join(path, basename)); |
| } |
| |
| @override |
| File childFile(String basename) { |
| return fileSystem.file(fileSystem.path.join(path, basename)); |
| } |
| |
| @override |
| Link childLink(String basename) { |
| return fileSystem.link(fileSystem.path.join(path, basename)); |
| } |
| |
| @override |
| void createSync({bool recursive = false}) { |
| return _runSync<void>( |
| () => delegate.createSync(recursive: recursive), |
| platform: _platform, |
| failureMessage: |
| 'Flutter failed to create a directory at "${delegate.path}"', |
| posixPermissionSuggestion: recursive ? null : _posixPermissionSuggestion(delegate.parent.path), |
| ); |
| } |
| |
| @override |
| Future<Directory> createTemp([String? prefix]) { |
| return _run<Directory>( |
| () async => wrap(await delegate.createTemp(prefix)), |
| platform: _platform, |
| failureMessage: |
| 'Flutter failed to create a temporary directory with prefix "$prefix"', |
| ); |
| } |
| |
| @override |
| Directory createTempSync([String? prefix]) { |
| return _runSync<Directory>( |
| () => wrap(delegate.createTempSync(prefix)), |
| platform: _platform, |
| failureMessage: |
| 'Flutter failed to create a temporary directory with prefix "$prefix"', |
| ); |
| } |
| |
| @override |
| Future<Directory> create({bool recursive = false}) { |
| return _run<Directory>( |
| () async => wrap(await delegate.create(recursive: recursive)), |
| platform: _platform, |
| failureMessage: |
| 'Flutter failed to create a directory at "${delegate.path}"', |
| posixPermissionSuggestion: recursive ? null : _posixPermissionSuggestion(delegate.parent.path), |
| ); |
| } |
| |
| @override |
| Future<Directory> delete({bool recursive = false}) { |
| return _run<Directory>( |
| () async => wrap(fileSystem.directory((await delegate.delete(recursive: recursive)).path)), |
| platform: _platform, |
| failureMessage: |
| 'Flutter failed to delete a directory at "${delegate.path}"', |
| posixPermissionSuggestion: recursive ? null : _posixPermissionSuggestion(delegate.path), |
| ); |
| } |
| |
| @override |
| void deleteSync({bool recursive = false}) { |
| return _runSync<void>( |
| () => delegate.deleteSync(recursive: recursive), |
| platform: _platform, |
| failureMessage: |
| 'Flutter failed to delete a directory at "${delegate.path}"', |
| posixPermissionSuggestion: recursive ? null : _posixPermissionSuggestion(delegate.path), |
| ); |
| } |
| |
| @override |
| bool existsSync() { |
| return _runSync<bool>( |
| () => delegate.existsSync(), |
| platform: _platform, |
| failureMessage: |
| 'Flutter failed to check for directory existence at "${delegate.path}"', |
| posixPermissionSuggestion: _posixPermissionSuggestion(delegate.parent.path), |
| ); |
| } |
| |
| String _posixPermissionSuggestion(String path) => 'Try running:\n' |
| ' sudo chown -R \$(whoami) ${fileSystem.path.absolute(path)}'; |
| |
| @override |
| String toString() => delegate.toString(); |
| } |
| |
| class ErrorHandlingLink |
| extends ForwardingFileSystemEntity<Link, io.Link> |
| with ForwardingLink { |
| ErrorHandlingLink({ |
| required Platform platform, |
| required this.fileSystem, |
| required this.delegate, |
| }) : |
| assert(platform != null), |
| assert(fileSystem != null), |
| assert(delegate != null), |
| _platform = platform; |
| |
| @override |
| final io.Link delegate; |
| |
| @override |
| final ErrorHandlingFileSystem fileSystem; |
| |
| final Platform _platform; |
| |
| @override |
| File wrapFile(io.File delegate) => ErrorHandlingFile( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| Directory wrapDirectory(io.Directory delegate) => ErrorHandlingDirectory( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| Link wrapLink(io.Link delegate) => ErrorHandlingLink( |
| platform: _platform, |
| fileSystem: fileSystem, |
| delegate: delegate, |
| ); |
| |
| @override |
| String toString() => delegate.toString(); |
| } |
| |
| const String _kNoExecutableFound = 'The Flutter tool could not locate an executable with suitable permissions'; |
| |
| Future<T> _run<T>(Future<T> Function() op, { |
| required Platform platform, |
| String? failureMessage, |
| String? posixPermissionSuggestion, |
| }) async { |
| assert(platform != null); |
| try { |
| return await op(); |
| } on ProcessPackageExecutableNotFoundException catch (e) { |
| if (e.candidates.isNotEmpty) { |
| throwToolExit('$_kNoExecutableFound: $e'); |
| } |
| rethrow; |
| } on FileSystemException catch (e) { |
| if (platform.isWindows) { |
| _handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0); |
| } else if (platform.isLinux || platform.isMacOS) { |
| _handlePosixException(e, failureMessage, e.osError?.errorCode ?? 0, posixPermissionSuggestion); |
| } |
| rethrow; |
| } on io.ProcessException catch (e) { |
| if (platform.isWindows) { |
| _handleWindowsException(e, failureMessage, e.errorCode); |
| } else if (platform.isLinux || platform.isMacOS) { |
| _handlePosixException(e, failureMessage, e.errorCode, posixPermissionSuggestion); |
| } |
| rethrow; |
| } |
| } |
| |
| T _runSync<T>(T Function() op, { |
| required Platform platform, |
| String? failureMessage, |
| String? posixPermissionSuggestion, |
| }) { |
| assert(platform != null); |
| try { |
| return op(); |
| } on ProcessPackageExecutableNotFoundException catch (e) { |
| if (e.candidates.isNotEmpty) { |
| throwToolExit('$_kNoExecutableFound: $e'); |
| } |
| rethrow; |
| } on FileSystemException catch (e) { |
| if (platform.isWindows) { |
| _handleWindowsException(e, failureMessage, e.osError?.errorCode ?? 0); |
| } else if (platform.isLinux || platform.isMacOS) { |
| _handlePosixException(e, failureMessage, e.osError?.errorCode ?? 0, posixPermissionSuggestion); |
| } |
| rethrow; |
| } on io.ProcessException catch (e) { |
| if (platform.isWindows) { |
| _handleWindowsException(e, failureMessage, e.errorCode); |
| } else if (platform.isLinux || platform.isMacOS) { |
| _handlePosixException(e, failureMessage, e.errorCode, posixPermissionSuggestion); |
| } |
| rethrow; |
| } |
| } |
| |
| |
| /// A [ProcessManager] that throws a [ToolExit] on certain errors. |
| /// |
| /// If a [ProcessException] is not caused by the Flutter tool, and can only be |
| /// addressed by the user, it should be caught by this [ProcessManager] and thrown |
| /// as a [ToolExit] using [throwToolExit]. |
| /// |
| /// See also: |
| /// * [ErrorHandlingFileSystem], for a similar file system strategy. |
| class ErrorHandlingProcessManager extends ProcessManager { |
| ErrorHandlingProcessManager({ |
| required ProcessManager delegate, |
| required Platform platform, |
| }) : _delegate = delegate, |
| _platform = platform; |
| |
| final ProcessManager _delegate; |
| final Platform _platform; |
| |
| @override |
| bool canRun(dynamic executable, {String? workingDirectory}) { |
| return _runSync( |
| () => _delegate.canRun(executable, workingDirectory: workingDirectory), |
| platform: _platform, |
| failureMessage: 'Flutter failed to run "$executable"', |
| posixPermissionSuggestion: 'Try running:\n' |
| ' sudo chown -R \$(whoami) $executable && chmod u+rx $executable', |
| ); |
| } |
| |
| @override |
| bool killPid(int pid, [io.ProcessSignal signal = io.ProcessSignal.sigterm]) { |
| return _runSync( |
| () => _delegate.killPid(pid, signal), |
| platform: _platform, |
| ); |
| } |
| |
| @override |
| Future<io.ProcessResult> run( |
| List<Object> command, { |
| String? workingDirectory, |
| Map<String, String>? environment, |
| bool includeParentEnvironment = true, |
| bool runInShell = false, |
| Encoding? stdoutEncoding = io.systemEncoding, |
| Encoding? stderrEncoding = io.systemEncoding, |
| }) { |
| return _run(() { |
| return _delegate.run( |
| command, |
| workingDirectory: workingDirectory, |
| environment: environment, |
| includeParentEnvironment: includeParentEnvironment, |
| runInShell: runInShell, |
| stdoutEncoding: stdoutEncoding, |
| stderrEncoding: stderrEncoding, |
| ); |
| }, platform: _platform); |
| } |
| |
| @override |
| Future<io.Process> start( |
| List<Object> command, { |
| String? workingDirectory, |
| Map<String, String>? environment, |
| bool includeParentEnvironment = true, |
| bool runInShell = false, |
| io.ProcessStartMode mode = io.ProcessStartMode.normal, |
| }) { |
| return _run(() { |
| return _delegate.start( |
| command, |
| workingDirectory: workingDirectory, |
| environment: environment, |
| includeParentEnvironment: includeParentEnvironment, |
| runInShell: runInShell, |
| ); |
| }, platform: _platform); |
| } |
| |
| @override |
| io.ProcessResult runSync( |
| List<Object> command, { |
| String? workingDirectory, |
| Map<String, String>? environment, |
| bool includeParentEnvironment = true, |
| bool runInShell = false, |
| Encoding? stdoutEncoding = io.systemEncoding, |
| Encoding? stderrEncoding = io.systemEncoding, |
| }) { |
| return _runSync(() { |
| return _delegate.runSync( |
| command, |
| workingDirectory: workingDirectory, |
| environment: environment, |
| includeParentEnvironment: includeParentEnvironment, |
| runInShell: runInShell, |
| stdoutEncoding: stdoutEncoding, |
| stderrEncoding: stderrEncoding, |
| ); |
| }, platform: _platform); |
| } |
| } |
| |
| void _handlePosixException(Exception e, String? message, int errorCode, String? posixPermissionSuggestion) { |
| // From: |
| // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno.h |
| // https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/errno-base.h |
| // https://github.com/apple/darwin-xnu/blob/master/bsd/dev/dtrace/scripts/errno.d |
| const int eperm = 1; |
| const int enospc = 28; |
| const int eacces = 13; |
| // Catch errors and bail when: |
| String? errorMessage; |
| switch (errorCode) { |
| case enospc: |
| errorMessage = |
| '$message. The target device is full.' |
| '\n$e\n' |
| 'Free up space and try again.'; |
| break; |
| case eperm: |
| case eacces: |
| final StringBuffer errorBuffer = StringBuffer(); |
| if (message != null && message.isNotEmpty) { |
| errorBuffer.writeln('$message.'); |
| } else { |
| errorBuffer.writeln('The flutter tool cannot access the file or directory.'); |
| } |
| errorBuffer.writeln('Please ensure that the SDK and/or project is installed in a location ' |
| 'that has read/write permissions for the current user.'); |
| if (posixPermissionSuggestion != null && posixPermissionSuggestion.isNotEmpty) { |
| errorBuffer.writeln(posixPermissionSuggestion); |
| } |
| errorMessage = errorBuffer.toString(); |
| break; |
| default: |
| // Caller must rethrow the exception. |
| break; |
| } |
| _throwFileSystemException(errorMessage); |
| } |
| |
| void _handleWindowsException(Exception e, String? message, int errorCode) { |
| // From: |
| // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes |
| const int kDeviceFull = 112; |
| const int kUserMappedSectionOpened = 1224; |
| const int kAccessDenied = 5; |
| const int kFatalDeviceHardwareError = 483; |
| const int kDeviceDoesNotExist = 433; |
| |
| // Catch errors and bail when: |
| String? errorMessage; |
| switch (errorCode) { |
| case kAccessDenied: |
| errorMessage = |
| '$message. The flutter tool cannot access the file or directory.\n' |
| 'Please ensure that the SDK and/or project is installed in a location ' |
| 'that has read/write permissions for the current user.'; |
| break; |
| case kDeviceFull: |
| errorMessage = |
| '$message. The target device is full.' |
| '\n$e\n' |
| 'Free up space and try again.'; |
| break; |
| case kUserMappedSectionOpened: |
| errorMessage = |
| '$message. The file is being used by another program.' |
| '\n$e\n' |
| 'Do you have an antivirus program running? ' |
| 'Try disabling your antivirus program and try again.'; |
| break; |
| case kFatalDeviceHardwareError: |
| errorMessage = |
| '$message. There is a problem with the device driver ' |
| 'that this file or directory is stored on.'; |
| break; |
| case kDeviceDoesNotExist: |
| errorMessage = |
| '$message. The device was not found.' |
| '\n$e\n' |
| 'Verify the device is mounted and try again.'; |
| break; |
| default: |
| // Caller must rethrow the exception. |
| break; |
| } |
| _throwFileSystemException(errorMessage); |
| } |
| |
| void _throwFileSystemException(String? errorMessage) { |
| if (errorMessage == null) { |
| return; |
| } |
| if (ErrorHandlingFileSystem._noExitOnFailure) { |
| throw Exception(errorMessage); |
| } |
| throwToolExit(errorMessage); |
| } |