| // 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(trackCreationLocations: 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(trackCreationLocations: 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); | |
| } |