/
devtools_launcher.dart
187 lines (172 loc) · 6.18 KB
/
devtools_launcher.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
// 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.
// @dart = 2.8
import 'dart:async';
import 'package:http/http.dart' as http;
import 'package:meta/meta.dart';
import 'package:process/process.dart';
import 'base/io.dart' as io;
import 'base/logger.dart';
import 'base/platform.dart';
import 'convert.dart';
import 'persistent_tool_state.dart';
import 'resident_runner.dart';
/// An implementation of the devtools launcher that uses the server package.
///
/// This is implemented in isolated to prevent the flutter_tool from needing
/// a devtools dep in google3.
class DevtoolsServerLauncher extends DevtoolsLauncher {
DevtoolsServerLauncher({
@required Platform platform,
@required ProcessManager processManager,
@required String pubExecutable,
@required Logger logger,
@required PersistentToolState persistentToolState,
}) : _processManager = processManager,
_pubExecutable = pubExecutable,
_logger = logger,
_platform = platform,
_persistentToolState = persistentToolState;
final ProcessManager _processManager;
final String _pubExecutable;
final Logger _logger;
final Platform _platform;
final PersistentToolState _persistentToolState;
io.Process _devToolsProcess;
static final RegExp _serveDevToolsPattern =
RegExp(r'Serving DevTools at ((http|//)[a-zA-Z0-9:/=_\-\.\[\]]+)');
@override
Future<void> launch(Uri vmServiceUri) async {
// Place this entire method in a try/catch that swallows exceptions because
// this method is guaranteed not to return a Future that throws.
try {
bool offline = false;
try {
const String pubHostedUrlKey = 'PUB_HOSTED_URL';
if (_platform.environment.containsKey(pubHostedUrlKey)) {
await http.head(Uri.parse(_platform.environment[pubHostedUrlKey]));
} else {
await http.head(Uri.https('pub.dev', ''));
}
} on Exception {
offline = true;
}
if (offline) {
// TODO(kenz): we should launch an already activated version of DevTools
// here, if available, once DevTools has offline support. DevTools does
// not work without internet currently due to the failed request of a
// couple scripts. See https://github.com/flutter/devtools/issues/2420.
return;
} else {
final bool didActivateDevTools = await _activateDevTools();
final bool devToolsActive = await _checkForActiveDevTools();
if (!didActivateDevTools && !devToolsActive) {
// At this point, we failed to activate the DevTools package and the
// package is not already active.
return;
}
}
_devToolsProcess = await _processManager.start(<String>[
_pubExecutable,
'global',
'run',
'devtools',
'--no-launch-browser',
if (vmServiceUri != null) '--vm-uri=$vmServiceUri',
]);
final Completer<Uri> completer = Completer<Uri>();
_devToolsProcess.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen((String line) {
final Match match = _serveDevToolsPattern.firstMatch(line);
if (match != null) {
// We are trying to pull "http://127.0.0.1:9101" from "Serving
// DevTools at http://127.0.0.1:9101.". `match[1]` will return
// "http://127.0.0.1:9101.", and we need to trim the trailing period
// so that we don't throw an exception from `Uri.parse`.
String uri = match[1];
if (uri.endsWith('.')) {
uri = uri.substring(0, uri.length - 1);
}
completer.complete(Uri.parse(uri));
}
});
_devToolsProcess.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(_logger.printError);
devToolsUrl = await completer.future;
} on Exception catch (e, st) {
_logger.printError('Failed to launch DevTools: $e', stackTrace: st);
}
}
Future<bool> _checkForActiveDevTools() async {
// We are offline, and cannot activate DevTools, so check if the DevTools
// package is already active.
final io.ProcessResult _pubGlobalListProcess = await _processManager.run(<String>[
_pubExecutable,
'global',
'list',
]);
if (_pubGlobalListProcess.stdout.toString().contains('devtools ')) {
return true;
}
return false;
}
/// Helper method to activate the DevTools pub package.
///
/// Returns a bool indicating whether or not the package was successfully
/// activated from pub.
Future<bool> _activateDevTools() async {
final DateTime now = DateTime.now();
// Only attempt to activate DevTools twice a day.
final bool shouldActivate =
_persistentToolState.lastDevToolsActivationTime == null ||
now.difference(_persistentToolState.lastDevToolsActivationTime).inHours >= 12;
if (!shouldActivate) {
return false;
}
final Status status = _logger.startProgress(
'Activating Dart DevTools...',
);
try {
final io.ProcessResult _devToolsActivateProcess = await _processManager
.run(<String>[
_pubExecutable,
'global',
'activate',
'devtools'
]);
if (_devToolsActivateProcess.exitCode != 0) {
status.cancel();
_logger.printError('Error running `pub global activate '
'devtools`:\n${_devToolsActivateProcess.stderr}');
return false;
}
status.stop();
_persistentToolState.lastDevToolsActivationTime = DateTime.now();
return true;
} on Exception catch (e, _) {
status.stop();
_logger.printError('Error running `pub global activate devtools`: $e');
return false;
}
}
@override
Future<DevToolsServerAddress> serve() async {
if (activeDevToolsServer == null) {
await launch(null);
}
return activeDevToolsServer;
}
@override
Future<void> close() async {
devToolsUrl = null;
if (_devToolsProcess != null) {
_devToolsProcess.kill();
await _devToolsProcess.exitCode;
}
}
}