attach.dart 5.68 KB
Newer Older
1 2 3 4 5 6 7
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import '../base/common.dart';
8
import '../base/file_system.dart';
9 10
import '../base/io.dart';
import '../cache.dart';
11
import '../commands/daemon.dart';
12
import '../device.dart';
13
import '../globals.dart';
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
import '../protocol_discovery.dart';
import '../resident_runner.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';

final String ipv4Loopback = InternetAddress.loopbackIPv4.address;

/// A Flutter-command that attaches to applications that have been launched
/// without `flutter run`.
///
/// With an application already running, a HotRunner can be attached to it
/// with:
/// ```
/// $ flutter attach --debug-port 12345
/// ```
///
/// Alternatively, the attach command can start listening and scan for new
/// programs that become active:
/// ```
/// $ flutter attach
/// ```
/// As soon as a new observatory is detected the command attaches to it and
/// enables hot reloading.
class AttachCommand extends FlutterCommand {
38
  AttachCommand({bool verboseHelp = false, this.hotRunnerFactory}) {
39
    addBuildModeFlags(defaultToRelease: false);
40 41
    usesTargetOption();
    usesFilesystemOptions(hide: !verboseHelp);
42 43
    argParser
      ..addOption(
44 45
        'debug-port',
        help: 'Local port where the observatory is listening.',
46 47 48 49
      )..addOption(
        'project-root',
        hide: !verboseHelp,
        help: 'Normally used only in run target',
50 51 52
      )..addFlag('machine',
          hide: !verboseHelp,
          negatable: false,
53
          help: 'Handle machine structured JSON command input and provide output '
54 55
                'and progress in machine friendly format.',
      );
56
    hotRunnerFactory ??= HotRunnerFactory();
57 58
  }

59 60
  HotRunnerFactory hotRunnerFactory;

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
  @override
  final String name = 'attach';

  @override
  final String description = 'Attach to a running application.';

  int get observatoryPort {
    if (argResults['debug-port'] == null)
      return null;
    try {
      return int.parse(argResults['debug-port']);
    } catch (error) {
      throwToolExit('Invalid port for `--debug-port`: $error');
    }
    return null;
  }

78
  @override
79
  Future<void> validateCommand() async {
80
    await super.validateCommand();
81 82 83 84 85
    if (await findTargetDevice() == null)
      throwToolExit(null);
    observatoryPort;
  }

86
  @override
87
  Future<FlutterCommandResult> runCommand() async {
88 89 90 91 92 93
    Cache.releaseLockEarly();

    await _validateArguments();

    final Device device = await findTargetDevice();
    final int devicePort = observatoryPort;
94 95

    final Daemon daemon = argResults['machine']
96 97
      ? Daemon(stdinCommandStream, stdoutCommandResponse,
            notifyingLogger: NotifyingLogger(), logToStdout: true)
98 99
      : null;

100 101 102 103
    Uri observatoryUri;
    if (devicePort == null) {
      ProtocolDiscovery observatoryDiscovery;
      try {
104
        observatoryDiscovery = ProtocolDiscovery.observatory(
105 106 107 108
          device.getLogReader(),
          portForwarder: device.portForwarder,
        );
        printStatus('Waiting for a connection from Flutter on ${device.name}...');
109
        observatoryUri = await observatoryDiscovery.uri;
110
        printStatus('Done.');
111 112 113 114 115 116 117 118
      } finally {
        await observatoryDiscovery?.cancel();
      }
    } else {
      final int localPort = await device.portForwarder.forward(devicePort);
      observatoryUri = Uri.parse('http://$ipv4Loopback:$localPort/');
    }
    try {
119
      final FlutterDevice flutterDevice = FlutterDevice(
120 121 122 123 124 125
        device,
        trackWidgetCreation: false,
        dillOutputPath: argResults['output-dill'],
        fileSystemRoots: argResults['filesystem-root'],
        fileSystemScheme: argResults['filesystem-scheme'],
      );
126
      flutterDevice.observatoryUris = <Uri>[ observatoryUri ];
127
      final HotRunner hotRunner = hotRunnerFactory.build(
128
        <FlutterDevice>[flutterDevice],
129
        target: targetFile,
130
        debuggingOptions: DebuggingOptions.enabled(getBuildInfo()),
131
        packagesFilePath: globalResults['packages'],
132
        usesTerminalUI: daemon == null,
133 134
        projectRootPath: argResults['project-root'],
        dillOutputPath: argResults['output-dill'],
135
      );
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

      if (daemon != null) {
        AppInstance app;
        try {
          app = await daemon.appDomain.launch(hotRunner, hotRunner.attach,
              device, null, true, fs.currentDirectory);
        } catch (error) {
          throwToolExit(error.toString());
        }
        final int result = await app.runner.waitForAppToFinish();
        if (result != 0)
          throwToolExit(null, exitCode: result);
      } else {
        await hotRunner.attach();
      }
151
    } finally {
152 153
      final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
      ports.forEach(device.portForwarder.unforward);
154
    }
155
    return null;
156 157 158 159
  }

  Future<void> _validateArguments() async {}
}
160 161 162 163 164 165 166 167 168 169 170 171 172 173

class HotRunnerFactory {
  HotRunner build(List<FlutterDevice> devices, {
      String target,
      DebuggingOptions debuggingOptions,
      bool usesTerminalUI = true,
      bool benchmarkMode = false,
      File applicationBinary,
      bool hostIsIde = false,
      String projectRootPath,
      String packagesFilePath,
      String dillOutputPath,
      bool stayResident = true,
      bool ipv6 = false,
174
  }) => HotRunner(
175 176 177 178 179 180 181 182 183 184 185 186 187
    devices,
    target: target,
    debuggingOptions: debuggingOptions,
    usesTerminalUI: usesTerminalUI,
    benchmarkMode: benchmarkMode,
    applicationBinary: applicationBinary,
    hostIsIde: hostIsIde,
    projectRootPath: projectRootPath,
    packagesFilePath: packagesFilePath,
    dillOutputPath: dillOutputPath,
    stayResident: stayResident,
    ipv6: ipv6,
  );
188
}