From 49f5980970de0cdff12a26caceefb70083ac768f Mon Sep 17 00:00:00 2001 From: Jenn Magder Date: Thu, 1 Dec 2022 11:17:07 -0800 Subject: [PATCH] Suggest Rosetta when x64 binary cannot be run (#114558) * Suggest Rosetta when x64 binary cannot be run * validator * Adjust error message --- .../lib/src/base/error_handling_io.dart | 22 ++++++++- .../lib/src/base/user_messages.dart | 2 +- packages/flutter_tools/lib/src/doctor.dart | 3 ++ .../base/error_handling_io_test.dart | 23 +++++++++ .../general.shard/flutter_validator_test.dart | 49 ++++++++++++++++++- 5 files changed, 95 insertions(+), 4 deletions(-) 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 16d19916f7..e5c427787a 100644 --- a/packages/flutter_tools/lib/src/base/error_handling_io.dart +++ b/packages/flutter_tools/lib/src/base/error_handling_io.dart @@ -581,8 +581,10 @@ Future _run(Future Function() op, { } on io.ProcessException catch (e) { if (platform.isWindows) { _handleWindowsException(e, failureMessage, e.errorCode); - } else if (platform.isLinux || platform.isMacOS) { + } else if (platform.isLinux) { _handlePosixException(e, failureMessage, e.errorCode, posixPermissionSuggestion); + } if (platform.isMacOS) { + _handleMacOSException(e, failureMessage, e.errorCode, posixPermissionSuggestion); } rethrow; } @@ -611,8 +613,10 @@ T _runSync(T Function() op, { } on io.ProcessException catch (e) { if (platform.isWindows) { _handleWindowsException(e, failureMessage, e.errorCode); - } else if (platform.isLinux || platform.isMacOS) { + } else if (platform.isLinux) { _handlePosixException(e, failureMessage, e.errorCode, posixPermissionSuggestion); + } if (platform.isMacOS) { + _handleMacOSException(e, failureMessage, e.errorCode, posixPermissionSuggestion); } rethrow; } @@ -762,6 +766,20 @@ void _handlePosixException(Exception e, String? message, int errorCode, String? _throwFileSystemException(errorMessage); } +void _handleMacOSException(Exception e, String? message, int errorCode, String? posixPermissionSuggestion) { + // https://github.com/apple/darwin-xnu/blob/master/bsd/dev/dtrace/scripts/errno.d + const int ebadarch = 86; + if (errorCode == ebadarch) { + final StringBuffer errorBuffer = StringBuffer(); + errorBuffer.writeln(message); + errorBuffer.writeln('This binary was built with the incorrect architecture to run on this machine.'); + errorBuffer.writeln('Flutter requires the Rosetta translation environment. If you are on an ARM Mac, try running:'); + errorBuffer.writeln(' sudo softwareupdate --install-rosetta --agree-to-license'); + _throwFileSystemException(errorBuffer.toString()); + } + _handlePosixException(e, message, errorCode, posixPermissionSuggestion); +} + void _handleWindowsException(Exception e, String? message, int errorCode) { // From: // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index bf4cb6e055..63cb28f7c2 100644 --- a/packages/flutter_tools/lib/src/base/user_messages.dart +++ b/packages/flutter_tools/lib/src/base/user_messages.dart @@ -42,7 +42,7 @@ class UserMessages { String flutterMirrorURL(String url) => 'Flutter download mirror $url'; String get flutterBinariesDoNotRun => 'Downloaded executables cannot execute on host.\n' - 'See https://github.com/flutter/flutter/issues/6207 for more information'; + 'See https://github.com/flutter/flutter/issues/6207 for more information.'; String get flutterBinariesLinuxRepairCommands => 'On Debian/Ubuntu/Mint: sudo apt-get install lib32stdc++6\n' 'On Fedora: dnf install libstdc++.i686\n' diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index 8f511e65fa..651dc6cf36 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -549,6 +549,9 @@ class FlutterValidator extends DoctorValidator { buffer.writeln(_userMessages.flutterBinariesDoNotRun); if (_platform.isLinux) { buffer.writeln(_userMessages.flutterBinariesLinuxRepairCommands); + } else if (_platform.isMacOS && _operatingSystemUtils.hostPlatform == HostPlatform.darwin_arm64) { + buffer.writeln('Flutter requires the Rosetta translation environment on ARM Macs. Try running:'); + buffer.writeln(' sudo softwareupdate --install-rosetta --agree-to-license'); } messages.add(ValidationMessage.error(buffer.toString())); } 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 fd2bf9bb33..4172518d5d 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 @@ -1051,6 +1051,7 @@ void main() { group('ProcessManager on macOS throws tool exit', () { const int enospc = 28; const int eacces = 13; + const int ebadarch = 86; testWithoutContext('when writing to a full device', () { final FakeProcessManager fakeProcessManager = FakeProcessManager.list([ @@ -1109,6 +1110,28 @@ void main() { expect(() async => processManager.canRun('/path/to/dart'), throwsToolExit(message: expectedMessage)); }); + + testWithoutContext('when bad CPU type', () async { + final FakeProcessManager fakeProcessManager = FakeProcessManager.list([ + const FakeCommand(command: ['foo'], exception: ProcessException('', [], '', ebadarch)), + const FakeCommand(command: ['foo'], exception: ProcessException('', [], '', ebadarch)), + const FakeCommand(command: ['foo'], exception: ProcessException('', [], '', ebadarch)), + ]); + + final ProcessManager processManager = ErrorHandlingProcessManager( + delegate: fakeProcessManager, + platform: macOSPlatform, + ); + + const String expectedMessage = 'Flutter requires the Rosetta translation environment'; + + expect(() async => processManager.start(['foo']), + throwsToolExit(message: expectedMessage)); + expect(() async => processManager.run(['foo']), + throwsToolExit(message: expectedMessage)); + expect(() => processManager.runSync(['foo']), + throwsToolExit(message: expectedMessage)); + }); }); testWithoutContext('ErrorHandlingProcessManager delegates killPid correctly', () async { diff --git a/packages/flutter_tools/test/general.shard/flutter_validator_test.dart b/packages/flutter_tools/test/general.shard/flutter_validator_test.dart index 4876baea87..16f9006d29 100644 --- a/packages/flutter_tools/test/general.shard/flutter_validator_test.dart +++ b/packages/flutter_tools/test/general.shard/flutter_validator_test.dart @@ -66,7 +66,7 @@ void main() { messages: containsAll(const [ ValidationMessage.error( 'Downloaded executables cannot execute on host.\n' - 'See https://github.com/flutter/flutter/issues/6207 for more information\n' + 'See https://github.com/flutter/flutter/issues/6207 for more information.\n' 'On Debian/Ubuntu/Mint: sudo apt-get install lib32stdc++6\n' 'On Fedora: dnf install libstdc++.i686\n' 'On Arch: pacman -S lib32-gcc-libs\n', @@ -75,6 +75,49 @@ void main() { ); }); + testWithoutContext('FlutterValidator shows an error message if Rosetta is needed', () async { + final FakeFlutterVersion flutterVersion = FakeFlutterVersion( + frameworkVersion: '1.0.0', + channel: 'beta', + ); + final MemoryFileSystem fileSystem = MemoryFileSystem.test(); + final Artifacts artifacts = Artifacts.test(); + final FlutterValidator flutterValidator = FlutterValidator( + platform: FakePlatform( + operatingSystem: 'macos', + localeName: 'en_US.UTF-8', + environment: {}, + ), + flutterVersion: () => flutterVersion, + devToolsVersion: () => '2.8.0', + userMessages: UserMessages(), + artifacts: artifacts, + fileSystem: fileSystem, + flutterRoot: () => 'sdk/flutter', + operatingSystemUtils: FakeOperatingSystemUtils(name: 'macOS', hostPlatform: HostPlatform.darwin_arm64), + processManager: FakeProcessManager.list([ + const FakeCommand( + command: ['Artifact.genSnapshot'], + exitCode: 1, + ), + ]) + ); + fileSystem.file(artifacts.getArtifactPath(Artifact.genSnapshot)).createSync(recursive: true); + + expect(await flutterValidator.validate(), _matchDoctorValidation( + validationType: ValidationType.partial, + statusInfo: 'Channel beta, 1.0.0, on macOS, locale en_US.UTF-8', + messages: containsAll(const [ + ValidationMessage.error( + 'Downloaded executables cannot execute on host.\n' + 'See https://github.com/flutter/flutter/issues/6207 for more information.\n' + 'Flutter requires the Rosetta translation environment on ARM Macs. Try running:\n' + ' sudo softwareupdate --install-rosetta --agree-to-license\n' + ), + ])), + ); + }); + testWithoutContext('FlutterValidator does not run gen_snapshot binary check if it is not already downloaded', () async { final FakeFlutterVersion flutterVersion = FakeFlutterVersion( frameworkVersion: '1.0.0', @@ -569,6 +612,7 @@ void main() { class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils { FakeOperatingSystemUtils({ required this.name, + this.hostPlatform = HostPlatform.linux_x64, this.whichLookup, FileSystem? fs, }) { @@ -587,6 +631,9 @@ class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils { @override final String name; + + @override + final HostPlatform hostPlatform; } class FakeThrowingFlutterVersion extends FakeFlutterVersion {