| // Copyright 2014 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import 'dart:async'; | |
| import 'package:dds/dds.dart' as dds; | |
| import 'package:meta/meta.dart'; | |
| import 'common.dart'; | |
| import 'context.dart'; | |
| import 'io.dart' as io; | |
| import 'logger.dart'; | |
| // TODO(fujino): This should be direct injected, rather than mutable global state. | |
| @visibleForTesting | |
| Future<dds.DartDevelopmentService> Function( | |
| Uri remoteVmServiceUri, { | |
| bool enableAuthCodes, | |
| bool ipv6, | |
| Uri? serviceUri, | |
| List<String> cachedUserTags, | |
| dds.UriConverter? uriConverter, | |
| }) ddsLauncherCallback = dds.DartDevelopmentService.startDartDevelopmentService; | |
| /// Helper class to launch a [dds.DartDevelopmentService]. Allows for us to | |
| /// mock out this functionality for testing purposes. | |
| class DartDevelopmentService { | |
| dds.DartDevelopmentService? _ddsInstance; | |
| Uri? get uri => _ddsInstance?.uri ?? _existingDdsUri; | |
| Uri? _existingDdsUri; | |
| Future<void> get done => _completer.future; | |
| final Completer<void> _completer = Completer<void>(); | |
| Future<void> startDartDevelopmentService( | |
| Uri vmServiceUri, { | |
| required Logger logger, | |
| int? hostPort, | |
| bool? ipv6, | |
| bool? disableServiceAuthCodes, | |
| bool cacheStartupProfile = false, | |
| }) async { | |
| final Uri ddsUri = Uri( | |
| scheme: 'http', | |
| host: ((ipv6 ?? false) ? io.InternetAddress.loopbackIPv6 : io.InternetAddress.loopbackIPv4).host, | |
| port: hostPort ?? 0, | |
| ); | |
| logger.printTrace( | |
| 'Launching a Dart Developer Service (DDS) instance at $ddsUri, ' | |
| 'connecting to VM service at $vmServiceUri.', | |
| ); | |
| try { | |
| _ddsInstance = await ddsLauncherCallback( | |
| vmServiceUri, | |
| serviceUri: ddsUri, | |
| enableAuthCodes: disableServiceAuthCodes != true, | |
| ipv6: ipv6 ?? false, | |
| // Enables caching of CPU samples collected during application startup. | |
| cachedUserTags: cacheStartupProfile ? const <String>['AppStartUp'] : const <String>[], | |
| uriConverter: context.get<dds.UriConverter>(), | |
| ); | |
| unawaited(_ddsInstance?.done.whenComplete(() { | |
| if (!_completer.isCompleted) { | |
| _completer.complete(); | |
| } | |
| })); | |
| logger.printTrace('DDS is listening at ${_ddsInstance?.uri}.'); | |
| } on dds.DartDevelopmentServiceException catch (e) { | |
| logger.printTrace('Warning: Failed to start DDS: ${e.message}'); | |
| if (e.errorCode == dds.DartDevelopmentServiceException.existingDdsInstanceError) { | |
| try { | |
| // First try to use the new field to avoid parsing from the message. | |
| _existingDdsUri = e is dds.ExistingDartDevelopmentServiceException ? e.ddsUri : null; | |
| // Otherwise, fall back to parsing from the exception (old DDS). | |
| // This is not completely reliable which is why the new field above | |
| // was added. | |
| if (_existingDdsUri == null) { | |
| String parsedUrl = e.message.split(' ').firstWhere((String e) => e.startsWith('http')); | |
| // Trim trailing full stops from the message. | |
| // https://github.com/flutter/flutter/issues/118609. | |
| if (parsedUrl.endsWith('.')) { | |
| parsedUrl = parsedUrl.substring(0, parsedUrl.length - 1); | |
| } | |
| _existingDdsUri ??= Uri.parse(parsedUrl); | |
| } | |
| } on StateError { | |
| if (e.message.contains('Existing VM service clients prevent DDS from taking control.')) { | |
| throwToolExit('${e.message}. Please rebuild your application with a newer version of Flutter.'); | |
| } | |
| logger.printError( | |
| 'DDS has failed to start and there is not an existing DDS instance ' | |
| 'available to connect to. Please file an issue at https://github.com/flutter/flutter/issues ' | |
| 'with the following error message:\n\n ${e.message}.' | |
| ); | |
| // DDS was unable to start for an unknown reason. Raise a StateError | |
| // so it can be reported by the crash reporter. | |
| throw StateError(e.message); | |
| } | |
| } | |
| if (!_completer.isCompleted) { | |
| _completer.complete(); | |
| } | |
| rethrow; | |
| } | |
| } | |
| Future<void> shutdown() async => _ddsInstance?.shutdown(); | |
| void setExternalDevToolsUri(Uri uri) { | |
| _ddsInstance?.setExternalDevToolsUri(uri); | |
| } | |
| } |