blob: 4c05e4a3d9a2d1458b2f3be063bfdc5bef1725c6 [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_core/flutter_tools_core.dart';
import 'package:flutter_tools_extension/flutter_tools_extension.dart';
import '../base/file_system.dart';
import '../base/logger.dart';
import '../cache.dart';
import '../features.dart';
import 'extension_discovery.dart';
import 'extension_manager.dart';
const Duration _kRpcTimeout = Duration(seconds: 5);
/// Manages querying project templates from extension isolates.
///
/// This manager interacts with active [ExtensionConnection]s to discover custom
/// project templates, resolve their directories on the host, and generate
/// template parameters over the extension protocol RPC.
base class ExtensionTemplateManager extends TemplateService {
/// Creates an [ExtensionTemplateManager].
ExtensionTemplateManager({
required ExtensionManager extensionManager,
required FeatureFlags featureFlags,
required FileSystem fileSystem,
required Logger logger,
}) : _extensionManager = extensionManager,
_featureFlags = featureFlags,
_fileSystem = fileSystem,
_logger = logger;
final ExtensionManager _extensionManager;
final FeatureFlags _featureFlags;
final FileSystem _fileSystem;
final Logger _logger;
List<ProjectTemplate>? _cachedTemplates;
/// Retrieve the cached templates synchronously.
///
/// Returns the list of project templates cached from the last [getProjectTemplates] call.
List<ProjectTemplate> get cachedTemplates => _cachedTemplates ?? const <ProjectTemplate>[];
/// Retrieve templates by routing `template.getProjectTemplates` to active tool extensions.
///
/// Results are cached after the first successful call.
Future<List<ProjectTemplate>> getProjectTemplates() async {
if (!_featureFlags.isToolExtensionsEnabled) {
return const <ProjectTemplate>[];
}
if (_cachedTemplates != null) {
return _cachedTemplates!;
}
await _extensionManager.ensureInitialized();
final templates = <ProjectTemplate>[];
final List<ExtensionConnection> connections = _extensionManager.connections
.where(
(ExtensionConnection c) =>
c.capabilities.services.contains(TemplateService.serviceNamespace),
)
.toList();
final List<List<ExtensionProjectTemplate>> results = await Future.wait(
connections.map((ExtensionConnection connection) async {
try {
final Object? rpcResult = await connection
.sendRequest(TemplateService.getProjectTemplatesMethod)
.timeout(_kRpcTimeout);
return ExtensionProjectTemplate.listFromJson(rpcResult);
} on Object catch (e) {
_logger.printTrace(
'Failed to get results from extension for ${TemplateService.getProjectTemplatesMethod}: $e',
);
return const <ExtensionProjectTemplate>[];
}
}),
);
results.forEach(templates.addAll);
_cachedTemplates = templates;
return templates;
}
/// Resolves a template package URI to a local directory.
///
/// Currently only supports 'package:flutter_tools/' and
/// 'package:flutter_tools_extension_linux_prototype/' URIs, resolving them
/// relative to the Flutter SDK root.
Directory resolveTemplateDirectory(String templatePath) {
if (templatePath.startsWith('package:flutter_tools/')) {
final String relativePath = templatePath.substring('package:flutter_tools/'.length);
final String absolutePath = _fileSystem.path.join(
Cache.flutterRoot!,
'packages',
'flutter_tools',
'lib',
relativePath,
);
return _fileSystem.directory(absolutePath);
}
// TODO(bkonyi): Resolve package URIs generically using package config or discovered paths.
if (templatePath.startsWith('package:flutter_tools_extension_linux_prototype/')) {
final String relativePath = templatePath.substring(
'package:flutter_tools_extension_linux_prototype/'.length,
);
final String absolutePath = _fileSystem.path.join(
Cache.flutterRoot!,
'packages',
'flutter_tools',
'packages',
'flutter_tools_extension_linux_prototype',
'lib',
relativePath,
);
return _fileSystem.directory(absolutePath);
}
throw ArgumentError('Unsupported template path format: $templatePath');
}
/// Request template parameter generation over extension protocol RPC.
///
/// Delegates the parameter generation for [templateName] to the active tool
/// extension, passing the host's [toolParameters]. Falls back to returning
/// [toolParameters] unchanged if the extension fails or is not available.
Future<Map<String, Object?>> generateTemplateParameters(
String templateName,
Map<String, Object?> toolParameters,
) async {
if (!_featureFlags.isToolExtensionsEnabled) {
return toolParameters;
}
final List<ExtensionConnection> connections = _extensionManager.connections
.where(
(ExtensionConnection c) =>
c.capabilities.services.contains(TemplateService.serviceNamespace),
)
.toList();
for (final connection in connections) {
try {
final Object? result = await connection
.sendRequest(TemplateService.generateTemplateParametersMethod, <String, Object?>{
'templateName': templateName,
'toolParameters': toolParameters,
})
.timeout(_kRpcTimeout);
if (result is Map) {
return result.cast<String, Object?>();
}
} on Object catch (e) {
_logger.printTrace('Failed to generate template parameters from extension: $e');
}
}
return toolParameters;
}
@override
Set<ProjectTemplate> get projectTemplates => cachedTemplates.toSet();
@override
Set<String> get appPlatformTemplates => const <String>{};
@override
Set<String> get pluginPlatformTemplates => const <String>{};
}