devtools_launcher.dart 3.36 KB
Newer Older
1 2 3 4
// 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.

5

6

7 8 9
import 'dart:async';

import 'package:meta/meta.dart';
10
import 'package:process/process.dart';
11

12 13
import 'base/bot_detector.dart';
import 'base/common.dart';
14 15 16 17 18
import 'base/io.dart' as io;
import 'base/logger.dart';
import 'convert.dart';
import 'resident_runner.dart';

19 20
/// An implementation of the devtools launcher that uses `pub global activate` to
/// start a server instance.
21 22
class DevtoolsServerLauncher extends DevtoolsLauncher {
  DevtoolsServerLauncher({
23 24
    required ProcessManager processManager,
    required String dartExecutable,
25
    required Logger logger,
26
    required BotDetector botDetector,
27
  })  : _processManager = processManager,
28
        _dartExecutable = dartExecutable,
29 30
        _logger = logger,
        _botDetector = botDetector;
31 32

  final ProcessManager _processManager;
33
  final String _dartExecutable;
34
  final Logger _logger;
35
  final BotDetector _botDetector;
36
  final Completer<void> _processStartCompleter = Completer<void>();
37

38
  io.Process? _devToolsProcess;
39 40
  bool _devToolsProcessKilled = false;
  @visibleForTesting
41
  Future<void>? devToolsProcessExit;
42 43

  static final RegExp _serveDevToolsPattern =
44 45
      RegExp(r'Serving DevTools at ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+?)\.?$');

46
  @override
47 48 49
  Future<void> get processStart => _processStartCompleter.future;

  @override
50
  Future<void> launch(Uri? vmServiceUri, {List<String>? additionalArguments}) async {
51
    // Place this entire method in a try/catch that swallows exceptions because
52
    // this method is guaranteed not to return a Future that throws.
53
    try {
54
      _devToolsProcess = await _processManager.start(<String>[
55
        _dartExecutable,
56
        'devtools',
57
        '--no-launch-browser',
58
        if (vmServiceUri != null) '--vm-uri=$vmServiceUri',
59
        ...?additionalArguments,
60
      ]);
61
      _processStartCompleter.complete();
62
      final Completer<Uri> completer = Completer<Uri>();
63
      _devToolsProcess!.stdout
64 65 66
          .transform(utf8.decoder)
          .transform(const LineSplitter())
          .listen((String line) {
67
            final Match? match = _serveDevToolsPattern.firstMatch(line);
68
            if (match != null) {
69
              final String url = match[1]!;
70
              completer.complete(Uri.parse(url));
71 72
            }
         });
73
      _devToolsProcess!.stderr
74 75
          .transform(utf8.decoder)
          .transform(const LineSplitter())
76
          .listen(_logger.printError);
77 78

      final bool runningOnBot = await _botDetector.isRunningOnBot;
79
      devToolsProcessExit = _devToolsProcess!.exitCode.then(
80 81 82 83 84 85 86
        (int exitCode) {
          if (!_devToolsProcessKilled && runningOnBot) {
            throwToolExit('DevTools process failed: exitCode=$exitCode');
          }
        }
      );

87
      devToolsUrl = await completer.future;
88
    } on Exception catch (e, st) {
89
      _logger.printError('Failed to launch DevTools: $e', stackTrace: st);
90 91 92
    }
  }

93
  @override
94
  Future<DevToolsServerAddress?> serve() async {
95 96 97
    if (activeDevToolsServer == null) {
      await launch(null);
    }
98
    return activeDevToolsServer;
99 100 101 102
  }

  @override
  Future<void> close() async {
103 104 105
    if (devToolsUrl != null) {
      devToolsUrl = null;
    }
106
    if (_devToolsProcess != null) {
107
      _devToolsProcessKilled = true;
108
      _devToolsProcess!.kill();
109 110 111
    }
  }
}