mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
Pull XCDevice out of xcode.dart (#80324)
This commit is contained in:
parent
65388ee2ee
commit
ddc1c296a1
|
@ -53,6 +53,7 @@ import 'ios/xcodeproj.dart';
|
||||||
import 'macos/cocoapods.dart';
|
import 'macos/cocoapods.dart';
|
||||||
import 'macos/cocoapods_validator.dart';
|
import 'macos/cocoapods_validator.dart';
|
||||||
import 'macos/macos_workflow.dart';
|
import 'macos/macos_workflow.dart';
|
||||||
|
import 'macos/xcdevice.dart';
|
||||||
import 'macos/xcode.dart';
|
import 'macos/xcode.dart';
|
||||||
import 'mdns_discovery.dart';
|
import 'mdns_discovery.dart';
|
||||||
import 'persistent_tool_state.dart';
|
import 'persistent_tool_state.dart';
|
||||||
|
|
|
@ -31,7 +31,7 @@ import 'ios/simulators.dart';
|
||||||
import 'linux/linux_device.dart';
|
import 'linux/linux_device.dart';
|
||||||
import 'macos/macos_device.dart';
|
import 'macos/macos_device.dart';
|
||||||
import 'macos/macos_workflow.dart';
|
import 'macos/macos_workflow.dart';
|
||||||
import 'macos/xcode.dart';
|
import 'macos/xcdevice.dart';
|
||||||
import 'tester/flutter_tester.dart';
|
import 'tester/flutter_tester.dart';
|
||||||
import 'version.dart';
|
import 'version.dart';
|
||||||
import 'web/web_device.dart';
|
import 'web/web_device.dart';
|
||||||
|
|
|
@ -17,6 +17,7 @@ import 'ios/simulators.dart';
|
||||||
import 'ios/xcodeproj.dart';
|
import 'ios/xcodeproj.dart';
|
||||||
import 'macos/cocoapods.dart';
|
import 'macos/cocoapods.dart';
|
||||||
import 'macos/cocoapods_validator.dart';
|
import 'macos/cocoapods_validator.dart';
|
||||||
|
import 'macos/xcdevice.dart';
|
||||||
import 'macos/xcode.dart';
|
import 'macos/xcode.dart';
|
||||||
import 'project.dart';
|
import 'project.dart';
|
||||||
import 'reporting/crash_reporting.dart';
|
import 'reporting/crash_reporting.dart';
|
||||||
|
|
|
@ -22,7 +22,7 @@ import '../convert.dart';
|
||||||
import '../device.dart';
|
import '../device.dart';
|
||||||
import '../device_port_forwarder.dart';
|
import '../device_port_forwarder.dart';
|
||||||
import '../globals.dart' as globals;
|
import '../globals.dart' as globals;
|
||||||
import '../macos/xcode.dart';
|
import '../macos/xcdevice.dart';
|
||||||
import '../project.dart';
|
import '../project.dart';
|
||||||
import '../protocol_discovery.dart';
|
import '../protocol_discovery.dart';
|
||||||
import '../vmservice.dart';
|
import '../vmservice.dart';
|
||||||
|
|
503
packages/flutter_tools/lib/src/macos/xcdevice.dart
Normal file
503
packages/flutter_tools/lib/src/macos/xcdevice.dart
Normal file
|
@ -0,0 +1,503 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// @dart = 2.8
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
|
import '../artifacts.dart';
|
||||||
|
import '../base/common.dart';
|
||||||
|
import '../base/io.dart';
|
||||||
|
import '../base/logger.dart';
|
||||||
|
import '../base/platform.dart';
|
||||||
|
import '../base/process.dart';
|
||||||
|
import '../build_info.dart';
|
||||||
|
import '../cache.dart';
|
||||||
|
import '../convert.dart';
|
||||||
|
import '../globals.dart' as globals;
|
||||||
|
import '../ios/devices.dart';
|
||||||
|
import '../ios/ios_deploy.dart';
|
||||||
|
import '../ios/iproxy.dart';
|
||||||
|
import '../ios/mac.dart';
|
||||||
|
import '../reporting/reporting.dart';
|
||||||
|
import 'xcode.dart';
|
||||||
|
|
||||||
|
enum XCDeviceEvent {
|
||||||
|
attach,
|
||||||
|
detach,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A utility class for interacting with Xcode xcdevice command line tools.
|
||||||
|
class XCDevice {
|
||||||
|
XCDevice({
|
||||||
|
@required Artifacts artifacts,
|
||||||
|
@required Cache cache,
|
||||||
|
@required ProcessManager processManager,
|
||||||
|
@required Logger logger,
|
||||||
|
@required Xcode xcode,
|
||||||
|
@required Platform platform,
|
||||||
|
@required IProxy iproxy,
|
||||||
|
}) : _processUtils = ProcessUtils(logger: logger, processManager: processManager),
|
||||||
|
_logger = logger,
|
||||||
|
_iMobileDevice = IMobileDevice(
|
||||||
|
artifacts: artifacts,
|
||||||
|
cache: cache,
|
||||||
|
logger: logger,
|
||||||
|
processManager: processManager,
|
||||||
|
),
|
||||||
|
_iosDeploy = IOSDeploy(
|
||||||
|
artifacts: artifacts,
|
||||||
|
cache: cache,
|
||||||
|
logger: logger,
|
||||||
|
platform: platform,
|
||||||
|
processManager: processManager,
|
||||||
|
),
|
||||||
|
_iProxy = iproxy,
|
||||||
|
_xcode = xcode {
|
||||||
|
|
||||||
|
_setupDeviceIdentifierByEventStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_deviceObservationProcess?.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
final ProcessUtils _processUtils;
|
||||||
|
final Logger _logger;
|
||||||
|
final IMobileDevice _iMobileDevice;
|
||||||
|
final IOSDeploy _iosDeploy;
|
||||||
|
final Xcode _xcode;
|
||||||
|
final IProxy _iProxy;
|
||||||
|
|
||||||
|
List<dynamic> _cachedListResults;
|
||||||
|
Process _deviceObservationProcess;
|
||||||
|
StreamController<Map<XCDeviceEvent, String>> _deviceIdentifierByEvent;
|
||||||
|
|
||||||
|
void _setupDeviceIdentifierByEventStream() {
|
||||||
|
// _deviceIdentifierByEvent Should always be available for listeners
|
||||||
|
// in case polling needs to be stopped and restarted.
|
||||||
|
_deviceIdentifierByEvent = StreamController<Map<XCDeviceEvent, String>>.broadcast(
|
||||||
|
onListen: _startObservingTetheredIOSDevices,
|
||||||
|
onCancel: _stopObservingTetheredIOSDevices,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isInstalled => _xcode.isInstalledAndMeetsVersionCheck;
|
||||||
|
|
||||||
|
Future<List<dynamic>> _getAllDevices({
|
||||||
|
bool useCache = false,
|
||||||
|
@required Duration timeout
|
||||||
|
}) async {
|
||||||
|
if (!isInstalled) {
|
||||||
|
_logger.printTrace("Xcode not found. Run 'flutter doctor' for more information.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (useCache && _cachedListResults != null) {
|
||||||
|
return _cachedListResults;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// USB-tethered devices should be found quickly. 1 second timeout is faster than the default.
|
||||||
|
final RunResult result = await _processUtils.run(
|
||||||
|
<String>[
|
||||||
|
..._xcode.xcrunCommand(),
|
||||||
|
'xcdevice',
|
||||||
|
'list',
|
||||||
|
'--timeout',
|
||||||
|
timeout.inSeconds.toString(),
|
||||||
|
],
|
||||||
|
throwOnError: true,
|
||||||
|
);
|
||||||
|
if (result.exitCode == 0) {
|
||||||
|
final List<dynamic> listResults = json.decode(result.stdout) as List<dynamic>;
|
||||||
|
_cachedListResults = listResults;
|
||||||
|
return listResults;
|
||||||
|
}
|
||||||
|
_logger.printTrace('xcdevice returned an error:\n${result.stderr}');
|
||||||
|
} on ProcessException catch (exception) {
|
||||||
|
_logger.printTrace('Process exception running xcdevice list:\n$exception');
|
||||||
|
} on ArgumentError catch (exception) {
|
||||||
|
_logger.printTrace('Argument exception running xcdevice list:\n$exception');
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Observe identifiers (UDIDs) of devices as they attach and detach.
|
||||||
|
///
|
||||||
|
/// Each attach and detach event is a tuple of one event type
|
||||||
|
/// and identifier.
|
||||||
|
Stream<Map<XCDeviceEvent, String>> observedDeviceEvents() {
|
||||||
|
if (!isInstalled) {
|
||||||
|
_logger.printTrace("Xcode not found. Run 'flutter doctor' for more information.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return _deviceIdentifierByEvent.stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
||||||
|
// Attach: 00008027-00192736010F802E
|
||||||
|
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
||||||
|
final RegExp _observationIdentifierPattern = RegExp(r'^(\w*): ([\w-]*)$');
|
||||||
|
|
||||||
|
Future<void> _startObservingTetheredIOSDevices() async {
|
||||||
|
try {
|
||||||
|
if (_deviceObservationProcess != null) {
|
||||||
|
throw Exception('xcdevice observe restart failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run in interactive mode (via script) to convince
|
||||||
|
// xcdevice it has a terminal attached in order to redirect stdout.
|
||||||
|
_deviceObservationProcess = await _processUtils.start(
|
||||||
|
<String>[
|
||||||
|
'script',
|
||||||
|
'-t',
|
||||||
|
'0',
|
||||||
|
'/dev/null',
|
||||||
|
..._xcode.xcrunCommand(),
|
||||||
|
'xcdevice',
|
||||||
|
'observe',
|
||||||
|
'--both',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
final StreamSubscription<String> stdoutSubscription = _deviceObservationProcess.stdout
|
||||||
|
.transform<String>(utf8.decoder)
|
||||||
|
.transform<String>(const LineSplitter())
|
||||||
|
.listen((String line) {
|
||||||
|
|
||||||
|
// xcdevice observe example output of UDIDs:
|
||||||
|
//
|
||||||
|
// Listening for all devices, on both interfaces.
|
||||||
|
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
||||||
|
// Attach: 00008027-00192736010F802E
|
||||||
|
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
||||||
|
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
||||||
|
final RegExpMatch match = _observationIdentifierPattern.firstMatch(line);
|
||||||
|
if (match != null && match.groupCount == 2) {
|
||||||
|
final String verb = match.group(1).toLowerCase();
|
||||||
|
final String identifier = match.group(2);
|
||||||
|
if (verb.startsWith('attach')) {
|
||||||
|
_deviceIdentifierByEvent.add(<XCDeviceEvent, String>{
|
||||||
|
XCDeviceEvent.attach: identifier
|
||||||
|
});
|
||||||
|
} else if (verb.startsWith('detach')) {
|
||||||
|
_deviceIdentifierByEvent.add(<XCDeviceEvent, String>{
|
||||||
|
XCDeviceEvent.detach: identifier
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
final StreamSubscription<String> stderrSubscription = _deviceObservationProcess.stderr
|
||||||
|
.transform<String>(utf8.decoder)
|
||||||
|
.transform<String>(const LineSplitter())
|
||||||
|
.listen((String line) {
|
||||||
|
_logger.printTrace('xcdevice observe error: $line');
|
||||||
|
});
|
||||||
|
unawaited(_deviceObservationProcess.exitCode.then((int status) {
|
||||||
|
_logger.printTrace('xcdevice exited with code $exitCode');
|
||||||
|
unawaited(stdoutSubscription.cancel());
|
||||||
|
unawaited(stderrSubscription.cancel());
|
||||||
|
}).whenComplete(() async {
|
||||||
|
if (_deviceIdentifierByEvent.hasListener) {
|
||||||
|
// Tell listeners the process died.
|
||||||
|
await _deviceIdentifierByEvent.close();
|
||||||
|
}
|
||||||
|
_deviceObservationProcess = null;
|
||||||
|
|
||||||
|
// Reopen it so new listeners can resume polling.
|
||||||
|
_setupDeviceIdentifierByEventStream();
|
||||||
|
}));
|
||||||
|
} on ProcessException catch (exception, stackTrace) {
|
||||||
|
_deviceIdentifierByEvent.addError(exception, stackTrace);
|
||||||
|
} on ArgumentError catch (exception, stackTrace) {
|
||||||
|
_deviceIdentifierByEvent.addError(exception, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopObservingTetheredIOSDevices() {
|
||||||
|
_deviceObservationProcess?.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [timeout] defaults to 2 seconds.
|
||||||
|
Future<List<IOSDevice>> getAvailableIOSDevices({ Duration timeout }) async {
|
||||||
|
final List<dynamic> allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2));
|
||||||
|
|
||||||
|
if (allAvailableDevices == null) {
|
||||||
|
return const <IOSDevice>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// [
|
||||||
|
// {
|
||||||
|
// "simulator" : true,
|
||||||
|
// "operatingSystemVersion" : "13.3 (17K446)",
|
||||||
|
// "available" : true,
|
||||||
|
// "platform" : "com.apple.platform.appletvsimulator",
|
||||||
|
// "modelCode" : "AppleTV5,3",
|
||||||
|
// "identifier" : "CBB5E1ED-2172-446E-B4E7-F2B5823DBBA6",
|
||||||
|
// "architecture" : "x86_64",
|
||||||
|
// "modelName" : "Apple TV",
|
||||||
|
// "name" : "Apple TV"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "simulator" : false,
|
||||||
|
// "operatingSystemVersion" : "13.3 (17C54)",
|
||||||
|
// "interface" : "usb",
|
||||||
|
// "available" : true,
|
||||||
|
// "platform" : "com.apple.platform.iphoneos",
|
||||||
|
// "modelCode" : "iPhone8,1",
|
||||||
|
// "identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
|
||||||
|
// "architecture" : "arm64",
|
||||||
|
// "modelName" : "iPhone 6s",
|
||||||
|
// "name" : "iPhone"
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "simulator" : true,
|
||||||
|
// "operatingSystemVersion" : "6.1.1 (17S445)",
|
||||||
|
// "available" : true,
|
||||||
|
// "platform" : "com.apple.platform.watchsimulator",
|
||||||
|
// "modelCode" : "Watch5,4",
|
||||||
|
// "identifier" : "2D74FB11-88A0-44D0-B81E-C0C142B1C94A",
|
||||||
|
// "architecture" : "i386",
|
||||||
|
// "modelName" : "Apple Watch Series 5 - 44mm",
|
||||||
|
// "name" : "Apple Watch Series 5 - 44mm"
|
||||||
|
// },
|
||||||
|
// ...
|
||||||
|
|
||||||
|
final List<IOSDevice> devices = <IOSDevice>[];
|
||||||
|
for (final dynamic device in allAvailableDevices) {
|
||||||
|
if (device is! Map) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Map<String, dynamic> deviceProperties = device as Map<String, dynamic>;
|
||||||
|
|
||||||
|
// Only include iPhone, iPad, iPod, or other iOS devices.
|
||||||
|
if (!_isIPhoneOSDevice(deviceProperties)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, dynamic> errorProperties = _errorProperties(deviceProperties);
|
||||||
|
if (errorProperties != null) {
|
||||||
|
final String errorMessage = _parseErrorMessage(errorProperties);
|
||||||
|
if (errorMessage.contains('not paired')) {
|
||||||
|
UsageEvent('device', 'ios-trust-failure', flutterUsage: globals.flutterUsage).send();
|
||||||
|
}
|
||||||
|
_logger.printTrace(errorMessage);
|
||||||
|
|
||||||
|
final int code = _errorCode(errorProperties);
|
||||||
|
|
||||||
|
// Temporary error -10: iPhone is busy: Preparing debugger support for iPhone.
|
||||||
|
// Sometimes the app launch will fail on these devices until Xcode is done setting up the device.
|
||||||
|
// Other times this is a false positive and the app will successfully launch despite the error.
|
||||||
|
if (code != -10) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final IOSDeviceInterface interface = _interfaceType(deviceProperties);
|
||||||
|
|
||||||
|
// Only support USB devices, skip "network" interface (Xcode > Window > Devices and Simulators > Connect via network).
|
||||||
|
// TODO(jmagman): Remove this check once wirelessly detected devices can be observed and attached, https://github.com/flutter/flutter/issues/15072.
|
||||||
|
if (interface != IOSDeviceInterface.usb) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
devices.add(IOSDevice(
|
||||||
|
device['identifier'] as String,
|
||||||
|
name: device['name'] as String,
|
||||||
|
cpuArchitecture: _cpuArchitecture(deviceProperties),
|
||||||
|
interfaceType: interface,
|
||||||
|
sdkVersion: _sdkVersion(deviceProperties),
|
||||||
|
iProxy: _iProxy,
|
||||||
|
fileSystem: globals.fs,
|
||||||
|
logger: _logger,
|
||||||
|
iosDeploy: _iosDeploy,
|
||||||
|
iMobileDevice: _iMobileDevice,
|
||||||
|
platform: globals.platform,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Despite the name, com.apple.platform.iphoneos includes iPhone, iPads, and all iOS devices.
|
||||||
|
/// Excludes simulators.
|
||||||
|
static bool _isIPhoneOSDevice(Map<String, dynamic> deviceProperties) {
|
||||||
|
if (deviceProperties.containsKey('platform')) {
|
||||||
|
final String platform = deviceProperties['platform'] as String;
|
||||||
|
return platform == 'com.apple.platform.iphoneos';
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, dynamic> _errorProperties(Map<String, dynamic> deviceProperties) {
|
||||||
|
if (deviceProperties.containsKey('error')) {
|
||||||
|
return deviceProperties['error'] as Map<String, dynamic>;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int _errorCode(Map<String, dynamic> errorProperties) {
|
||||||
|
if (errorProperties.containsKey('code') && errorProperties['code'] is int) {
|
||||||
|
return errorProperties['code'] as int;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IOSDeviceInterface _interfaceType(Map<String, dynamic> deviceProperties) {
|
||||||
|
// Interface can be "usb", "network", or "none" for simulators
|
||||||
|
// and unknown future interfaces.
|
||||||
|
if (deviceProperties.containsKey('interface')) {
|
||||||
|
if ((deviceProperties['interface'] as String).toLowerCase() == 'network') {
|
||||||
|
return IOSDeviceInterface.network;
|
||||||
|
} else {
|
||||||
|
return IOSDeviceInterface.usb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return IOSDeviceInterface.none;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String _sdkVersion(Map<String, dynamic> deviceProperties) {
|
||||||
|
if (deviceProperties.containsKey('operatingSystemVersion')) {
|
||||||
|
// Parse out the OS version, ignore the build number in parentheses.
|
||||||
|
// "13.3 (17C54)"
|
||||||
|
final RegExp operatingSystemRegex = RegExp(r'(.*) \(.*\)$');
|
||||||
|
final String operatingSystemVersion = deviceProperties['operatingSystemVersion'] as String;
|
||||||
|
return operatingSystemRegex.firstMatch(operatingSystemVersion.trim())?.group(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
DarwinArch _cpuArchitecture(Map<String, dynamic> deviceProperties) {
|
||||||
|
DarwinArch cpuArchitecture;
|
||||||
|
if (deviceProperties.containsKey('architecture')) {
|
||||||
|
final String architecture = deviceProperties['architecture'] as String;
|
||||||
|
try {
|
||||||
|
cpuArchitecture = getIOSArchForName(architecture);
|
||||||
|
} on Exception {
|
||||||
|
// Fallback to default iOS architecture. Future-proof against a
|
||||||
|
// theoretical version of Xcode that changes this string to something
|
||||||
|
// slightly different like "ARM64", or armv7 variations like
|
||||||
|
// armv7s and armv7f.
|
||||||
|
if (architecture.startsWith('armv7')) {
|
||||||
|
cpuArchitecture = DarwinArch.armv7;
|
||||||
|
} else {
|
||||||
|
cpuArchitecture = DarwinArch.arm64;
|
||||||
|
}
|
||||||
|
_logger.printError(
|
||||||
|
'Unknown architecture $architecture, defaulting to '
|
||||||
|
'${getNameForDarwinArch(cpuArchitecture)}',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cpuArchitecture;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error message parsed from xcdevice. null if no error.
|
||||||
|
static String _parseErrorMessage(Map<String, dynamic> errorProperties) {
|
||||||
|
// {
|
||||||
|
// "simulator" : false,
|
||||||
|
// "operatingSystemVersion" : "13.3 (17C54)",
|
||||||
|
// "interface" : "usb",
|
||||||
|
// "available" : false,
|
||||||
|
// "platform" : "com.apple.platform.iphoneos",
|
||||||
|
// "modelCode" : "iPhone8,1",
|
||||||
|
// "identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44",
|
||||||
|
// "architecture" : "arm64",
|
||||||
|
// "modelName" : "iPhone 6s",
|
||||||
|
// "name" : "iPhone",
|
||||||
|
// "error" : {
|
||||||
|
// "code" : -9,
|
||||||
|
// "failureReason" : "",
|
||||||
|
// "underlyingErrors" : [
|
||||||
|
// {
|
||||||
|
// "code" : 5,
|
||||||
|
// "failureReason" : "allowsSecureServices: 1. isConnected: 0. Platform: <DVTPlatform:0x7f804ce32880:'com.apple.platform.iphoneos':<DVTFilePath:0x7f804ce32800:'\/Users\/magder\/Applications\/Xcode_11-3-1.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform'>>. DTDKDeviceIdentifierIsIDID: 0",
|
||||||
|
// "description" : "📱<DVTiOSDevice (0x7f801f190450), iPhone, iPhone, 13.3 (17C54), d83d5bc53967baa0ee18626ba87b6254b2ab5418> -- Failed _shouldMakeReadyForDevelopment check even though device is not locked by passcode.",
|
||||||
|
// "recoverySuggestion" : "",
|
||||||
|
// "domain" : "com.apple.platform.iphoneos"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "description" : "iPhone is not paired with your computer.",
|
||||||
|
// "recoverySuggestion" : "To use iPhone with Xcode, unlock it and choose to trust this computer when prompted.",
|
||||||
|
// "domain" : "com.apple.platform.iphoneos"
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "simulator" : false,
|
||||||
|
// "operatingSystemVersion" : "13.3 (17C54)",
|
||||||
|
// "interface" : "usb",
|
||||||
|
// "available" : false,
|
||||||
|
// "platform" : "com.apple.platform.iphoneos",
|
||||||
|
// "modelCode" : "iPhone8,1",
|
||||||
|
// "identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
|
||||||
|
// "architecture" : "arm64",
|
||||||
|
// "modelName" : "iPhone 6s",
|
||||||
|
// "name" : "iPhone",
|
||||||
|
// "error" : {
|
||||||
|
// "code" : -9,
|
||||||
|
// "failureReason" : "",
|
||||||
|
// "description" : "iPhone is not paired with your computer.",
|
||||||
|
// "domain" : "com.apple.platform.iphoneos"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// ...
|
||||||
|
|
||||||
|
if (errorProperties == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final StringBuffer errorMessage = StringBuffer('Error: ');
|
||||||
|
|
||||||
|
if (errorProperties.containsKey('description')) {
|
||||||
|
final String description = errorProperties['description'] as String;
|
||||||
|
errorMessage.write(description);
|
||||||
|
if (!description.endsWith('.')) {
|
||||||
|
errorMessage.write('.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errorMessage.write('Xcode pairing error.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorProperties.containsKey('recoverySuggestion')) {
|
||||||
|
final String recoverySuggestion = errorProperties['recoverySuggestion'] as String;
|
||||||
|
errorMessage.write(' $recoverySuggestion');
|
||||||
|
}
|
||||||
|
|
||||||
|
final int code = _errorCode(errorProperties);
|
||||||
|
if (code != null) {
|
||||||
|
errorMessage.write(' (code $code)');
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List of all devices reporting errors.
|
||||||
|
Future<List<String>> getDiagnostics() async {
|
||||||
|
final List<dynamic> allAvailableDevices = await _getAllDevices(
|
||||||
|
useCache: true,
|
||||||
|
timeout: const Duration(seconds: 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (allAvailableDevices == null) {
|
||||||
|
return const <String>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> diagnostics = <String>[];
|
||||||
|
for (final dynamic device in allAvailableDevices) {
|
||||||
|
if (device is! Map) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Map<String, dynamic> deviceProperties = device as Map<String, dynamic>;
|
||||||
|
final Map<String, dynamic> errorProperties = _errorProperties(deviceProperties);
|
||||||
|
final String errorMessage = _parseErrorMessage(errorProperties);
|
||||||
|
if (errorMessage != null) {
|
||||||
|
diagnostics.add(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return diagnostics;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,7 +10,6 @@ import 'package:file/memory.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:process/process.dart';
|
import 'package:process/process.dart';
|
||||||
|
|
||||||
import '../artifacts.dart';
|
|
||||||
import '../base/common.dart';
|
import '../base/common.dart';
|
||||||
import '../base/file_system.dart';
|
import '../base/file_system.dart';
|
||||||
import '../base/io.dart';
|
import '../base/io.dart';
|
||||||
|
@ -19,15 +18,7 @@ import '../base/platform.dart';
|
||||||
import '../base/process.dart';
|
import '../base/process.dart';
|
||||||
import '../base/version.dart';
|
import '../base/version.dart';
|
||||||
import '../build_info.dart';
|
import '../build_info.dart';
|
||||||
import '../cache.dart';
|
|
||||||
import '../convert.dart';
|
|
||||||
import '../globals.dart' as globals;
|
|
||||||
import '../ios/devices.dart';
|
|
||||||
import '../ios/ios_deploy.dart';
|
|
||||||
import '../ios/iproxy.dart';
|
|
||||||
import '../ios/mac.dart';
|
|
||||||
import '../ios/xcodeproj.dart';
|
import '../ios/xcodeproj.dart';
|
||||||
import '../reporting/reporting.dart';
|
|
||||||
|
|
||||||
Version get xcodeRequiredVersion => Version(12, 0, 1, text: '12.0.1');
|
Version get xcodeRequiredVersion => Version(12, 0, 1, text: '12.0.1');
|
||||||
|
|
||||||
|
@ -227,479 +218,3 @@ EnvironmentType environmentTypeFromSdkroot(Directory sdkroot) {
|
||||||
assert(false);
|
assert(false);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum XCDeviceEvent {
|
|
||||||
attach,
|
|
||||||
detach,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A utility class for interacting with Xcode xcdevice command line tools.
|
|
||||||
class XCDevice {
|
|
||||||
XCDevice({
|
|
||||||
@required Artifacts artifacts,
|
|
||||||
@required Cache cache,
|
|
||||||
@required ProcessManager processManager,
|
|
||||||
@required Logger logger,
|
|
||||||
@required Xcode xcode,
|
|
||||||
@required Platform platform,
|
|
||||||
@required IProxy iproxy,
|
|
||||||
}) : _processUtils = ProcessUtils(logger: logger, processManager: processManager),
|
|
||||||
_logger = logger,
|
|
||||||
_iMobileDevice = IMobileDevice(
|
|
||||||
artifacts: artifacts,
|
|
||||||
cache: cache,
|
|
||||||
logger: logger,
|
|
||||||
processManager: processManager,
|
|
||||||
),
|
|
||||||
_iosDeploy = IOSDeploy(
|
|
||||||
artifacts: artifacts,
|
|
||||||
cache: cache,
|
|
||||||
logger: logger,
|
|
||||||
platform: platform,
|
|
||||||
processManager: processManager,
|
|
||||||
),
|
|
||||||
_iProxy = iproxy,
|
|
||||||
_xcode = xcode {
|
|
||||||
|
|
||||||
_setupDeviceIdentifierByEventStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
_deviceObservationProcess?.kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
final ProcessUtils _processUtils;
|
|
||||||
final Logger _logger;
|
|
||||||
final IMobileDevice _iMobileDevice;
|
|
||||||
final IOSDeploy _iosDeploy;
|
|
||||||
final Xcode _xcode;
|
|
||||||
final IProxy _iProxy;
|
|
||||||
|
|
||||||
List<dynamic> _cachedListResults;
|
|
||||||
Process _deviceObservationProcess;
|
|
||||||
StreamController<Map<XCDeviceEvent, String>> _deviceIdentifierByEvent;
|
|
||||||
|
|
||||||
void _setupDeviceIdentifierByEventStream() {
|
|
||||||
// _deviceIdentifierByEvent Should always be available for listeners
|
|
||||||
// in case polling needs to be stopped and restarted.
|
|
||||||
_deviceIdentifierByEvent = StreamController<Map<XCDeviceEvent, String>>.broadcast(
|
|
||||||
onListen: _startObservingTetheredIOSDevices,
|
|
||||||
onCancel: _stopObservingTetheredIOSDevices,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isInstalled => _xcode.isInstalledAndMeetsVersionCheck;
|
|
||||||
|
|
||||||
Future<List<dynamic>> _getAllDevices({
|
|
||||||
bool useCache = false,
|
|
||||||
@required Duration timeout
|
|
||||||
}) async {
|
|
||||||
if (!isInstalled) {
|
|
||||||
_logger.printTrace("Xcode not found. Run 'flutter doctor' for more information.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (useCache && _cachedListResults != null) {
|
|
||||||
return _cachedListResults;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// USB-tethered devices should be found quickly. 1 second timeout is faster than the default.
|
|
||||||
final RunResult result = await _processUtils.run(
|
|
||||||
<String>[
|
|
||||||
..._xcode.xcrunCommand(),
|
|
||||||
'xcdevice',
|
|
||||||
'list',
|
|
||||||
'--timeout',
|
|
||||||
timeout.inSeconds.toString(),
|
|
||||||
],
|
|
||||||
throwOnError: true,
|
|
||||||
);
|
|
||||||
if (result.exitCode == 0) {
|
|
||||||
final List<dynamic> listResults = json.decode(result.stdout) as List<dynamic>;
|
|
||||||
_cachedListResults = listResults;
|
|
||||||
return listResults;
|
|
||||||
}
|
|
||||||
_logger.printTrace('xcdevice returned an error:\n${result.stderr}');
|
|
||||||
} on ProcessException catch (exception) {
|
|
||||||
_logger.printTrace('Process exception running xcdevice list:\n$exception');
|
|
||||||
} on ArgumentError catch (exception) {
|
|
||||||
_logger.printTrace('Argument exception running xcdevice list:\n$exception');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Observe identifiers (UDIDs) of devices as they attach and detach.
|
|
||||||
///
|
|
||||||
/// Each attach and detach event is a tuple of one event type
|
|
||||||
/// and identifier.
|
|
||||||
Stream<Map<XCDeviceEvent, String>> observedDeviceEvents() {
|
|
||||||
if (!isInstalled) {
|
|
||||||
_logger.printTrace("Xcode not found. Run 'flutter doctor' for more information.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return _deviceIdentifierByEvent.stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
|
||||||
// Attach: 00008027-00192736010F802E
|
|
||||||
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
|
||||||
final RegExp _observationIdentifierPattern = RegExp(r'^(\w*): ([\w-]*)$');
|
|
||||||
|
|
||||||
Future<void> _startObservingTetheredIOSDevices() async {
|
|
||||||
try {
|
|
||||||
if (_deviceObservationProcess != null) {
|
|
||||||
throw Exception('xcdevice observe restart failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run in interactive mode (via script) to convince
|
|
||||||
// xcdevice it has a terminal attached in order to redirect stdout.
|
|
||||||
_deviceObservationProcess = await _processUtils.start(
|
|
||||||
<String>[
|
|
||||||
'script',
|
|
||||||
'-t',
|
|
||||||
'0',
|
|
||||||
'/dev/null',
|
|
||||||
..._xcode.xcrunCommand(),
|
|
||||||
'xcdevice',
|
|
||||||
'observe',
|
|
||||||
'--both',
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final StreamSubscription<String> stdoutSubscription = _deviceObservationProcess.stdout
|
|
||||||
.transform<String>(utf8.decoder)
|
|
||||||
.transform<String>(const LineSplitter())
|
|
||||||
.listen((String line) {
|
|
||||||
|
|
||||||
// xcdevice observe example output of UDIDs:
|
|
||||||
//
|
|
||||||
// Listening for all devices, on both interfaces.
|
|
||||||
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
|
||||||
// Attach: 00008027-00192736010F802E
|
|
||||||
// Detach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
|
||||||
// Attach: d83d5bc53967baa0ee18626ba87b6254b2ab5418
|
|
||||||
final RegExpMatch match = _observationIdentifierPattern.firstMatch(line);
|
|
||||||
if (match != null && match.groupCount == 2) {
|
|
||||||
final String verb = match.group(1).toLowerCase();
|
|
||||||
final String identifier = match.group(2);
|
|
||||||
if (verb.startsWith('attach')) {
|
|
||||||
_deviceIdentifierByEvent.add(<XCDeviceEvent, String>{
|
|
||||||
XCDeviceEvent.attach: identifier
|
|
||||||
});
|
|
||||||
} else if (verb.startsWith('detach')) {
|
|
||||||
_deviceIdentifierByEvent.add(<XCDeviceEvent, String>{
|
|
||||||
XCDeviceEvent.detach: identifier
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
final StreamSubscription<String> stderrSubscription = _deviceObservationProcess.stderr
|
|
||||||
.transform<String>(utf8.decoder)
|
|
||||||
.transform<String>(const LineSplitter())
|
|
||||||
.listen((String line) {
|
|
||||||
_logger.printTrace('xcdevice observe error: $line');
|
|
||||||
});
|
|
||||||
unawaited(_deviceObservationProcess.exitCode.then((int status) {
|
|
||||||
_logger.printTrace('xcdevice exited with code $exitCode');
|
|
||||||
unawaited(stdoutSubscription.cancel());
|
|
||||||
unawaited(stderrSubscription.cancel());
|
|
||||||
}).whenComplete(() async {
|
|
||||||
if (_deviceIdentifierByEvent.hasListener) {
|
|
||||||
// Tell listeners the process died.
|
|
||||||
await _deviceIdentifierByEvent.close();
|
|
||||||
}
|
|
||||||
_deviceObservationProcess = null;
|
|
||||||
|
|
||||||
// Reopen it so new listeners can resume polling.
|
|
||||||
_setupDeviceIdentifierByEventStream();
|
|
||||||
}));
|
|
||||||
} on ProcessException catch (exception, stackTrace) {
|
|
||||||
_deviceIdentifierByEvent.addError(exception, stackTrace);
|
|
||||||
} on ArgumentError catch (exception, stackTrace) {
|
|
||||||
_deviceIdentifierByEvent.addError(exception, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _stopObservingTetheredIOSDevices() {
|
|
||||||
_deviceObservationProcess?.kill();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [timeout] defaults to 2 seconds.
|
|
||||||
Future<List<IOSDevice>> getAvailableIOSDevices({ Duration timeout }) async {
|
|
||||||
final List<dynamic> allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2));
|
|
||||||
|
|
||||||
if (allAvailableDevices == null) {
|
|
||||||
return const <IOSDevice>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
// [
|
|
||||||
// {
|
|
||||||
// "simulator" : true,
|
|
||||||
// "operatingSystemVersion" : "13.3 (17K446)",
|
|
||||||
// "available" : true,
|
|
||||||
// "platform" : "com.apple.platform.appletvsimulator",
|
|
||||||
// "modelCode" : "AppleTV5,3",
|
|
||||||
// "identifier" : "CBB5E1ED-2172-446E-B4E7-F2B5823DBBA6",
|
|
||||||
// "architecture" : "x86_64",
|
|
||||||
// "modelName" : "Apple TV",
|
|
||||||
// "name" : "Apple TV"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "simulator" : false,
|
|
||||||
// "operatingSystemVersion" : "13.3 (17C54)",
|
|
||||||
// "interface" : "usb",
|
|
||||||
// "available" : true,
|
|
||||||
// "platform" : "com.apple.platform.iphoneos",
|
|
||||||
// "modelCode" : "iPhone8,1",
|
|
||||||
// "identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
|
|
||||||
// "architecture" : "arm64",
|
|
||||||
// "modelName" : "iPhone 6s",
|
|
||||||
// "name" : "iPhone"
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "simulator" : true,
|
|
||||||
// "operatingSystemVersion" : "6.1.1 (17S445)",
|
|
||||||
// "available" : true,
|
|
||||||
// "platform" : "com.apple.platform.watchsimulator",
|
|
||||||
// "modelCode" : "Watch5,4",
|
|
||||||
// "identifier" : "2D74FB11-88A0-44D0-B81E-C0C142B1C94A",
|
|
||||||
// "architecture" : "i386",
|
|
||||||
// "modelName" : "Apple Watch Series 5 - 44mm",
|
|
||||||
// "name" : "Apple Watch Series 5 - 44mm"
|
|
||||||
// },
|
|
||||||
// ...
|
|
||||||
|
|
||||||
final List<IOSDevice> devices = <IOSDevice>[];
|
|
||||||
for (final dynamic device in allAvailableDevices) {
|
|
||||||
if (device is! Map) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final Map<String, dynamic> deviceProperties = device as Map<String, dynamic>;
|
|
||||||
|
|
||||||
// Only include iPhone, iPad, iPod, or other iOS devices.
|
|
||||||
if (!_isIPhoneOSDevice(deviceProperties)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, dynamic> errorProperties = _errorProperties(deviceProperties);
|
|
||||||
if (errorProperties != null) {
|
|
||||||
final String errorMessage = _parseErrorMessage(errorProperties);
|
|
||||||
if (errorMessage.contains('not paired')) {
|
|
||||||
UsageEvent('device', 'ios-trust-failure', flutterUsage: globals.flutterUsage).send();
|
|
||||||
}
|
|
||||||
_logger.printTrace(errorMessage);
|
|
||||||
|
|
||||||
final int code = _errorCode(errorProperties);
|
|
||||||
|
|
||||||
// Temporary error -10: iPhone is busy: Preparing debugger support for iPhone.
|
|
||||||
// Sometimes the app launch will fail on these devices until Xcode is done setting up the device.
|
|
||||||
// Other times this is a false positive and the app will successfully launch despite the error.
|
|
||||||
if (code != -10) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final IOSDeviceInterface interface = _interfaceType(deviceProperties);
|
|
||||||
|
|
||||||
// Only support USB devices, skip "network" interface (Xcode > Window > Devices and Simulators > Connect via network).
|
|
||||||
// TODO(jmagman): Remove this check once wirelessly detected devices can be observed and attached, https://github.com/flutter/flutter/issues/15072.
|
|
||||||
if (interface != IOSDeviceInterface.usb) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
devices.add(IOSDevice(
|
|
||||||
device['identifier'] as String,
|
|
||||||
name: device['name'] as String,
|
|
||||||
cpuArchitecture: _cpuArchitecture(deviceProperties),
|
|
||||||
interfaceType: interface,
|
|
||||||
sdkVersion: _sdkVersion(deviceProperties),
|
|
||||||
iProxy: _iProxy,
|
|
||||||
fileSystem: globals.fs,
|
|
||||||
logger: _logger,
|
|
||||||
iosDeploy: _iosDeploy,
|
|
||||||
iMobileDevice: _iMobileDevice,
|
|
||||||
platform: globals.platform,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Despite the name, com.apple.platform.iphoneos includes iPhone, iPads, and all iOS devices.
|
|
||||||
/// Excludes simulators.
|
|
||||||
static bool _isIPhoneOSDevice(Map<String, dynamic> deviceProperties) {
|
|
||||||
if (deviceProperties.containsKey('platform')) {
|
|
||||||
final String platform = deviceProperties['platform'] as String;
|
|
||||||
return platform == 'com.apple.platform.iphoneos';
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, dynamic> _errorProperties(Map<String, dynamic> deviceProperties) {
|
|
||||||
if (deviceProperties.containsKey('error')) {
|
|
||||||
return deviceProperties['error'] as Map<String, dynamic>;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int _errorCode(Map<String, dynamic> errorProperties) {
|
|
||||||
if (errorProperties.containsKey('code') && errorProperties['code'] is int) {
|
|
||||||
return errorProperties['code'] as int;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static IOSDeviceInterface _interfaceType(Map<String, dynamic> deviceProperties) {
|
|
||||||
// Interface can be "usb", "network", or "none" for simulators
|
|
||||||
// and unknown future interfaces.
|
|
||||||
if (deviceProperties.containsKey('interface')) {
|
|
||||||
if ((deviceProperties['interface'] as String).toLowerCase() == 'network') {
|
|
||||||
return IOSDeviceInterface.network;
|
|
||||||
} else {
|
|
||||||
return IOSDeviceInterface.usb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return IOSDeviceInterface.none;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String _sdkVersion(Map<String, dynamic> deviceProperties) {
|
|
||||||
if (deviceProperties.containsKey('operatingSystemVersion')) {
|
|
||||||
// Parse out the OS version, ignore the build number in parentheses.
|
|
||||||
// "13.3 (17C54)"
|
|
||||||
final RegExp operatingSystemRegex = RegExp(r'(.*) \(.*\)$');
|
|
||||||
final String operatingSystemVersion = deviceProperties['operatingSystemVersion'] as String;
|
|
||||||
return operatingSystemRegex.firstMatch(operatingSystemVersion.trim())?.group(1);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
DarwinArch _cpuArchitecture(Map<String, dynamic> deviceProperties) {
|
|
||||||
DarwinArch cpuArchitecture;
|
|
||||||
if (deviceProperties.containsKey('architecture')) {
|
|
||||||
final String architecture = deviceProperties['architecture'] as String;
|
|
||||||
try {
|
|
||||||
cpuArchitecture = getIOSArchForName(architecture);
|
|
||||||
} on Exception {
|
|
||||||
// Fallback to default iOS architecture. Future-proof against a
|
|
||||||
// theoretical version of Xcode that changes this string to something
|
|
||||||
// slightly different like "ARM64", or armv7 variations like
|
|
||||||
// armv7s and armv7f.
|
|
||||||
if (architecture.startsWith('armv7')) {
|
|
||||||
cpuArchitecture = DarwinArch.armv7;
|
|
||||||
} else {
|
|
||||||
cpuArchitecture = DarwinArch.arm64;
|
|
||||||
}
|
|
||||||
_logger.printError(
|
|
||||||
'Unknown architecture $architecture, defaulting to '
|
|
||||||
'${getNameForDarwinArch(cpuArchitecture)}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cpuArchitecture;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Error message parsed from xcdevice. null if no error.
|
|
||||||
static String _parseErrorMessage(Map<String, dynamic> errorProperties) {
|
|
||||||
// {
|
|
||||||
// "simulator" : false,
|
|
||||||
// "operatingSystemVersion" : "13.3 (17C54)",
|
|
||||||
// "interface" : "usb",
|
|
||||||
// "available" : false,
|
|
||||||
// "platform" : "com.apple.platform.iphoneos",
|
|
||||||
// "modelCode" : "iPhone8,1",
|
|
||||||
// "identifier" : "98206e7a4afd4aedaff06e687594e089dede3c44",
|
|
||||||
// "architecture" : "arm64",
|
|
||||||
// "modelName" : "iPhone 6s",
|
|
||||||
// "name" : "iPhone",
|
|
||||||
// "error" : {
|
|
||||||
// "code" : -9,
|
|
||||||
// "failureReason" : "",
|
|
||||||
// "underlyingErrors" : [
|
|
||||||
// {
|
|
||||||
// "code" : 5,
|
|
||||||
// "failureReason" : "allowsSecureServices: 1. isConnected: 0. Platform: <DVTPlatform:0x7f804ce32880:'com.apple.platform.iphoneos':<DVTFilePath:0x7f804ce32800:'\/Users\/magder\/Applications\/Xcode_11-3-1.app\/Contents\/Developer\/Platforms\/iPhoneOS.platform'>>. DTDKDeviceIdentifierIsIDID: 0",
|
|
||||||
// "description" : "📱<DVTiOSDevice (0x7f801f190450), iPhone, iPhone, 13.3 (17C54), d83d5bc53967baa0ee18626ba87b6254b2ab5418> -- Failed _shouldMakeReadyForDevelopment check even though device is not locked by passcode.",
|
|
||||||
// "recoverySuggestion" : "",
|
|
||||||
// "domain" : "com.apple.platform.iphoneos"
|
|
||||||
// }
|
|
||||||
// ],
|
|
||||||
// "description" : "iPhone is not paired with your computer.",
|
|
||||||
// "recoverySuggestion" : "To use iPhone with Xcode, unlock it and choose to trust this computer when prompted.",
|
|
||||||
// "domain" : "com.apple.platform.iphoneos"
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// "simulator" : false,
|
|
||||||
// "operatingSystemVersion" : "13.3 (17C54)",
|
|
||||||
// "interface" : "usb",
|
|
||||||
// "available" : false,
|
|
||||||
// "platform" : "com.apple.platform.iphoneos",
|
|
||||||
// "modelCode" : "iPhone8,1",
|
|
||||||
// "identifier" : "d83d5bc53967baa0ee18626ba87b6254b2ab5418",
|
|
||||||
// "architecture" : "arm64",
|
|
||||||
// "modelName" : "iPhone 6s",
|
|
||||||
// "name" : "iPhone",
|
|
||||||
// "error" : {
|
|
||||||
// "code" : -9,
|
|
||||||
// "failureReason" : "",
|
|
||||||
// "description" : "iPhone is not paired with your computer.",
|
|
||||||
// "domain" : "com.apple.platform.iphoneos"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ...
|
|
||||||
|
|
||||||
if (errorProperties == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final StringBuffer errorMessage = StringBuffer('Error: ');
|
|
||||||
|
|
||||||
if (errorProperties.containsKey('description')) {
|
|
||||||
final String description = errorProperties['description'] as String;
|
|
||||||
errorMessage.write(description);
|
|
||||||
if (!description.endsWith('.')) {
|
|
||||||
errorMessage.write('.');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
errorMessage.write('Xcode pairing error.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (errorProperties.containsKey('recoverySuggestion')) {
|
|
||||||
final String recoverySuggestion = errorProperties['recoverySuggestion'] as String;
|
|
||||||
errorMessage.write(' $recoverySuggestion');
|
|
||||||
}
|
|
||||||
|
|
||||||
final int code = _errorCode(errorProperties);
|
|
||||||
if (code != null) {
|
|
||||||
errorMessage.write(' (code $code)');
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorMessage.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of all devices reporting errors.
|
|
||||||
Future<List<String>> getDiagnostics() async {
|
|
||||||
final List<dynamic> allAvailableDevices = await _getAllDevices(
|
|
||||||
useCache: true,
|
|
||||||
timeout: const Duration(seconds: 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (allAvailableDevices == null) {
|
|
||||||
return const <String>[];
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<String> diagnostics = <String>[];
|
|
||||||
for (final dynamic device in allAvailableDevices) {
|
|
||||||
if (device is! Map) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final Map<String, dynamic> deviceProperties = device as Map<String, dynamic>;
|
|
||||||
final Map<String, dynamic> errorProperties = _errorProperties(deviceProperties);
|
|
||||||
final String errorMessage = _parseErrorMessage(errorProperties);
|
|
||||||
if (errorMessage != null) {
|
|
||||||
diagnostics.add(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return diagnostics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import 'package:flutter_tools/src/ios/ios_deploy.dart';
|
||||||
import 'package:flutter_tools/src/ios/ios_workflow.dart';
|
import 'package:flutter_tools/src/ios/ios_workflow.dart';
|
||||||
import 'package:flutter_tools/src/ios/iproxy.dart';
|
import 'package:flutter_tools/src/ios/iproxy.dart';
|
||||||
import 'package:flutter_tools/src/ios/mac.dart';
|
import 'package:flutter_tools/src/ios/mac.dart';
|
||||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
import 'package:flutter_tools/src/macos/xcdevice.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
import '../../src/common.dart';
|
import '../../src/common.dart';
|
||||||
|
|
|
@ -15,6 +15,7 @@ import 'package:flutter_tools/src/cache.dart';
|
||||||
import 'package:flutter_tools/src/ios/devices.dart';
|
import 'package:flutter_tools/src/ios/devices.dart';
|
||||||
import 'package:flutter_tools/src/ios/iproxy.dart';
|
import 'package:flutter_tools/src/ios/iproxy.dart';
|
||||||
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||||
|
import 'package:flutter_tools/src/macos/xcdevice.dart';
|
||||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||||
import 'package:mockito/mockito.dart';
|
import 'package:mockito/mockito.dart';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue