Skip to content

Commit 04595bc

Browse files
bkonyiandrewkolos
andauthored
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 <[email protected]>
1 parent 055350f commit 04595bc

File tree

5 files changed

+104
-245
lines changed

5 files changed

+104
-245
lines changed

packages/flutter_tools/lib/src/base/dds.dart

Lines changed: 26 additions & 200 deletions
Original file line numberDiff line numberDiff line change
@@ -4,220 +4,46 @@
44

55
import 'dart:async';
66

7+
import 'package:dds/dds.dart';
8+
import 'package:dds/dds_launcher.dart';
79
import 'package:meta/meta.dart';
810

911
import '../artifacts.dart';
10-
import '../convert.dart';
1112
import '../device.dart';
1213
import '../globals.dart' as globals;
1314
import 'io.dart' as io;
1415
import 'logger.dart';
1516

16-
/// A representation of the current DDS state including:
17-
///
18-
/// - The process the external DDS instance is running in
19-
/// - The service URI DDS is being served on
20-
/// - The URI DevTools is being served on, if applicable
21-
/// - The URI DTD is being served on, if applicable
22-
typedef DartDevelopmentServiceInstance = ({
23-
io.Process? process,
24-
Uri? serviceUri,
25-
Uri? devToolsUri,
26-
Uri? dtdUri,
27-
});
28-
29-
/// The default DDSLauncherCallback used to spawn DDS.
30-
Future<DartDevelopmentServiceInstance> defaultStartDartDevelopmentService(
31-
Uri remoteVmServiceUri, {
32-
required bool enableAuthCodes,
33-
required bool ipv6,
34-
required bool enableDevTools,
35-
required List<String> cachedUserTags,
36-
Uri? serviceUri,
37-
String? google3WorkspaceRoot,
38-
Uri? devToolsServerAddress,
39-
}) async {
40-
final String exe = globals.artifacts!.getArtifactPath(
41-
Artifact.engineDartBinary,
42-
);
43-
final io.Process process = await io.Process.start(
44-
exe,
45-
<String>[
46-
'development-service',
47-
'--vm-service-uri=$remoteVmServiceUri',
48-
if (serviceUri != null) ...<String>[
49-
'--bind-address=${serviceUri.host}',
50-
'--bind-port=${serviceUri.port}',
51-
],
52-
if (!enableAuthCodes) '--disable-service-auth-codes',
53-
if (google3WorkspaceRoot != null)
54-
'--google3-workspace-root=$google3WorkspaceRoot',
55-
for (final String tag in cachedUserTags) '--cached-user-tags=$tag',
56-
],
57-
);
58-
final Completer<DartDevelopmentServiceInstance> completer =
59-
Completer<DartDevelopmentServiceInstance>();
60-
late StreamSubscription<Object?> stderrSub;
61-
stderrSub = process.stderr
62-
.transform(utf8.decoder)
63-
.transform(json.decoder)
64-
.listen((Object? result) {
65-
if (result
66-
case {
67-
'state': 'started',
68-
'ddsUri': final String ddsUriStr,
69-
}) {
70-
final Uri ddsUri = Uri.parse(ddsUriStr);
71-
final String? devToolsUriStr = result['devToolsUri'] as String?;
72-
final Uri? devToolsUri =
73-
devToolsUriStr == null ? null : Uri.parse(devToolsUriStr);
74-
final String? dtdUriStr =
75-
(result['dtd'] as Map<String, Object?>?)?['uri'] as String?;
76-
final Uri? dtdUri = dtdUriStr == null ? null : Uri.parse(dtdUriStr);
77-
78-
completer.complete((
79-
process: process,
80-
serviceUri: ddsUri,
81-
devToolsUri: devToolsUri,
82-
dtdUri: dtdUri,
83-
));
84-
} else if (result
85-
case {
86-
'state': 'error',
87-
'error': final String error,
88-
}) {
89-
final Map<String, Object?>? exceptionDetails =
90-
result['ddsExceptionDetails'] as Map<String, Object?>?;
91-
completer.completeError(
92-
exceptionDetails != null
93-
? DartDevelopmentServiceException.fromJson(exceptionDetails)
94-
: StateError(error),
95-
);
96-
} else {
97-
throw StateError('Unexpected result from DDS: $result');
98-
}
99-
stderrSub.cancel();
100-
});
101-
return completer.future;
102-
}
17+
export 'package:dds/dds.dart'
18+
show
19+
DartDevelopmentServiceException,
20+
ExistingDartDevelopmentServiceException;
10321

104-
typedef DDSLauncherCallback = Future<DartDevelopmentServiceInstance> Function(
105-
Uri remoteVmServiceUri, {
106-
required bool enableAuthCodes,
107-
required bool ipv6,
108-
required bool enableDevTools,
109-
required List<String> cachedUserTags,
22+
typedef DDSLauncherCallback = Future<DartDevelopmentServiceLauncher> Function({
23+
required Uri remoteVmServiceUri,
11024
Uri? serviceUri,
111-
String? google3WorkspaceRoot,
25+
bool enableAuthCodes,
26+
bool serveDevTools,
11227
Uri? devToolsServerAddress,
28+
bool enableServicePortFallback,
29+
List<String> cachedUserTags,
30+
String? dartExecutable,
31+
Uri? google3WorkspaceRoot,
11332
});
11433

11534
// TODO(fujino): This should be direct injected, rather than mutable global state.
11635
/// Used by tests to override the DDS spawn behavior for mocking purposes.
11736
@visibleForTesting
118-
DDSLauncherCallback ddsLauncherCallback = defaultStartDartDevelopmentService;
119-
120-
/// Thrown by DDS during initialization failures, unexpected connection issues,
121-
/// and when attempting to spawn DDS when an existing DDS instance exists.
122-
class DartDevelopmentServiceException implements Exception {
123-
factory DartDevelopmentServiceException.fromJson(Map<String, Object?> json) {
124-
if (json
125-
case {
126-
'error_code': final int errorCode,
127-
'message': final String message,
128-
}) {
129-
return switch (errorCode) {
130-
existingDdsInstanceError =>
131-
DartDevelopmentServiceException.existingDdsInstance(
132-
message,
133-
ddsUri: Uri.parse(json['uri']! as String),
134-
),
135-
failedToStartError => DartDevelopmentServiceException.failedToStart(),
136-
connectionError =>
137-
DartDevelopmentServiceException.connectionIssue(message),
138-
_ => throw StateError(
139-
'Invalid DartDevelopmentServiceException error_code: $errorCode',
140-
),
141-
};
142-
}
143-
throw StateError('Invalid DartDevelopmentServiceException JSON: $json');
144-
}
145-
146-
/// Thrown when `DartDeveloperService.startDartDevelopmentService` is called
147-
/// and the target VM service already has a Dart Developer Service instance
148-
/// connected.
149-
factory DartDevelopmentServiceException.existingDdsInstance(
150-
String message, {
151-
Uri? ddsUri,
152-
}) {
153-
return ExistingDartDevelopmentServiceException._(
154-
message,
155-
ddsUri: ddsUri,
156-
);
157-
}
158-
159-
/// Thrown when the connection to the remote VM service terminates unexpectedly
160-
/// during Dart Development Service startup.
161-
factory DartDevelopmentServiceException.failedToStart() {
162-
return DartDevelopmentServiceException._(
163-
failedToStartError,
164-
'Failed to start Dart Development Service',
165-
);
166-
}
167-
168-
/// Thrown when a connection error has occurred after startup.
169-
factory DartDevelopmentServiceException.connectionIssue(String message) {
170-
return DartDevelopmentServiceException._(connectionError, message);
171-
}
172-
173-
DartDevelopmentServiceException._(this.errorCode, this.message);
174-
175-
/// Set when `DartDeveloperService.startDartDevelopmentService` is called and
176-
/// the target VM service already has a Dart Developer Service instance
177-
/// connected.
178-
static const int existingDdsInstanceError = 1;
179-
180-
/// Set when the connection to the remote VM service terminates unexpectedly
181-
/// during Dart Development Service startup.
182-
static const int failedToStartError = 2;
183-
184-
/// Set when a connection error has occurred after startup.
185-
static const int connectionError = 3;
186-
187-
@override
188-
String toString() => 'DartDevelopmentServiceException: $message';
189-
190-
final int errorCode;
191-
final String message;
192-
}
193-
194-
/// Thrown when attempting to start a new DDS instance when one already exists.
195-
class ExistingDartDevelopmentServiceException
196-
extends DartDevelopmentServiceException {
197-
ExistingDartDevelopmentServiceException._(
198-
String message, {
199-
this.ddsUri,
200-
}) : super._(
201-
DartDevelopmentServiceException.existingDdsInstanceError,
202-
message,
203-
);
204-
205-
/// The URI of the existing DDS instance, if available.
206-
///
207-
/// This URI is the base HTTP URI such as `http://127.0.0.1:1234/AbcDefg=/`,
208-
/// not the WebSocket URI (which can be obtained by mapping the scheme to
209-
/// `ws` (or `wss`) and appending `ws` to the path segments).
210-
final Uri? ddsUri;
211-
}
37+
DDSLauncherCallback ddsLauncherCallback = DartDevelopmentServiceLauncher.start;
21238

21339
/// Helper class to launch a [dds.DartDevelopmentService]. Allows for us to
21440
/// mock out this functionality for testing purposes.
21541
class DartDevelopmentService with DartDevelopmentServiceLocalOperationsMixin {
21642
DartDevelopmentService({required Logger logger}) : _logger = logger;
21743

218-
DartDevelopmentServiceInstance? _ddsInstance;
44+
DartDevelopmentServiceLauncher? _ddsInstance;
21945

220-
Uri? get uri => _ddsInstance?.serviceUri ?? _existingDdsUri;
46+
Uri? get uri => _ddsInstance?.uri ?? _existingDdsUri;
22147
Uri? _existingDdsUri;
22248

22349
Future<void> get done => _completer.future;
@@ -257,25 +83,25 @@ class DartDevelopmentService with DartDevelopmentServiceLocalOperationsMixin {
25783

25884
try {
25985
_ddsInstance = await ddsLauncherCallback(
260-
vmServiceUri,
86+
remoteVmServiceUri: vmServiceUri,
26187
serviceUri: ddsUri,
26288
enableAuthCodes: disableServiceAuthCodes != true,
263-
ipv6: ipv6 ?? false,
264-
enableDevTools: enableDevTools,
26589
// Enables caching of CPU samples collected during application startup.
26690
cachedUserTags: cacheStartupProfile
26791
? const <String>['AppStartUp']
26892
: const <String>[],
269-
google3WorkspaceRoot: google3WorkspaceRoot,
27093
devToolsServerAddress: devToolsServerAddress,
94+
google3WorkspaceRoot: google3WorkspaceRoot != null
95+
? Uri.parse(google3WorkspaceRoot)
96+
: null,
97+
dartExecutable: globals.artifacts!.getArtifactPath(
98+
Artifact.engineDartBinary,
99+
),
271100
);
272-
final io.Process? process = _ddsInstance?.process;
273101

274102
// Complete the future if the DDS process is null, which happens in
275103
// testing.
276-
if (process != null) {
277-
unawaited(process.exitCode.whenComplete(completeFuture));
278-
}
104+
unawaited(_ddsInstance!.done.whenComplete(completeFuture));
279105
} on DartDevelopmentServiceException catch (e) {
280106
_logger.printTrace('Warning: Failed to start DDS: ${e.message}');
281107
if (e is ExistingDartDevelopmentServiceException) {
@@ -294,7 +120,7 @@ class DartDevelopmentService with DartDevelopmentServiceLocalOperationsMixin {
294120
}
295121
}
296122

297-
void shutdown() => _ddsInstance?.process?.kill();
123+
void shutdown() => _ddsInstance?.shutdown();
298124
}
299125

300126
/// Contains common functionality that can be used with any implementation of

packages/flutter_tools/test/general.shard/flutter_tester_device_test.dart

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import 'package:test/fake.dart';
2323
import '../src/context.dart';
2424
import '../src/fake_process_manager.dart';
2525
import '../src/fake_vm_services.dart';
26+
import '../src/fakes.dart';
2627

2728
void main() {
2829
late FakePlatform platform;
@@ -267,16 +268,18 @@ void main() {
267268
]);
268269
device = createDevice(enableVmService: true);
269270
originalDdsLauncher = ddsLauncherCallback;
270-
ddsLauncherCallback = (Uri remoteVmServiceUri, {
271-
required bool enableAuthCodes,
272-
required bool ipv6,
273-
required bool enableDevTools,
274-
required List<String> cachedUserTags,
271+
ddsLauncherCallback = ({
272+
required Uri remoteVmServiceUri,
275273
Uri? serviceUri,
276-
String? google3WorkspaceRoot,
274+
bool enableAuthCodes = true,
275+
bool serveDevTools = false,
277276
Uri? devToolsServerAddress,
277+
bool enableServicePortFallback = false,
278+
List<String> cachedUserTags = const <String>[],
279+
String? dartExecutable,
280+
Uri? google3WorkspaceRoot,
278281
}) async {
279-
return (process: null, serviceUri: Uri.parse('http://localhost:1234'), devToolsUri: null, dtdUri: null);
282+
return FakeDartDevelopmentServiceLauncher(uri: Uri.parse('http://localhost:1234'));
280283
};
281284
});
282285

packages/flutter_tools/test/general.shard/resident_runner_helpers.dart

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,13 +138,6 @@ const FakeVmServiceRequest evictShader = FakeVmServiceRequest(
138138
}
139139
);
140140

141-
const DartDevelopmentServiceInstance fakeDartDevelopmentServiceInstance = (
142-
process: null,
143-
serviceUri: null,
144-
devToolsUri: null,
145-
dtdUri: null,
146-
);
147-
148141
final Uri testUri = Uri.parse('foo://bar');
149142

150143
class FakeDartDevelopmentService extends Fake with DartDevelopmentServiceLocalOperationsMixin implements DartDevelopmentService {
@@ -164,6 +157,11 @@ class FakeDartDevelopmentServiceException implements DartDevelopmentServiceExcep
164157
@override
165158
final String message;
166159
static const String defaultMessage = 'A DDS instance is already connected at http://localhost:8181';
160+
161+
@override
162+
Map<String, Object?> toJson() {
163+
throw UnimplementedError();
164+
}
167165
}
168166

169167
class TestFlutterDevice extends FlutterDevice {

0 commit comments

Comments
 (0)