Launch DDS using DartDevelopmentServiceLauncher
(#154015)
`DartDevelopmentServiceLauncher` was created to share the DDS launch logic from flutter_tools with other Dart tooling. --------- Co-authored-by: Andrew Kolos <andrewrkolos@gmail.com>
This commit is contained in:
parent
055350f84a
commit
04595bc088
@ -4,220 +4,46 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dds/dds.dart';
|
||||
import 'package:dds/dds_launcher.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../artifacts.dart';
|
||||
import '../convert.dart';
|
||||
import '../device.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import 'io.dart' as io;
|
||||
import 'logger.dart';
|
||||
|
||||
/// A representation of the current DDS state including:
|
||||
///
|
||||
/// - The process the external DDS instance is running in
|
||||
/// - The service URI DDS is being served on
|
||||
/// - The URI DevTools is being served on, if applicable
|
||||
/// - The URI DTD is being served on, if applicable
|
||||
typedef DartDevelopmentServiceInstance = ({
|
||||
io.Process? process,
|
||||
Uri? serviceUri,
|
||||
Uri? devToolsUri,
|
||||
Uri? dtdUri,
|
||||
});
|
||||
export 'package:dds/dds.dart'
|
||||
show
|
||||
DartDevelopmentServiceException,
|
||||
ExistingDartDevelopmentServiceException;
|
||||
|
||||
/// The default DDSLauncherCallback used to spawn DDS.
|
||||
Future<DartDevelopmentServiceInstance> defaultStartDartDevelopmentService(
|
||||
Uri remoteVmServiceUri, {
|
||||
required bool enableAuthCodes,
|
||||
required bool ipv6,
|
||||
required bool enableDevTools,
|
||||
required List<String> cachedUserTags,
|
||||
typedef DDSLauncherCallback = Future<DartDevelopmentServiceLauncher> Function({
|
||||
required Uri remoteVmServiceUri,
|
||||
Uri? serviceUri,
|
||||
String? google3WorkspaceRoot,
|
||||
Uri? devToolsServerAddress,
|
||||
}) async {
|
||||
final String exe = globals.artifacts!.getArtifactPath(
|
||||
Artifact.engineDartBinary,
|
||||
);
|
||||
final io.Process process = await io.Process.start(
|
||||
exe,
|
||||
<String>[
|
||||
'development-service',
|
||||
'--vm-service-uri=$remoteVmServiceUri',
|
||||
if (serviceUri != null) ...<String>[
|
||||
'--bind-address=${serviceUri.host}',
|
||||
'--bind-port=${serviceUri.port}',
|
||||
],
|
||||
if (!enableAuthCodes) '--disable-service-auth-codes',
|
||||
if (google3WorkspaceRoot != null)
|
||||
'--google3-workspace-root=$google3WorkspaceRoot',
|
||||
for (final String tag in cachedUserTags) '--cached-user-tags=$tag',
|
||||
],
|
||||
);
|
||||
final Completer<DartDevelopmentServiceInstance> completer =
|
||||
Completer<DartDevelopmentServiceInstance>();
|
||||
late StreamSubscription<Object?> stderrSub;
|
||||
stderrSub = process.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(json.decoder)
|
||||
.listen((Object? result) {
|
||||
if (result
|
||||
case {
|
||||
'state': 'started',
|
||||
'ddsUri': final String ddsUriStr,
|
||||
}) {
|
||||
final Uri ddsUri = Uri.parse(ddsUriStr);
|
||||
final String? devToolsUriStr = result['devToolsUri'] as String?;
|
||||
final Uri? devToolsUri =
|
||||
devToolsUriStr == null ? null : Uri.parse(devToolsUriStr);
|
||||
final String? dtdUriStr =
|
||||
(result['dtd'] as Map<String, Object?>?)?['uri'] as String?;
|
||||
final Uri? dtdUri = dtdUriStr == null ? null : Uri.parse(dtdUriStr);
|
||||
|
||||
completer.complete((
|
||||
process: process,
|
||||
serviceUri: ddsUri,
|
||||
devToolsUri: devToolsUri,
|
||||
dtdUri: dtdUri,
|
||||
));
|
||||
} else if (result
|
||||
case {
|
||||
'state': 'error',
|
||||
'error': final String error,
|
||||
}) {
|
||||
final Map<String, Object?>? exceptionDetails =
|
||||
result['ddsExceptionDetails'] as Map<String, Object?>?;
|
||||
completer.completeError(
|
||||
exceptionDetails != null
|
||||
? DartDevelopmentServiceException.fromJson(exceptionDetails)
|
||||
: StateError(error),
|
||||
);
|
||||
} else {
|
||||
throw StateError('Unexpected result from DDS: $result');
|
||||
}
|
||||
stderrSub.cancel();
|
||||
});
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
typedef DDSLauncherCallback = Future<DartDevelopmentServiceInstance> Function(
|
||||
Uri remoteVmServiceUri, {
|
||||
required bool enableAuthCodes,
|
||||
required bool ipv6,
|
||||
required bool enableDevTools,
|
||||
required List<String> cachedUserTags,
|
||||
Uri? serviceUri,
|
||||
String? google3WorkspaceRoot,
|
||||
bool enableAuthCodes,
|
||||
bool serveDevTools,
|
||||
Uri? devToolsServerAddress,
|
||||
bool enableServicePortFallback,
|
||||
List<String> cachedUserTags,
|
||||
String? dartExecutable,
|
||||
Uri? google3WorkspaceRoot,
|
||||
});
|
||||
|
||||
// TODO(fujino): This should be direct injected, rather than mutable global state.
|
||||
/// Used by tests to override the DDS spawn behavior for mocking purposes.
|
||||
@visibleForTesting
|
||||
DDSLauncherCallback ddsLauncherCallback = defaultStartDartDevelopmentService;
|
||||
|
||||
/// Thrown by DDS during initialization failures, unexpected connection issues,
|
||||
/// and when attempting to spawn DDS when an existing DDS instance exists.
|
||||
class DartDevelopmentServiceException implements Exception {
|
||||
factory DartDevelopmentServiceException.fromJson(Map<String, Object?> json) {
|
||||
if (json
|
||||
case {
|
||||
'error_code': final int errorCode,
|
||||
'message': final String message,
|
||||
}) {
|
||||
return switch (errorCode) {
|
||||
existingDdsInstanceError =>
|
||||
DartDevelopmentServiceException.existingDdsInstance(
|
||||
message,
|
||||
ddsUri: Uri.parse(json['uri']! as String),
|
||||
),
|
||||
failedToStartError => DartDevelopmentServiceException.failedToStart(),
|
||||
connectionError =>
|
||||
DartDevelopmentServiceException.connectionIssue(message),
|
||||
_ => throw StateError(
|
||||
'Invalid DartDevelopmentServiceException error_code: $errorCode',
|
||||
),
|
||||
};
|
||||
}
|
||||
throw StateError('Invalid DartDevelopmentServiceException JSON: $json');
|
||||
}
|
||||
|
||||
/// Thrown when `DartDeveloperService.startDartDevelopmentService` is called
|
||||
/// and the target VM service already has a Dart Developer Service instance
|
||||
/// connected.
|
||||
factory DartDevelopmentServiceException.existingDdsInstance(
|
||||
String message, {
|
||||
Uri? ddsUri,
|
||||
}) {
|
||||
return ExistingDartDevelopmentServiceException._(
|
||||
message,
|
||||
ddsUri: ddsUri,
|
||||
);
|
||||
}
|
||||
|
||||
/// Thrown when the connection to the remote VM service terminates unexpectedly
|
||||
/// during Dart Development Service startup.
|
||||
factory DartDevelopmentServiceException.failedToStart() {
|
||||
return DartDevelopmentServiceException._(
|
||||
failedToStartError,
|
||||
'Failed to start Dart Development Service',
|
||||
);
|
||||
}
|
||||
|
||||
/// Thrown when a connection error has occurred after startup.
|
||||
factory DartDevelopmentServiceException.connectionIssue(String message) {
|
||||
return DartDevelopmentServiceException._(connectionError, message);
|
||||
}
|
||||
|
||||
DartDevelopmentServiceException._(this.errorCode, this.message);
|
||||
|
||||
/// Set when `DartDeveloperService.startDartDevelopmentService` is called and
|
||||
/// the target VM service already has a Dart Developer Service instance
|
||||
/// connected.
|
||||
static const int existingDdsInstanceError = 1;
|
||||
|
||||
/// Set when the connection to the remote VM service terminates unexpectedly
|
||||
/// during Dart Development Service startup.
|
||||
static const int failedToStartError = 2;
|
||||
|
||||
/// Set when a connection error has occurred after startup.
|
||||
static const int connectionError = 3;
|
||||
|
||||
@override
|
||||
String toString() => 'DartDevelopmentServiceException: $message';
|
||||
|
||||
final int errorCode;
|
||||
final String message;
|
||||
}
|
||||
|
||||
/// Thrown when attempting to start a new DDS instance when one already exists.
|
||||
class ExistingDartDevelopmentServiceException
|
||||
extends DartDevelopmentServiceException {
|
||||
ExistingDartDevelopmentServiceException._(
|
||||
String message, {
|
||||
this.ddsUri,
|
||||
}) : super._(
|
||||
DartDevelopmentServiceException.existingDdsInstanceError,
|
||||
message,
|
||||
);
|
||||
|
||||
/// The URI of the existing DDS instance, if available.
|
||||
///
|
||||
/// This URI is the base HTTP URI such as `http://127.0.0.1:1234/AbcDefg=/`,
|
||||
/// not the WebSocket URI (which can be obtained by mapping the scheme to
|
||||
/// `ws` (or `wss`) and appending `ws` to the path segments).
|
||||
final Uri? ddsUri;
|
||||
}
|
||||
DDSLauncherCallback ddsLauncherCallback = DartDevelopmentServiceLauncher.start;
|
||||
|
||||
/// Helper class to launch a [dds.DartDevelopmentService]. Allows for us to
|
||||
/// mock out this functionality for testing purposes.
|
||||
class DartDevelopmentService with DartDevelopmentServiceLocalOperationsMixin {
|
||||
DartDevelopmentService({required Logger logger}) : _logger = logger;
|
||||
|
||||
DartDevelopmentServiceInstance? _ddsInstance;
|
||||
DartDevelopmentServiceLauncher? _ddsInstance;
|
||||
|
||||
Uri? get uri => _ddsInstance?.serviceUri ?? _existingDdsUri;
|
||||
Uri? get uri => _ddsInstance?.uri ?? _existingDdsUri;
|
||||
Uri? _existingDdsUri;
|
||||
|
||||
Future<void> get done => _completer.future;
|
||||
@ -257,25 +83,25 @@ class DartDevelopmentService with DartDevelopmentServiceLocalOperationsMixin {
|
||||
|
||||
try {
|
||||
_ddsInstance = await ddsLauncherCallback(
|
||||
vmServiceUri,
|
||||
remoteVmServiceUri: vmServiceUri,
|
||||
serviceUri: ddsUri,
|
||||
enableAuthCodes: disableServiceAuthCodes != true,
|
||||
ipv6: ipv6 ?? false,
|
||||
enableDevTools: enableDevTools,
|
||||
// Enables caching of CPU samples collected during application startup.
|
||||
cachedUserTags: cacheStartupProfile
|
||||
? const <String>['AppStartUp']
|
||||
: const <String>[],
|
||||
google3WorkspaceRoot: google3WorkspaceRoot,
|
||||
devToolsServerAddress: devToolsServerAddress,
|
||||
google3WorkspaceRoot: google3WorkspaceRoot != null
|
||||
? Uri.parse(google3WorkspaceRoot)
|
||||
: null,
|
||||
dartExecutable: globals.artifacts!.getArtifactPath(
|
||||
Artifact.engineDartBinary,
|
||||
),
|
||||
);
|
||||
final io.Process? process = _ddsInstance?.process;
|
||||
|
||||
// Complete the future if the DDS process is null, which happens in
|
||||
// testing.
|
||||
if (process != null) {
|
||||
unawaited(process.exitCode.whenComplete(completeFuture));
|
||||
}
|
||||
unawaited(_ddsInstance!.done.whenComplete(completeFuture));
|
||||
} on DartDevelopmentServiceException catch (e) {
|
||||
_logger.printTrace('Warning: Failed to start DDS: ${e.message}');
|
||||
if (e is ExistingDartDevelopmentServiceException) {
|
||||
@ -294,7 +120,7 @@ class DartDevelopmentService with DartDevelopmentServiceLocalOperationsMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void shutdown() => _ddsInstance?.process?.kill();
|
||||
void shutdown() => _ddsInstance?.shutdown();
|
||||
}
|
||||
|
||||
/// Contains common functionality that can be used with any implementation of
|
||||
|
@ -23,6 +23,7 @@ import 'package:test/fake.dart';
|
||||
import '../src/context.dart';
|
||||
import '../src/fake_process_manager.dart';
|
||||
import '../src/fake_vm_services.dart';
|
||||
import '../src/fakes.dart';
|
||||
|
||||
void main() {
|
||||
late FakePlatform platform;
|
||||
@ -267,16 +268,18 @@ void main() {
|
||||
]);
|
||||
device = createDevice(enableVmService: true);
|
||||
originalDdsLauncher = ddsLauncherCallback;
|
||||
ddsLauncherCallback = (Uri remoteVmServiceUri, {
|
||||
required bool enableAuthCodes,
|
||||
required bool ipv6,
|
||||
required bool enableDevTools,
|
||||
required List<String> cachedUserTags,
|
||||
ddsLauncherCallback = ({
|
||||
required Uri remoteVmServiceUri,
|
||||
Uri? serviceUri,
|
||||
String? google3WorkspaceRoot,
|
||||
bool enableAuthCodes = true,
|
||||
bool serveDevTools = false,
|
||||
Uri? devToolsServerAddress,
|
||||
bool enableServicePortFallback = false,
|
||||
List<String> cachedUserTags = const <String>[],
|
||||
String? dartExecutable,
|
||||
Uri? google3WorkspaceRoot,
|
||||
}) async {
|
||||
return (process: null, serviceUri: Uri.parse('http://localhost:1234'), devToolsUri: null, dtdUri: null);
|
||||
return FakeDartDevelopmentServiceLauncher(uri: Uri.parse('http://localhost:1234'));
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -138,13 +138,6 @@ const FakeVmServiceRequest evictShader = FakeVmServiceRequest(
|
||||
}
|
||||
);
|
||||
|
||||
const DartDevelopmentServiceInstance fakeDartDevelopmentServiceInstance = (
|
||||
process: null,
|
||||
serviceUri: null,
|
||||
devToolsUri: null,
|
||||
dtdUri: null,
|
||||
);
|
||||
|
||||
final Uri testUri = Uri.parse('foo://bar');
|
||||
|
||||
class FakeDartDevelopmentService extends Fake with DartDevelopmentServiceLocalOperationsMixin implements DartDevelopmentService {
|
||||
@ -164,6 +157,11 @@ class FakeDartDevelopmentServiceException implements DartDevelopmentServiceExcep
|
||||
@override
|
||||
final String message;
|
||||
static const String defaultMessage = 'A DDS instance is already connected at http://localhost:8181';
|
||||
|
||||
@override
|
||||
Map<String, Object?> toJson() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
||||
class TestFlutterDevice extends FlutterDevice {
|
||||
|
@ -1938,13 +1938,16 @@ flutter:
|
||||
fakeVmServiceHost = FakeVmServiceHost(requests: <VmServiceExpectation>[]);
|
||||
final FakeDevice device = FakeDevice()
|
||||
..dds = DartDevelopmentService(logger: testLogger);
|
||||
ddsLauncherCallback = (Uri uri,
|
||||
{bool enableDevTools = false,
|
||||
ddsLauncherCallback = ({
|
||||
required Uri remoteVmServiceUri,
|
||||
Uri? serviceUri,
|
||||
bool enableAuthCodes = true,
|
||||
bool ipv6 = false, Uri? serviceUri,
|
||||
List<String> cachedUserTags = const <String>[],
|
||||
String? google3WorkspaceRoot,
|
||||
bool serveDevTools = false,
|
||||
Uri? devToolsServerAddress,
|
||||
bool enableServicePortFallback = false,
|
||||
List<String> cachedUserTags = const <String>[],
|
||||
String? dartExecutable,
|
||||
Uri? google3WorkspaceRoot,
|
||||
}) {
|
||||
throw DartDevelopmentServiceException.existingDdsInstance(
|
||||
'Existing DDS at http://localhost/existingDdsInMessage.',
|
||||
@ -1984,21 +1987,23 @@ flutter:
|
||||
final FakeDevice device = FakeDevice()
|
||||
..dds = DartDevelopmentService(logger: testLogger);
|
||||
final Completer<void>done = Completer<void>();
|
||||
ddsLauncherCallback = (Uri uri,
|
||||
{bool enableDevTools = false,
|
||||
ddsLauncherCallback = ({
|
||||
required Uri remoteVmServiceUri,
|
||||
Uri? serviceUri,
|
||||
bool enableAuthCodes = true,
|
||||
bool ipv6 = false, Uri? serviceUri,
|
||||
List<String> cachedUserTags = const <String>[],
|
||||
String? google3WorkspaceRoot,
|
||||
bool serveDevTools = false,
|
||||
Uri? devToolsServerAddress,
|
||||
bool enableServicePortFallback = false,
|
||||
List<String> cachedUserTags = const <String>[],
|
||||
String? dartExecutable,
|
||||
Uri? google3WorkspaceRoot,
|
||||
}) async {
|
||||
expect(uri, Uri(scheme: 'foo', host: 'bar'));
|
||||
expect(remoteVmServiceUri, Uri(scheme: 'foo', host: 'bar'));
|
||||
expect(enableAuthCodes, isFalse);
|
||||
expect(ipv6, isTrue);
|
||||
expect(serviceUri, Uri(scheme: 'http', host: '::1', port: 0));
|
||||
expect(cachedUserTags, isEmpty);
|
||||
done.complete();
|
||||
return fakeDartDevelopmentServiceInstance;
|
||||
return FakeDartDevelopmentServiceLauncher(uri: remoteVmServiceUri);
|
||||
};
|
||||
final TestFlutterDevice flutterDevice = TestFlutterDevice(
|
||||
device,
|
||||
@ -2029,19 +2034,19 @@ flutter:
|
||||
// See https://github.com/flutter/flutter/issues/72385 for context.
|
||||
final FakeDevice device = FakeDevice()
|
||||
..dds = DartDevelopmentService(logger: testLogger);
|
||||
ddsLauncherCallback = (
|
||||
Uri uri, {
|
||||
bool enableDevTools = false,
|
||||
bool enableAuthCodes = false,
|
||||
bool ipv6 = false,
|
||||
ddsLauncherCallback = ({
|
||||
required Uri remoteVmServiceUri,
|
||||
Uri? serviceUri,
|
||||
List<String> cachedUserTags = const <String>[],
|
||||
String? google3WorkspaceRoot,
|
||||
bool enableAuthCodes = true,
|
||||
bool serveDevTools = false,
|
||||
Uri? devToolsServerAddress,
|
||||
bool enableServicePortFallback = false,
|
||||
List<String> cachedUserTags = const <String>[],
|
||||
String? dartExecutable,
|
||||
Uri? google3WorkspaceRoot,
|
||||
}) {
|
||||
expect(uri, Uri(scheme: 'foo', host: 'bar'));
|
||||
expect(remoteVmServiceUri, Uri(scheme: 'foo', host: 'bar'));
|
||||
expect(enableAuthCodes, isTrue);
|
||||
expect(ipv6, isFalse);
|
||||
expect(serviceUri, Uri(scheme: 'http', host: '127.0.0.1', port: 0));
|
||||
expect(cachedUserTags, isEmpty);
|
||||
throw FakeDartDevelopmentServiceException(message: 'No URI');
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io' as io show IOSink, ProcessSignal, Stdout, StdoutException;
|
||||
|
||||
import 'package:dds/dds_launcher.dart';
|
||||
import 'package:flutter_tools/src/android/android_sdk.dart';
|
||||
import 'package:flutter_tools/src/android/android_studio.dart';
|
||||
import 'package:flutter_tools/src/android/java.dart';
|
||||
@ -696,6 +697,32 @@ class FakeJava extends Fake implements Java {
|
||||
}
|
||||
}
|
||||
|
||||
class FakeDartDevelopmentServiceLauncher extends Fake
|
||||
implements DartDevelopmentServiceLauncher {
|
||||
FakeDartDevelopmentServiceLauncher({
|
||||
required this.uri,
|
||||
this.devToolsUri,
|
||||
this.dtdUri,
|
||||
});
|
||||
|
||||
@override
|
||||
final Uri uri;
|
||||
|
||||
@override
|
||||
final Uri? devToolsUri;
|
||||
|
||||
@override
|
||||
final Uri? dtdUri;
|
||||
|
||||
@override
|
||||
Future<void> get done => _completer.future;
|
||||
|
||||
@override
|
||||
Future<void> shutdown() async => _completer.complete();
|
||||
|
||||
final Completer<void> _completer = Completer<void>();
|
||||
}
|
||||
|
||||
class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher {
|
||||
FakeDevtoolsLauncher({DevToolsServerAddress? serverAddress})
|
||||
: _serverAddress = serverAddress;
|
||||
|
Loading…
x
Reference in New Issue
Block a user