"Fossies" - the Fresh Open Source Software Archive

Member "flutter-1.22.4/packages/flutter_tools/lib/src/commands/attach.dart" (13 Nov 2020, 15138 Bytes) of package /linux/misc/flutter-1.22.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Dart source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Copyright 2014 The Flutter Authors. All rights reserved.
    2 // Use of this source code is governed by a BSD-style license that can be
    3 // found in the LICENSE file.
    4 
    5 import 'dart:async';
    6 
    7 import 'package:meta/meta.dart';
    8 
    9 import '../android/android_device.dart';
   10 import '../artifacts.dart';
   11 import '../base/common.dart';
   12 import '../base/context.dart';
   13 import '../base/file_system.dart';
   14 import '../base/io.dart';
   15 import '../commands/daemon.dart';
   16 import '../compile.dart';
   17 import '../device.dart';
   18 import '../features.dart';
   19 import '../fuchsia/fuchsia_device.dart';
   20 import '../globals.dart' as globals;
   21 import '../ios/devices.dart';
   22 import '../ios/simulators.dart';
   23 import '../mdns_discovery.dart';
   24 import '../project.dart';
   25 import '../protocol_discovery.dart';
   26 import '../resident_runner.dart';
   27 import '../run_cold.dart';
   28 import '../run_hot.dart';
   29 import '../runner/flutter_command.dart';
   30 import '../widget_cache.dart';
   31 
   32 /// A Flutter-command that attaches to applications that have been launched
   33 /// without `flutter run`.
   34 ///
   35 /// With an application already running, a HotRunner can be attached to it
   36 /// with:
   37 /// ```
   38 /// $ flutter attach --debug-uri http://127.0.0.1:12345/QqL7EFEDNG0=/
   39 /// ```
   40 ///
   41 /// If `--disable-service-auth-codes` was provided to the application at startup
   42 /// time, a HotRunner can be attached with just a port:
   43 /// ```
   44 /// $ flutter attach --debug-port 12345
   45 /// ```
   46 ///
   47 /// Alternatively, the attach command can start listening and scan for new
   48 /// programs that become active:
   49 /// ```
   50 /// $ flutter attach
   51 /// ```
   52 /// As soon as a new observatory is detected the command attaches to it and
   53 /// enables hot reloading.
   54 ///
   55 /// To attach to a flutter mod running on a fuchsia device, `--module` must
   56 /// also be provided.
   57 class AttachCommand extends FlutterCommand {
   58   AttachCommand({bool verboseHelp = false, this.hotRunnerFactory}) {
   59     addBuildModeFlags(defaultToRelease: false);
   60     usesTargetOption();
   61     usesPortOptions();
   62     usesIpv6Flag();
   63     usesFilesystemOptions(hide: !verboseHelp);
   64     usesFuchsiaOptions(hide: !verboseHelp);
   65     usesDartDefineOption();
   66     usesDeviceUserOption();
   67     addEnableExperimentation(hide: !verboseHelp);
   68     addNullSafetyModeOptions(hide: !verboseHelp);
   69     argParser
   70       ..addOption(
   71         'debug-port',
   72         hide: !verboseHelp,
   73         help: 'Device port where the observatory is listening. Requires '
   74         '--disable-service-auth-codes to also be provided to the Flutter '
   75         'application at launch, otherwise this command will fail to connect to '
   76         'the application. In general, --debug-uri should be used instead.',
   77       )..addOption(
   78         'debug-uri',
   79         help: 'The URI at which the observatory is listening.',
   80       )..addOption(
   81         'app-id',
   82         help: 'The package name (Android) or bundle identifier (iOS) for the application. '
   83               'This can be specified to avoid being prompted if multiple observatory ports '
   84               'are advertised.\n'
   85               'If you have multiple devices or emulators running, you should include the '
   86               'device hostname as well, e.g. "com.example.myApp@my-iphone".\n'
   87               'This parameter is case-insensitive.',
   88       )..addOption(
   89         'pid-file',
   90         help: 'Specify a file to write the process id to. '
   91               'You can send SIGUSR1 to trigger a hot reload '
   92               'and SIGUSR2 to trigger a hot restart.',
   93       )..addOption(
   94         'project-root',
   95         hide: !verboseHelp,
   96         help: 'Normally used only in run target',
   97       )..addFlag('machine',
   98         hide: !verboseHelp,
   99         negatable: false,
  100         help: 'Handle machine structured JSON command input and provide output '
  101               'and progress in machine friendly format.',
  102       );
  103     usesTrackWidgetCreation(verboseHelp: verboseHelp);
  104     addDdsOptions(verboseHelp: verboseHelp);
  105     usesDeviceTimeoutOption();
  106     hotRunnerFactory ??= HotRunnerFactory();
  107   }
  108 
  109   HotRunnerFactory hotRunnerFactory;
  110 
  111   @override
  112   final String name = 'attach';
  113 
  114   @override
  115   final String description = '''Attach to a running application.
  116 
  117   For attaching to Android or iOS devices, simply using `flutter attach` is
  118   usually sufficient. The tool will search for a running Flutter app or module,
  119   if available. Otherwise, the tool will wait for the next Flutter app or module
  120   to launch before attaching.
  121 
  122   For Fuchsia, the module name must be provided, e.g. `\$flutter attach
  123   --module=mod_name`. This can be called either before or after the application
  124   is started.
  125 
  126   If the app or module is already running and the specific observatory port is
  127   known, it can be explicitly provided to attach via the command-line, e.g.
  128   `\$ flutter attach --debug-port 12345`''';
  129 
  130   int get debugPort {
  131     if (argResults['debug-port'] == null) {
  132       return null;
  133     }
  134     try {
  135       return int.parse(stringArg('debug-port'));
  136     } on Exception catch (error) {
  137       throwToolExit('Invalid port for `--debug-port`: $error');
  138     }
  139     return null;
  140   }
  141 
  142   Uri get debugUri {
  143     if (argResults['debug-uri'] == null) {
  144       return null;
  145     }
  146     final Uri uri = Uri.parse(stringArg('debug-uri'));
  147     if (!uri.hasPort) {
  148       throwToolExit('Port not specified for `--debug-uri`: $uri');
  149     }
  150     return uri;
  151   }
  152 
  153   String get appId {
  154     return stringArg('app-id');
  155   }
  156 
  157   String get userIdentifier => stringArg(FlutterOptions.kDeviceUser);
  158 
  159   @override
  160   Future<void> validateCommand() async {
  161     await super.validateCommand();
  162     if (await findTargetDevice() == null) {
  163       throwToolExit(null);
  164     }
  165     debugPort;
  166     if (debugPort == null && debugUri == null && argResults.wasParsed(FlutterCommand.ipv6Flag)) {
  167       throwToolExit(
  168         'When the --debug-port or --debug-uri is unknown, this command determines '
  169         'the value of --ipv6 on its own.',
  170       );
  171     }
  172     if (debugPort == null && debugUri == null && argResults.wasParsed(FlutterCommand.observatoryPortOption)) {
  173       throwToolExit(
  174         'When the --debug-port or --debug-uri is unknown, this command does not use '
  175         'the value of --observatory-port.',
  176       );
  177     }
  178     if (debugPort != null && debugUri != null) {
  179       throwToolExit(
  180         'Either --debugPort or --debugUri can be provided, not both.');
  181     }
  182 
  183     if (userIdentifier != null) {
  184       final Device device = await findTargetDevice();
  185       if (device is! AndroidDevice) {
  186         throwToolExit('--${FlutterOptions.kDeviceUser} is only supported for Android');
  187       }
  188     }
  189   }
  190 
  191   @override
  192   Future<FlutterCommandResult> runCommand() async {
  193     await _validateArguments();
  194 
  195     writePidFile(stringArg('pid-file'));
  196 
  197     final Device device = await findTargetDevice();
  198 
  199     final Artifacts overrideArtifacts = device.artifactOverrides ?? globals.artifacts;
  200     await context.run<void>(
  201       body: () => _attachToDevice(device),
  202       overrides: <Type, Generator>{
  203         Artifacts: () => overrideArtifacts,
  204     });
  205 
  206     return FlutterCommandResult.success();
  207   }
  208 
  209   Future<void> _attachToDevice(Device device) async {
  210     final FlutterProject flutterProject = FlutterProject.current();
  211     Future<int> getDevicePort() async {
  212       if (debugPort != null) {
  213         return debugPort;
  214       }
  215       // This call takes a non-trivial amount of time, and only iOS devices and
  216       // simulators support it.
  217       // If/when we do this on Android or other platforms, we can update it here.
  218       if (device is IOSDevice || device is IOSSimulator) {
  219       }
  220       return null;
  221     }
  222     final int devicePort = await getDevicePort();
  223 
  224     final Daemon daemon = boolArg('machine')
  225       ? Daemon(
  226           stdinCommandStream,
  227           stdoutCommandResponse,
  228           notifyingLogger: (globals.logger is NotifyingLogger)
  229             ? globals.logger as NotifyingLogger
  230             : NotifyingLogger(verbose: globals.logger.isVerbose, parent: globals.logger),
  231           logToStdout: true,
  232         )
  233       : null;
  234 
  235     Stream<Uri> observatoryUri;
  236     bool usesIpv6 = ipv6;
  237     final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
  238     final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
  239     final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
  240 
  241     if (devicePort == null && debugUri == null) {
  242       if (device is FuchsiaDevice) {
  243         final String module = stringArg('module');
  244         if (module == null) {
  245           throwToolExit("'--module' is required for attaching to a Fuchsia device");
  246         }
  247         usesIpv6 = device.ipv6;
  248         FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol;
  249         try {
  250           isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module);
  251           observatoryUri = Stream<Uri>.value(await isolateDiscoveryProtocol.uri).asBroadcastStream();
  252         } on Exception {
  253           isolateDiscoveryProtocol?.dispose();
  254           final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
  255           for (final ForwardedPort port in ports) {
  256             await device.portForwarder.unforward(port);
  257           }
  258           rethrow;
  259         }
  260       } else if ((device is IOSDevice) || (device is IOSSimulator)) {
  261         final Uri uriFromMdns =
  262           await MDnsObservatoryDiscovery.instance.getObservatoryUri(
  263             appId,
  264             device,
  265             usesIpv6: usesIpv6,
  266             deviceVmservicePort: deviceVmservicePort,
  267           );
  268         observatoryUri = uriFromMdns == null
  269           ? null
  270           : Stream<Uri>.value(uriFromMdns).asBroadcastStream();
  271       }
  272       // If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
  273       if (observatoryUri == null) {
  274         final ProtocolDiscovery observatoryDiscovery =
  275           ProtocolDiscovery.observatory(
  276             // If it's an Android device, attaching relies on past log searching
  277             // to find the service protocol.
  278             await device.getLogReader(includePastLogs: device is AndroidDevice),
  279             portForwarder: device.portForwarder,
  280             ipv6: ipv6,
  281             devicePort: deviceVmservicePort,
  282             hostPort: hostVmservicePort,
  283           );
  284         globals.printStatus('Waiting for a connection from Flutter on ${device.name}...');
  285         observatoryUri = observatoryDiscovery.uris;
  286         // Determine ipv6 status from the scanned logs.
  287         usesIpv6 = observatoryDiscovery.ipv6;
  288       }
  289     } else {
  290       observatoryUri = Stream<Uri>
  291         .fromFuture(
  292           buildObservatoryUri(
  293             device,
  294             debugUri?.host ?? hostname,
  295             devicePort ?? debugUri.port,
  296             hostVmservicePort,
  297             debugUri?.path,
  298           )
  299         ).asBroadcastStream();
  300     }
  301 
  302     globals.terminal.usesTerminalUi = daemon == null;
  303 
  304     try {
  305       int result;
  306       if (daemon != null) {
  307         final ResidentRunner runner = await createResidentRunner(
  308           observatoryUris: observatoryUri,
  309           device: device,
  310           flutterProject: flutterProject,
  311           usesIpv6: usesIpv6,
  312         );
  313         AppInstance app;
  314         try {
  315           app = await daemon.appDomain.launch(
  316             runner,
  317             runner.attach,
  318             device,
  319             null,
  320             true,
  321             globals.fs.currentDirectory,
  322             LaunchMode.attach,
  323             globals.logger as AppRunLogger,
  324           );
  325         } on Exception catch (error) {
  326           throwToolExit(error.toString());
  327         }
  328         result = await app.runner.waitForAppToFinish();
  329         assert(result != null);
  330         return;
  331       }
  332       while (true) {
  333         final ResidentRunner runner = await createResidentRunner(
  334           observatoryUris: observatoryUri,
  335           device: device,
  336           flutterProject: flutterProject,
  337           usesIpv6: usesIpv6,
  338         );
  339         final Completer<void> onAppStart = Completer<void>.sync();
  340         TerminalHandler terminalHandler;
  341         unawaited(onAppStart.future.whenComplete(() {
  342           terminalHandler = TerminalHandler(runner)
  343             ..setupTerminal()
  344             ..registerSignalHandlers();
  345         }));
  346         result = await runner.attach(
  347           appStartedCompleter: onAppStart,
  348         );
  349         if (result != 0) {
  350           throwToolExit(null, exitCode: result);
  351         }
  352         terminalHandler?.stop();
  353         assert(result != null);
  354         if (runner.exited || !runner.isWaitingForObservatory) {
  355           break;
  356         }
  357         globals.printStatus('Waiting for a new connection from Flutter on ${device.name}...');
  358       }
  359     } finally {
  360       final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
  361       for (final ForwardedPort port in ports) {
  362         await device.portForwarder.unforward(port);
  363       }
  364     }
  365   }
  366 
  367   Future<ResidentRunner> createResidentRunner({
  368     @required Stream<Uri> observatoryUris,
  369     @required Device device,
  370     @required FlutterProject flutterProject,
  371     @required bool usesIpv6,
  372   }) async {
  373     assert(observatoryUris != null);
  374     assert(device != null);
  375     assert(flutterProject != null);
  376     assert(usesIpv6 != null);
  377 
  378     final FlutterDevice flutterDevice = await FlutterDevice.create(
  379       device,
  380       flutterProject: flutterProject,
  381       fileSystemRoots: stringsArg('filesystem-root'),
  382       fileSystemScheme: stringArg('filesystem-scheme'),
  383       target: stringArg('target'),
  384       targetModel: TargetModel(stringArg('target-model')),
  385       buildInfo: getBuildInfo(),
  386       userIdentifier: userIdentifier,
  387       widgetCache: WidgetCache(featureFlags: featureFlags),
  388     );
  389     flutterDevice.observatoryUris = observatoryUris;
  390     final List<FlutterDevice> flutterDevices =  <FlutterDevice>[flutterDevice];
  391     final DebuggingOptions debuggingOptions = DebuggingOptions.enabled(getBuildInfo(), disableDds: boolArg('disable-dds'));
  392 
  393     return getBuildInfo().isDebug
  394       ? hotRunnerFactory.build(
  395           flutterDevices,
  396           target: targetFile,
  397           debuggingOptions: debuggingOptions,
  398           packagesFilePath: globalResults['packages'] as String,
  399           projectRootPath: stringArg('project-root'),
  400           dillOutputPath: stringArg('output-dill'),
  401           ipv6: usesIpv6,
  402           flutterProject: flutterProject,
  403         )
  404       : ColdRunner(
  405           flutterDevices,
  406           target: targetFile,
  407           debuggingOptions: debuggingOptions,
  408           ipv6: usesIpv6,
  409         );
  410   }
  411 
  412   Future<void> _validateArguments() async { }
  413 }
  414 
  415 class HotRunnerFactory {
  416   HotRunner build(
  417     List<FlutterDevice> devices, {
  418     String target,
  419     DebuggingOptions debuggingOptions,
  420     bool benchmarkMode = false,
  421     File applicationBinary,
  422     bool hostIsIde = false,
  423     String projectRootPath,
  424     String packagesFilePath,
  425     String dillOutputPath,
  426     bool stayResident = true,
  427     bool ipv6 = false,
  428     FlutterProject flutterProject,
  429   }) => HotRunner(
  430     devices,
  431     target: target,
  432     debuggingOptions: debuggingOptions,
  433     benchmarkMode: benchmarkMode,
  434     applicationBinary: applicationBinary,
  435     hostIsIde: hostIsIde,
  436     projectRootPath: projectRootPath,
  437     dillOutputPath: dillOutputPath,
  438     stayResident: stayResident,
  439     ipv6: ipv6,
  440   );
  441 }