blob: 62e650811b7fa8c68038db5d1e1654072f6b4a3b [file]
// 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:flutter_tools_extension/flutter_tools_extension.dart';
import '../base/context.dart';
import '../base/logger.dart';
import '../base/os.dart';
import '../features.dart';
import 'config.dart';
import 'diagnostics.dart';
import 'extension_discovery.dart';
/// Manages active tool extension isolate connections and exposes capability proxies.
class ExtensionManager {
/// Creates an [ExtensionManager] targeting the active [hostPlatform].
ExtensionManager({
required this.hostPlatform,
required Logger logger,
List<ExtensionEntryPoint> entryPoints = const <ExtensionEntryPoint>[],
ExtensionDiscovery? discovery,
FeatureFlags? featureFlags,
}) : _logger = logger,
_entryPoints = entryPoints,
_discovery = discovery ?? ExtensionDiscovery(logger: logger),
_featureFlags = featureFlags ?? context.get<FeatureFlags>()!;
/// The active [HostPlatform].
final HostPlatform hostPlatform;
final Logger _logger;
final ExtensionDiscovery _discovery;
final List<ExtensionEntryPoint> _entryPoints;
final FeatureFlags _featureFlags;
bool get isInitialized => _isInitialized;
bool _isInitialized = false;
Future<void>? _initFuture;
final List<DiagnosticsExtension> _diagnosticsExtensions = <DiagnosticsExtension>[];
final List<ConfigurationExtension> _configurationExtensions = <ConfigurationExtension>[];
/// Ensures entrypoints are initialized; idempotent.
Future<void> ensureInitialized() {
return _initFuture ??= _doInitialize();
}
Future<void> _doInitialize() async {
if (!_featureFlags.isToolExtensionsEnabled) {
_isInitialized = true;
return;
}
if (_entryPoints.isNotEmpty) {
await initialize(entryPoints: _entryPoints);
} else {
_isInitialized = true;
}
}
/// Active extension connections compatible with [hostPlatform].
List<ExtensionConnection> get connections => _discovery.connections;
/// The operating system name of the host platform (e.g. `'linux'`, `'macos'`, `'windows'`).
String get hostPlatformName => switch (hostPlatform) {
HostPlatform.darwin_x64 || HostPlatform.darwin_arm64 => 'macos',
HostPlatform.linux_x64 || HostPlatform.linux_arm64 || HostPlatform.linux_riscv64 => 'linux',
HostPlatform.windows_x64 || HostPlatform.windows_arm64 => 'windows',
};
/// Spawns entrypoints without host OS checks; disposes any extension that reports
/// it does not support [hostPlatform].
Future<void> initialize({
List<ExtensionEntryPoint> entryPoints = const <ExtensionEntryPoint>[],
}) async {
_logger.printTrace(
'ExtensionManager initializing for platform "$hostPlatformName" with ${entryPoints.length} entrypoint(s).',
);
for (final entryPoint in entryPoints) {
final ExtensionConnection connection = await ExtensionConnection.spawn(
entryPoint,
logger: _logger,
);
if (connection.capabilities.supportsHostPlatform(hostPlatformName) ||
connection.capabilities.supportsHostPlatform(hostPlatform.cliName)) {
_logger.printTrace(
'Extension connection supported on host platform "$hostPlatformName"; registering.',
);
_discovery.registerConnection(connection);
} else {
_logger.printTrace(
'Extension connection does not support host platform "$hostPlatformName" '
'(supported platforms: ${connection.capabilities.supportedPlatforms}); disposing connection.',
);
await connection.dispose();
}
}
_diagnosticsExtensions.clear();
_configurationExtensions.clear();
for (final ExtensionConnection connection in _discovery.connections) {
if (connection.capabilities.services.contains(DiagnosticsExtension.serviceNamespace)) {
final client = DiagnosticsExtensionClient(connection, logger: _logger);
await client.fetchTitle();
_diagnosticsExtensions.add(client);
}
if (connection.capabilities.services.contains(ConfigurationExtension.serviceNamespace)) {
final client = ConfigurationExtensionClient(connection, logger: _logger);
await client.fetchTitle();
_configurationExtensions.add(client);
}
}
_isInitialized = true;
}
/// Active [DiagnosticsExtension] proxies for extensions supporting `'diagnostics'`.
List<DiagnosticsExtension> get diagnosticsExtensions {
assert(
_isInitialized,
'ExtensionManager.ensureInitialized() must be called before accessing diagnosticsExtensions.',
);
return List<DiagnosticsExtension>.unmodifiable(_diagnosticsExtensions);
}
/// Active [ConfigurationExtension] proxies for extensions supporting `'config'`.
List<ConfigurationExtension> get configurationExtensions {
assert(
_isInitialized,
'ExtensionManager.ensureInitialized() must be called before accessing configurationExtensions.',
);
return List<ConfigurationExtension>.unmodifiable(_configurationExtensions);
}
/// Disposes all active extension isolate connections.
Future<void> dispose() async {
_logger.printTrace('ExtensionManager disposing all active connections.');
_diagnosticsExtensions.clear();
_configurationExtensions.clear();
_isInitialized = false;
_initFuture = null;
await _discovery.dispose();
}
}