Sitelet https://github.com/flutter/flutter/pull/118104/commits/8a607d32fba21c7d83fecd330f18a1ab7334dfce
Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
setup wireless debugging to use device IP
  • Loading branch information
vashworth committed Jan 5, 2023
commit 8a607d32fba21c7d83fecd330f18a1ab7334dfce
3 changes: 3 additions & 0 deletions packages/flutter_tools/lib/src/commands/attach.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import '../device.dart';
import '../device_port_forwarder.dart';
import '../fuchsia/fuchsia_device.dart';
import '../ios/devices.dart';
import '../ios/iproxy.dart';
import '../ios/simulators.dart';
import '../macos/macos_ipad_device.dart';
import '../mdns_discovery.dart';
Expand Down Expand Up @@ -303,12 +304,14 @@ known, it can be explicitly provided to attach via the command-line, e.g.
rethrow;
}
} else if ((device is IOSDevice) || (device is IOSSimulator) || (device is MacOSDesignedForIPadDevice)) {
final bool isNetworkDevice = (device is IOSDevice) && device.interfaceType == IOSDeviceConnectionInterface.network;
final Uri? uriFromMdns =
await MDnsObservatoryDiscovery.instance!.getObservatoryUri(
appId,
device,
usesIpv6: usesIpv6,
deviceVmservicePort: deviceVmservicePort,
isNetworkDevice: isNetworkDevice,
);
observatoryUri = uriFromMdns == null
? null
Expand Down
12 changes: 11 additions & 1 deletion packages/flutter_tools/lib/src/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'base/utils.dart';
import 'build_info.dart';
import 'devfs.dart';
import 'device_port_forwarder.dart';
import 'ios/iproxy.dart';
import 'project.dart';
import 'vmservice.dart';

Expand Down Expand Up @@ -917,7 +918,13 @@ class DebuggingOptions {
/// * https://github.com/dart-lang/sdk/blob/main/sdk/lib/html/doc/NATIVE_NULL_ASSERTIONS.md
final bool nativeNullAssertions;

List<String> getIOSLaunchArguments(EnvironmentType environmentType, String? route, Map<String, Object?> platformArgs) {
List<String> getIOSLaunchArguments(
EnvironmentType environmentType,
String? route,
Map<String, Object?> platformArgs, {
bool ipv6 = false,
IOSDeviceConnectionInterface interfaceType = IOSDeviceConnectionInterface.none
}) {
final String dartVmFlags = computeDartVmFlags(this);
return <String>[
if (enableDartProfiling) '--enable-dart-profiling',
Expand Down Expand Up @@ -954,6 +961,9 @@ class DebuggingOptions {
// Use the suggested host port.
if (environmentType == EnvironmentType.simulator && hostVmServicePort != null)
'--observatory-port=$hostVmServicePort',
// Tell the observatory to listen on all interfaces, don't restrict to the loopback.
if (interfaceType == IOSDeviceConnectionInterface.network)
'--observatory-host=${ipv6 ? '::0' : '0.0.0.0'}',
];
}

Expand Down
49 changes: 35 additions & 14 deletions packages/flutter_tools/lib/src/ios/devices.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import '../device.dart';
import '../device_port_forwarder.dart';
import '../globals.dart' as globals;
import '../macos/xcdevice.dart';
import '../mdns_discovery.dart';
import '../project.dart';
import '../protocol_discovery.dart';
import '../vmservice.dart';
Expand Down Expand Up @@ -189,15 +190,6 @@ class IOSDevice extends Device {
return majorVersionString != null ? int.tryParse(majorVersionString) ?? 0 : 0;
}

@override
bool get supportsHotReload => interfaceType == IOSDeviceConnectionInterface.usb;

@override
bool get supportsHotRestart => interfaceType == IOSDeviceConnectionInterface.usb;

@override
bool get supportsFlutterExit => interfaceType == IOSDeviceConnectionInterface.usb;

@override
final String name;

Expand Down Expand Up @@ -318,7 +310,12 @@ class IOSDevice extends Device {
@visibleForTesting Duration? discoveryTimeout,
}) async {
String? packageId;

if (interfaceType == IOSDeviceConnectionInterface.network &&
debuggingOptions.debuggingEnabled &&
debuggingOptions.disablePortPublication) {
_logger.printError('Port publication (publish-port) must be enabled for wireless debugging.');
return LaunchResult.failed();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Messages like this need to be actionable by the user. What are they supposed to do in this case to fix it? When we have actionable errors like this we should throwToolExit.

Something like:

Suggested change
_logger.printError('Port publication (publish-port) must be enabled for wireless debugging.');
return LaunchResult.failed();
throwToolExit('Cannot start app on wirelessly tethered iOS device. Try running again with the --publish-port flag');

Introduced in #67452 to prevent the pop-up from blocking the app in a CI setting.

Maybe drive should default to always publishing, and we update our CI scripts to pass --no-publish-port so users manually running tests won't hit this? wdyt?

@vashworth vashworth Jan 9, 2023 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the error message.

And thanks for the reminder about drive, I misunderstood the first time we talked about it. I tried a different approach, so basically added an override for disablePortPublication that checks if the flag wasn't provided as an actual argument and it's a network device, then changes publish-port to be enabled. It's a little bit hacky, but allows drive to continue functioning as it was before for all wired devices without needing to update anything. Let me know if you think that's not a good solution.

}
if (!prebuiltApplication) {
_logger.printTrace('Building ${package.name} for $id');

Expand Down Expand Up @@ -353,6 +350,8 @@ class IOSDevice extends Device {
EnvironmentType.physical,
route,
platformArgs,
ipv6: ipv6,
interfaceType: interfaceType,
);
final Status installStatus = _logger.startProgress(
'Installing and launching...',
Expand All @@ -379,9 +378,10 @@ class IOSDevice extends Device {
deviceLogReader.debuggerStream = iosDeployDebugger;
}
}
// Don't port foward if debugging with a network device.
observatoryDiscovery = ProtocolDiscovery.observatory(
deviceLogReader,
portForwarder: portForwarder,
portForwarder: interfaceType == IOSDeviceConnectionInterface.network ? null : portForwarder,
hostPort: debuggingOptions.hostVmServicePort,
devicePort: debuggingOptions.deviceVmServicePort,
ipv6: ipv6,
Expand Down Expand Up @@ -413,11 +413,32 @@ class IOSDevice extends Device {
}

_logger.printTrace('Application launched on the device. Waiting for observatory url.');
final Timer timer = Timer(discoveryTimeout ?? const Duration(seconds: 30), () {
_logger.printError('iOS Observatory not discovered after 30 seconds. This is taking much longer than expected...');
final int defaultTimeout = interfaceType == IOSDeviceConnectionInterface.network ? 60 : 30;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that this took longer when using a network device. 60 seconds might be a bit generous, though. Sometimes 30 seconds is enough, but sometimes it's more like 35. Not sure if people's internet connections might influence this (mine is pretty fast), which is why I overestimated.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow that's a long time. Maybe 45 and we'll see if we get complaints?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. Updated.

final Timer timer = Timer(discoveryTimeout ?? Duration(seconds: defaultTimeout), () {
_logger.printError('iOS Observatory not discovered after ${discoveryTimeout ?? defaultTimeout} seconds. This is taking much longer than expected...');
iosDeployDebugger?.pauseDumpBacktraceResume();
});
final Uri? localUri = await observatoryDiscovery?.uri;

Uri? localUri;
if (interfaceType == IOSDeviceConnectionInterface.network) {
try {
// Wait for iOS Observatory to start up.
await observatoryDiscovery?.uri;

// Get Observatory URL with the device IP.
localUri = await MDnsObservatoryDiscovery.instance!.getObservatoryUri(
package.id,
this,
usesIpv6: ipv6,
deviceVmservicePort: debuggingOptions.deviceVmServicePort,
isNetworkDevice: true,
);
} on StateError {
_logger.printError('iOS Observatory not discovered before stream was closed.');
}
} else {
localUri = await observatoryDiscovery?.uri;
}
timer.cancel();
if (localUri == null) {
await iosDeployDebugger?.stopAndDumpBacktrace();
Expand Down
18 changes: 10 additions & 8 deletions packages/flutter_tools/lib/src/macos/xcdevice.dart
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,16 @@ class XCDevice {

/// [timeout] defaults to 2 seconds.
Future<List<IOSDevice>> getAvailableIOSDevices({ Duration? timeout }) async {
Status? loadDevicesStatus;
if (timeout != null && timeout.inSeconds > 2) {
loadDevicesStatus = _logger.startProgress(
'Loading devices...',
);
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change just gives some visual feedback that it's loading the devices.

final List<Object>? allAvailableDevices = await _getAllDevices(timeout: timeout ?? const Duration(seconds: 2));

if (loadDevicesStatus != null) {
loadDevicesStatus.stop();
}
if (allAvailableDevices == null) {
return const <IOSDevice>[];
}
Expand Down Expand Up @@ -305,12 +313,6 @@ class XCDevice {

final IOSDeviceConnectionInterface interface = _interfaceType(device);

// 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 != IOSDeviceConnectionInterface.usb) {
continue;
}

String? sdkVersion = _sdkVersion(device);

if (sdkVersion != null) {
Expand All @@ -324,7 +326,7 @@ class XCDevice {
identifier,
name: name,
cpuArchitecture: _cpuArchitecture(device),
interfaceType: interface,
interfaceType: _interfaceType(device),
sdkVersion: sdkVersion,
iProxy: _iProxy,
fileSystem: globals.fs,
Expand Down
58 changes: 52 additions & 6 deletions packages/flutter_tools/lib/src/mdns_discovery.dart
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,12 @@ class MDnsObservatoryDiscovery {
/// it will return that instance's information regardless of what application
/// the Observatory instance is for.
@visibleForTesting
Future<MDnsObservatoryDiscoveryResult?> query({String? applicationId, int? deviceVmservicePort}) async {
Future<MDnsObservatoryDiscoveryResult?> query({
String? applicationId,
int? deviceVmservicePort,
bool ipv6 = false,
bool isNetworkDevice = false
}) async {
_logger.printTrace('Checking for advertised Dart observatories...');
try {
await _client.start();
Expand Down Expand Up @@ -113,6 +118,31 @@ class MDnsObservatoryDiscovery {
_logger.printWarning('Unexpectedly found more than one observatory report for $domainName '
'- using first one (${srv.first.port}).');
}

// Get the IP address of the service if using a network device.
InternetAddress? ipAddress;
if (isNetworkDevice) {
List<IPAddressResourceRecord> ipAddresses = await _client
.lookup<IPAddressResourceRecord>(
ipv6 ? ResourceRecordQuery.addressIPv6(srv.first.target) : ResourceRecordQuery.addressIPv4(srv.first.target),
)
.toList();
if (ipAddresses.isEmpty) {
throwToolExit('Did not find IP for service ${srv.first.target}.');
}

// Filter out link-local addresses.
if (ipAddresses.length > 1) {
ipAddresses = ipAddresses.where((IPAddressResourceRecord element) => element.address.isLinkLocal == false).toList();
Comment thread
vashworth marked this conversation as resolved.
Outdated
}

if (ipAddresses.length > 1) {
_logger.printWarning('Unexpectedly found more than one IP for observatory for service ${srv.first.target} '
'- using first one (${ipAddresses.first.address}).');
}
ipAddress = ipAddresses.first.address;
}

_logger.printTrace('Checking for authentication code for $domainName');
final List<TxtResourceRecord> txt = await _client
.lookup<TxtResourceRecord>(
Expand All @@ -139,7 +169,7 @@ class MDnsObservatoryDiscovery {
if (!authCode.endsWith('/')) {
authCode += '/';
}
return MDnsObservatoryDiscoveryResult(srv.first.port, authCode);
return MDnsObservatoryDiscoveryResult(srv.first.port, authCode, ipAddress: ipAddress);
} finally {
_client.stop();
}
Expand All @@ -149,25 +179,33 @@ class MDnsObservatoryDiscovery {
bool usesIpv6 = false,
int? hostVmservicePort,
int? deviceVmservicePort,
bool isNetworkDevice = false,
}) async {
final MDnsObservatoryDiscoveryResult? result = await query(
applicationId: applicationId,
deviceVmservicePort: deviceVmservicePort,
ipv6: usesIpv6,
isNetworkDevice: isNetworkDevice,
);
if (result == null) {
await _checkForIPv4LinkLocal(device);
return null;
}

final String host = usesIpv6
final String host;
if (isNetworkDevice && result.ipAddress != null) {
host = result.ipAddress!.address;
Comment thread
vashworth marked this conversation as resolved.
Outdated
} else {
host = usesIpv6
? InternetAddress.loopbackIPv6.address
: InternetAddress.loopbackIPv4.address;
}
return buildObservatoryUri(
device,
host,
result.port,
hostVmservicePort,
result.authCode,
isNetworkDevice,
);
}

Expand Down Expand Up @@ -237,9 +275,10 @@ class MDnsObservatoryDiscovery {
}

class MDnsObservatoryDiscoveryResult {
MDnsObservatoryDiscoveryResult(this.port, this.authCode);
MDnsObservatoryDiscoveryResult(this.port, this.authCode, {this.ipAddress});
final int port;
final String authCode;
final InternetAddress? ipAddress;
}

Future<Uri> buildObservatoryUri(
Expand All @@ -248,6 +287,7 @@ Future<Uri> buildObservatoryUri(
int devicePort, [
int? hostVmservicePort,
String? authCode,
bool isNetworkDevice = false,
]) async {
String path = '/';
if (authCode != null) {
Expand All @@ -259,8 +299,14 @@ Future<Uri> buildObservatoryUri(
path += '/';
}
hostVmservicePort ??= 0;
final int? actualHostPort = hostVmservicePort == 0 ?

final int? actualHostPort;
if (isNetworkDevice) {
Comment thread
vashworth marked this conversation as resolved.
actualHostPort = devicePort;
} else {
actualHostPort = hostVmservicePort == 0 ?
await device.portForwarder?.forward(devicePort) :
hostVmservicePort;
}
return Uri(scheme: 'http', host: host, port: actualHostPort, path: path);
}
48 changes: 48 additions & 0 deletions packages/flutter_tools/test/general.shard/device_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/ios/iproxy.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';

Expand Down Expand Up @@ -554,6 +555,53 @@ void main() {
);
});

testWithoutContext('Get launch arguments for physical device with iPv4 network connection', () {
final DebuggingOptions original = DebuggingOptions.enabled(
BuildInfo.debug,
);

final List<String> launchArguments = original.getIOSLaunchArguments(
EnvironmentType.physical,
null,
<String, Object?>{},
interfaceType: IOSDeviceConnectionInterface.network,
);

expect(
launchArguments.join(' '),
<String>[
'--enable-dart-profiling',
'--enable-checked-mode',
'--verify-entry-points',
'--observatory-host=0.0.0.0',
].join(' '),
);
});

testWithoutContext('Get launch arguments for physical device with iPv6 network connection', () {
final DebuggingOptions original = DebuggingOptions.enabled(
BuildInfo.debug,
);

final List<String> launchArguments = original.getIOSLaunchArguments(
EnvironmentType.physical,
null,
<String, Object?>{},
ipv6: true,
interfaceType: IOSDeviceConnectionInterface.network,
);

expect(
launchArguments.join(' '),
<String>[
'--enable-dart-profiling',
'--enable-checked-mode',
'--verify-entry-points',
'--observatory-host=::0',
].join(' '),
);
});

testWithoutContext('Get launch arguments for physical device with debugging disabled with available launch arguments', () {
final DebuggingOptions original = DebuggingOptions.disabled(
BuildInfo.debug,
Expand Down
Loading