blob: a4fd2165a4518d640cd7f9f55f31badba5608c52 [file] [log] [blame]
// Copyright (c) 2018, 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:collection';
import 'dart:typed_data';
import 'package:analyzer/dart/analysis/declared_variables.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/file_system/file_system.dart' show ResourceProvider;
import 'package:analyzer/src/dart/analysis/byte_store.dart';
import 'package:analyzer/src/dart/analysis/driver.dart' show AnalysisDriver;
import 'package:analyzer/src/dart/analysis/file_state.dart';
import 'package:analyzer/src/dart/analysis/library_analyzer.dart';
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
import 'package:analyzer/src/dart/analysis/restricted_analysis_context.dart';
import 'package:analyzer/src/dart/element/inheritance_manager2.dart';
import 'package:analyzer/src/generated/engine.dart';
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
import 'package:analyzer/src/generated/sdk.dart';
import 'package:analyzer/src/generated/source.dart';
import 'package:analyzer/src/summary/idl.dart';
import 'package:analyzer/src/summary/link.dart' as summary_link;
import 'package:analyzer/src/summary/package_bundle_reader.dart';
import 'package:analyzer/src/summary/resynthesize.dart';
import 'package:analyzer/src/summary/summarize_ast.dart';
import 'package:analyzer/src/summary/summarize_elements.dart';
import 'package:meta/meta.dart';
import '../compiler/shared_command.dart' show sdkLibraryVariables;
import 'context.dart' show AnalyzerOptions, createSourceFactory;
import 'extension_types.dart' show ExtensionTypeSet;
/// The analysis driver for `dartdevc`.
///
/// [linkLibraries] can be used to link input sources and input summaries,
/// producing a [LinkedAnalysisDriver] that can analyze those sources.
///
/// This class can be reused to link different input files if they share the
/// same [analysisOptions] and [summaryData].
class CompilerAnalysisDriver {
/// The Analyzer options used for analyzing the input sources.
final AnalysisOptionsImpl analysisOptions;
/// The input summaries used for analyzing/compiling the input sources.
///
/// This should contain the summary of all imported/exported libraries and
/// transitive dependencies, including the Dart SDK.
final SummaryDataStore summaryData;
final ResourceProvider _resourceProvider;
final List<String> _summaryPaths;
@visibleForTesting
final DartSdk dartSdk;
/// SDK summary path, used by [isCompatibleWith] for batch/worker mode.
final String _dartSdkSummaryPath;
ExtensionTypeSet _extensionTypes;
CompilerAnalysisDriver._(this.dartSdk, this._summaryPaths, this.summaryData,
this.analysisOptions, this._resourceProvider, this._dartSdkSummaryPath) {
var bundle = dartSdk.getLinkedBundle();
if (bundle != null) summaryData.addBundle(null, bundle);
}
/// Information about native extension types.
///
/// This will be `null` until [linkLibraries] has been called (because we
/// could be compiling the Dart SDK, so it would not be available yet).
ExtensionTypeSet get extensionTypes => _extensionTypes;
factory CompilerAnalysisDriver(AnalyzerOptions options,
{SummaryDataStore summaryData,
List<String> summaryPaths = const [],
Map<String, bool> experiments = const {}}) {
var resourceProvider = options.resourceProvider;
var contextBuilder = options.createContextBuilder();
var analysisOptions =
contextBuilder.getAnalysisOptions(options.analysisRoot);
(analysisOptions as AnalysisOptionsImpl).enabledExperiments =
experiments.entries.where((e) => e.value).map((e) => e.key).toList();
var dartSdk = contextBuilder.findSdk(null, analysisOptions);
// Read the summaries.
summaryData ??= SummaryDataStore(summaryPaths,
resourceProvider: resourceProvider,
// TODO(vsm): Reset this to true once we cleanup internal build rules.
disallowOverlappingSummaries: false);
return CompilerAnalysisDriver._(dartSdk, summaryPaths, summaryData,
analysisOptions, resourceProvider, options.dartSdkSummaryPath);
}
/// Whether this driver can be reused for the given [dartSdkSummaryPath] and
/// [summaryPaths].
bool isCompatibleWith(AnalyzerOptions options, List<String> summaryPaths) {
return _dartSdkSummaryPath == options.dartSdkSummaryPath &&
_summaryPaths.toSet().containsAll(summaryPaths);
}
/// Parses [explicitSources] and any imports/exports/parts (that are not
/// included in [summaryData]), and links the results so
/// [LinkedAnalysisDriver.analyzeLibrary] can be called.
///
/// The analyzer [options] are used to configure URI resolution (Analyzer's
/// [SourceFactory]) and declared variables, if any (`-Dfoo=bar`).
LinkedAnalysisDriver linkLibraries(
List<Uri> explicitSources, AnalyzerOptions options) {
/// This code was ported from analyzer_cli (with a few changes/improvements).
///
/// Here's a summary of the process:
///
/// 1. starting with [explicitSources], visit all transitive
/// imports/exports/parts, and create an unlinked unit for each
/// (unless it's provided by an input summary). Add these to [assembler].
///
/// 2. call [summary_link.link] to create the linked libraries, and add the
/// results to the assembler.
///
/// 3. serialize the data into [summaryBytes], then deserialize it back into
/// the [bundle] that contains the summary for all [explicitSources] and
/// their transitive dependencies.
///
/// 4. create the analysis [context] and element [resynthesizer], and use
/// them to return a new [LinkedAnalysisDriver] that can analyze all of
/// the compilation units (and provide the resolved AST/errors for each).
var assembler = PackageBundleAssembler();
/// The URI resolution logic for this build unit.
var sourceFactory = createSourceFactory(options,
sdkResolver: DartUriResolver(dartSdk), summaryData: summaryData);
/// A fresh file system state for this list of [explicitSources].
var fsState = _createFileSystemState(sourceFactory);
var uriToUnit = <String, UnlinkedUnit>{};
/// The sources that have been added to [sourcesToProcess], used to ensure
/// we only visit a given source once.
var knownSources = HashSet<Uri>.from(explicitSources);
/// The pending list of sources to visit.
var sourcesToProcess = Queue<Uri>.from(explicitSources);
/// Prepare URIs of unlinked units (for libraries) that should be linked.
var libraryUris = <String>[];
/// Ensure that the [UnlinkedUnit] for [absoluteUri] is available.
///
/// If the unit is in the input [summaryData], do nothing.
/// Otherwise compute it and store into the [uriToUnit] and [assembler].
void prepareUnlinkedUnit(Uri uri) {
var absoluteUri = uri.toString();
// Maybe an input package contains the source.
if (summaryData.unlinkedMap[absoluteUri] != null) {
return;
}
// Parse the source and serialize its AST.
var source = sourceFactory.forUri2(uri);
if (source == null || !source.exists()) {
// Skip this source. We don't need to report an error here because it
// will be reported later during analysis.
return;
}
var file = fsState.getFileForPath(source.fullName);
var unit = file.parse();
var unlinkedUnit = serializeAstUnlinked(unit);
uriToUnit[absoluteUri] = unlinkedUnit;
assembler.addUnlinkedUnit(source, unlinkedUnit);
/// The URI to resolve imports/exports/parts against.
var baseUri = uri;
if (baseUri.scheme == 'dart' && baseUri.pathSegments.length == 1) {
// Add a trailing slash so relative URIs will resolve correctly, e.g.
// "map.dart" from "dart:core/" yields "dart:core/map.dart".
baseUri = Uri(scheme: 'dart', path: baseUri.path + '/');
}
void enqueueSource(String relativeUri) {
var sourceUri = baseUri.resolve(relativeUri);
if (knownSources.add(sourceUri)) {
sourcesToProcess.add(sourceUri);
}
}
// Add reachable imports/exports/parts, if any.
var isPart = false;
for (var directive in unit.directives) {
if (directive is UriBasedDirective) {
enqueueSource(directive.uri.stringValue);
// Handle conditional imports.
if (directive is NamespaceDirective) {
for (var config in directive.configurations) {
enqueueSource(config.uri.stringValue);
}
}
} else if (directive is PartOfDirective) {
isPart = true;
}
}
// Remember library URIs, so we can use it for linking libraries and
// compiling them.
if (!isPart) libraryUris.add(absoluteUri);
}
// Collect the unlinked units for all transitive sources.
//
// TODO(jmesserly): consider using parallelism via asynchronous IO here,
// once we fix debugger extension (web/web_command.dart) to allow async.
//
// It would let computation tasks (parsing/serializing unlinked units)
// proceed in parallel with reading the sources from disk.
while (sourcesToProcess.isNotEmpty) {
prepareUnlinkedUnit(sourcesToProcess.removeFirst());
}
var declaredVariables = DeclaredVariables.fromMap(
Map.of(options.declaredVariables)..addAll(sdkLibraryVariables));
/// Perform the linking step and store the result.
///
/// TODO(jmesserly): can we pass in `getAst` to reuse existing ASTs we
/// created when we did `file.parse()` in [prepareUnlinkedUnit]?
var linkResult = summary_link.link(
libraryUris.toSet(),
(uri) => summaryData.linkedMap[uri],
(uri) => summaryData.unlinkedMap[uri] ?? uriToUnit[uri],
declaredVariables,
analysisOptions);
linkResult.forEach(assembler.addLinkedLibrary);
var summaryBytes = assembler.assemble().toBuffer();
var bundle = PackageBundle.fromBuffer(summaryBytes);
/// Create an analysis context to contain the state for this build unit.
var context = RestrictedAnalysisContext(
analysisOptions, declaredVariables, sourceFactory);
var resultProvider = InputPackagesResultProvider(
context,
SummaryDataStore([])
..addStore(summaryData)
..addBundle(null, bundle));
var resynthesizer = resultProvider.resynthesizer;
_extensionTypes ??= ExtensionTypeSet(context.typeProvider, resynthesizer);
return LinkedAnalysisDriver(
analysisOptions,
resynthesizer,
sourceFactory,
libraryUris,
declaredVariables,
summaryBytes,
fsState,
_resourceProvider);
}
FileSystemState _createFileSystemState(SourceFactory sourceFactory) {
var unlinkedSalt =
Uint32List(1 + AnalysisOptionsImpl.unlinkedSignatureLength);
unlinkedSalt[0] = AnalysisDriver.DATA_VERSION;
unlinkedSalt.setAll(1, analysisOptions.unlinkedSignature);
var linkedSalt = Uint32List(1 + AnalysisOptions.signatureLength);
linkedSalt[0] = AnalysisDriver.DATA_VERSION;
linkedSalt.setAll(1, analysisOptions.signature);
return FileSystemState(
PerformanceLog(StringBuffer()),
MemoryByteStore(),
FileContentOverlay(),
_resourceProvider,
sourceFactory,
analysisOptions,
unlinkedSalt,
linkedSalt,
externalSummaries: summaryData);
}
}
/// The analysis driver used after linking all input summaries and explicit
/// sources, produced by [CompilerAnalysisDriver.linkLibraries].
class LinkedAnalysisDriver {
final AnalysisOptions analysisOptions;
final SummaryResynthesizer resynthesizer;
final SourceFactory sourceFactory;
final List<String> libraryUris;
final DeclaredVariables declaredVariables;
/// The summary bytes for this linked build unit.
final List<int> summaryBytes;
final FileSystemState _fsState;
final ResourceProvider _resourceProvider;
LinkedAnalysisDriver(
this.analysisOptions,
this.resynthesizer,
this.sourceFactory,
this.libraryUris,
this.declaredVariables,
this.summaryBytes,
this._fsState,
this._resourceProvider);
TypeProvider get typeProvider => resynthesizer.typeProvider;
/// True if [uri] refers to a Dart library (i.e. a Dart source file exists
/// with this uri, and it is not a part file).
bool _isLibraryUri(String uri) {
return resynthesizer.hasLibrarySummary(uri);
}
/// Analyzes the library at [uri] and returns the results of analysis for all
/// file(s) in that library.
Map<FileState, UnitAnalysisResult> analyzeLibrary(String libraryUri) {
if (!_isLibraryUri(libraryUri)) {
throw ArgumentError('"$libraryUri" is not a library');
}
var libraryFile = _fsState.getFileForUri(Uri.parse(libraryUri));
var analyzer = LibraryAnalyzer(
analysisOptions,
declaredVariables,
resynthesizer.sourceFactory,
(uri) => _isLibraryUri('$uri'),
resynthesizer.context,
resynthesizer,
InheritanceManager2(resynthesizer.typeSystem),
libraryFile,
_resourceProvider);
// TODO(jmesserly): ideally we'd use the existing public `analyze()` method,
// but it's async. We can't use `async` here because it would break our
// developer tools extension (see web/web_command.dart). We should be able
// to fix it, but it requires significant changes to code outside of this
// repository.
return analyzer.analyzeSync();
}
ClassElement getClass(String uri, String name) {
return getLibrary(uri).getType(name);
}
LibraryElement getLibrary(String uri) {
return resynthesizer.getLibraryElement(uri);
}
}