Enable launching applications on the iOS device with observatory and diagnostics server connected. (#4424)
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index 775473f..e23e27e 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -307,10 +307,12 @@
}
class ForwardedPort {
- ForwardedPort(this.hostPort, this.devicePort);
+ ForwardedPort(this.hostPort, this.devicePort) : context = null;
+ ForwardedPort.withContext(this.hostPort, this.devicePort, this.context);
final int hostPort;
final int devicePort;
+ final dynamic context;
@override
String toString() => 'ForwardedPort HOST:$hostPort to DEVICE:$devicePort';
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index 603ebc1..591eb9e 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -12,6 +12,8 @@
import '../build_info.dart';
import '../device.dart';
import '../globals.dart';
+import '../observatory.dart';
+import '../protocol_discovery.dart';
import 'mac.dart';
const String _ideviceinstallerInstructions =
@@ -33,6 +35,7 @@
_installerPath = _checkForCommand('ideviceinstaller');
_listerPath = _checkForCommand('idevice_id');
_informerPath = _checkForCommand('ideviceinfo');
+ _iproxyPath = _checkForCommand('iproxy');
_debuggerPath = _checkForCommand('idevicedebug');
_loggerPath = _checkForCommand('idevicesyslog');
_pusherPath = _checkForCommand(
@@ -52,6 +55,9 @@
String _informerPath;
String get informerPath => _informerPath;
+ String _iproxyPath;
+ String get iproxyPath => _iproxyPath;
+
String _debuggerPath;
String get debuggerPath => _debuggerPath;
@@ -194,7 +200,20 @@
}
// Step 3: Attempt to install the application on the device.
- int installationResult = await runCommandAndStreamOutput(<String>[
+ List<String> launchArguments = <String>[];
+
+ if (debuggingOptions.startPaused)
+ launchArguments.add("--start-paused");
+
+ if (debuggingOptions.debuggingEnabled) {
+ launchArguments.add("--enable-checked-mode");
+
+ // Note: We do NOT need to set the observatory port since this is going to
+ // be setup on the device. Let it pick a port automatically. We will check
+ // the port picked and scrape that later.
+ }
+
+ List<String> launchCommand = <String>[
'/usr/bin/env',
'ios-deploy',
'--id',
@@ -202,14 +221,109 @@
'--bundle',
bundle.path,
'--justlaunch',
- ], trace: true);
+ ];
+
+ if (launchArguments.length > 0) {
+ launchCommand.add('--args');
+ launchCommand.add('"${launchArguments.join(" ")}"');
+ }
+
+ int installationResult = -1;
+ int localObsPort;
+ int localDiagPort;
+
+ if (!debuggingOptions.debuggingEnabled || mode == BuildMode.release) {
+ // If debugging is not enabled, just launch the application and continue.
+ printTrace("Debugging is not enabled");
+ installationResult = await runCommandAndStreamOutput(launchCommand, trace: true);
+ } else {
+ // Debugging is enabled, look for the observatory and diagnostic server
+ // ports post launch.
+ printTrace("Debugging is enabled, connecting to observatory and the diagnostic server");
+
+ Future<int> launch = runCommandAndStreamOutput(launchCommand, trace: true);
+
+ List<int> ports = await launch.then((int result) async {
+ installationResult = result;
+
+ if (result != 0) {
+ printTrace("Failed to launch the application on device.");
+ return <int>[null, null];
+ }
+
+ printTrace("Application launched on the device. Attempting to forward ports.");
+
+ return Future.wait(<Future<int>>[
+ _acquireAndForwardPort(ProtocolDiscovery.kObservatoryService, debuggingOptions.observatoryPort),
+ _acquireAndForwardPort(ProtocolDiscovery.kDiagnosticService, debuggingOptions.diagnosticPort),
+ ]);
+
+ });
+
+ printTrace("Local Observatory Port: ${ports[0]}");
+ printTrace("Local Diagnostic Server Port: ${ports[1]}");
+
+ localObsPort = ports[0];
+ localDiagPort = ports[1];
+ }
if (installationResult != 0) {
printError('Could not install ${bundle.path} on $id.');
return new LaunchResult.failed();
}
- return new LaunchResult.succeeded();
+ return new LaunchResult.succeeded(observatoryPort: localObsPort, diagnosticPort: localDiagPort);
+ }
+
+ Future<int> _acquireAndForwardPort(String serviceName, int localPort) async {
+ Duration stepTimeout = const Duration(seconds: 10);
+
+ Future<int> remote = new ProtocolDiscovery(logReader, serviceName).nextPort();
+
+ int remotePort = await remote.timeout(stepTimeout,
+ onTimeout: () {
+ printTrace("Timeout while attempting to retrieve remote port for $serviceName");
+ return null;
+ });
+
+ if (remotePort == null) {
+ printTrace("Could not read port on device for $serviceName");
+ return null;
+ }
+
+ if ((localPort == null) || (localPort == 0)) {
+ localPort = await findAvailablePort();
+ printTrace("Auto selected local port to $localPort");
+ }
+
+ int forwardResult = await portForwarder.forward(remotePort,
+ hostPort: localPort).timeout(stepTimeout, onTimeout: () {
+ printTrace("Timeout while atempting to foward port for $serviceName");
+ return null;
+ });
+
+ if (forwardResult == null) {
+ printTrace("Could not foward remote $serviceName port $remotePort to local port $localPort");
+ return null;
+ }
+
+ printTrace("Successfully forwarded remote $serviceName port $remotePort to $localPort.");
+ return localPort;
+ }
+
+ @override
+ Future<bool> restartApp(
+ ApplicationPackage package,
+ LaunchResult result, {
+ String mainPath,
+ Observatory observatory
+ }) async {
+ return observatory.isolateReload(observatory.firstIsolateId).then((Response response) {
+ return true;
+ }).catchError((dynamic error) {
+ printError('Error restarting app: $error');
+ return false;
+ });
}
@override
@@ -306,11 +420,16 @@
});
}
- static final RegExp _runnerRegex = new RegExp(r'FlutterRunner');
+ // Match for lines like "Runner[297] <Notice>: " in syslog.
+ static final RegExp _runnerRegex = new RegExp(r'Runner\[[\d]+\] <[A-Za-z]+>: ');
void _onLine(String line) {
- if (_runnerRegex.hasMatch(line))
- _linesController.add(line);
+ Match match = _runnerRegex.firstMatch(line);
+
+ if (match != null) {
+ // Only display the log line after the initial device and executable information.
+ _linesController.add(line.substring(match.end));
+ }
}
void _stop() {
@@ -319,16 +438,14 @@
}
class _IOSDevicePortForwarder extends DevicePortForwarder {
- _IOSDevicePortForwarder(this.device);
+ _IOSDevicePortForwarder(this.device) : _forwardedPorts = new List<ForwardedPort>();
final IOSDevice device;
+ final List<ForwardedPort> _forwardedPorts;
+
@override
- List<ForwardedPort> get forwardedPorts {
- final List<ForwardedPort> ports = <ForwardedPort>[];
- // TODO(chinmaygarde): Implement.
- return ports;
- }
+ List<ForwardedPort> get forwardedPorts => _forwardedPorts;
@override
Future<int> forward(int devicePort, {int hostPort: null}) async {
@@ -336,12 +453,42 @@
// Auto select host port.
hostPort = await findAvailablePort();
}
- // TODO(chinmaygarde): Implement.
- return hostPort;
+
+ // Usage: iproxy LOCAL_TCP_PORT DEVICE_TCP_PORT UDID
+ Process process = await runCommand(<String>[
+ device.iproxyPath,
+ hostPort.toString(),
+ devicePort.toString(),
+ device.id,
+ ]);
+
+ ForwardedPort forwardedPort = new ForwardedPort.withContext(hostPort,
+ devicePort, process);
+
+ printTrace("Forwarded port $forwardedPort");
+
+ _forwardedPorts.add(forwardedPort);
+
+ return 1;
}
@override
Future<Null> unforward(ForwardedPort forwardedPort) async {
- // TODO(chinmaygarde): Implement.
+ if (!_forwardedPorts.remove(forwardedPort)) {
+ // Not in list. Nothing to remove.
+ return null;
+ }
+
+ printTrace("Unforwarding port $forwardedPort");
+
+ Process process = forwardedPort.context;
+
+ if (process != null) {
+ Process.killPid(process.pid);
+ } else {
+ printError("Forwarded port did not have a valid process");
+ }
+
+ return null;
}
}