mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[ Widget Preview ] Add UUID to registered DTD streams and services (#180140)
DTD only supports a single instance of a registered service with a given name. For widget preview development, we sometimes want to use a DTD instance that's attached to an IDE to test IDE integration. However, IDEs frequently spawn their own widget preview instances which register services with DTD. In the case where `flutter widget-preview start --dtd-url=<dtd-url>` is run and `dtd-url` points to a DTD instance with another widget preview service running, the process simply crashes. This change adds a unique identifier to the widget preview DTD service and stream names that allows for each `flutter widget-preview start` instance to register its own unique widget preview DTD services, even if other widget preview instances are using the same DTD instance. Fixes https://github.com/flutter/flutter/issues/179883
This commit is contained in:
parent
5832fcef02
commit
afb13d8bb7
@ -166,6 +166,11 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
|
||||
'Generated the widget preview environment scaffolding at a given location '
|
||||
'for testing purposes.',
|
||||
hide: !verbose,
|
||||
)
|
||||
..addFlag(
|
||||
kDisableDtdServiceUuid,
|
||||
help: 'Disables the addition of a UUID to the widget preview DTD service and stream.',
|
||||
hide: !verbose,
|
||||
);
|
||||
}
|
||||
|
||||
@ -175,9 +180,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
|
||||
static const kHeadless = 'headless';
|
||||
static const kWebServer = 'web-server';
|
||||
static const kWidgetPreviewScaffoldOutputDir = 'scaffold-output-dir';
|
||||
|
||||
/// Environment variable used to pass the DTD URI to the widget preview scaffold.
|
||||
static const kWidgetPreviewDtdUriEnvVar = 'WIDGET_PREVIEW_DTD_URI';
|
||||
static const kDisableDtdServiceUuid = 'disable-dtd-service-uuid';
|
||||
|
||||
@visibleForTesting
|
||||
static const kBrowserNotFoundErrorMessage =
|
||||
@ -258,6 +261,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
|
||||
onHotRestartPreviewerRequest: onHotRestartRequest,
|
||||
dtdLauncher: DtdLauncher(logger: logger, artifacts: artifacts, processManager: processManager),
|
||||
project: rootProject.widgetPreviewScaffoldProject,
|
||||
addUuidToServiceName: !boolArg(kDisableDtdServiceUuid),
|
||||
);
|
||||
|
||||
/// The currently running instance of the widget preview scaffold.
|
||||
@ -401,7 +405,11 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
|
||||
logger.printTrace('Connecting to existing DTD instance at: $existingDtdUri...');
|
||||
await _dtdService.connect(dtdWsUri: existingDtdUri);
|
||||
}
|
||||
_previewCodeGenerator.populateDtdConnectionInfo(_dtdService.dtdUri!);
|
||||
_previewCodeGenerator.populateDtdConnectionInfo(
|
||||
dtdUri: _dtdService.dtdUri!,
|
||||
widgetPreviewServiceName: _dtdService.widgetPreviewService,
|
||||
widgetPreviewScaffoldStreamName: _dtdService.widgetPreviewScaffoldStream,
|
||||
);
|
||||
}
|
||||
|
||||
Future<int> runPreviewEnvironment({required FlutterProject widgetPreviewScaffoldProject}) async {
|
||||
|
||||
@ -10,6 +10,7 @@ import 'package:json_rpc_2/json_rpc_2.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:package_config/package_config_types.dart';
|
||||
import 'package:process/process.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../artifacts.dart';
|
||||
import '../base/common.dart';
|
||||
@ -37,6 +38,7 @@ class WidgetPreviewDtdServices {
|
||||
required this.dtdLauncher,
|
||||
required this.onHotRestartPreviewerRequest,
|
||||
required this.project,
|
||||
required this.addUuidToServiceName,
|
||||
}) {
|
||||
shutdownHooks.addShutdownHook(() async {
|
||||
await _dtd?.close();
|
||||
@ -44,12 +46,35 @@ class WidgetPreviewDtdServices {
|
||||
});
|
||||
}
|
||||
|
||||
/// The name of the widget preview service, without a UUID.
|
||||
@visibleForTesting
|
||||
static const kWidgetPreviewServiceRoot = 'widget-preview';
|
||||
|
||||
/// The actual name of the registered widget preview service.
|
||||
late final String widgetPreviewService = _withUuid(kWidgetPreviewServiceRoot);
|
||||
|
||||
/// The name of the widget preview stream, without a UUID.
|
||||
@visibleForTesting
|
||||
static const kWidgetPreviewScaffoldStreamRoot = 'WidgetPreviewScaffold';
|
||||
|
||||
/// The actual name of the widget preview stream.
|
||||
late final String widgetPreviewScaffoldStream = _withUuid(kWidgetPreviewScaffoldStreamRoot);
|
||||
|
||||
/// The unique identifier added to registered service and stream names if [addUuidToServiceName]
|
||||
/// is true.
|
||||
late final String serviceUuid = const Uuid().v4();
|
||||
|
||||
/// Adds a unique identifier to the service and stream registered by the widget previewer to
|
||||
/// avoid conflicts with other widget previewer instances connected to DTD.
|
||||
///
|
||||
/// If false, no UUID is added to the registered service and stream names.
|
||||
final bool addUuidToServiceName;
|
||||
|
||||
// WARNING: Keep these constants and services in sync with those defined in the widget preview
|
||||
// scaffold's dtd_services.dart.
|
||||
//
|
||||
// START KEEP SYNCED
|
||||
|
||||
static const kWidgetPreviewService = 'widget-preview';
|
||||
static const kIsWindows = 'isWindows';
|
||||
static const kHotRestartPreviewer = 'hotRestartPreviewer';
|
||||
static const kResolveUri = 'resolveUri';
|
||||
@ -57,7 +82,6 @@ class WidgetPreviewDtdServices {
|
||||
static const kGetPreference = 'getPreference';
|
||||
static const kGetDevToolsUri = 'getDevToolsUri';
|
||||
|
||||
static const kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
|
||||
static const kWidgetPreviewConnectedEvent = 'Connected';
|
||||
|
||||
/// Error code for RpcException thrown when attempting to load a key from
|
||||
@ -144,21 +168,20 @@ class WidgetPreviewDtdServices {
|
||||
);
|
||||
}
|
||||
|
||||
String _withUuid(String name) => addUuidToServiceName ? '$name-$serviceUuid' : name;
|
||||
|
||||
Future<void> _registerServices() async {
|
||||
final DartToolingDaemon dtd = _dtd!;
|
||||
dtd.onEvent(kWidgetPreviewScaffoldStream).listen((DTDEvent event) {
|
||||
if (event case DTDEvent(
|
||||
stream: kWidgetPreviewScaffoldStream,
|
||||
kind: kWidgetPreviewConnectedEvent,
|
||||
)) {
|
||||
dtd.onEvent(widgetPreviewScaffoldStream).listen((DTDEvent event) {
|
||||
if (event.kind == kWidgetPreviewConnectedEvent) {
|
||||
previewAnalytics.reportPreviewerConnected();
|
||||
}
|
||||
});
|
||||
await Future.wait(<Future<void>>[
|
||||
dtd.streamListen(kWidgetPreviewScaffoldStream),
|
||||
dtd.streamListen(widgetPreviewScaffoldStream),
|
||||
for (final (String method, DTDServiceCallback callback) in services)
|
||||
dtd
|
||||
.registerService(kWidgetPreviewService, method, callback)
|
||||
.registerService(widgetPreviewService, method, callback)
|
||||
.then((_) => logger.printTrace('Registered DTD method: $method')),
|
||||
]);
|
||||
}
|
||||
|
||||
@ -55,7 +55,11 @@ class PreviewCodeGenerator {
|
||||
static String getGeneratedDtdConnectionInfoFilePath(FileSystem fs) =>
|
||||
fs.path.join('lib', 'src', 'dtd', 'dtd_connection_info.dart');
|
||||
|
||||
void populateDtdConnectionInfo(Uri dtdUri) {
|
||||
void populateDtdConnectionInfo({
|
||||
required Uri dtdUri,
|
||||
required String widgetPreviewServiceName,
|
||||
required String widgetPreviewScaffoldStreamName,
|
||||
}) {
|
||||
final emitter = cb.DartEmitter.scoped(useNullSafetySyntax: true);
|
||||
final lib = cb.Library(
|
||||
(cb.LibraryBuilder b) => b
|
||||
@ -68,6 +72,20 @@ class PreviewCodeGenerator {
|
||||
..type = cb.refer('String')
|
||||
..assignment = cb.literalString(dtdUri.toString()).code;
|
||||
}),
|
||||
cb.Field((b) {
|
||||
b
|
||||
..name = 'kWidgetPreviewService'
|
||||
..modifier = cb.FieldModifier.constant
|
||||
..type = cb.refer('String')
|
||||
..assignment = cb.literalString(widgetPreviewServiceName).code;
|
||||
}),
|
||||
cb.Field((b) {
|
||||
b
|
||||
..name = 'kWidgetPreviewScaffoldStream'
|
||||
..modifier = cb.FieldModifier.constant
|
||||
..type = cb.refer('String')
|
||||
..assignment = cb.literalString(widgetPreviewScaffoldStreamName).code;
|
||||
}),
|
||||
]),
|
||||
);
|
||||
final File generatedDtdConnectionInfoFile = fs.file(
|
||||
|
||||
@ -3,3 +3,5 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
const String kWidgetPreviewDtdUri = '';
|
||||
const String kWidgetPreviewService = 'widget-preview';
|
||||
const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
|
||||
|
||||
@ -17,7 +17,6 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService {
|
||||
//
|
||||
// START KEEP SYNCED
|
||||
|
||||
static const kWidgetPreviewService = 'widget-preview';
|
||||
static const kIsWindows = 'isWindows';
|
||||
static const kHotRestartPreviewer = 'hotRestartPreviewer';
|
||||
static const kResolveUri = 'resolveUri';
|
||||
@ -40,7 +39,7 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService {
|
||||
dtd = await DartToolingDaemon.connect(dtdWsUri);
|
||||
unawaited(
|
||||
dtd.postEvent(
|
||||
'WidgetPreviewScaffold',
|
||||
kWidgetPreviewScaffoldStream,
|
||||
'Connected',
|
||||
const <String, Object?>{},
|
||||
),
|
||||
|
||||
@ -418,13 +418,19 @@ List<_i1.WidgetPreview> previews() => [];
|
||||
|
||||
// Populate the DTD connection info.
|
||||
final Uri dtdUri = Uri.parse('ws://localhost:1234');
|
||||
codeGenerator.populateDtdConnectionInfo(dtdUri);
|
||||
codeGenerator.populateDtdConnectionInfo(
|
||||
dtdUri: dtdUri,
|
||||
widgetPreviewServiceName: 'widget-preview-service',
|
||||
widgetPreviewScaffoldStreamName: 'widget-preview-stream',
|
||||
);
|
||||
|
||||
final expectedDtdConnectionInfo =
|
||||
'''
|
||||
// ignore_for_file: implementation_imports
|
||||
|
||||
const String kWidgetPreviewDtdUri = '$dtdUri';
|
||||
const String kWidgetPreviewService = 'widget-preview-service';
|
||||
const String kWidgetPreviewScaffoldStream = 'widget-preview-stream';
|
||||
''';
|
||||
expect(generatedDtdConnectionInfoFile.readAsStringSync(), expectedDtdConnectionInfo);
|
||||
},
|
||||
|
||||
@ -45,6 +45,13 @@ class FakeWidgetPreviewScaffoldDtdServices extends Fake implements WidgetPreview
|
||||
@override
|
||||
Uri? get dtdUri => Uri();
|
||||
|
||||
@override
|
||||
final String widgetPreviewService = WidgetPreviewDtdServices.kWidgetPreviewServiceRoot;
|
||||
|
||||
@override
|
||||
final String widgetPreviewScaffoldStream =
|
||||
WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot;
|
||||
|
||||
@override
|
||||
Future<void> launchAndConnect() async {}
|
||||
}
|
||||
|
||||
@ -79,6 +79,7 @@ void main() {
|
||||
'start',
|
||||
'--verbose',
|
||||
'--${WidgetPreviewStartCommand.kHeadless}',
|
||||
'--${WidgetPreviewStartCommand.kDisableDtdServiceUuid}',
|
||||
if (useWebServer) '--${WidgetPreviewStartCommand.kWebServer}',
|
||||
if (dtdUri != null) '--${WidgetPreviewStartCommand.kDtdUrl}=$dtdUri',
|
||||
if (devToolsServerAddress != null)
|
||||
@ -192,14 +193,15 @@ void main() {
|
||||
// The preview scaffold will send a 'Connected' event on this stream once it has initialized
|
||||
// and is ready.
|
||||
final DartToolingDaemon dtdConnection = await DartToolingDaemon.connect(dtdUri);
|
||||
const kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
|
||||
final completer = Completer<void>();
|
||||
dtdConnection.onEvent(kWidgetPreviewScaffoldStream).listen((DTDEvent event) {
|
||||
expect(event.stream, kWidgetPreviewScaffoldStream);
|
||||
dtdConnection.onEvent(WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot).listen((
|
||||
DTDEvent event,
|
||||
) {
|
||||
expect(event.stream, WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot);
|
||||
expect(event.kind, 'Connected');
|
||||
completer.complete();
|
||||
});
|
||||
await dtdConnection.streamListen(kWidgetPreviewScaffoldStream);
|
||||
await dtdConnection.streamListen(WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot);
|
||||
|
||||
// Start the widget preview and wait for the 'Connected' event.
|
||||
await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb, dtdUri: dtdUri);
|
||||
@ -242,17 +244,18 @@ void main() {
|
||||
// The preview scaffold will send a 'Connected' event on this stream once it has initialized
|
||||
// and is ready.
|
||||
final DartToolingDaemon dtdConnection = await DartToolingDaemon.connect(dtdUri);
|
||||
const kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
|
||||
final completer = Completer<void>();
|
||||
var firstConnection = true;
|
||||
dtdConnection.onEvent(kWidgetPreviewScaffoldStream).listen((DTDEvent event) {
|
||||
expect(event.stream, kWidgetPreviewScaffoldStream);
|
||||
dtdConnection.onEvent(WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot).listen((
|
||||
DTDEvent event,
|
||||
) {
|
||||
expect(event.stream, WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot);
|
||||
expect(event.kind, 'Connected');
|
||||
if (firstConnection) {
|
||||
firstConnection = false;
|
||||
runFlutterClean();
|
||||
dtdConnection.call(
|
||||
WidgetPreviewDtdServices.kWidgetPreviewService,
|
||||
WidgetPreviewDtdServices.kWidgetPreviewServiceRoot,
|
||||
WidgetPreviewDtdServices.kHotRestartPreviewer,
|
||||
);
|
||||
return;
|
||||
@ -262,7 +265,7 @@ void main() {
|
||||
// crashed.
|
||||
completer.complete();
|
||||
});
|
||||
await dtdConnection.streamListen(kWidgetPreviewScaffoldStream);
|
||||
await dtdConnection.streamListen(WidgetPreviewDtdServices.kWidgetPreviewScaffoldStreamRoot);
|
||||
|
||||
// Start the widget preview and wait for the 'Connected' event.
|
||||
await runWidgetPreview(expectedMessages: firstLaunchMessagesWeb, dtdUri: dtdUri);
|
||||
|
||||
@ -3,3 +3,5 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
const String kWidgetPreviewDtdUri = '';
|
||||
const String kWidgetPreviewService = 'widget-preview';
|
||||
const String kWidgetPreviewScaffoldStream = 'WidgetPreviewScaffold';
|
||||
|
||||
@ -17,7 +17,6 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService {
|
||||
//
|
||||
// START KEEP SYNCED
|
||||
|
||||
static const kWidgetPreviewService = 'widget-preview';
|
||||
static const kIsWindows = 'isWindows';
|
||||
static const kHotRestartPreviewer = 'hotRestartPreviewer';
|
||||
static const kResolveUri = 'resolveUri';
|
||||
@ -40,7 +39,7 @@ class WidgetPreviewScaffoldDtdServices with DtdEditorService {
|
||||
dtd = await DartToolingDaemon.connect(dtdWsUri);
|
||||
unawaited(
|
||||
dtd.postEvent(
|
||||
'WidgetPreviewScaffold',
|
||||
kWidgetPreviewScaffoldStream,
|
||||
'Connected',
|
||||
const <String, Object?>{},
|
||||
),
|
||||
|
||||
@ -62,6 +62,7 @@ void main() {
|
||||
),
|
||||
onHotRestartPreviewerRequest: onHotRestartPreviewerRequest ?? () {},
|
||||
project: FakeFlutterProject(),
|
||||
addUuidToServiceName: false,
|
||||
);
|
||||
await server.launchAndConnect();
|
||||
return server;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user