blob: 8d5a4283fc0f2d7e6c556be83263332977a754ab [file] [log] [blame] [edit]
// Copyright (c) 2023, 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 'package:dev_compiler/dev_compiler.dart';
import 'package:dev_compiler/src/command/command.dart';
import 'package:dev_compiler/src/js_ast/js_ast.dart';
import 'package:front_end/src/api_unstable/ddc.dart' as fe;
import 'package:kernel/target/targets.dart';
/// Result compiling using [compileFromMemory].
///
/// This is meant for testing and therefore include not only the resulting
/// [Program] various artifacts of the compilation.
class MemoryCompilerResult {
final fe.DdcResult ddcResult;
final ProgramCompiler compiler;
final Program program;
final List<fe.CfeDiagnosticMessage> errors;
MemoryCompilerResult(
this.ddcResult,
this.compiler,
this.program,
this.errors,
);
}
/// Result of compiling using [componentFromMemory].
///
/// This is meant for use in tests and performs the front end compilation to
/// components without calling DDC.
class MemoryComponentResult {
final fe.DdcResult ddcResult;
final List<fe.CfeDiagnosticMessage> errors;
fe.InitializedCompilerState? initialCompilerState;
MemoryComponentResult(this.ddcResult, this.errors, this.initialCompilerState);
}
/// Uri used as the base uri for files provided in memory through the
/// [MemoryFileSystem].
Uri memoryDirectory = Uri.parse('memory://');
/// Compiles [entryPoint] to a kernel `Component` using the [memoryFiles] as
/// sources.
///
/// [memoryFiles] maps relative paths to their source text. [entryPoint] must
/// be absolute, using [baseUri] (defaulted to [memoryDirectory]) to refer to a
/// file from [memoryFiles].
Future<MemoryComponentResult> componentFromMemory(
Map<String, String> memoryFiles,
Uri entryPoint, {
Map<fe.ExperimentalFlag, bool> explicitExperimentalFlags = const {},
Uri? baseUri,
}) async {
baseUri ??= memoryDirectory;
var errors = <fe.CfeDiagnosticMessage>[];
void diagnosticMessageHandler(fe.CfeDiagnosticMessage message) {
if (message.severity == fe.CfeSeverity.error) {
errors.add(message);
}
fe.printDiagnosticMessage(message, print);
}
var memoryFileSystem = fe.MemoryFileSystem(baseUri);
for (var entry in memoryFiles.entries) {
memoryFileSystem
.entityForUri(baseUri.resolve(entry.key))
.writeAsStringSync(entry.value);
}
var compilerState = fe.initializeCompiler(
null,
false,
sourcePathToUri(getSdkPath()),
sourcePathToUri(defaultSdkSummaryPath),
null,
sourcePathToUri(defaultLibrarySpecPath),
[],
DevCompilerTarget(TargetFlags(trackWidgetCreation: false)),
fileSystem: fe.HybridFileSystem(memoryFileSystem),
environmentDefines: {},
explicitExperimentalFlags: explicitExperimentalFlags,
);
var result = await fe.compile(compilerState, [
entryPoint,
], diagnosticMessageHandler);
if (result == null) {
throw 'Memory compilation failed';
}
return MemoryComponentResult(result, errors, compilerState);
}
/// Compiles [entryPoint] to a kernel `Component` using the [memoryFiles] as
/// sources. Uses the incremental compiler.
///
/// [memoryFiles] maps relative paths to their source text. [entryPoint] must
/// be absolute, using [baseUri] (defaulted to [memoryDirectory]) to refer to a
/// file from [memoryFiles].
Future<MemoryComponentResult> incrementalComponentFromMemory(
Map<String, String> memoryFiles,
Uri entryPoint, {
Map<fe.ExperimentalFlag, bool> explicitExperimentalFlags = const {},
Uri? baseUri,
fe.InitializedCompilerState? initialCompilerState,
Uri? packageConfigUri,
}) async {
baseUri ??= memoryDirectory;
var errors = <fe.CfeDiagnosticMessage>[];
void diagnosticMessageHandler(fe.CfeDiagnosticMessage message) {
if (message.severity == fe.CfeSeverity.error) {
errors.add(message);
}
fe.printDiagnosticMessage(message, print);
}
var memoryFileSystem = fe.MemoryFileSystem(baseUri);
for (var entry in memoryFiles.entries) {
memoryFileSystem
.entityForUri(baseUri.resolve(entry.key))
.writeAsStringSync(entry.value);
}
var inputDigests = {
sourcePathToUri(defaultSdkSummaryPath): const [0],
};
var compilerState = await fe.initializeIncrementalCompiler(
initialCompilerState,
{},
[],
false,
sourcePathToUri(getSdkPath()),
sourcePathToUri(defaultSdkSummaryPath),
packageConfigUri,
sourcePathToUri(defaultLibrarySpecPath),
[],
inputDigests,
DevCompilerTarget(TargetFlags(trackWidgetCreation: false)),
fileSystem: fe.HybridFileSystem(memoryFileSystem),
environmentDefines: {},
explicitExperimentalFlags: explicitExperimentalFlags,
);
var incrementalCompiler = compilerState.incrementalCompiler!;
compilerState.options.onDiagnostic = diagnosticMessageHandler;
var incrementalCompilerResult = await incrementalCompiler.computeDelta(
entryPoints: [entryPoint],
fullComponent: false,
);
var cachedSdkInput =
compilerState.workerInputCache![sourcePathToUri(defaultSdkSummaryPath)];
var result = fe.DdcResult(
incrementalCompilerResult.component,
cachedSdkInput?.component,
[],
incrementalCompilerResult.classHierarchy,
incrementalCompilerResult.neededDillLibraries,
);
return MemoryComponentResult(result, errors, compilerState);
}
/// Compiles [entryPoint] to JavaScript using the [memoryFiles] as sources.
///
/// [memoryFiles] maps relative paths to their source text. [entryPoint] must
/// be absolute, using [memoryDirectory] as a base uri to refer to a file from
/// [memoryFiles].
Future<MemoryCompilerResult> compileFromMemory(
Map<String, String> memoryFiles,
Uri entryPoint, {
Map<fe.ExperimentalFlag, bool>? explicitExperimentalFlags,
Uri? baseUri,
}) async {
baseUri ??= memoryDirectory;
var MemoryComponentResult(ddcResult: result, :errors) =
await componentFromMemory(memoryFiles, entryPoint, baseUri: baseUri);
var options = Options(moduleName: 'test');
var compiler =
// TODO(nshahan): Do we need to support [importToSummary] and
// [summaryToModule].
ProgramCompiler(result.component, result.classHierarchy, options, {}, {});
var jsModule = compiler.emitModule(result.compiledLibraries);
return MemoryCompilerResult(result, compiler, jsModule, errors);
}