| // 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); |
| } |
| } |