Unverified Commit 4dffc851 authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Detect ARM macOS arch with sysctl hw.optional.arm64 (#67970)

parent 0343555a
...@@ -328,7 +328,7 @@ abstract class IOSApp extends ApplicationPackage { ...@@ -328,7 +328,7 @@ abstract class IOSApp extends ApplicationPackage {
} }
static Future<IOSApp> fromIosProject(IosProject project, BuildInfo buildInfo) { static Future<IOSApp> fromIosProject(IosProject project, BuildInfo buildInfo) {
if (getCurrentHostPlatform() != HostPlatform.darwin_x64) { if (!globals.platform.isMacOS) {
return null; return null;
} }
if (!project.exists) { if (!project.exists) {
......
...@@ -7,6 +7,7 @@ import 'package:file/file.dart'; ...@@ -7,6 +7,7 @@ import 'package:file/file.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import '../build_info.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import 'common.dart'; import 'common.dart';
import 'file_system.dart'; import 'file_system.dart';
...@@ -29,6 +30,13 @@ abstract class OperatingSystemUtils { ...@@ -29,6 +30,13 @@ abstract class OperatingSystemUtils {
platform: platform, platform: platform,
processManager: processManager, processManager: processManager,
); );
} else if (platform.isMacOS) {
return _MacOSUtils(
fileSystem: fileSystem,
logger: logger,
platform: platform,
processManager: processManager,
);
} else { } else {
return _PosixUtils( return _PosixUtils(
fileSystem: fileSystem, fileSystem: fileSystem,
...@@ -112,6 +120,8 @@ abstract class OperatingSystemUtils { ...@@ -112,6 +120,8 @@ abstract class OperatingSystemUtils {
return osNames.containsKey(osName) ? osNames[osName] : osName; return osNames.containsKey(osName) ? osNames[osName] : osName;
} }
HostPlatform get hostPlatform;
List<File> _which(String execName, { bool all = false }); List<File> _which(String execName, { bool all = false });
/// Returns the separator between items in the PATH environment variable. /// Returns the separator between items in the PATH environment variable.
...@@ -246,30 +256,63 @@ class _PosixUtils extends OperatingSystemUtils { ...@@ -246,30 +256,63 @@ class _PosixUtils extends OperatingSystemUtils {
return _fileSystem.file(path); return _fileSystem.file(path);
} }
@override
String get pathVarSeparator => ':';
@override
HostPlatform hostPlatform = HostPlatform.linux_x64;
}
class _MacOSUtils extends _PosixUtils {
_MacOSUtils({
@required FileSystem fileSystem,
@required Logger logger,
@required Platform platform,
@required ProcessManager processManager,
}) : super(
fileSystem: fileSystem,
logger: logger,
platform: platform,
processManager: processManager,
);
String _name; String _name;
@override @override
String get name { String get name {
if (_name == null) { if (_name == null) {
if (_platform.isMacOS) { final List<RunResult> results = <RunResult>[
final List<RunResult> results = <RunResult>[ _processUtils.runSync(<String>['sw_vers', '-productName']),
_processUtils.runSync(<String>['sw_vers', '-productName']), _processUtils.runSync(<String>['sw_vers', '-productVersion']),
_processUtils.runSync(<String>['sw_vers', '-productVersion']), _processUtils.runSync(<String>['sw_vers', '-buildVersion']),
_processUtils.runSync(<String>['sw_vers', '-buildVersion']), ];
_processUtils.runSync(<String>['uname', '-m']), if (results.every((RunResult result) => result.exitCode == 0)) {
]; _name =
if (results.every((RunResult result) => result.exitCode == 0)) { '${results[0].stdout.trim()} ${results[1].stdout.trim()} ${results[2].stdout.trim()} ${getNameForHostPlatform(hostPlatform)}';
_name = '${results[0].stdout.trim()} ${results[1].stdout
.trim()} ${results[2].stdout.trim()} ${results[3].stdout.trim()}';
}
} }
_name ??= super.name; _name ??= super.name;
} }
return _name; return _name;
} }
HostPlatform _hostPlatform;
// On ARM returns arm64, even when this process is running in Rosetta.
@override @override
String get pathVarSeparator => ':'; HostPlatform get hostPlatform {
if (_hostPlatform == null) {
final RunResult arm64Check =
_processUtils.runSync(<String>['sysctl', 'hw.optional.arm64']);
// hw.optional.arm64 is unavailable on < macOS 11 and exits with 1, assume x86 on failure.
// On arm64 stdout is "sysctl hw.optional.arm64: 1"
if (arm64Check.exitCode == 0 && arm64Check.stdout.trim().endsWith('1')) {
_hostPlatform = HostPlatform.darwin_arm;
} else {
_hostPlatform = HostPlatform.darwin_x64;
}
}
return _hostPlatform;
}
} }
class _WindowsUtils extends OperatingSystemUtils { class _WindowsUtils extends OperatingSystemUtils {
...@@ -285,6 +328,9 @@ class _WindowsUtils extends OperatingSystemUtils { ...@@ -285,6 +328,9 @@ class _WindowsUtils extends OperatingSystemUtils {
processManager: processManager, processManager: processManager,
); );
@override
HostPlatform hostPlatform = HostPlatform.windows_x64;
@override @override
void makeExecutable(File file) {} void makeExecutable(File file) {}
......
...@@ -398,6 +398,7 @@ bool isEmulatorBuildMode(BuildMode mode) { ...@@ -398,6 +398,7 @@ bool isEmulatorBuildMode(BuildMode mode) {
enum HostPlatform { enum HostPlatform {
darwin_x64, darwin_x64,
darwin_arm,
linux_x64, linux_x64,
windows_x64, windows_x64,
} }
...@@ -406,6 +407,8 @@ String getNameForHostPlatform(HostPlatform platform) { ...@@ -406,6 +407,8 @@ String getNameForHostPlatform(HostPlatform platform) {
switch (platform) { switch (platform) {
case HostPlatform.darwin_x64: case HostPlatform.darwin_x64:
return 'darwin-x64'; return 'darwin-x64';
case HostPlatform.darwin_arm:
return 'darwin-arm';
case HostPlatform.linux_x64: case HostPlatform.linux_x64:
return 'linux-x64'; return 'linux-x64';
case HostPlatform.windows_x64: case HostPlatform.windows_x64:
...@@ -418,6 +421,7 @@ String getNameForHostPlatform(HostPlatform platform) { ...@@ -418,6 +421,7 @@ String getNameForHostPlatform(HostPlatform platform) {
enum TargetPlatform { enum TargetPlatform {
android, android,
ios, ios,
// darwin_arm64 not yet supported, macOS desktop targets run in Rosetta as x86.
darwin_x64, darwin_x64,
linux_x64, linux_x64,
windows_x64, windows_x64,
......
...@@ -6,10 +6,10 @@ import 'package:file/file.dart'; ...@@ -6,10 +6,10 @@ import 'package:file/file.dart';
import 'package:file/memory.dart'; import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/os.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -22,9 +22,11 @@ const String kPath2 = '/another/bin/$kExecutable'; ...@@ -22,9 +22,11 @@ const String kPath2 = '/another/bin/$kExecutable';
void main() { void main() {
MockProcessManager mockProcessManager; MockProcessManager mockProcessManager;
FakeProcessManager fakeProcessManager;
setUp(() { setUp(() {
mockProcessManager = MockProcessManager(); mockProcessManager = MockProcessManager();
fakeProcessManager = FakeProcessManager.list(<FakeCommand>[]);
}); });
OperatingSystemUtils createOSUtils(Platform platform) { OperatingSystemUtils createOSUtils(Platform platform) {
...@@ -32,28 +34,50 @@ void main() { ...@@ -32,28 +34,50 @@ void main() {
fileSystem: MemoryFileSystem.test(), fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(), logger: BufferLogger.test(),
platform: platform, platform: platform,
processManager: mockProcessManager, processManager: fakeProcessManager,
); );
} }
group('which on POSIX', () { group('which on POSIX', () {
testWithoutContext('returns null when executable does not exist', () async { testWithoutContext('returns null when executable does not exist', () async {
when(mockProcessManager.runSync(<String>['which', kExecutable])) fakeProcessManager.addCommand(
.thenReturn(ProcessResult(0, 1, null, null)); const FakeCommand(
command: <String>[
'which',
kExecutable,
],
exitCode: 1,
),
);
final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'linux')); final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'linux'));
expect(utils.which(kExecutable), isNull); expect(utils.which(kExecutable), isNull);
}); });
testWithoutContext('returns exactly one result', () async { testWithoutContext('returns exactly one result', () async {
when(mockProcessManager.runSync(<String>['which', 'foo'])) fakeProcessManager.addCommand(
.thenReturn(ProcessResult(0, 0, kPath1, null)); const FakeCommand(
command: <String>[
'which',
'foo',
],
stdout: kPath1,
),
);
final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'linux')); final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'linux'));
expect(utils.which(kExecutable).path, kPath1); expect(utils.which(kExecutable).path, kPath1);
}); });
testWithoutContext('returns all results for whichAll', () async { testWithoutContext('returns all results for whichAll', () async {
when(mockProcessManager.runSync(<String>['which', '-a', kExecutable])) fakeProcessManager.addCommand(
.thenReturn(ProcessResult(0, 0, '$kPath1\n$kPath2', null)); const FakeCommand(
command: <String>[
'which',
'-a',
kExecutable,
],
stdout: '$kPath1\n$kPath2',
),
);
final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'linux')); final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'linux'));
final List<File> result = utils.whichAll(kExecutable); final List<File> result = utils.whichAll(kExecutable);
expect(result, hasLength(2)); expect(result, hasLength(2));
...@@ -66,27 +90,55 @@ void main() { ...@@ -66,27 +90,55 @@ void main() {
testWithoutContext('throws tool exit if where throws an argument error', () async { testWithoutContext('throws tool exit if where throws an argument error', () async {
when(mockProcessManager.runSync(<String>['where', kExecutable])) when(mockProcessManager.runSync(<String>['where', kExecutable]))
.thenThrow(ArgumentError('Cannot find executable for where')); .thenThrow(ArgumentError('Cannot find executable for where'));
final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'windows')); final OperatingSystemUtils utils = OperatingSystemUtils(
fileSystem: MemoryFileSystem.test(),
logger: BufferLogger.test(),
platform: FakePlatform(operatingSystem: 'windows'),
processManager: mockProcessManager,
);
expect(() => utils.which(kExecutable), throwsA(isA<ToolExit>())); expect(() => utils.which(kExecutable), throwsA(isA<ToolExit>()));
}); });
testWithoutContext('returns null when executable does not exist', () async { testWithoutContext('returns null when executable does not exist', () async {
when(mockProcessManager.runSync(<String>['where', kExecutable])) fakeProcessManager.addCommand(
.thenReturn(ProcessResult(0, 1, null, null)); const FakeCommand(
command: <String>[
'where',
kExecutable,
],
exitCode: 1,
),
);
final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'windows')); final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'windows'));
expect(utils.which(kExecutable), isNull); expect(utils.which(kExecutable), isNull);
}); });
testWithoutContext('returns exactly one result', () async { testWithoutContext('returns exactly one result', () async {
when(mockProcessManager.runSync(<String>['where', 'foo'])) fakeProcessManager.addCommand(
.thenReturn(ProcessResult(0, 0, '$kPath1\n$kPath2', null)); const FakeCommand(
command: <String>[
'where',
'foo',
],
stdout: '$kPath1\n$kPath2',
),
);
final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'windows')); final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'windows'));
expect(utils.which(kExecutable).path, kPath1); expect(utils.which(kExecutable).path, kPath1);
}); });
testWithoutContext('returns all results for whichAll', () async { testWithoutContext('returns all results for whichAll', () async {
when(mockProcessManager.runSync(<String>['where', kExecutable])) fakeProcessManager.addCommand(
.thenReturn(ProcessResult(0, 0, '$kPath1\n$kPath2', null)); const FakeCommand(
command: <String>[
'where',
kExecutable,
],
stdout: '$kPath1\n$kPath2',
),
);
final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'windows')); final OperatingSystemUtils utils = createOSUtils(FakePlatform(operatingSystem: 'windows'));
final List<File> result = utils.whichAll(kExecutable); final List<File> result = utils.whichAll(kExecutable);
expect(result, hasLength(2)); expect(result, hasLength(2));
...@@ -95,34 +147,162 @@ void main() { ...@@ -95,34 +147,162 @@ void main() {
}); });
}); });
testWithoutContext('macos name', () async { group('host platform', () {
when(mockProcessManager.runSync( testWithoutContext('unknown defaults to Linux', () async {
<String>['sw_vers', '-productName'], final OperatingSystemUtils utils =
)).thenReturn(ProcessResult(0, 0, 'product', '')); createOSUtils(FakePlatform(operatingSystem: 'fuchsia'));
when(mockProcessManager.runSync( expect(utils.hostPlatform, HostPlatform.linux_x64);
<String>['sw_vers', '-productVersion'], });
)).thenReturn(ProcessResult(0, 0, 'version', ''));
when(mockProcessManager.runSync( testWithoutContext('Windows', () async {
<String>['sw_vers', '-buildVersion'], final OperatingSystemUtils utils =
)).thenReturn(ProcessResult(0, 0, 'build', '')); createOSUtils(FakePlatform(operatingSystem: 'windows'));
when(mockProcessManager.runSync( expect(utils.hostPlatform, HostPlatform.windows_x64);
<String>['uname', '-m'], });
)).thenReturn(ProcessResult(0, 0, 'arch', ''));
final MockFileSystem fileSystem = MockFileSystem(); testWithoutContext('Linux', () async {
final OperatingSystemUtils utils = OperatingSystemUtils( final OperatingSystemUtils utils =
fileSystem: fileSystem, createOSUtils(FakePlatform(operatingSystem: 'linux'));
logger: BufferLogger.test(), expect(utils.hostPlatform, HostPlatform.linux_x64);
platform: FakePlatform(operatingSystem: 'macos'), });
processManager: mockProcessManager,
); testWithoutContext('macOS ARM', () async {
expect(utils.name, 'product version build arch'); fakeProcessManager.addCommand(
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
stdout: 'hw.optional.arm64: 1',
),
);
final OperatingSystemUtils utils =
createOSUtils(FakePlatform(operatingSystem: 'macos'));
expect(utils.hostPlatform, HostPlatform.darwin_arm);
});
testWithoutContext('macOS 11 x86', () async {
fakeProcessManager.addCommand(
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
stdout: 'hw.optional.arm64: 0',
),
);
final OperatingSystemUtils utils =
createOSUtils(FakePlatform(operatingSystem: 'macos'));
expect(utils.hostPlatform, HostPlatform.darwin_x64);
});
testWithoutContext('macOS 10 x86', () async {
fakeProcessManager.addCommand(
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
);
final OperatingSystemUtils utils =
createOSUtils(FakePlatform(operatingSystem: 'macos'));
expect(utils.hostPlatform, HostPlatform.darwin_x64);
});
testWithoutContext('macOS ARM name', () async {
fakeProcessManager.addCommands(<FakeCommand>[
const FakeCommand(
command: <String>[
'sw_vers',
'-productName',
],
stdout: 'product',
),
const FakeCommand(
command: <String>[
'sw_vers',
'-productVersion',
],
stdout: 'version',
),
const FakeCommand(
command: <String>[
'sw_vers',
'-buildVersion',
],
stdout: 'build',
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
stdout: 'hw.optional.arm64: 1',
),
]);
final OperatingSystemUtils utils =
createOSUtils(FakePlatform(operatingSystem: 'macos'));
expect(utils.name, 'product version build darwin-arm');
});
testWithoutContext('macOS x86 name', () async {
fakeProcessManager.addCommands(<FakeCommand>[
const FakeCommand(
command: <String>[
'sw_vers',
'-productName',
],
stdout: 'product',
),
const FakeCommand(
command: <String>[
'sw_vers',
'-productVersion',
],
stdout: 'version',
),
const FakeCommand(
command: <String>[
'sw_vers',
'-buildVersion',
],
stdout: 'build',
),
const FakeCommand(
command: <String>[
'sysctl',
'hw.optional.arm64',
],
exitCode: 1,
),
]);
final OperatingSystemUtils utils =
createOSUtils(FakePlatform(operatingSystem: 'macos'));
expect(utils.name, 'product version build darwin-x64');
});
}); });
testWithoutContext('If unzip fails, include stderr in exception text', () { testWithoutContext('If unzip fails, include stderr in exception text', () {
const String exceptionMessage = 'Something really bad happened.'; const String exceptionMessage = 'Something really bad happened.';
when(mockProcessManager.runSync(
<String>['unzip', '-o', '-q', null, '-d', null], fakeProcessManager.addCommand(
)).thenReturn(ProcessResult(0, 1, '', exceptionMessage)); const FakeCommand(command: <String>[
'unzip',
'-o',
'-q',
null,
'-d',
null,
], exitCode: 1, stderr: exceptionMessage),
);
final MockFileSystem fileSystem = MockFileSystem(); final MockFileSystem fileSystem = MockFileSystem();
final MockFile mockFile = MockFile(); final MockFile mockFile = MockFile();
final MockDirectory mockDirectory = MockDirectory(); final MockDirectory mockDirectory = MockDirectory();
...@@ -134,7 +314,7 @@ void main() { ...@@ -134,7 +314,7 @@ void main() {
fileSystem: fileSystem, fileSystem: fileSystem,
logger: BufferLogger.test(), logger: BufferLogger.test(),
platform: FakePlatform(operatingSystem: 'linux'), platform: FakePlatform(operatingSystem: 'linux'),
processManager: mockProcessManager, processManager: fakeProcessManager,
); );
expect( expect(
......
...@@ -17,6 +17,7 @@ import 'package:flutter_tools/src/base/signals.dart'; ...@@ -17,6 +17,7 @@ import 'package:flutter_tools/src/base/signals.dart';
import 'package:flutter_tools/src/base/template.dart'; import 'package:flutter_tools/src/base/template.dart';
import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/base/time.dart'; import 'package:flutter_tools/src/base/time.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/isolated/mustache_template.dart'; import 'package:flutter_tools/src/isolated/mustache_template.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/context_runner.dart'; import 'package:flutter_tools/src/context_runner.dart';
...@@ -294,6 +295,9 @@ class FakeOperatingSystemUtils implements OperatingSystemUtils { ...@@ -294,6 +295,9 @@ class FakeOperatingSystemUtils implements OperatingSystemUtils {
@override @override
ProcessResult makeExecutable(File file) => null; ProcessResult makeExecutable(File file) => null;
@override
HostPlatform hostPlatform = HostPlatform.linux_x64;
@override @override
void chmod(FileSystemEntity entity, String mode) { } void chmod(FileSystemEntity entity, String mode) { }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment