mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 16:59:47 +00:00
[ VM / DDS / CLI ] Add DevTools support to the standalone VM
Example output on stdout when DevTools is enabled: Observatory listening on http://127.0.0.1:8181/CzkZzZaONW4=/ The Dart DevTools debugger and profiler is available at: http://127.0.0.1:8181/devtools/#/?uri=ws%3A%2F%2F127.0.0.1%3A8181%2FCzkZzZaONW4%3D%2Fws hello world! vm-service: isolate(1674461414267555) 'main' has no debugger attached and is paused at exit. Connect to Observatory at http://127.0.0.1:8181/CzkZzZaONW4=/ to debug. TEST=pkg/dartdev/test/commands/run_test.dart Change-Id: Icd1afda87ad4a46f228125d53094d10adf8056ec Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/188361 Commit-Queue: Ben Konyi <bkonyi@google.com> Reviewed-by: Siva Annamalai <asiva@google.com>
This commit is contained in:
parent
ec48e8f323
commit
ef0e4ea107
|
@ -11,7 +11,7 @@
|
|||
"constraint, update this by running tools/generate_package_config.dart."
|
||||
],
|
||||
"configVersion": 2,
|
||||
"generated": "2021-04-30T16:02:33.294454",
|
||||
"generated": "2021-05-03T09:47:39.938400",
|
||||
"generator": "tools/generate_package_config.dart",
|
||||
"packages": [
|
||||
{
|
||||
|
@ -252,6 +252,18 @@
|
|||
"packageUri": "lib/",
|
||||
"languageVersion": "2.3"
|
||||
},
|
||||
{
|
||||
"name": "devtools_server",
|
||||
"rootUri": "../third_party/devtools/devtools_server",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.6"
|
||||
},
|
||||
{
|
||||
"name": "devtools_shared",
|
||||
"rootUri": "../third_party/devtools/devtools_shared",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.3"
|
||||
},
|
||||
{
|
||||
"name": "diagnostic",
|
||||
"rootUri": "../pkg/diagnostic",
|
||||
|
@ -727,6 +739,12 @@
|
|||
"packageUri": "lib/",
|
||||
"languageVersion": "2.12"
|
||||
},
|
||||
{
|
||||
"name": "uuid",
|
||||
"rootUri": "../third_party/pkg/uuid",
|
||||
"packageUri": "lib/",
|
||||
"languageVersion": "2.0"
|
||||
},
|
||||
{
|
||||
"name": "vector_math",
|
||||
"rootUri": "../third_party/pkg/vector_math",
|
||||
|
|
|
@ -22,6 +22,7 @@ benchmark_harness:third_party/pkg/benchmark_harness/lib
|
|||
boolean_selector:third_party/pkg/boolean_selector/lib
|
||||
browser_launcher:third_party/pkg/browser_launcher/lib
|
||||
build_integration:pkg/build_integration/lib
|
||||
browser_launcher:third_party/pkg/browser_launcher/lib
|
||||
charcode:third_party/pkg/charcode/lib
|
||||
cli_util:third_party/pkg/cli_util/lib
|
||||
collection:third_party/pkg/collection/lib
|
||||
|
@ -38,6 +39,7 @@ dartdev:pkg/dartdev/lib
|
|||
dartdoc:third_party/pkg/dartdoc/lib
|
||||
dds:pkg/dds/lib
|
||||
dev_compiler:pkg/dev_compiler/lib
|
||||
devtools_shared:third_party/devtools/devtools_shared/lib
|
||||
diagnostic:pkg/diagnostic/lib
|
||||
expect:pkg/expect/lib
|
||||
ffi:third_party/pkg/ffi/lib
|
||||
|
|
10
DEPS
10
DEPS
|
@ -80,6 +80,7 @@ vars = {
|
|||
"boringssl_gen_rev": "7322fc15cc065d8d2957fccce6b62a509dc4d641",
|
||||
"boringssl_rev" : "1607f54fed72c6589d560254626909a64124f091",
|
||||
"browser-compat-data_tag": "v1.0.22",
|
||||
"browser_launcher_rev": "12ab9f351a44ac803de9bc17bb2180bb312a9dd7",
|
||||
"charcode_rev": "bcd8a12c315b7a83390e4865ad847ecd9344cba2",
|
||||
"chrome_rev" : "19997",
|
||||
"cli_util_rev" : "fd1b716e8a350a454e01ae56df540293d31ff6c8",
|
||||
|
@ -105,7 +106,6 @@ vars = {
|
|||
"dart_style_rev": "f17c23e0eea9a870601c19d904e2a9c1a7c81470",
|
||||
|
||||
"chromedriver_tag": "83.0.4103.39",
|
||||
"browser_launcher_rev": "12ab9f351a44ac803de9bc17bb2180bb312a9dd7",
|
||||
"dartdoc_rev" : "505f163f7cb48e917503e4a23fbff1227e08b263",
|
||||
"jsshell_tag": "version:88.0",
|
||||
"ffi_rev": "f3346299c55669cc0db48afae85b8110088bf8da",
|
||||
|
@ -246,7 +246,7 @@ deps = {
|
|||
Var("dart_root") + "/third_party/devtools": {
|
||||
"packages": [{
|
||||
"package": "dart/third_party/flutter/devtools",
|
||||
"version": "revision:6729ec62c3548839018c32fa711756202431ccf7",
|
||||
"version": "git_revision:12ad5341ae0a275042c84a4e7be9a6c98db65612",
|
||||
}],
|
||||
"dep_type": "cipd",
|
||||
},
|
||||
|
@ -319,6 +319,9 @@ deps = {
|
|||
Var('chromium_git') + '/external/github.com/mdn/browser-compat-data' +
|
||||
"@" + Var("browser-compat-data_tag"),
|
||||
|
||||
Var("dart_root") + "/third_party/pkg/browser_launcher":
|
||||
Var("dart_git") + "browser_launcher.git" + "@" + Var("browser_launcher_rev"),
|
||||
|
||||
Var("dart_root") + "/third_party/tcmalloc/gperftools":
|
||||
Var('chromium_git') + '/external/github.com/gperftools/gperftools.git' +
|
||||
"@" + Var("gperftools_revision"),
|
||||
|
@ -335,9 +338,6 @@ deps = {
|
|||
Var("dart_root") + "/third_party/pkg/boolean_selector":
|
||||
Var("dart_git") + "boolean_selector.git" +
|
||||
"@" + Var("boolean_selector_rev"),
|
||||
Var("dart_root") + "/third_party/pkg/browser_launcher":
|
||||
Var("dart_git") + "browser_launcher.git" +
|
||||
"@" + Var("browser_launcher_rev"),
|
||||
Var("dart_root") + "/third_party/pkg/charcode":
|
||||
Var("dart_git") + "charcode.git" + "@" + Var("charcode_rev"),
|
||||
Var("dart_root") + "/third_party/pkg/cli_util":
|
||||
|
|
|
@ -40,7 +40,8 @@ Future<void> runDartdev(List<String> args, SendPort port) async {
|
|||
args = args
|
||||
.where(
|
||||
(element) => !(element.contains('--observe') ||
|
||||
element.contains('--enable-vm-service')),
|
||||
element.contains('--enable-vm-service') ||
|
||||
element.contains('--devtools')),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
|
|
@ -158,6 +158,10 @@ class RunCommand extends DartdevCommand {
|
|||
hide: !verbose,
|
||||
negatable: false,
|
||||
help: 'Enables tracing of library and script loading.',
|
||||
)
|
||||
..addFlag(
|
||||
'debug-dds',
|
||||
hide: true,
|
||||
);
|
||||
addExperimentalFlags(argParser, verbose);
|
||||
}
|
||||
|
@ -179,13 +183,18 @@ class RunCommand extends DartdevCommand {
|
|||
String launchDdsArg = argResults['launch-dds'];
|
||||
String ddsHost = '';
|
||||
String ddsPort = '';
|
||||
|
||||
// TODO(bkonyi): allow for users to choose not to launch DevTools
|
||||
// See https://github.com/dart-lang/sdk/issues/45867.
|
||||
const bool launchDevTools = true;
|
||||
bool launchDds = false;
|
||||
if (launchDdsArg != null) {
|
||||
launchDds = true;
|
||||
final ddsUrl = launchDdsArg.split(':');
|
||||
final ddsUrl = launchDdsArg.split('\\:');
|
||||
ddsHost = ddsUrl[0];
|
||||
ddsPort = ddsUrl[1];
|
||||
}
|
||||
final bool debugDds = argResults['debug-dds'];
|
||||
|
||||
bool disableServiceAuthCodes = argResults['disable-service-auth-codes'];
|
||||
|
||||
|
@ -198,7 +207,12 @@ class RunCommand extends DartdevCommand {
|
|||
if (launchDds) {
|
||||
debugSession = _DebuggingSession();
|
||||
if (!await debugSession.start(
|
||||
ddsHost, ddsPort, disableServiceAuthCodes)) {
|
||||
ddsHost,
|
||||
ddsPort,
|
||||
disableServiceAuthCodes,
|
||||
launchDevTools,
|
||||
debugDds,
|
||||
)) {
|
||||
return errorExitCode;
|
||||
}
|
||||
}
|
||||
|
@ -242,10 +256,19 @@ String maybeUriToFilename(String maybeUri) {
|
|||
|
||||
class _DebuggingSession {
|
||||
Future<bool> start(
|
||||
String host, String port, bool disableServiceAuthCodes) async {
|
||||
final ddsSnapshot = (dirname(sdk.dart).endsWith('bin'))
|
||||
String host,
|
||||
String port,
|
||||
bool disableServiceAuthCodes,
|
||||
bool enableDevTools,
|
||||
bool debugDds,
|
||||
) async {
|
||||
final sdkDir = dirname(sdk.dart);
|
||||
final fullSdk = sdkDir.endsWith('bin');
|
||||
final ddsSnapshot = fullSdk
|
||||
? sdk.ddsSnapshot
|
||||
: absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot');
|
||||
: absolute(sdkDir, 'gen', 'dds.dart.snapshot');
|
||||
final devToolsBinaries =
|
||||
fullSdk ? sdk.devToolsBinaries : absolute(sdkDir, 'devtools');
|
||||
if (!Sdk.checkArtifactExists(ddsSnapshot)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -258,28 +281,49 @@ class _DebuggingSession {
|
|||
final process = await Process.start(
|
||||
sdk.dart,
|
||||
[
|
||||
if (dirname(sdk.dart).endsWith('bin'))
|
||||
sdk.ddsSnapshot
|
||||
else
|
||||
absolute(dirname(sdk.dart), 'gen', 'dds.dart.snapshot'),
|
||||
if (debugDds) '--enable-vm-service=0',
|
||||
ddsSnapshot,
|
||||
serviceInfo.serverUri.toString(),
|
||||
host,
|
||||
port,
|
||||
disableServiceAuthCodes.toString(),
|
||||
enableDevTools.toString(),
|
||||
devToolsBinaries,
|
||||
debugDds.toString(),
|
||||
],
|
||||
mode: ProcessStartMode.detachedWithStdio);
|
||||
mode: ProcessStartMode.detachedWithStdio,
|
||||
);
|
||||
final completer = Completer<void>();
|
||||
StreamSubscription sub;
|
||||
sub = process.stderr.transform(utf8.decoder).listen((event) {
|
||||
if (event == 'DDS started') {
|
||||
sub.cancel();
|
||||
const devToolsMessagePrefix =
|
||||
'The Dart DevTools debugger and profiler is available at:';
|
||||
if (debugDds) {
|
||||
StreamSubscription stdoutSub;
|
||||
stdoutSub = process.stdout.transform(utf8.decoder).listen((event) {
|
||||
if (event.startsWith(devToolsMessagePrefix)) {
|
||||
final ddsDebuggingUri = event.split(' ').last;
|
||||
print(
|
||||
'A DevTools debugger for DDS is available at: $ddsDebuggingUri',
|
||||
);
|
||||
stdoutSub.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
StreamSubscription stderrSub;
|
||||
stderrSub = process.stderr.transform(utf8.decoder).listen((event) {
|
||||
final result = json.decode(event) as Map<String, dynamic>;
|
||||
final state = result['state'];
|
||||
if (state == 'started') {
|
||||
if (result.containsKey('devToolsUri')) {
|
||||
final devToolsUri = result['devToolsUri'];
|
||||
print('$devToolsMessagePrefix $devToolsUri');
|
||||
}
|
||||
stderrSub.cancel();
|
||||
completer.complete();
|
||||
} else if (event.contains('Failed to start DDS')) {
|
||||
sub.cancel();
|
||||
completer.completeError(event.replaceAll(
|
||||
'Failed to start DDS',
|
||||
} else {
|
||||
stderrSub.cancel();
|
||||
completer.completeError(
|
||||
'Could not start Observatory HTTP server',
|
||||
));
|
||||
);
|
||||
}
|
||||
});
|
||||
try {
|
||||
|
|
|
@ -68,6 +68,13 @@ class Sdk {
|
|||
'dds.dart.snapshot',
|
||||
);
|
||||
|
||||
String get devToolsBinaries => path.absolute(
|
||||
sdkPath,
|
||||
'bin',
|
||||
'resources',
|
||||
'devtools',
|
||||
);
|
||||
|
||||
String get pubSnapshot => path.absolute(
|
||||
sdkPath,
|
||||
'bin',
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
// for details. 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 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
@ -300,4 +302,61 @@ void main(List<String> args) => print("$b $args");
|
|||
expect(result.stderr, isEmpty);
|
||||
expect(result.exitCode, 0);
|
||||
});
|
||||
|
||||
group('DevTools', () {
|
||||
const devToolsMessagePrefix =
|
||||
'The Dart DevTools debugger and profiler is available at: http://127.0.0.1:';
|
||||
|
||||
test('spawn simple', () async {
|
||||
p = project(mainSrc: "void main() { print('Hello World'); }");
|
||||
ProcessResult result = p.runSync([
|
||||
'run',
|
||||
'--enable-vm-service',
|
||||
p.relativeFilePath,
|
||||
]);
|
||||
expect(result.stdout, contains(devToolsMessagePrefix));
|
||||
});
|
||||
|
||||
test('implicit spawn', () async {
|
||||
p = project(mainSrc: "void main() { print('Hello World'); }");
|
||||
ProcessResult result = p.runSync([
|
||||
'--enable-vm-service',
|
||||
p.relativeFilePath,
|
||||
]);
|
||||
expect(result.stdout, contains(devToolsMessagePrefix));
|
||||
});
|
||||
|
||||
test(
|
||||
'spawn via SIGQUIT',
|
||||
() async {
|
||||
p = project(
|
||||
mainSrc:
|
||||
'void main() { print("ready"); int i = 0; while(true) { i++; } }',
|
||||
);
|
||||
Process process = await p.start([
|
||||
p.relativeFilePath,
|
||||
]);
|
||||
|
||||
final readyCompleter = Completer<void>();
|
||||
final completer = Completer<void>();
|
||||
|
||||
StreamSubscription sub;
|
||||
sub = process.stdout.transform(utf8.decoder).listen((event) async {
|
||||
if (event.contains('ready')) {
|
||||
readyCompleter.complete();
|
||||
} else if (event.contains(devToolsMessagePrefix)) {
|
||||
await sub.cancel();
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
// Wait for process to start.
|
||||
await readyCompleter.future;
|
||||
process.kill(ProcessSignal.sigquit);
|
||||
await completer.future;
|
||||
process.kill();
|
||||
},
|
||||
// No support for SIGQUIT on Windows.
|
||||
skip: Platform.isWindows,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# 1.7.7-dev
|
||||
# 1.8.0-dev
|
||||
- Add support for launching DevTools from DDS.
|
||||
- Fixed issue where two clients subscribing to the same stream in close succession
|
||||
could result in DDS sending multiple `streamListen` requests to the VM service.
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
// @dart=2.10
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dds/dds.dart';
|
||||
|
@ -16,6 +17,9 @@ import 'package:dds/dds.dart';
|
|||
/// - DDS bind address
|
||||
/// - DDS port
|
||||
/// - Disable service authentication codes
|
||||
/// - Start DevTools
|
||||
/// - DevTools build directory
|
||||
/// - Enable logging
|
||||
Future<void> main(List<String> args) async {
|
||||
if (args.isEmpty) return;
|
||||
|
||||
|
@ -37,16 +41,37 @@ Future<void> main(List<String> args) async {
|
|||
port: int.parse(args[2]),
|
||||
);
|
||||
final disableServiceAuthCodes = args[3] == 'true';
|
||||
|
||||
final startDevTools = args[4] == 'true';
|
||||
Uri devToolsBuildDirectory;
|
||||
if (args[5].isNotEmpty) {
|
||||
devToolsBuildDirectory = Uri.parse(args[5]);
|
||||
}
|
||||
final logRequests = args[6] == 'true';
|
||||
try {
|
||||
// TODO(bkonyi): add retry logic similar to that in vmservice_server.dart
|
||||
// See https://github.com/dart-lang/sdk/issues/43192.
|
||||
await DartDevelopmentService.startDartDevelopmentService(
|
||||
final dds = await DartDevelopmentService.startDartDevelopmentService(
|
||||
remoteVmServiceUri,
|
||||
serviceUri: serviceUri,
|
||||
enableAuthCodes: !disableServiceAuthCodes,
|
||||
devToolsConfiguration: startDevTools
|
||||
? DevToolsConfiguration(
|
||||
enable: startDevTools,
|
||||
customBuildDirectoryPath: devToolsBuildDirectory,
|
||||
)
|
||||
: null,
|
||||
logRequests: logRequests,
|
||||
);
|
||||
stderr.write('DDS started');
|
||||
} catch (e) {
|
||||
stderr.writeln('Failed to start DDS:\n$e');
|
||||
stderr.write(json.encode({
|
||||
'state': 'started',
|
||||
if (dds.devToolsUri != null) 'devToolsUri': dds.devToolsUri.toString(),
|
||||
}));
|
||||
} catch (e, st) {
|
||||
stderr.write(json.encode({
|
||||
'state': 'error',
|
||||
'error': '$e',
|
||||
'stacktrace': '$st',
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,8 @@ abstract class DartDevelopmentService {
|
|||
Uri serviceUri,
|
||||
bool enableAuthCodes = true,
|
||||
bool ipv6 = false,
|
||||
DevToolsConfiguration devToolsConfiguration = const DevToolsConfiguration(),
|
||||
bool logRequests = false,
|
||||
}) async {
|
||||
if (remoteVmServiceUri == null) {
|
||||
throw ArgumentError.notNull('remoteVmServiceUri');
|
||||
|
@ -80,6 +82,8 @@ abstract class DartDevelopmentService {
|
|||
serviceUri,
|
||||
enableAuthCodes,
|
||||
ipv6,
|
||||
devToolsConfiguration,
|
||||
logRequests,
|
||||
);
|
||||
await service.startService();
|
||||
return service;
|
||||
|
@ -125,6 +129,11 @@ abstract class DartDevelopmentService {
|
|||
/// Returns `null` if the service is not running.
|
||||
Uri get wsUri;
|
||||
|
||||
/// The HTTP [Uri] of the hosted DevTools instance.
|
||||
///
|
||||
/// Returns `null` if DevTools is not running.
|
||||
Uri get devToolsUri;
|
||||
|
||||
/// Set to `true` if this instance of [DartDevelopmentService] is accepting
|
||||
/// requests.
|
||||
bool get isRunning;
|
||||
|
@ -168,3 +177,13 @@ class DartDevelopmentServiceException implements Exception {
|
|||
final int errorCode;
|
||||
final String message;
|
||||
}
|
||||
|
||||
class DevToolsConfiguration {
|
||||
const DevToolsConfiguration({
|
||||
this.enable = false,
|
||||
this.customBuildDirectoryPath,
|
||||
});
|
||||
|
||||
final bool enable;
|
||||
final Uri customBuildDirectoryPath;
|
||||
}
|
||||
|
|
|
@ -21,23 +21,21 @@ import 'stream_manager.dart';
|
|||
/// Representation of a single DDS client which manages the connection and
|
||||
/// DDS request intercepting / forwarding.
|
||||
class DartDevelopmentServiceClient {
|
||||
factory DartDevelopmentServiceClient.fromWebSocket(
|
||||
DartDevelopmentServiceClient.fromWebSocket(
|
||||
DartDevelopmentService dds,
|
||||
WebSocketChannel ws,
|
||||
json_rpc.Peer vmServicePeer,
|
||||
) =>
|
||||
DartDevelopmentServiceClient._(
|
||||
) : this._(
|
||||
dds,
|
||||
ws,
|
||||
vmServicePeer,
|
||||
);
|
||||
|
||||
factory DartDevelopmentServiceClient.fromSSEConnection(
|
||||
DartDevelopmentServiceClient.fromSSEConnection(
|
||||
DartDevelopmentService dds,
|
||||
SseConnection sse,
|
||||
json_rpc.Peer vmServicePeer,
|
||||
) =>
|
||||
DartDevelopmentServiceClient._(
|
||||
) : this._(
|
||||
dds,
|
||||
sse,
|
||||
vmServicePeer,
|
||||
|
|
|
@ -16,6 +16,10 @@ abstract class RPCResponses {
|
|||
};
|
||||
}
|
||||
|
||||
// Give connections time to reestablish before considering them closed.
|
||||
// Required to reestablish connections killed by UberProxy.
|
||||
const sseKeepAlive = Duration(seconds: 30);
|
||||
|
||||
abstract class PauseTypeMasks {
|
||||
static const pauseOnStartMask = 1 << 0;
|
||||
static const pauseOnReloadMask = 1 << 1;
|
||||
|
|
|
@ -24,6 +24,8 @@ import '../dds.dart';
|
|||
import 'binary_compatible_peer.dart';
|
||||
import 'client.dart';
|
||||
import 'client_manager.dart';
|
||||
import 'constants.dart';
|
||||
import 'devtools/devtools_handler.dart';
|
||||
import 'expression_evaluator.dart';
|
||||
import 'isolate_manager.dart';
|
||||
import 'stream_manager.dart';
|
||||
|
@ -51,7 +53,13 @@ WebSocketChannel _defaultWebSocketBuilder(Uri uri) {
|
|||
|
||||
class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
||||
DartDevelopmentServiceImpl(
|
||||
this._remoteVmServiceUri, this._uri, this._authCodesEnabled, this._ipv6) {
|
||||
this._remoteVmServiceUri,
|
||||
this._uri,
|
||||
this._authCodesEnabled,
|
||||
this._ipv6,
|
||||
this._devToolsConfiguration,
|
||||
this.shouldLogRequests,
|
||||
) {
|
||||
_clientManager = ClientManager(this);
|
||||
_expressionEvaluator = ExpressionEvaluator(this);
|
||||
_isolateManager = IsolateManager(this);
|
||||
|
@ -113,20 +121,26 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
(_ipv6 ? InternetAddress.loopbackIPv6 : InternetAddress.loopbackIPv4)
|
||||
.host;
|
||||
final port = uri?.port ?? 0;
|
||||
|
||||
final pipeline = const Pipeline();
|
||||
if (shouldLogRequests) {
|
||||
pipeline.addMiddleware(
|
||||
logRequests(
|
||||
logger: (String message, bool isError) {
|
||||
print('Log: $message');
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
pipeline.addMiddleware(_authCodeMiddleware);
|
||||
final handler = pipeline.addHandler(_handlers().handler);
|
||||
// Start the DDS server.
|
||||
_server = await io.serve(
|
||||
const Pipeline()
|
||||
.addMiddleware(_authCodeMiddleware)
|
||||
.addHandler(_handlers().handler),
|
||||
host,
|
||||
port);
|
||||
_server = await io.serve(handler, host, port);
|
||||
|
||||
final tmpUri = Uri(
|
||||
scheme: 'http',
|
||||
host: host,
|
||||
port: _server.port,
|
||||
path: '$_authCode/',
|
||||
path: '$authCode/',
|
||||
);
|
||||
|
||||
// Notify the VM service that this client is DDS and that it should close
|
||||
|
@ -157,7 +171,7 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
return;
|
||||
}
|
||||
_shuttingDown = true;
|
||||
// Don't accept anymore HTTP requests.
|
||||
// Don't accept any more HTTP requests.
|
||||
await _server?.close();
|
||||
|
||||
// Close connections to clients.
|
||||
|
@ -197,7 +211,7 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
return forbidden;
|
||||
}
|
||||
final authToken = pathSegments[0];
|
||||
if (authToken != _authCode) {
|
||||
if (authToken != authCode) {
|
||||
return forbidden;
|
||||
}
|
||||
// Creates a new request with the authentication code stripped from
|
||||
|
@ -233,17 +247,11 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
});
|
||||
|
||||
Handler _sseHandler() {
|
||||
// Give connections time to reestablish before considering them closed.
|
||||
// Required to reestablish connections killed by UberProxy.
|
||||
const keepAlive = Duration(seconds: 30);
|
||||
final handler = authCodesEnabled
|
||||
? SseHandler(
|
||||
Uri.parse('/$_authCode/$_kSseHandlerPath'),
|
||||
keepAlive: keepAlive,
|
||||
)
|
||||
: SseHandler(
|
||||
Uri.parse('/$_kSseHandlerPath'),
|
||||
keepAlive: keepAlive,
|
||||
final handler = SseHandler(
|
||||
authCodesEnabled
|
||||
? Uri.parse('/$authCode/$_kSseHandlerPath')
|
||||
: Uri.parse('/$_kSseHandlerPath'),
|
||||
keepAlive: sseKeepAlive,
|
||||
);
|
||||
|
||||
handler.connections.rest.listen((sseConnection) {
|
||||
|
@ -259,10 +267,18 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
}
|
||||
|
||||
Handler _httpHandler() {
|
||||
// DDS doesn't support any HTTP requests itself, so we just forward all of
|
||||
// them to the VM service.
|
||||
final cascade = Cascade().add(proxyHandler(remoteVmServiceUri));
|
||||
return cascade.handler;
|
||||
if (_devToolsConfiguration != null && _devToolsConfiguration.enable) {
|
||||
// Install the DevTools handlers and forward any unhandled HTTP requests to
|
||||
// the VM service.
|
||||
final buildDir =
|
||||
_devToolsConfiguration.customBuildDirectoryPath?.toFilePath();
|
||||
return devtoolsHandler(
|
||||
dds: this,
|
||||
buildDir: buildDir,
|
||||
notFoundHandler: proxyHandler(remoteVmServiceUri),
|
||||
);
|
||||
}
|
||||
return proxyHandler(remoteVmServiceUri);
|
||||
}
|
||||
|
||||
List<String> _cleanupPathSegments(Uri uri) {
|
||||
|
@ -296,14 +312,43 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
return uri.replace(scheme: 'sse', pathSegments: pathSegments);
|
||||
}
|
||||
|
||||
Uri _toDevTools(Uri uri) {
|
||||
// The DevTools URI is a bit strange as the query parameters appear after
|
||||
// the fragment. There's no nice way to encode the query parameters
|
||||
// properly, so we create another Uri just to grab the formatted query.
|
||||
// The result will need to have '/?' prepended when being used as the
|
||||
// fragment to get the correct format.
|
||||
final query = Uri(
|
||||
queryParameters: {
|
||||
'uri': wsUri.toString(),
|
||||
},
|
||||
).query;
|
||||
return Uri(
|
||||
scheme: 'http',
|
||||
host: uri.host,
|
||||
port: uri.port,
|
||||
pathSegments: [
|
||||
...uri.pathSegments.where(
|
||||
(e) => e.isNotEmpty,
|
||||
),
|
||||
'devtools',
|
||||
'',
|
||||
],
|
||||
fragment: '/?$query',
|
||||
);
|
||||
}
|
||||
|
||||
String getNamespace(DartDevelopmentServiceClient client) =>
|
||||
clientManager.clients.keyOf(client);
|
||||
|
||||
@override
|
||||
bool get authCodesEnabled => _authCodesEnabled;
|
||||
final bool _authCodesEnabled;
|
||||
String get authCode => _authCode;
|
||||
String _authCode;
|
||||
|
||||
final bool shouldLogRequests;
|
||||
|
||||
@override
|
||||
Uri get remoteVmServiceUri => _remoteVmServiceUri;
|
||||
|
||||
|
@ -313,17 +358,21 @@ class DartDevelopmentServiceImpl implements DartDevelopmentService {
|
|||
|
||||
@override
|
||||
Uri get uri => _uri;
|
||||
Uri _uri;
|
||||
|
||||
@override
|
||||
Uri get sseUri => _toSse(_uri);
|
||||
|
||||
Uri get wsUri => _toWebSocket(_uri);
|
||||
Uri _uri;
|
||||
|
||||
Uri get devToolsUri => _toDevTools(_uri);
|
||||
|
||||
final bool _ipv6;
|
||||
|
||||
bool get isRunning => _uri != null;
|
||||
|
||||
final DevToolsConfiguration _devToolsConfiguration;
|
||||
|
||||
Future<void> get done => _done.future;
|
||||
Completer _done = Completer<void>();
|
||||
bool _shuttingDown = false;
|
||||
|
|
96
pkg/dds/lib/src/devtools/devtools_client.dart
Normal file
96
pkg/dds/lib/src/devtools/devtools_client.dart
Normal file
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. 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.9
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:json_rpc_2/src/server.dart' as json_rpc;
|
||||
import 'package:sse/src/server/sse_handler.dart';
|
||||
import 'package:stream_channel/stream_channel.dart';
|
||||
|
||||
import 'server_api.dart';
|
||||
|
||||
class LoggingMiddlewareSink<S> implements StreamSink<S> {
|
||||
LoggingMiddlewareSink(this.sink);
|
||||
|
||||
@override
|
||||
void add(S event) {
|
||||
print('DevTools SSE response: $event');
|
||||
sink.add(event);
|
||||
}
|
||||
|
||||
@override
|
||||
void addError(Object error, [StackTrace stackTrace]) {
|
||||
print('DevTools SSE error response: $error');
|
||||
sink.addError(error);
|
||||
}
|
||||
|
||||
@override
|
||||
Future addStream(Stream<S> stream) {
|
||||
return sink.addStream(stream);
|
||||
}
|
||||
|
||||
@override
|
||||
Future close() => sink.close();
|
||||
|
||||
@override
|
||||
Future get done => sink.done;
|
||||
|
||||
final StreamSink sink;
|
||||
}
|
||||
|
||||
/// Represents a DevTools client connection to the DevTools server API.
|
||||
class DevToolsClient {
|
||||
DevToolsClient.fromSSEConnection(
|
||||
SseConnection sse,
|
||||
bool loggingEnabled,
|
||||
) {
|
||||
Stream<String> stream = sse.stream;
|
||||
StreamSink sink = sse.sink;
|
||||
|
||||
if (loggingEnabled) {
|
||||
stream = stream.map<String>((String e) {
|
||||
print('DevTools SSE request: $e');
|
||||
return e;
|
||||
});
|
||||
sink = LoggingMiddlewareSink(sink);
|
||||
}
|
||||
|
||||
_server = json_rpc.Server(
|
||||
StreamChannel(stream, sink),
|
||||
strictProtocolChecks: false,
|
||||
);
|
||||
_registerJsonRpcMethods();
|
||||
_server.listen();
|
||||
}
|
||||
|
||||
void _registerJsonRpcMethods() {
|
||||
_server.registerMethod('connected', (parameters) {
|
||||
// Nothing to do here.
|
||||
});
|
||||
|
||||
_server.registerMethod('currentPage', (parameters) {
|
||||
// Nothing to do here.
|
||||
});
|
||||
|
||||
_server.registerMethod('disconnected', (parameters) {
|
||||
// Nothing to do here.
|
||||
});
|
||||
|
||||
_server.registerMethod('getPreferenceValue', (parameters) {
|
||||
final key = parameters['key'].asString;
|
||||
final value = ServerApi.devToolsPreferences.properties[key];
|
||||
return value;
|
||||
});
|
||||
|
||||
_server.registerMethod('setPreferenceValue', (parameters) {
|
||||
final key = parameters['key'].asString;
|
||||
final value = parameters['value'].value;
|
||||
ServerApi.devToolsPreferences.properties[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
json_rpc.Server _server;
|
||||
}
|
87
pkg/dds/lib/src/devtools/devtools_handler.dart
Normal file
87
pkg/dds/lib/src/devtools/devtools_handler.dart
Normal file
|
@ -0,0 +1,87 @@
|
|||
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. 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.9
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dds/src/constants.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf_static/shelf_static.dart';
|
||||
import 'package:sse/server/sse_handler.dart';
|
||||
|
||||
import '../dds_impl.dart';
|
||||
import 'devtools_client.dart';
|
||||
import 'server_api.dart';
|
||||
|
||||
/// Returns a [Handler] which handles serving DevTools and the DevTools server
|
||||
/// API under $DDS_URI/devtools/.
|
||||
///
|
||||
/// [buildDir] is the path to the pre-compiled DevTools instance to be served.
|
||||
///
|
||||
/// [notFoundHandler] is a [Handler] to which requests that could not be handled
|
||||
/// by the DevTools handler are forwarded (e.g., a proxy to the VM service).
|
||||
FutureOr<Handler> devtoolsHandler({
|
||||
@required DartDevelopmentServiceImpl dds,
|
||||
@required String buildDir,
|
||||
@required Handler notFoundHandler,
|
||||
}) {
|
||||
// Serves the web assets for DevTools.
|
||||
final devtoolsAssetHandler = createStaticHandler(
|
||||
buildDir,
|
||||
defaultDocument: 'index.html',
|
||||
);
|
||||
|
||||
// Support DevTools client-server interface via SSE.
|
||||
// Note: the handler path needs to match the full *original* path, not the
|
||||
// current request URL (we remove '/devtools' in the initial router but we
|
||||
// need to include it here).
|
||||
const devToolsSseHandlerPath = '/devtools/api/sse';
|
||||
final devToolsApiHandler = SseHandler(
|
||||
dds.authCodesEnabled
|
||||
? Uri.parse('/${dds.authCode}$devToolsSseHandlerPath')
|
||||
: Uri.parse(devToolsSseHandlerPath),
|
||||
keepAlive: sseKeepAlive,
|
||||
);
|
||||
|
||||
devToolsApiHandler.connections.rest.listen(
|
||||
(sseConnection) => DevToolsClient.fromSSEConnection(
|
||||
sseConnection,
|
||||
dds.shouldLogRequests,
|
||||
),
|
||||
);
|
||||
|
||||
final devtoolsHandler = (Request request) {
|
||||
// If the request isn't of the form api/<method> assume it's a request for
|
||||
// DevTools assets.
|
||||
if (request.url.pathSegments.length < 2 ||
|
||||
request.url.pathSegments.first != 'api') {
|
||||
return devtoolsAssetHandler(request);
|
||||
}
|
||||
final method = request.url.pathSegments[1];
|
||||
if (method == 'ping') {
|
||||
// Note: we have an 'OK' body response, otherwise the response has an
|
||||
// incorrect status code (204 instead of 200).
|
||||
return Response.ok('OK');
|
||||
}
|
||||
if (method == 'sse') {
|
||||
return devToolsApiHandler.handler(request);
|
||||
}
|
||||
if (!ServerApi.canHandle(request)) {
|
||||
return Response.notFound('$method is not a valid API');
|
||||
}
|
||||
return ServerApi.handle(request);
|
||||
};
|
||||
|
||||
return (request) {
|
||||
final pathSegments = request.url.pathSegments;
|
||||
if (pathSegments.isEmpty || pathSegments.first != 'devtools') {
|
||||
return notFoundHandler(request);
|
||||
}
|
||||
// Forward all requests to /devtools/* to the DevTools handler.
|
||||
request = request.change(path: 'devtools');
|
||||
return devtoolsHandler(request);
|
||||
};
|
||||
}
|
84
pkg/dds/lib/src/devtools/file_system.dart
Normal file
84
pkg/dds/lib/src/devtools/file_system.dart
Normal file
|
@ -0,0 +1,84 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
// @dart=2.9
|
||||
|
||||
// TODO(bkonyi): remove once package:devtools_server_api is available
|
||||
// See https://github.com/flutter/devtools/issues/2958.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
import 'usage.dart';
|
||||
|
||||
class LocalFileSystem {
|
||||
static String _userHomeDir() {
|
||||
final String envKey =
|
||||
Platform.operatingSystem == 'windows' ? 'APPDATA' : 'HOME';
|
||||
final String value = Platform.environment[envKey];
|
||||
return value == null ? '.' : value;
|
||||
}
|
||||
|
||||
/// Returns the path to the DevTools storage directory.
|
||||
static String devToolsDir() {
|
||||
return path.join(_userHomeDir(), '.flutter-devtools');
|
||||
}
|
||||
|
||||
/// Moves the .devtools file to ~/.flutter-devtools/.devtools if the .devtools file
|
||||
/// exists in the user's home directory.
|
||||
static void maybeMoveLegacyDevToolsStore() {
|
||||
final file = File(path.join(_userHomeDir(), DevToolsUsage.storeName));
|
||||
if (file.existsSync()) {
|
||||
ensureDevToolsDirectory();
|
||||
file.copySync(path.join(devToolsDir(), DevToolsUsage.storeName));
|
||||
file.deleteSync();
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates the ~/.flutter-devtools directory if it does not already exist.
|
||||
static void ensureDevToolsDirectory() {
|
||||
Directory('${LocalFileSystem.devToolsDir()}').createSync();
|
||||
}
|
||||
|
||||
/// Returns a DevTools file from the given path.
|
||||
///
|
||||
/// Only files within ~/.flutter-devtools/ can be accessed.
|
||||
static File devToolsFileFromPath(String pathFromDevToolsDir) {
|
||||
if (pathFromDevToolsDir.contains('..')) {
|
||||
// The passed in path should not be able to walk up the directory tree
|
||||
// outside of the ~/.flutter-devtools/ directory.
|
||||
return null;
|
||||
}
|
||||
ensureDevToolsDirectory();
|
||||
final file = File(path.join(devToolsDir(), pathFromDevToolsDir));
|
||||
if (!file.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
return file;
|
||||
}
|
||||
|
||||
/// Returns a DevTools file from the given path as encoded json.
|
||||
///
|
||||
/// Only files within ~/.flutter-devtools/ can be accessed.
|
||||
static String devToolsFileAsJson(String pathFromDevToolsDir) {
|
||||
final file = devToolsFileFromPath(pathFromDevToolsDir);
|
||||
if (file == null) return null;
|
||||
|
||||
final fileName = path.basename(file.path);
|
||||
if (!fileName.endsWith('.json')) return null;
|
||||
|
||||
final content = file.readAsStringSync();
|
||||
final json = jsonDecode(content);
|
||||
json['lastModifiedTime'] = file.lastModifiedSync().toString();
|
||||
return jsonEncode(json);
|
||||
}
|
||||
|
||||
/// Whether the flutter store file exists.
|
||||
static bool flutterStoreExists() {
|
||||
final flutterStore = File('${_userHomeDir()}/.flutter');
|
||||
return flutterStore.existsSync();
|
||||
}
|
||||
}
|
230
pkg/dds/lib/src/devtools/server_api.dart
Normal file
230
pkg/dds/lib/src/devtools/server_api.dart
Normal file
|
@ -0,0 +1,230 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
// @dart=2.9
|
||||
|
||||
// TODO(bkonyi): remove once package:devtools_server_api is available
|
||||
// See https://github.com/flutter/devtools/issues/2958.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:devtools_shared/devtools_shared.dart';
|
||||
import 'package:shelf/shelf.dart' as shelf;
|
||||
|
||||
import 'file_system.dart';
|
||||
import 'usage.dart';
|
||||
|
||||
/// The DevTools server API.
|
||||
///
|
||||
/// This defines endpoints that serve all requests that come in over api/.
|
||||
class ServerApi {
|
||||
static const errorNoActiveSurvey = 'ERROR: setActiveSurvey not called.';
|
||||
|
||||
/// Determines whether or not [request] is an API call.
|
||||
static bool canHandle(shelf.Request request) {
|
||||
return request.url.path.startsWith(apiPrefix);
|
||||
}
|
||||
|
||||
/// Handles all requests.
|
||||
///
|
||||
/// To override an API call, pass in a subclass of [ServerApi].
|
||||
static FutureOr<shelf.Response> handle(
|
||||
shelf.Request request, [
|
||||
ServerApi api,
|
||||
]) {
|
||||
api ??= ServerApi();
|
||||
switch (request.url.path) {
|
||||
// ----- Flutter Tool GA store. -----
|
||||
case apiGetFlutterGAEnabled:
|
||||
// Is Analytics collection enabled?
|
||||
return api.getCompleted(
|
||||
request,
|
||||
json.encode(FlutterUsage.doesStoreExist ? _usage.enabled : null),
|
||||
);
|
||||
case apiGetFlutterGAClientId:
|
||||
// Flutter Tool GA clientId - ONLY get Flutter's clientId if enabled is
|
||||
// true.
|
||||
return (FlutterUsage.doesStoreExist)
|
||||
? api.getCompleted(
|
||||
request,
|
||||
json.encode(_usage.enabled ? _usage.clientId : null),
|
||||
)
|
||||
: api.getCompleted(
|
||||
request,
|
||||
json.encode(null),
|
||||
);
|
||||
|
||||
// ----- DevTools GA store. -----
|
||||
|
||||
case apiResetDevTools:
|
||||
_devToolsUsage.reset();
|
||||
return api.getCompleted(request, json.encode(true));
|
||||
case apiGetDevToolsFirstRun:
|
||||
// Has DevTools been run first time? To bring up welcome screen.
|
||||
return api.getCompleted(
|
||||
request,
|
||||
json.encode(_devToolsUsage.isFirstRun),
|
||||
);
|
||||
case apiGetDevToolsEnabled:
|
||||
// Is DevTools Analytics collection enabled?
|
||||
return api.getCompleted(request, json.encode(_devToolsUsage.enabled));
|
||||
case apiSetDevToolsEnabled:
|
||||
// Enable or disable DevTools analytics collection.
|
||||
final queryParams = request.requestedUri.queryParameters;
|
||||
if (queryParams.containsKey(devToolsEnabledPropertyName)) {
|
||||
_devToolsUsage.enabled =
|
||||
json.decode(queryParams[devToolsEnabledPropertyName]);
|
||||
}
|
||||
return api.setCompleted(request, json.encode(_devToolsUsage.enabled));
|
||||
|
||||
// ----- DevTools survey store. -----
|
||||
|
||||
case apiSetActiveSurvey:
|
||||
// Assume failure.
|
||||
bool result = false;
|
||||
|
||||
// Set the active survey used to store subsequent apiGetSurveyActionTaken,
|
||||
// apiSetSurveyActionTaken, apiGetSurveyShownCount, and
|
||||
// apiIncrementSurveyShownCount calls.
|
||||
final queryParams = request.requestedUri.queryParameters;
|
||||
if (queryParams.keys.length == 1 &&
|
||||
queryParams.containsKey(activeSurveyName)) {
|
||||
final String theSurveyName = queryParams[activeSurveyName];
|
||||
|
||||
// Set the current activeSurvey.
|
||||
_devToolsUsage.activeSurvey = theSurveyName;
|
||||
result = true;
|
||||
}
|
||||
|
||||
return api.getCompleted(request, json.encode(result));
|
||||
case apiGetSurveyActionTaken:
|
||||
// Request setActiveSurvey has not been requested.
|
||||
if (_devToolsUsage.activeSurvey == null) {
|
||||
return api.badRequest('$errorNoActiveSurvey '
|
||||
'- $apiGetSurveyActionTaken');
|
||||
}
|
||||
// SurveyActionTaken has the survey been acted upon (taken or dismissed)
|
||||
return api.getCompleted(
|
||||
request,
|
||||
json.encode(_devToolsUsage.surveyActionTaken),
|
||||
);
|
||||
// TODO(terry): remove the query param logic for this request.
|
||||
// setSurveyActionTaken should only be called with the value of true, so
|
||||
// we can remove the extra complexity.
|
||||
case apiSetSurveyActionTaken:
|
||||
// Request setActiveSurvey has not been requested.
|
||||
if (_devToolsUsage.activeSurvey == null) {
|
||||
return api.badRequest('$errorNoActiveSurvey '
|
||||
'- $apiSetSurveyActionTaken');
|
||||
}
|
||||
// Set the SurveyActionTaken.
|
||||
// Has the survey been taken or dismissed..
|
||||
final queryParams = request.requestedUri.queryParameters;
|
||||
if (queryParams.containsKey(surveyActionTakenPropertyName)) {
|
||||
_devToolsUsage.surveyActionTaken =
|
||||
json.decode(queryParams[surveyActionTakenPropertyName]);
|
||||
}
|
||||
return api.setCompleted(
|
||||
request,
|
||||
json.encode(_devToolsUsage.surveyActionTaken),
|
||||
);
|
||||
case apiGetSurveyShownCount:
|
||||
// Request setActiveSurvey has not been requested.
|
||||
if (_devToolsUsage.activeSurvey == null) {
|
||||
return api.badRequest('$errorNoActiveSurvey '
|
||||
'- $apiGetSurveyShownCount');
|
||||
}
|
||||
// SurveyShownCount how many times have we asked to take survey.
|
||||
return api.getCompleted(
|
||||
request,
|
||||
json.encode(_devToolsUsage.surveyShownCount),
|
||||
);
|
||||
case apiIncrementSurveyShownCount:
|
||||
// Request setActiveSurvey has not been requested.
|
||||
if (_devToolsUsage.activeSurvey == null) {
|
||||
return api.badRequest('$errorNoActiveSurvey '
|
||||
'- $apiIncrementSurveyShownCount');
|
||||
}
|
||||
// Increment the SurveyShownCount, we've asked about the survey.
|
||||
_devToolsUsage.incrementSurveyShownCount();
|
||||
return api.getCompleted(
|
||||
request,
|
||||
json.encode(_devToolsUsage.surveyShownCount),
|
||||
);
|
||||
case apiGetBaseAppSizeFile:
|
||||
final queryParams = request.requestedUri.queryParameters;
|
||||
if (queryParams.containsKey(baseAppSizeFilePropertyName)) {
|
||||
final filePath = queryParams[baseAppSizeFilePropertyName];
|
||||
final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
|
||||
if (fileJson == null) {
|
||||
return api.badRequest('No JSON file available at $filePath.');
|
||||
}
|
||||
return api.getCompleted(request, fileJson);
|
||||
}
|
||||
return api.badRequest('Request for base app size file does not '
|
||||
'contain a query parameter with the expected key: '
|
||||
'$baseAppSizeFilePropertyName');
|
||||
case apiGetTestAppSizeFile:
|
||||
final queryParams = request.requestedUri.queryParameters;
|
||||
if (queryParams.containsKey(testAppSizeFilePropertyName)) {
|
||||
final filePath = queryParams[testAppSizeFilePropertyName];
|
||||
final fileJson = LocalFileSystem.devToolsFileAsJson(filePath);
|
||||
if (fileJson == null) {
|
||||
return api.badRequest('No JSON file available at $filePath.');
|
||||
}
|
||||
return api.getCompleted(request, fileJson);
|
||||
}
|
||||
return api.badRequest('Request for test app size file does not '
|
||||
'contain a query parameter with the expected key: '
|
||||
'$testAppSizeFilePropertyName');
|
||||
default:
|
||||
return api.notImplemented(request);
|
||||
}
|
||||
}
|
||||
|
||||
// Accessing Flutter usage file e.g., ~/.flutter.
|
||||
// NOTE: Only access the file if it exists otherwise Flutter Tool hasn't yet
|
||||
// been run.
|
||||
static final FlutterUsage _usage =
|
||||
FlutterUsage.doesStoreExist ? FlutterUsage() : null;
|
||||
|
||||
// Accessing DevTools usage file e.g., ~/.devtools
|
||||
static final DevToolsUsage _devToolsUsage = DevToolsUsage();
|
||||
|
||||
static DevToolsUsage get devToolsPreferences => _devToolsUsage;
|
||||
|
||||
/// Logs a page view in the DevTools server.
|
||||
///
|
||||
/// In the open-source version of DevTools, Google Analytics handles this
|
||||
/// without any need to involve the server.
|
||||
FutureOr<shelf.Response> logScreenView(shelf.Request request) =>
|
||||
notImplemented(request);
|
||||
|
||||
/// Return the value of the property.
|
||||
FutureOr<shelf.Response> getCompleted(shelf.Request request, String value) =>
|
||||
shelf.Response.ok('$value');
|
||||
|
||||
/// Return the value of the property after the property value has been set.
|
||||
FutureOr<shelf.Response> setCompleted(shelf.Request request, String value) =>
|
||||
shelf.Response.ok('$value');
|
||||
|
||||
/// A [shelf.Response] for API calls that encountered a request problem e.g.,
|
||||
/// setActiveSurvey not called.
|
||||
///
|
||||
/// This is a 400 Bad Request response.
|
||||
FutureOr<shelf.Response> badRequest([String logError]) {
|
||||
if (logError != null) print(logError);
|
||||
return shelf.Response(HttpStatus.badRequest);
|
||||
}
|
||||
|
||||
/// A [shelf.Response] for API calls that have not been implemented in this
|
||||
/// server.
|
||||
///
|
||||
/// This is a no-op 204 No Content response because returning 404 Not Found
|
||||
/// creates unnecessary noise in the console.
|
||||
FutureOr<shelf.Response> notImplemented(shelf.Request request) =>
|
||||
shelf.Response(HttpStatus.noContent);
|
||||
}
|
236
pkg/dds/lib/src/devtools/usage.dart
Normal file
236
pkg/dds/lib/src/devtools/usage.dart
Normal file
|
@ -0,0 +1,236 @@
|
|||
// Copyright 2021 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.
|
||||
|
||||
// @dart=2.9
|
||||
|
||||
// TODO(bkonyi): remove once package:devtools_server_api is available
|
||||
// See https://github.com/flutter/devtools/issues/2958.
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:usage/usage_io.dart';
|
||||
|
||||
import 'file_system.dart';
|
||||
|
||||
/// Access the file '~/.flutter'.
|
||||
class FlutterUsage {
|
||||
/// Create a new Usage instance; [versionOverride] and [configDirOverride] are
|
||||
/// used for testing.
|
||||
FlutterUsage({
|
||||
String settingsName = 'flutter',
|
||||
String versionOverride,
|
||||
String configDirOverride,
|
||||
}) {
|
||||
_analytics = AnalyticsIO('', settingsName, '');
|
||||
}
|
||||
|
||||
Analytics _analytics;
|
||||
|
||||
/// Does the .flutter store exist?
|
||||
static bool get doesStoreExist {
|
||||
return LocalFileSystem.flutterStoreExists();
|
||||
}
|
||||
|
||||
bool get isFirstRun => _analytics.firstRun;
|
||||
|
||||
bool get enabled => _analytics.enabled;
|
||||
|
||||
set enabled(bool value) => _analytics.enabled = value;
|
||||
|
||||
String get clientId => _analytics.clientId;
|
||||
}
|
||||
|
||||
// Access the DevTools on disk store (~/.devtools/.devtools).
|
||||
class DevToolsUsage {
|
||||
/// Create a new Usage instance; [versionOverride] and [configDirOverride] are
|
||||
/// used for testing.
|
||||
DevToolsUsage({
|
||||
String versionOverride,
|
||||
String configDirOverride,
|
||||
}) {
|
||||
LocalFileSystem.maybeMoveLegacyDevToolsStore();
|
||||
properties = IOPersistentProperties(
|
||||
storeName,
|
||||
documentDirPath: LocalFileSystem.devToolsDir(),
|
||||
);
|
||||
}
|
||||
|
||||
static const storeName = '.devtools';
|
||||
|
||||
/// The activeSurvey is the property name of a top-level property
|
||||
/// existing or created in the file ~/.devtools
|
||||
/// If the property doesn't exist it is created with default survey values:
|
||||
///
|
||||
/// properties[activeSurvey]['surveyActionTaken'] = false;
|
||||
/// properties[activeSurvey]['surveyShownCount'] = 0;
|
||||
///
|
||||
/// It is a requirement that the API apiSetActiveSurvey must be called before
|
||||
/// calling any survey method on DevToolsUsage (addSurvey, rewriteActiveSurvey,
|
||||
/// surveyShownCount, incrementSurveyShownCount, or surveyActionTaken).
|
||||
String _activeSurvey;
|
||||
|
||||
IOPersistentProperties properties;
|
||||
|
||||
static const _surveyActionTaken = 'surveyActionTaken';
|
||||
static const _surveyShownCount = 'surveyShownCount';
|
||||
|
||||
void reset() {
|
||||
properties.remove('firstRun');
|
||||
properties['enabled'] = false;
|
||||
}
|
||||
|
||||
bool get isFirstRun {
|
||||
properties['firstRun'] = properties['firstRun'] == null;
|
||||
return properties['firstRun'];
|
||||
}
|
||||
|
||||
bool get enabled {
|
||||
if (properties['enabled'] == null) {
|
||||
properties['enabled'] = false;
|
||||
}
|
||||
|
||||
return properties['enabled'];
|
||||
}
|
||||
|
||||
set enabled(bool value) {
|
||||
properties['enabled'] = value;
|
||||
return properties['enabled'];
|
||||
}
|
||||
|
||||
bool surveyNameExists(String surveyName) => properties[surveyName] != null;
|
||||
|
||||
void _addSurvey(String surveyName) {
|
||||
assert(activeSurvey != null);
|
||||
assert(activeSurvey == surveyName);
|
||||
rewriteActiveSurvey(false, 0);
|
||||
}
|
||||
|
||||
String get activeSurvey => _activeSurvey;
|
||||
|
||||
set activeSurvey(String surveyName) {
|
||||
assert(surveyName != null);
|
||||
_activeSurvey = surveyName;
|
||||
|
||||
if (!surveyNameExists(activeSurvey)) {
|
||||
// Create the survey if property is non-existent in ~/.devtools
|
||||
_addSurvey(activeSurvey);
|
||||
}
|
||||
}
|
||||
|
||||
/// Need to rewrite the entire survey structure for property to be persisted.
|
||||
void rewriteActiveSurvey(bool actionTaken, int shownCount) {
|
||||
assert(activeSurvey != null);
|
||||
properties[activeSurvey] = {
|
||||
_surveyActionTaken: actionTaken,
|
||||
_surveyShownCount: shownCount,
|
||||
};
|
||||
}
|
||||
|
||||
int get surveyShownCount {
|
||||
assert(activeSurvey != null);
|
||||
final prop = properties[activeSurvey];
|
||||
if (prop[_surveyShownCount] == null) {
|
||||
rewriteActiveSurvey(prop[_surveyActionTaken], 0);
|
||||
}
|
||||
return properties[activeSurvey][_surveyShownCount];
|
||||
}
|
||||
|
||||
void incrementSurveyShownCount() {
|
||||
assert(activeSurvey != null);
|
||||
surveyShownCount; // Ensure surveyShownCount has been initialized.
|
||||
final prop = properties[activeSurvey];
|
||||
rewriteActiveSurvey(prop[_surveyActionTaken], prop[_surveyShownCount] + 1);
|
||||
}
|
||||
|
||||
bool get surveyActionTaken {
|
||||
assert(activeSurvey != null);
|
||||
return properties[activeSurvey][_surveyActionTaken] == true;
|
||||
}
|
||||
|
||||
set surveyActionTaken(bool value) {
|
||||
assert(activeSurvey != null);
|
||||
final prop = properties[activeSurvey];
|
||||
rewriteActiveSurvey(value, prop[_surveyShownCount]);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class PersistentProperties {
|
||||
PersistentProperties(this.name);
|
||||
|
||||
final String name;
|
||||
|
||||
dynamic operator [](String key);
|
||||
|
||||
void operator []=(String key, dynamic value);
|
||||
|
||||
/// Re-read settings from the backing store.
|
||||
///
|
||||
/// May be a no-op on some platforms.
|
||||
void syncSettings();
|
||||
}
|
||||
|
||||
const JsonEncoder _jsonEncoder = JsonEncoder.withIndent(' ');
|
||||
|
||||
class IOPersistentProperties extends PersistentProperties {
|
||||
IOPersistentProperties(
|
||||
String name, {
|
||||
String documentDirPath,
|
||||
}) : super(name) {
|
||||
final String fileName = name.replaceAll(' ', '_');
|
||||
documentDirPath ??= LocalFileSystem.devToolsDir();
|
||||
_file = File(path.join(documentDirPath, fileName));
|
||||
if (!_file.existsSync()) {
|
||||
_file.createSync(recursive: true);
|
||||
}
|
||||
syncSettings();
|
||||
}
|
||||
|
||||
IOPersistentProperties.fromFile(File file) : super(path.basename(file.path)) {
|
||||
_file = file;
|
||||
if (!_file.existsSync()) {
|
||||
_file.createSync(recursive: true);
|
||||
}
|
||||
syncSettings();
|
||||
}
|
||||
|
||||
File _file;
|
||||
|
||||
Map _map;
|
||||
|
||||
@override
|
||||
dynamic operator [](String key) => _map[key];
|
||||
|
||||
@override
|
||||
void operator []=(String key, dynamic value) {
|
||||
if (value == null && !_map.containsKey(key)) return;
|
||||
if (_map[key] == value) return;
|
||||
|
||||
if (value == null) {
|
||||
_map.remove(key);
|
||||
} else {
|
||||
_map[key] = value;
|
||||
}
|
||||
|
||||
try {
|
||||
_file.writeAsStringSync(_jsonEncoder.convert(_map) + '\n');
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@override
|
||||
void syncSettings() {
|
||||
try {
|
||||
String contents = _file.readAsStringSync();
|
||||
if (contents.isEmpty) contents = '{}';
|
||||
_map = jsonDecode(contents);
|
||||
} catch (_) {
|
||||
_map = {};
|
||||
}
|
||||
}
|
||||
|
||||
void remove(String propertyName) {
|
||||
_map.remove(propertyName);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ description: >-
|
|||
A library used to spawn the Dart Developer Service, used to communicate with
|
||||
a Dart VM Service instance.
|
||||
|
||||
version: 1.7.6
|
||||
version: 1.8.0-dev
|
||||
|
||||
homepage: https://github.com/dart-lang/sdk/tree/master/pkg/dds
|
||||
|
||||
|
@ -12,18 +12,21 @@ environment:
|
|||
|
||||
dependencies:
|
||||
async: ^2.4.1
|
||||
devtools_shared: ^2.0.0
|
||||
json_rpc_2: ^2.2.0
|
||||
meta: ^1.1.8
|
||||
path: ^1.8.0
|
||||
pedantic: ^1.7.0
|
||||
shelf: ^1.0.0
|
||||
shelf_proxy: ^1.0.0
|
||||
shelf_static: ^1.0.0-dev
|
||||
shelf_web_socket: ^1.0.0
|
||||
sse: ^3.7.0
|
||||
stream_channel: ^2.0.0
|
||||
usage: ^4.0.0
|
||||
vm_service: ^6.0.1-nullsafety.0
|
||||
web_socket_channel: ^2.0.0
|
||||
|
||||
dev_dependencies:
|
||||
shelf_static: ^1.0.0
|
||||
test: ^1.0.0
|
||||
webdriver: ^3.0.0
|
||||
|
|
|
@ -553,13 +553,12 @@ static Dart_Isolate CreateAndSetupServiceIsolate(const char* script_uri,
|
|||
vm_service_server_port = 0;
|
||||
}
|
||||
|
||||
// We do not want to wait for DDS to advertise availability of VM service in the
|
||||
// following scenarios:
|
||||
// - When the VM service is disabled (can be started at a later time via SIGQUIT).
|
||||
// - The DartDev CLI is disabled (CLI isolate starts DDS) and VM service is enabled.
|
||||
bool wait_for_dds_to_advertise_service =
|
||||
!Options::disable_dart_dev() && Options::enable_vm_service();
|
||||
|
||||
// We do not want to wait for DDS to advertise availability of VM service in
|
||||
// the following scenarios:
|
||||
// - The DartDev CLI is disabled (CLI isolate starts DDS) and VM service is
|
||||
// enabled.
|
||||
// TODO(bkonyi): do we want to tie DevTools / DDS to the CLI in the long run?
|
||||
bool wait_for_dds_to_advertise_service = !Options::disable_dart_dev();
|
||||
// Load embedder specific bits and return.
|
||||
if (!VmService::Setup(
|
||||
Options::disable_dart_dev() ? Options::vm_service_server_ip()
|
||||
|
|
|
@ -583,7 +583,7 @@ bool Options::ParseArguments(int argc,
|
|||
run_command = true;
|
||||
}
|
||||
if (!Options::disable_dart_dev() && enable_vm_service_ && run_command) {
|
||||
const char* dds_format_str = "--launch-dds=%s:%d";
|
||||
const char* dds_format_str = "--launch-dds=%s\\:%d";
|
||||
size_t size =
|
||||
snprintf(nullptr, 0, dds_format_str, vm_service_server_ip(),
|
||||
vm_service_server_port());
|
||||
|
@ -603,6 +603,7 @@ bool Options::ParseArguments(int argc,
|
|||
first_option = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify consistency of arguments.
|
||||
|
||||
// snapshot_depfile is an alias for depfile. Passing them both is an error.
|
||||
|
|
13
sdk/BUILD.gn
13
sdk/BUILD.gn
|
@ -252,6 +252,18 @@ copy_tree_specs += [
|
|||
},
|
||||
]
|
||||
|
||||
# This rule copies the pre-built DevTools application to
|
||||
# bin/resources/devtools/
|
||||
copy_tree_specs += [
|
||||
{
|
||||
target = "copy_prebuilt_devtools"
|
||||
visibility = [ ":create_common_sdk" ]
|
||||
source = "../third_party/devtools/web"
|
||||
dest = "$root_out_dir/dart-sdk/bin/resources/devtools"
|
||||
ignore_patterns = "{}"
|
||||
},
|
||||
]
|
||||
|
||||
# This loop generates rules to copy libraries to lib/
|
||||
foreach(library, _full_sdk_libraries) {
|
||||
copy_tree_specs += [
|
||||
|
@ -811,6 +823,7 @@ group("create_common_sdk") {
|
|||
":copy_libraries_dart",
|
||||
":copy_libraries_specification",
|
||||
":copy_license",
|
||||
":copy_prebuilt_devtools",
|
||||
":copy_readme",
|
||||
":copy_vm_dill_files",
|
||||
":write_dartdoc_options",
|
||||
|
|
|
@ -43,6 +43,7 @@ bool _waitForDdsToAdvertiseService = false;
|
|||
// HTTP server.
|
||||
Server? server;
|
||||
Future<Server>? serverFuture;
|
||||
_DebuggingSession? ddsInstance;
|
||||
|
||||
Server _lazyServerBoot() {
|
||||
var localServer = server;
|
||||
|
@ -58,6 +59,90 @@ Server _lazyServerBoot() {
|
|||
return localServer;
|
||||
}
|
||||
|
||||
/// Responsible for launching a DevTools instance when the service is started
|
||||
/// via SIGQUIT.
|
||||
class _DebuggingSession {
|
||||
Future<bool> start(
|
||||
String host,
|
||||
String port,
|
||||
bool disableServiceAuthCodes,
|
||||
bool enableDevTools,
|
||||
) async {
|
||||
final dartPath = Uri.parse(Platform.resolvedExecutable);
|
||||
final dartDir = [
|
||||
'', // Include leading '/'
|
||||
...dartPath.pathSegments.sublist(
|
||||
0,
|
||||
dartPath.pathSegments.length - 1,
|
||||
),
|
||||
].join('/');
|
||||
|
||||
final fullSdk = dartDir.endsWith('bin');
|
||||
|
||||
final ddsSnapshot = [
|
||||
dartDir,
|
||||
fullSdk ? 'snapshots' : 'gen',
|
||||
'dds.dart.snapshot',
|
||||
].join('/');
|
||||
|
||||
final devToolsBinaries = [
|
||||
dartDir,
|
||||
if (fullSdk) 'resources',
|
||||
'devtools',
|
||||
].join('/');
|
||||
|
||||
const enableLogging = false;
|
||||
_process = await Process.start(
|
||||
dartPath.toString(),
|
||||
[
|
||||
ddsSnapshot,
|
||||
server!.serverAddress!.toString(),
|
||||
host,
|
||||
port,
|
||||
disableServiceAuthCodes.toString(),
|
||||
enableDevTools.toString(),
|
||||
devToolsBinaries,
|
||||
enableLogging.toString(),
|
||||
],
|
||||
mode: ProcessStartMode.detachedWithStdio,
|
||||
);
|
||||
final completer = Completer<void>();
|
||||
late StreamSubscription stderrSub;
|
||||
stderrSub = _process!.stderr.transform(utf8.decoder).listen((event) {
|
||||
final result = json.decode(event) as Map<String, dynamic>;
|
||||
final state = result['state'];
|
||||
if (state == 'started') {
|
||||
if (result.containsKey('devToolsUri')) {
|
||||
// NOTE: update pkg/dartdev/lib/src/commands/run.dart if this message
|
||||
// is changed to ensure consistency.
|
||||
const devToolsMessagePrefix =
|
||||
'The Dart DevTools debugger and profiler is available at:';
|
||||
final devToolsUri = result['devToolsUri'];
|
||||
print('$devToolsMessagePrefix $devToolsUri');
|
||||
}
|
||||
stderrSub.cancel();
|
||||
completer.complete();
|
||||
} else {
|
||||
stderrSub.cancel();
|
||||
completer.completeError(
|
||||
'Could not start Observatory HTTP server',
|
||||
);
|
||||
}
|
||||
});
|
||||
try {
|
||||
await completer.future;
|
||||
return true;
|
||||
} catch (e) {
|
||||
stderr.write(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown() => _process!.kill();
|
||||
|
||||
Process? _process;
|
||||
}
|
||||
|
||||
Future cleanupCallback() async {
|
||||
// Cancel the sigquit subscription.
|
||||
if (_signalSubscription != null) {
|
||||
|
@ -221,10 +306,6 @@ void webServerAcceptNewWebSocketConnections(bool enable) {
|
|||
_server.acceptNewWebSocketConnections = enable;
|
||||
}
|
||||
|
||||
void _clearFuture(_) {
|
||||
serverFuture = null;
|
||||
}
|
||||
|
||||
_onSignal(ProcessSignal signal) {
|
||||
if (serverFuture != null) {
|
||||
// Still waiting.
|
||||
|
@ -233,9 +314,21 @@ _onSignal(ProcessSignal signal) {
|
|||
final _server = _lazyServerBoot();
|
||||
// Toggle HTTP server.
|
||||
if (_server.running) {
|
||||
_server.shutdown(true).then(_clearFuture);
|
||||
_server.shutdown(true).then((_) async {
|
||||
ddsInstance?.shutdown();
|
||||
await VMService().clearState();
|
||||
serverFuture = null;
|
||||
});
|
||||
} else {
|
||||
_server.startup().then(_clearFuture);
|
||||
_server.startup().then((_) {
|
||||
ddsInstance = _DebuggingSession()
|
||||
..start(
|
||||
_server._ip,
|
||||
_server._port.toString(),
|
||||
false,
|
||||
true,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ class WebSocketClient extends Client {
|
|||
socket.done.then((_) => close());
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
Future<void> disconnect() async {
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
await socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,8 +102,8 @@ class HttpRequestClient extends Client {
|
|||
HttpRequestClient(this.request, VMService service)
|
||||
: super(service, sendEvents: false);
|
||||
|
||||
disconnect() {
|
||||
request.response.close();
|
||||
Future<void> disconnect() async {
|
||||
await request.response.close();
|
||||
close();
|
||||
}
|
||||
|
||||
|
|
|
@ -411,6 +411,16 @@ class VMService extends MessageRouter {
|
|||
replyPort.send(bytes);
|
||||
}
|
||||
|
||||
Future<void> clearState() async {
|
||||
// Create a copy of the set as a list because client.disconnect() will
|
||||
// alter the connected clients set.
|
||||
final clientsList = clients.toList();
|
||||
for (final client in clientsList) {
|
||||
await client.disconnect();
|
||||
}
|
||||
devfs.cleanup();
|
||||
}
|
||||
|
||||
Future _exit() async {
|
||||
isExiting = true;
|
||||
|
||||
|
@ -423,14 +433,7 @@ class VMService extends MessageRouter {
|
|||
// Close receive ports.
|
||||
isolateControlPort.close();
|
||||
scriptLoadPort.close();
|
||||
|
||||
// Create a copy of the set as a list because client.disconnect() will
|
||||
// alter the connected clients set.
|
||||
final clientsList = clients.toList();
|
||||
for (final client in clientsList) {
|
||||
client.disconnect();
|
||||
}
|
||||
devfs.cleanup();
|
||||
await clearState();
|
||||
final cleanup = VMServiceEmbedderHooks.cleanup;
|
||||
if (cleanup != null) {
|
||||
await cleanup();
|
||||
|
|
3
third_party/devtools/update.sh
vendored
3
third_party/devtools/update.sh
vendored
|
@ -30,12 +30,11 @@ git checkout -b cipd_release $1
|
|||
# to serve from DDS.
|
||||
mkdir cipd_package
|
||||
cp -R packages/devtools/build/ cipd_package/web
|
||||
cp -r packages/devtools_server cipd_package
|
||||
cp -r packages/devtools_shared cipd_package
|
||||
|
||||
cipd create \
|
||||
-name dart/third_party/flutter/devtools \
|
||||
-in cipd_package \
|
||||
-install-mode copy \
|
||||
-tag revision:$1
|
||||
-tag git_revision:$1
|
||||
|
||||
|
|
|
@ -322,6 +322,7 @@
|
|||
"xcodebuild/ReleaseSIMARM64C/",
|
||||
"xcodebuild/ReleaseX64/",
|
||||
"xcodebuild/ReleaseX64C/",
|
||||
"pkg/",
|
||||
"samples/",
|
||||
"samples_2/",
|
||||
"samples-dev/",
|
||||
|
@ -329,6 +330,7 @@
|
|||
"third_party/android_tools/sdk/platform-tools/adb",
|
||||
"third_party/android_tools/ndk/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-strip",
|
||||
"third_party/android_tools/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-strip",
|
||||
"third_party/devtools/",
|
||||
"third_party/webdriver/",
|
||||
"third_party/pkg/",
|
||||
"third_party/pkg_tested/",
|
||||
|
|
|
@ -57,6 +57,8 @@ void main(List<String> args) {
|
|||
packageDirectory(
|
||||
'runtime/observatory_2/tests/service_2/observatory_test_package_2'),
|
||||
packageDirectory('sdk/lib/_internal/sdk_library_metadata'),
|
||||
packageDirectory('third_party/devtools/devtools_server'),
|
||||
packageDirectory('third_party/devtools/devtools_shared'),
|
||||
packageDirectory('third_party/pkg/protobuf/protobuf'),
|
||||
packageDirectory('tools/package_deps'),
|
||||
];
|
||||
|
|
|
@ -2,12 +2,14 @@
|
|||
# for details. All rights reserved. Use of this source code is governed by a
|
||||
# BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import("../../build/dart/copy_tree.gni")
|
||||
import("../application_snapshot.gni")
|
||||
|
||||
group("dartdev") {
|
||||
public_deps = [
|
||||
":copy_dartdev_kernel",
|
||||
":copy_dartdev_snapshot",
|
||||
":copy_prebuilt_devtools",
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -39,3 +41,15 @@ application_snapshot("generate_dartdev_snapshot") {
|
|||
deps = [ "../dds:dds" ]
|
||||
output = "$root_gen_dir/dartdev.dart.snapshot"
|
||||
}
|
||||
|
||||
copy_trees("copy_prebuilt_devtools") {
|
||||
sources = [
|
||||
{
|
||||
target = "copy_prebuilt_devtools"
|
||||
visibility = [ ":dartdev" ]
|
||||
source = "../../third_party/devtools/web"
|
||||
dest = "$root_out_dir/devtools"
|
||||
ignore_patterns = "{}"
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue