diff --git a/packages/flutter_tools/lib/src/base/error_handling_io.dart b/packages/flutter_tools/lib/src/base/error_handling_io.dart index c00036cf04c..e369c09d07f 100644 --- a/packages/flutter_tools/lib/src/base/error_handling_io.dart +++ b/packages/flutter_tools/lib/src/base/error_handling_io.dart @@ -533,6 +533,8 @@ class ErrorHandlingLink String toString() => delegate.toString(); } +const String _kNoExecutableFound = 'The Flutter tool could not locate an executable with suitable permissions'; + Future _run(Future Function() op, { required Platform platform, String? failureMessage, @@ -540,6 +542,11 @@ Future _run(Future Function() op, { 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); @@ -564,6 +571,11 @@ T _runSync(T Function() op, { 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); @@ -581,69 +593,6 @@ T _runSync(T Function() op, { } } -class _ProcessDelegate { - const _ProcessDelegate(); - - Future start( - List command, { - String? workingDirectory, - Map? environment, - bool includeParentEnvironment = true, - bool runInShell = false, - io.ProcessStartMode mode = io.ProcessStartMode.normal, - }) { - return io.Process.start( - command[0], - command.skip(1).toList(), - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - runInShell: runInShell, - ); - } - - Future run( - List command, { - String? workingDirectory, - Map? environment, - bool includeParentEnvironment = true, - bool runInShell = false, - Encoding stdoutEncoding = io.systemEncoding, - Encoding stderrEncoding = io.systemEncoding, - }) { - return io.Process.run( - command[0], - command.skip(1).toList(), - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - runInShell: runInShell, - stdoutEncoding: stdoutEncoding, - stderrEncoding: stderrEncoding, - ); - } - - io.ProcessResult runSync( - List command, { - String? workingDirectory, - Map? environment, - bool includeParentEnvironment = true, - bool runInShell = false, - Encoding stdoutEncoding = io.systemEncoding, - Encoding stderrEncoding = io.systemEncoding, - }) { - return io.Process.runSync( - command[0], - command.skip(1).toList(), - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - runInShell: runInShell, - stdoutEncoding: stdoutEncoding, - stderrEncoding: stderrEncoding, - ); - } -} /// A [ProcessManager] that throws a [ToolExit] on certain errors. /// @@ -662,21 +611,6 @@ class ErrorHandlingProcessManager extends ProcessManager { final ProcessManager _delegate; final Platform _platform; - static const _ProcessDelegate _processDelegate = _ProcessDelegate(); - static bool _skipCommandLookup = false; - - /// Bypass package:process command lookup for all functions in this block. - /// - /// This required that the fully resolved executable path is provided. - static Future skipCommandLookup(Future Function() operation) async { - final bool previousValue = ErrorHandlingProcessManager._skipCommandLookup; - try { - ErrorHandlingProcessManager._skipCommandLookup = true; - return await operation(); - } finally { - ErrorHandlingProcessManager._skipCommandLookup = previousValue; - } - } @override bool canRun(dynamic executable, {String? workingDirectory}) { @@ -705,17 +639,6 @@ class ErrorHandlingProcessManager extends ProcessManager { Encoding stderrEncoding = io.systemEncoding, }) { return _run(() { - if (_skipCommandLookup && _delegate is LocalProcessManager) { - return _processDelegate.run( - command.cast(), - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - runInShell: runInShell, - stdoutEncoding: stdoutEncoding, - stderrEncoding: stderrEncoding, - ); - } return _delegate.run( command, workingDirectory: workingDirectory, @@ -738,15 +661,6 @@ class ErrorHandlingProcessManager extends ProcessManager { io.ProcessStartMode mode = io.ProcessStartMode.normal, }) { return _run(() { - if (_skipCommandLookup && _delegate is LocalProcessManager) { - return _processDelegate.start( - command.cast(), - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - runInShell: runInShell, - ); - } return _delegate.start( command, workingDirectory: workingDirectory, @@ -768,17 +682,6 @@ class ErrorHandlingProcessManager extends ProcessManager { Encoding stderrEncoding = io.systemEncoding, }) { return _runSync(() { - if (_skipCommandLookup && _delegate is LocalProcessManager) { - return _processDelegate.runSync( - command.cast(), - workingDirectory: workingDirectory, - environment: environment, - includeParentEnvironment: includeParentEnvironment, - runInShell: runInShell, - stdoutEncoding: stdoutEncoding, - stderrEncoding: stderrEncoding, - ); - } return _delegate.runSync( command, workingDirectory: workingDirectory, diff --git a/packages/flutter_tools/lib/src/dart/pub.dart b/packages/flutter_tools/lib/src/dart/pub.dart index 535a08b41fe..f48658e6a1f 100644 --- a/packages/flutter_tools/lib/src/dart/pub.dart +++ b/packages/flutter_tools/lib/src/dart/pub.dart @@ -11,7 +11,6 @@ import 'package:process/process.dart'; import '../base/bot_detector.dart'; import '../base/common.dart'; import '../base/context.dart'; -import '../base/error_handling_io.dart'; import '../base/file_system.dart'; import '../base/io.dart' as io; import '../base/logger.dart'; @@ -334,13 +333,11 @@ class _DefaultPub implements Pub { bool generateSyntheticPackage = false, }) async { // Fully resolved pub or pub.bat is calculated based on current platform. - final io.Process process = await ErrorHandlingProcessManager.skipCommandLookup(() async { - return _processUtils.start( - _pubCommand(arguments), - workingDirectory: directory, - environment: await _createPubEnvironment(PubContext.interactive), - ); - }); + final io.Process process = await _processUtils.start( + _pubCommand(arguments), + workingDirectory: directory, + environment: await _createPubEnvironment(PubContext.interactive), + ); // Pipe the Flutter tool stdin to the pub stdin. unawaited(process.stdin.addStream(stdio.stdin) diff --git a/packages/flutter_tools/test/general.shard/base/error_handling_io_test.dart b/packages/flutter_tools/test/general.shard/base/error_handling_io_test.dart index ae4f2faf2c6..4281522f68b 100644 --- a/packages/flutter_tools/test/general.shard/base/error_handling_io_test.dart +++ b/packages/flutter_tools/test/general.shard/base/error_handling_io_test.dart @@ -684,25 +684,47 @@ void main() { }); }); - test('skipCommandLookup invokes Process calls directly', () async { - final ErrorHandlingProcessManager processManager = ErrorHandlingProcessManager( - delegate: const LocalProcessManager(), - platform: windowsPlatform, - ); - - // Throws process exception because the executable does not exist. - await ErrorHandlingProcessManager.skipCommandLookup(() async { - expect(() => processManager.runSync(['foo']), throwsA(isA())); - expect(() => processManager.run(['foo']), throwsA(isA())); - expect(() => processManager.start(['foo']), throwsA(isA())); - }); - }); - group('ProcessManager on windows throws tool exit', () { const int kDeviceFull = 112; const int kUserMappedSectionOpened = 1224; const int kUserPermissionDenied = 5; + test('when PackageProcess throws an exception containg non-executable bits', () { + final FakeProcessManager fakeProcessManager = FakeProcessManager.list([ + const FakeCommand(command: ['foo'], exception: ProcessPackageExecutableNotFoundException('', candidates: ['not-empty'])), + const FakeCommand(command: ['foo'], exception: ProcessPackageExecutableNotFoundException('', candidates: ['not-empty'])), + ]); + + final ProcessManager processManager = ErrorHandlingProcessManager( + delegate: fakeProcessManager, + platform: windowsPlatform, + ); + + const String expectedMessage = 'The Flutter tool could not locate an executable with suitable permissions'; + + expect(() async => processManager.start(['foo']), + throwsToolExit(message: expectedMessage)); + expect(() async => processManager.runSync(['foo']), + throwsToolExit(message: expectedMessage)); + }); + + test('when PackageProcess throws an exception without containing non-executable bits', () { + final FakeProcessManager fakeProcessManager = FakeProcessManager.list([ + const FakeCommand(command: ['foo'], exception: ProcessPackageExecutableNotFoundException('', candidates: [])), + const FakeCommand(command: ['foo'], exception: ProcessPackageExecutableNotFoundException('', candidates: [])), + ]); + + final ProcessManager processManager = ErrorHandlingProcessManager( + delegate: fakeProcessManager, + platform: windowsPlatform, + ); + + // If there were no located executables treat this as a programming error and rethrow the original + // exception. + expect(() async => processManager.start(['foo']), throwsProcessException()); + expect(() async => processManager.runSync(['foo']), throwsProcessException()); + }); + test('when the device is full', () { final FakeProcessManager fakeProcessManager = FakeProcessManager.list([ const FakeCommand(command: ['foo'], exception: ProcessException('', [], '', kDeviceFull)),