blob: f1bb3caeba2513f6dfb7e55e0ac9ed8ea109d87f [file] [log] [blame]
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. 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:io' as io;
import 'dart:isolate';
import 'dart:typed_data';
import 'package:analyzer/src/summary2/kernel_compilation_service.dart';
import 'package:macros/src/bootstrap.dart' as macro;
import 'package:macros/src/executor.dart' as macro;
import 'package:macros/src/executor/isolated_executor.dart'
as isolated_executor;
import 'package:macros/src/executor/multi_executor.dart' as macro;
import 'package:macros/src/executor/process_executor.dart' as process_executor;
import 'package:macros/src/executor/serialization.dart' as macro;
import 'package:meta/meta.dart';
import 'package:path/path.dart' as package_path;
/// The interface for a bundle of macros.
abstract class BundleMacroExecutor {
void dispose();
Future<macro.MacroInstanceIdentifier> instantiate({
required Uri libraryUri,
required String className,
required String constructorName,
required macro.Arguments arguments,
});
}
/// [BundleMacroExecutor] that runs pre-compiled native executables.
class ExecutableBundleMacroExecutor extends BundleMacroExecutor {
final ExecutableMacroSupport support;
late final macro.ExecutorFactoryToken _executorFactoryToken;
ExecutableBundleMacroExecutor({
required this.support,
required io.File executable,
required Set<Uri> libraries,
}) {
_executorFactoryToken = support.executor.registerExecutorFactory(
() => process_executor.start(
macro.SerializationMode.byteData,
process_executor.CommunicationChannel.socket,
executable.path,
),
libraries,
);
}
@override
void dispose() {
support.executor.unregisterExecutorFactory(_executorFactoryToken);
}
@override
Future<macro.MacroInstanceIdentifier> instantiate({
required Uri libraryUri,
required String className,
required String constructorName,
required macro.Arguments arguments,
}) async {
return await support.executor
.instantiateMacro(libraryUri, className, constructorName, arguments);
}
}
/// [MacroSupport] that can runs macros from native executables.
///
/// It does not support compilation, because it happens outside.
class ExecutableMacroSupport extends MacroSupport {
void add({
required io.File executable,
required Set<Uri> libraries,
}) {
var bundleExecutor = ExecutableBundleMacroExecutor(
support: this,
executable: executable,
libraries: libraries,
);
for (var libraryUri in libraries) {
_bundleExecutors[libraryUri] = bundleExecutor;
}
}
}
final class ExecutableMacroSupportFactory extends MacroSupportFactory {
final void Function(ExecutableMacroSupport macroSupport) configure;
final List<ExecutableMacroSupport> _instances = [];
ExecutableMacroSupportFactory({
required this.configure,
});
@override
Future<void> dispose() async {
for (var instance in _instances) {
await instance.dispose();
}
}
@override
MacroSupport newInstance() {
var instance = ExecutableMacroSupport();
configure(instance);
_instances.add(instance);
return instance;
}
}
/// [BundleMacroExecutor] that runs macros from kernels.
class KernelBundleMacroExecutor extends BundleMacroExecutor {
final KernelMacroSupport support;
late final macro.ExecutorFactoryToken _executorFactoryToken;
final Uint8List kernelBytes;
Uri? _kernelUriCached;
KernelBundleMacroExecutor({
required this.support,
required Uint8List kernelBytes,
required Set<Uri> libraries,
}) : kernelBytes = Uint8List.fromList(kernelBytes) {
_executorFactoryToken = support.executor.registerExecutorFactory(
() => isolated_executor.start(
macro.SerializationMode.byteData,
_kernelUri,
),
libraries,
);
}
Uri get _kernelUri {
return _kernelUriCached ??=
// ignore: avoid_dynamic_calls
(Isolate.current as dynamic).createUriForKernelBlob(kernelBytes) as Uri;
}
@override
void dispose() {
support.executor.unregisterExecutorFactory(_executorFactoryToken);
var kernelUriCached = _kernelUriCached;
if (kernelUriCached != null) {
// ignore: avoid_dynamic_calls
(Isolate.current as dynamic).unregisterKernelBlobUri(kernelUriCached);
_kernelUriCached = null;
}
}
@override
Future<macro.MacroInstanceIdentifier> instantiate({
required Uri libraryUri,
required String className,
required String constructorName,
required macro.Arguments arguments,
}) async {
return await support.executor
.instantiateMacro(libraryUri, className, constructorName, arguments);
}
}
/// [MacroSupport] that can compile and run macros from kernels.
class KernelMacroSupport extends MacroSupport {
final MacroKernelBuilder builder = MacroKernelBuilder();
void add({
required Uint8List kernelBytes,
required Set<Uri> libraries,
}) {
var bundleExecutor = KernelBundleMacroExecutor(
support: this,
kernelBytes: kernelBytes,
libraries: libraries,
);
for (var libraryUri in libraries) {
_bundleExecutors[libraryUri] = bundleExecutor;
}
}
}
final class KernelMacroSupportFactory extends MacroSupportFactory {
final List<KernelMacroSupport> _instances = [];
@override
Future<void> dispose() async {
for (var instance in _instances) {
await instance.dispose();
}
}
@override
MacroSupport newInstance() {
var instance = KernelMacroSupport();
_instances.add(instance);
return instance;
}
}
class MacroClass {
final String name;
final List<String> constructors;
MacroClass({
required this.name,
required this.constructors,
});
}
abstract class MacroFileEntry {
String get content;
/// When CFE searches for `package_config.json` we need to check this.
bool get exists;
}
abstract class MacroFileSystem {
/// Used to convert `file:` URIs into paths.
package_path.Context get pathContext;
MacroFileEntry getFile(String path);
}
class MacroKernelBuilder {
const MacroKernelBuilder();
Future<Uint8List> build({
required MacroFileSystem fileSystem,
required String packageFilePath,
required List<MacroLibrary> libraries,
}) async {
var macroMainContent = macro.bootstrapMacroIsolate(
{
for (var library in libraries)
library.uri.toString(): {
for (var c in library.classes) c.name: c.constructors
},
},
macro.SerializationMode.byteData,
);
var macroMainPath = '${libraries.first.path}.macro';
var overlayFileSystem = _OverlayMacroFileSystem(fileSystem);
overlayFileSystem.overlays[macroMainPath] = macroMainContent;
return KernelCompilationService.compile(
fileSystem: overlayFileSystem,
packageFilePath: packageFilePath,
path: macroMainPath,
);
}
}
class MacroLibrary {
final Uri uri;
final String path;
final List<MacroClass> classes;
MacroLibrary({
required this.uri,
required this.path,
required this.classes,
});
String get uriStr => uri.toString();
}
/// The interface for tracking macro executors for libraries.
class MacroSupport {
/// The instance of macro executor that is used for all macros.
final macro.MultiMacroExecutor executor = macro.MultiMacroExecutor();
final Map<Uri, BundleMacroExecutor> _bundleExecutors = {};
/// Disposes the whole macro support, with all its executors.
@mustCallSuper
Future<void> dispose() async {
await executor.close();
}
/// Returns the executor registered for this library.
BundleMacroExecutor? forLibrary(Uri uri) {
return _bundleExecutors[uri];
}
/// Removes and disposes executors for all libraries.
void removeLibraries() {
for (var bundleExecutor in _bundleExecutors.values) {
bundleExecutor.dispose();
}
_bundleExecutors.clear();
}
/// Removes and disposes the executor for the library.
void removeLibrary(Uri uri) {
var bundleExecutor = _bundleExecutors.remove(uri);
bundleExecutor?.dispose();
}
}
/// Each analysis context has to have its own [MacroSupport] instance.
/// The content collection is configured with this factory.
sealed class MacroSupportFactory {
Future<void> dispose();
MacroSupport newInstance();
}
// TODO(scheglov): remove after migration.
final class SingleInstanceMacroSupportFactory extends MacroSupportFactory {
final MacroSupport instance;
SingleInstanceMacroSupportFactory({
required this.instance,
});
@override
Future<void> dispose() async {
await instance.dispose();
}
@override
MacroSupport newInstance() {
return instance;
}
}
/// [MacroFileEntry] for a file with overridden content.
class _OverlayMacroFileEntry implements MacroFileEntry {
@override
final String content;
_OverlayMacroFileEntry(this.content);
@override
bool get exists => true;
}
/// Wrapper around another [MacroFileSystem] that can be configured to
/// provide (or override) content of files.
class _OverlayMacroFileSystem implements MacroFileSystem {
final MacroFileSystem _fileSystem;
/// The mapping from the path to the file content.
final Map<String, String> overlays = {};
_OverlayMacroFileSystem(this._fileSystem);
@override
package_path.Context get pathContext => _fileSystem.pathContext;
@override
MacroFileEntry getFile(String path) {
var overlayContent = overlays[path];
if (overlayContent != null) {
return _OverlayMacroFileEntry(overlayContent);
}
return _fileSystem.getFile(path);
}
}