| // Copyright (c) 2015, 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:async'; |
| import 'dart:io' as io; |
| import 'dart:isolate'; |
| |
| import 'package:analyzer/dart/analysis/declared_variables.dart'; |
| import 'package:analyzer/dart/analysis/results.dart'; |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analyzer/file_system/file_system.dart'; |
| import 'package:analyzer/src/context/context.dart'; |
| import 'package:analyzer/src/context/packages.dart'; |
| import 'package:analyzer/src/dart/analysis/byte_store.dart'; |
| import 'package:analyzer/src/dart/analysis/cache.dart'; |
| import 'package:analyzer/src/dart/analysis/driver.dart'; |
| import 'package:analyzer/src/dart/analysis/file_state.dart'; |
| import 'package:analyzer/src/dart/analysis/performance_logger.dart'; |
| import 'package:analyzer/src/dart/analysis/session.dart'; |
| import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl; |
| import 'package:analyzer/src/generated/engine.dart'; |
| import 'package:analyzer/src/generated/source.dart'; |
| import 'package:analyzer/src/generated/source_io.dart'; |
| import 'package:analyzer/src/source/source_resource.dart'; |
| import 'package:analyzer/src/summary/idl.dart'; |
| import 'package:analyzer/src/summary/package_bundle_reader.dart'; |
| import 'package:analyzer/src/summary/summarize_elements.dart'; |
| import 'package:analyzer/src/summary/summary_sdk.dart' show SummaryBasedDartSdk; |
| import 'package:analyzer/src/summary2/link.dart' as summary2; |
| import 'package:analyzer/src/summary2/linked_bundle_context.dart' as summary2; |
| import 'package:analyzer/src/summary2/linked_element_factory.dart' as summary2; |
| import 'package:analyzer/src/summary2/reference.dart' as summary2; |
| import 'package:analyzer_cli/src/context_cache.dart'; |
| import 'package:analyzer_cli/src/driver.dart'; |
| import 'package:analyzer_cli/src/error_formatter.dart'; |
| import 'package:analyzer_cli/src/error_severity.dart'; |
| import 'package:analyzer_cli/src/has_context_mixin.dart'; |
| import 'package:analyzer_cli/src/options.dart'; |
| import 'package:bazel_worker/bazel_worker.dart'; |
| import 'package:collection/collection.dart'; |
| import 'package:convert/convert.dart'; |
| |
| /// Persistent Bazel worker. |
| class AnalyzerWorkerLoop extends AsyncWorkerLoop { |
| final ResourceProvider resourceProvider; |
| final PerformanceLog logger = PerformanceLog(null); |
| final String dartSdkPath; |
| WorkerPackageBundleCache packageBundleCache; |
| |
| final StringBuffer errorBuffer = StringBuffer(); |
| final StringBuffer outBuffer = StringBuffer(); |
| |
| AnalyzerWorkerLoop(this.resourceProvider, AsyncWorkerConnection connection, |
| {this.dartSdkPath}) |
| : super(connection: connection) { |
| packageBundleCache = |
| WorkerPackageBundleCache(resourceProvider, logger, 256 * 1024 * 1024); |
| } |
| |
| factory AnalyzerWorkerLoop.sendPort( |
| ResourceProvider resourceProvider, SendPort sendPort, |
| {String dartSdkPath}) { |
| AsyncWorkerConnection connection = SendPortAsyncWorkerConnection(sendPort); |
| return AnalyzerWorkerLoop(resourceProvider, connection, |
| dartSdkPath: dartSdkPath); |
| } |
| |
| factory AnalyzerWorkerLoop.std(ResourceProvider resourceProvider, |
| {io.Stdin stdinStream, io.Stdout stdoutStream, String dartSdkPath}) { |
| AsyncWorkerConnection connection = StdAsyncWorkerConnection( |
| inputStream: stdinStream, outputStream: stdoutStream); |
| return AnalyzerWorkerLoop(resourceProvider, connection, |
| dartSdkPath: dartSdkPath); |
| } |
| |
| /// Performs analysis with given [options]. |
| Future<void> analyze( |
| CommandLineOptions options, Map<String, WorkerInput> inputs) async { |
| var packageBundleProvider = |
| WorkerPackageBundleProvider(packageBundleCache, inputs); |
| var buildMode = BuildMode(resourceProvider, options, AnalysisStats(), |
| ContextCache(resourceProvider, options, Driver.verbosePrint), |
| logger: logger, packageBundleProvider: packageBundleProvider); |
| await buildMode.analyze(); |
| AnalysisEngine.instance.clearCaches(); |
| } |
| |
| /// Perform a single loop step. |
| @override |
| Future<WorkResponse> performRequest(WorkRequest request) async { |
| return logger.runAsync('Perform request', () async { |
| errorBuffer.clear(); |
| outBuffer.clear(); |
| try { |
| // Prepare inputs with their digests. |
| var inputs = <String, WorkerInput>{}; |
| for (var input in request.inputs) { |
| inputs[input.path] = WorkerInput(input.path, input.digest); |
| } |
| |
| // Add in the dart-sdk argument if `dartSdkPath` is not null, |
| // otherwise it will try to find the currently installed sdk. |
| var arguments = request.arguments.toList(); |
| if (dartSdkPath != null && |
| !arguments.any((arg) => arg.startsWith('--dart-sdk'))) { |
| arguments.add('--dart-sdk=$dartSdkPath'); |
| } |
| |
| // Prepare options. |
| var options = |
| CommandLineOptions.parse(arguments, printAndFail: (String msg) { |
| throw ArgumentError(msg); |
| }); |
| |
| // Analyze and respond. |
| await analyze(options, inputs); |
| var msg = _getErrorOutputBuffersText(); |
| return WorkResponse() |
| ..exitCode = EXIT_CODE_OK |
| ..output = msg; |
| } catch (e, st) { |
| var msg = _getErrorOutputBuffersText(); |
| msg += '$e\n$st'; |
| return WorkResponse() |
| ..exitCode = EXIT_CODE_ERROR |
| ..output = msg; |
| } |
| }); |
| } |
| |
| /// Run the worker loop. |
| @override |
| Future<void> run() async { |
| errorSink = errorBuffer; |
| outSink = outBuffer; |
| exitHandler = (int exitCode) { |
| throw StateError('Exit called: $exitCode'); |
| }; |
| await super.run(); |
| } |
| |
| String _getErrorOutputBuffersText() { |
| var msg = ''; |
| if (errorBuffer.isNotEmpty) { |
| msg += errorBuffer.toString() + '\n'; |
| } |
| if (outBuffer.isNotEmpty) { |
| msg += outBuffer.toString() + '\n'; |
| } |
| return msg; |
| } |
| } |
| |
| /// Analyzer used when the "--build-mode" option is supplied. |
| class BuildMode with HasContextMixin { |
| @override |
| final ResourceProvider resourceProvider; |
| final CommandLineOptions options; |
| final AnalysisStats stats; |
| final PerformanceLog logger; |
| final PackageBundleProvider packageBundleProvider; |
| |
| @override |
| final ContextCache contextCache; |
| |
| SummaryDataStore summaryDataStore; |
| AnalysisOptionsImpl analysisOptions; |
| Map<Uri, File> uriToFileMap; |
| final List<Source> explicitSources = <Source>[]; |
| final List<PackageBundle> unlinkedBundles = <PackageBundle>[]; |
| |
| SourceFactory sourceFactory; |
| DeclaredVariables declaredVariables; |
| AnalysisDriver analysisDriver; |
| |
| PackageBundleAssembler assembler; |
| |
| final Map<String, ParsedUnitResult> inputParsedUnitResults = {}; |
| summary2.LinkedElementFactory elementFactory; |
| |
| // May be null. |
| final DependencyTracker dependencyTracker; |
| |
| BuildMode(this.resourceProvider, this.options, this.stats, this.contextCache, |
| {PerformanceLog logger, PackageBundleProvider packageBundleProvider}) |
| : logger = logger ?? PerformanceLog(null), |
| packageBundleProvider = packageBundleProvider ?? |
| DirectPackageBundleProvider(resourceProvider), |
| dependencyTracker = options.summaryDepsOutput != null |
| ? DependencyTracker(options.summaryDepsOutput) |
| : null; |
| |
| bool get _shouldOutputSummary => |
| options.buildSummaryOutput != null || |
| options.buildSummaryOutputSemantic != null; |
| |
| /// Perform package analysis according to the given [options]. |
| Future<ErrorSeverity> analyze() async { |
| return await logger.runAsync('Analyze', () async { |
| // Write initial progress message. |
| if (!options.machineFormat) { |
| outSink.writeln("Analyzing ${options.sourceFiles.join(', ')}..."); |
| } |
| |
| // Create the URI to file map. |
| uriToFileMap = _createUriToFileMap(options.sourceFiles); |
| if (uriToFileMap == null) { |
| io.exitCode = ErrorSeverity.ERROR.ordinal; |
| return ErrorSeverity.ERROR; |
| } |
| |
| // BuildMode expects sourceFiles in the format "<uri>|<filepath>", |
| // but the rest of the code base does not understand this format. |
| // Rewrite sourceFiles, stripping the "<uri>|" prefix, so that it |
| // does not cause problems with code that does not expect this format. |
| options.rewriteSourceFiles(options.sourceFiles |
| .map((String uriPipePath) => |
| uriPipePath.substring(uriPipePath.indexOf('|') + 1)) |
| .toList()); |
| |
| // Prepare the analysis driver. |
| try { |
| logger.run('Prepare analysis driver', () { |
| _createAnalysisDriver(); |
| }); |
| } on ConflictingSummaryException catch (e) { |
| errorSink.writeln('$e'); |
| io.exitCode = ErrorSeverity.ERROR.ordinal; |
| return ErrorSeverity.ERROR; |
| } |
| |
| // Add sources. |
| for (var uri in uriToFileMap.keys) { |
| var file = uriToFileMap[uri]; |
| if (!file.exists) { |
| errorSink.writeln('File not found: ${file.path}'); |
| io.exitCode = ErrorSeverity.ERROR.ordinal; |
| return ErrorSeverity.ERROR; |
| } |
| Source source = FileSource(file, uri); |
| explicitSources.add(source); |
| } |
| |
| // Write summary. |
| assembler = PackageBundleAssembler(); |
| if (_shouldOutputSummary) { |
| await logger.runAsync('Build and write output summary', () async { |
| // Prepare all unlinked units. |
| await logger.runAsync('Prepare unlinked units', () async { |
| for (var src in explicitSources) { |
| await _prepareUnit('${src.uri}'); |
| } |
| }); |
| |
| // Build and assemble linked libraries. |
| _computeLinkedLibraries2(); |
| |
| // Write the whole package bundle. |
| var bundle = assembler.assemble(); |
| if (options.buildSummaryOutput != null) { |
| var file = io.File(options.buildSummaryOutput); |
| file.writeAsBytesSync(bundle.toBuffer(), |
| mode: io.FileMode.writeOnly); |
| } |
| if (options.buildSummaryOutputSemantic != null) { |
| bundle.flushInformative(); |
| var file = io.File(options.buildSummaryOutputSemantic); |
| file.writeAsBytesSync(bundle.toBuffer(), |
| mode: io.FileMode.writeOnly); |
| } |
| }); |
| } else { |
| // Build the graph, e.g. associate parts with libraries. |
| for (var file in uriToFileMap.values) { |
| analysisDriver.fsState.getFileForPath(file.path); |
| } |
| } |
| |
| ErrorSeverity severity; |
| if (options.buildSummaryOnly) { |
| severity = ErrorSeverity.NONE; |
| } else { |
| // Process errors. |
| await _printErrors(outputPath: options.buildAnalysisOutput); |
| severity = await _computeMaxSeverity(); |
| } |
| |
| if (dependencyTracker != null) { |
| var file = io.File(dependencyTracker.outputPath); |
| file.writeAsStringSync(dependencyTracker.dependencies.join('\n')); |
| } |
| |
| return severity; |
| }); |
| } |
| |
| /// Use [elementFactory] filled with input summaries, and link prepared |
| /// [inputParsedUnitResults] to produce linked libraries in [assembler]. |
| void _computeLinkedLibraries2() { |
| logger.run('Link output summary2', () { |
| var inputLibraries = <summary2.LinkInputLibrary>[]; |
| |
| for (var librarySource in explicitSources) { |
| var path = librarySource.fullName; |
| |
| var parseResult = inputParsedUnitResults[path]; |
| if (parseResult == null) { |
| throw ArgumentError('No parsed unit for $path'); |
| } |
| |
| var unit = parseResult.unit; |
| var isPart = unit.directives.any((d) => d is PartOfDirective); |
| if (isPart) { |
| continue; |
| } |
| |
| var inputUnits = <summary2.LinkInputUnit>[]; |
| inputUnits.add( |
| summary2.LinkInputUnit(null, librarySource, false, unit), |
| ); |
| |
| for (var directive in unit.directives) { |
| if (directive is PartDirective) { |
| var partUri = directive.uri.stringValue; |
| var partSource = sourceFactory.resolveUri(librarySource, partUri); |
| |
| // Add empty synthetic units for unresolved `part` URIs. |
| if (partSource == null) { |
| var unit = analysisDriver.fsState.unresolvedFile.parse(); |
| inputUnits.add( |
| summary2.LinkInputUnit(partUri, null, true, unit), |
| ); |
| continue; |
| } |
| |
| var partPath = partSource.fullName; |
| var partParseResult = inputParsedUnitResults[partPath]; |
| if (partParseResult == null) { |
| throw ArgumentError('No parsed unit for part $partPath in $path'); |
| } |
| inputUnits.add( |
| summary2.LinkInputUnit( |
| partUri, |
| partSource, |
| false, |
| partParseResult.unit, |
| ), |
| ); |
| } |
| } |
| |
| inputLibraries.add( |
| summary2.LinkInputLibrary(librarySource, inputUnits), |
| ); |
| } |
| |
| var linkResult = summary2.link(elementFactory, inputLibraries); |
| assembler.setBundle2(linkResult.bundle); |
| }); |
| } |
| |
| Future<ErrorSeverity> _computeMaxSeverity() async { |
| var maxSeverity = ErrorSeverity.NONE; |
| if (!options.buildSuppressExitCode) { |
| for (var source in explicitSources) { |
| var result = await analysisDriver.getErrors(source.fullName); |
| for (var error in result.errors) { |
| var processedSeverity = determineProcessedSeverity( |
| error, options, analysisDriver.analysisOptions); |
| if (processedSeverity != null) { |
| maxSeverity = maxSeverity.max(processedSeverity); |
| } |
| } |
| } |
| } |
| return maxSeverity; |
| } |
| |
| void _createAnalysisDriver() { |
| // Read the summaries. |
| summaryDataStore = SummaryDataStore(<String>[]); |
| |
| // Adds a bundle at `path` to `summaryDataStore`. |
| PackageBundle addBundle(String path) { |
| var bundle = packageBundleProvider.get(path); |
| summaryDataStore.addBundle(path, bundle); |
| return bundle; |
| } |
| |
| SummaryBasedDartSdk sdk; |
| logger.run('Add SDK bundle', () { |
| sdk = SummaryBasedDartSdk(options.dartSdkSummaryPath, true); |
| summaryDataStore.addBundle(null, sdk.bundle); |
| }); |
| |
| var numInputs = options.buildSummaryInputs.length; |
| logger.run('Add $numInputs input summaries', () { |
| for (var path in options.buildSummaryInputs) { |
| addBundle(path); |
| } |
| }); |
| |
| var rootPath = |
| options.sourceFiles.isEmpty ? null : options.sourceFiles.first; |
| |
| var packages = _findPackages(rootPath); |
| |
| sourceFactory = SourceFactory(<UriResolver>[ |
| DartUriResolver(sdk), |
| TrackingInSummaryUriResolver( |
| InSummaryUriResolver(resourceProvider, summaryDataStore), |
| dependencyTracker), |
| ExplicitSourceResolver(uriToFileMap) |
| ]); |
| |
| analysisOptions = |
| createAnalysisOptionsForCommandLineOptions(options, rootPath); |
| |
| var scheduler = AnalysisDriverScheduler(logger); |
| analysisDriver = AnalysisDriver( |
| scheduler, |
| logger, |
| resourceProvider, |
| MemoryByteStore(), |
| FileContentOverlay(), |
| null, |
| sourceFactory, |
| analysisOptions, |
| externalSummaries: summaryDataStore, |
| packages: packages, |
| ); |
| |
| declaredVariables = DeclaredVariables.fromMap(options.definedVariables); |
| analysisDriver.declaredVariables = declaredVariables; |
| |
| _createLinkedElementFactory(); |
| |
| scheduler.start(); |
| } |
| |
| void _createLinkedElementFactory() { |
| var analysisContext = AnalysisContextImpl( |
| SynchronousSession(analysisOptions, declaredVariables), |
| sourceFactory, |
| ); |
| |
| elementFactory = summary2.LinkedElementFactory( |
| analysisContext, |
| AnalysisSessionImpl(null), |
| summary2.Reference.root(), |
| ); |
| |
| for (var bundle in summaryDataStore.bundles) { |
| elementFactory.addBundle( |
| summary2.LinkedBundleContext(elementFactory, bundle.bundle2), |
| ); |
| } |
| } |
| |
| /// Convert [sourceEntities] (a list of file specifications of the form |
| /// "$uri|$path") to a map from URI to path. If an error occurs, report the |
| /// error and return null. |
| Map<Uri, File> _createUriToFileMap(List<String> sourceEntities) { |
| var uriToFileMap = <Uri, File>{}; |
| for (var sourceFile in sourceEntities) { |
| var pipeIndex = sourceFile.indexOf('|'); |
| if (pipeIndex == -1) { |
| // TODO(paulberry): add the ability to guess the URI from the path. |
| errorSink.writeln( |
| 'Illegal input file (must be "\$uri|\$path"): $sourceFile'); |
| return null; |
| } |
| var uri = Uri.parse(sourceFile.substring(0, pipeIndex)); |
| var path = sourceFile.substring(pipeIndex + 1); |
| path = resourceProvider.pathContext.absolute(path); |
| path = resourceProvider.pathContext.normalize(path); |
| uriToFileMap[uri] = resourceProvider.getFile(path); |
| } |
| return uriToFileMap; |
| } |
| |
| Packages _findPackages(String path) { |
| var configPath = options.packageConfigPath; |
| if (configPath != null) { |
| var configFile = resourceProvider.getFile(configPath); |
| return parsePackagesFile(resourceProvider, configFile); |
| } |
| |
| if (path != null) { |
| var file = resourceProvider.getFile(path); |
| return findPackagesFrom(resourceProvider, file); |
| } |
| |
| return Packages.empty; |
| } |
| |
| /// Ensure that the parsed unit for [absoluteUri] is available. |
| /// |
| /// If the unit is in the input [summaryDataStore], do nothing. |
| Future<void> _prepareUnit(String absoluteUri) async { |
| // Parse the source and serialize its AST. |
| var uri = Uri.parse(absoluteUri); |
| var source = sourceFactory.forUri2(uri); |
| if (!source.exists()) { |
| // TODO(paulberry): we should report a warning/error because DDC |
| // compilations are unlikely to work. |
| return; |
| } |
| var result = await analysisDriver.parseFile(source.fullName); |
| inputParsedUnitResults[result.path] = result; |
| } |
| |
| /// Print errors for all explicit sources. If [outputPath] is supplied, output |
| /// is sent to a new file at that path. |
| Future<void> _printErrors({String outputPath}) async { |
| await logger.runAsync('Compute and print analysis errors', () async { |
| var buffer = StringBuffer(); |
| var severityProcessor = (AnalysisError error) => |
| determineProcessedSeverity(error, options, analysisOptions); |
| var formatter = options.machineFormat |
| ? MachineErrorFormatter(buffer, options, stats, |
| severityProcessor: severityProcessor) |
| : HumanErrorFormatter(buffer, options, stats, |
| severityProcessor: severityProcessor); |
| for (var source in explicitSources) { |
| var result = await analysisDriver.getErrors(source.fullName); |
| formatter.formatErrors([result]); |
| } |
| formatter.flush(); |
| if (!options.machineFormat) { |
| stats.print(buffer); |
| } |
| if (outputPath == null) { |
| var sink = options.machineFormat ? errorSink : outSink; |
| sink.write(buffer); |
| } else { |
| io.File(outputPath).writeAsStringSync(buffer.toString()); |
| } |
| }); |
| } |
| } |
| |
| /// Tracks paths to dependencies, really just a thin api around a Set<String>. |
| class DependencyTracker { |
| final _dependencies = <String>{}; |
| |
| /// The path to the file to create once tracking is done. |
| final String outputPath; |
| |
| DependencyTracker(this.outputPath); |
| |
| Iterable<String> get dependencies => _dependencies; |
| |
| void record(String path) => _dependencies.add(path); |
| } |
| |
| /// [PackageBundleProvider] that always reads from the [ResourceProvider]. |
| class DirectPackageBundleProvider implements PackageBundleProvider { |
| final ResourceProvider resourceProvider; |
| |
| DirectPackageBundleProvider(this.resourceProvider); |
| |
| @override |
| PackageBundle get(String path) { |
| var bytes = io.File(path).readAsBytesSync(); |
| return PackageBundle.fromBuffer(bytes); |
| } |
| } |
| |
| /// Instances of the class [ExplicitSourceResolver] map URIs to files on disk |
| /// using a fixed mapping provided at construction time. |
| class ExplicitSourceResolver extends UriResolver { |
| final Map<Uri, File> uriToFileMap; |
| final Map<String, Uri> pathToUriMap; |
| |
| /// Construct an [ExplicitSourceResolver] based on the given [uriToFileMap]. |
| ExplicitSourceResolver(Map<Uri, File> uriToFileMap) |
| : uriToFileMap = uriToFileMap, |
| pathToUriMap = _computePathToUriMap(uriToFileMap); |
| |
| @override |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| var file = uriToFileMap[uri]; |
| actualUri ??= uri; |
| if (file == null) { |
| return null; |
| } else { |
| return FileSource(file, actualUri); |
| } |
| } |
| |
| @override |
| Uri restoreAbsolute(Source source) { |
| return pathToUriMap[source.fullName]; |
| } |
| |
| /// Build the inverse mapping of [uriToSourceMap]. |
| static Map<String, Uri> _computePathToUriMap(Map<Uri, File> uriToSourceMap) { |
| var pathToUriMap = <String, Uri>{}; |
| uriToSourceMap.forEach((Uri uri, File file) { |
| pathToUriMap[file.path] = uri; |
| }); |
| return pathToUriMap; |
| } |
| } |
| |
| /// Provider for [PackageBundle]s by file paths. |
| abstract class PackageBundleProvider { |
| /// Return the [PackageBundle] for the file with the given [path]. |
| PackageBundle get(String path); |
| } |
| |
| /// Wrapper for [InSummaryUriResolver] that tracks accesses to summaries. |
| class TrackingInSummaryUriResolver extends UriResolver { |
| // May be null. |
| final DependencyTracker dependencyTracker; |
| final InSummaryUriResolver inSummaryUriResolver; |
| |
| TrackingInSummaryUriResolver( |
| this.inSummaryUriResolver, this.dependencyTracker); |
| |
| @override |
| Source resolveAbsolute(Uri uri, [Uri actualUri]) { |
| var source = inSummaryUriResolver.resolveAbsolute(uri, actualUri); |
| if (dependencyTracker != null && |
| source != null && |
| source is InSummarySource) { |
| dependencyTracker.record(source.summaryPath); |
| } |
| return source; |
| } |
| } |
| |
| /// Worker input. |
| /// |
| /// Bazel does not specify the format of the digest, so we cannot assume that |
| /// the digest itself is enough to uniquely identify inputs. So, we use a pair |
| /// of path + digest. |
| class WorkerInput { |
| static const _digestEquality = ListEquality<int>(); |
| |
| final String path; |
| final List<int> digest; |
| |
| WorkerInput(this.path, this.digest); |
| |
| @override |
| int get hashCode => _digestEquality.hash(digest); |
| |
| @override |
| bool operator ==(Object other) { |
| return other is WorkerInput && |
| other.path == path && |
| _digestEquality.equals(other.digest, digest); |
| } |
| |
| @override |
| String toString() => '$path @ ${hex.encode(digest)}'; |
| } |
| |
| /// Value object for [WorkerPackageBundleCache]. |
| class WorkerPackageBundle { |
| final List<int> bytes; |
| final PackageBundle bundle; |
| |
| WorkerPackageBundle(this.bytes, this.bundle); |
| |
| /// Approximation of a bundle size in memory. |
| int get size => bytes.length * 3; |
| } |
| |
| /// Cache of [PackageBundle]s. |
| class WorkerPackageBundleCache { |
| final ResourceProvider resourceProvider; |
| final PerformanceLog logger; |
| final Cache<WorkerInput, WorkerPackageBundle> _cache; |
| |
| WorkerPackageBundleCache(this.resourceProvider, this.logger, int maxSizeBytes) |
| : _cache = Cache<WorkerInput, WorkerPackageBundle>( |
| maxSizeBytes, (value) => value.size); |
| |
| /// Get the [PackageBundle] from the file with the given [path] in the context |
| /// of the given worker [inputs]. |
| PackageBundle get(Map<String, WorkerInput> inputs, String path) { |
| var input = inputs[path]; |
| |
| // The input must be not null, otherwise we're not expected to read |
| // this file, but we check anyway to be safe. |
| if (input == null) { |
| logger.writeln('Read $path outside of the inputs.'); |
| var bytes = resourceProvider.getFile(path).readAsBytesSync(); |
| return PackageBundle.fromBuffer(bytes); |
| } |
| |
| return _cache.get(input, () { |
| logger.writeln('Read $input.'); |
| var bytes = resourceProvider.getFile(path).readAsBytesSync(); |
| var bundle = PackageBundle.fromBuffer(bytes); |
| return WorkerPackageBundle(bytes, bundle); |
| }).bundle; |
| } |
| } |
| |
| /// [PackageBundleProvider] that reads from [WorkerPackageBundleCache] using |
| /// the request specific [inputs]. |
| class WorkerPackageBundleProvider implements PackageBundleProvider { |
| final WorkerPackageBundleCache cache; |
| final Map<String, WorkerInput> inputs; |
| |
| WorkerPackageBundleProvider(this.cache, this.inputs); |
| |
| @override |
| PackageBundle get(String path) { |
| return cache.get(inputs, path); |
| } |
| } |