Initial implementation of front_end hot reload API.
This implementation is based on the existing kernel and analysis driver logic, using proxy classes to account for interface incompatibilities between those components. In future CLs, I hope we can (a) move the necessary analysis driver logic into front_end, and (b) reduce or eliminate the number of proxy classes needed. This CL represents the very first inklings of functionality; all that is confirmed to work at this point is compilation of a single source file containing an empty `main` method. R=danrubel@google.com, scheglov@google.com Review-Url: https://codereview.chromium.org/2624193003 .
This commit is contained in:
@@ -18,7 +18,7 @@ import 'package:analyzer/src/util/fast_uri.dart';
|
||||
import 'package:analyzer/task/dart.dart';
|
||||
import 'package:analyzer/task/general.dart';
|
||||
import 'package:analyzer/task/model.dart';
|
||||
import 'package:path/path.dart' as pathos;
|
||||
import 'package:front_end/src/base/source.dart';
|
||||
|
||||
/**
|
||||
* The [ResultProvider] that provides results from input package summaries.
|
||||
@@ -66,43 +66,23 @@ class InSummaryPackageUriResolver extends UriResolver {
|
||||
* are served from its summary. This source uses its URI as [fullName] and has
|
||||
* empty contents.
|
||||
*/
|
||||
class InSummarySource extends Source {
|
||||
final Uri uri;
|
||||
|
||||
class InSummarySource extends BasicSource {
|
||||
/**
|
||||
* The summary file where this source was defined.
|
||||
*/
|
||||
final String summaryPath;
|
||||
|
||||
InSummarySource(this.uri, this.summaryPath);
|
||||
InSummarySource(Uri uri, this.summaryPath) : super(uri);
|
||||
|
||||
@override
|
||||
TimestampedData<String> get contents => new TimestampedData<String>(0, '');
|
||||
|
||||
@override
|
||||
String get encoding => uri.toString();
|
||||
|
||||
@override
|
||||
String get fullName => encoding;
|
||||
|
||||
@override
|
||||
int get hashCode => uri.hashCode;
|
||||
|
||||
@override
|
||||
bool get isInSystemLibrary => uri.scheme == DartUriResolver.DART_SCHEME;
|
||||
|
||||
@override
|
||||
int get modificationStamp => 0;
|
||||
|
||||
@override
|
||||
String get shortName => pathos.basename(fullName);
|
||||
|
||||
@override
|
||||
UriKind get uriKind => UriKind.PACKAGE_URI;
|
||||
|
||||
@override
|
||||
bool operator ==(Object object) => object is Source && object.uri == uri;
|
||||
|
||||
@override
|
||||
bool exists() => true;
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2017, 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 'package:front_end/src/base/processed_options.dart';
|
||||
import 'package:front_end/src/incremental_kernel_generator_impl.dart';
|
||||
import 'package:kernel/kernel.dart';
|
||||
|
||||
import 'compiler_options.dart';
|
||||
|
||||
/// Represents the difference between "old" and "new" states of a program.
|
||||
///
|
||||
/// Not intended to be implemented or extended by clients.
|
||||
class DeltaProgram {
|
||||
/// The new state of the program.
|
||||
///
|
||||
/// Libraries whose kernel representation is known to be unchanged since the
|
||||
/// last [DeltaProgram] are not included.
|
||||
final Map<Uri, Program> newState;
|
||||
|
||||
DeltaProgram(this.newState);
|
||||
|
||||
/// TODO(paulberry): add information about libraries that were removed.
|
||||
}
|
||||
|
||||
/// Interface for generating an initial kernel representation of a program and
|
||||
/// keeping it up to date as incremental changes are made.
|
||||
///
|
||||
/// This class maintains an internal "previous program state"; each
|
||||
/// time [computeDelta] is called, it updates the previous program state and
|
||||
/// produces a representation of what has changed. When there are few changes,
|
||||
/// a call to [computeDelta] should be much faster than compiling the whole
|
||||
/// program from scratch.
|
||||
///
|
||||
/// This class also maintains a set of "valid sources", which is a (possibly
|
||||
/// empty) subset of the sources constituting the previous program state. Files
|
||||
/// in this set are assumed to be unchanged since the last call to
|
||||
/// [computeDelta].
|
||||
///
|
||||
/// Behavior is undefined if the client does not obey the following concurrency
|
||||
/// restrictions:
|
||||
/// - no two invocations of [computeDelta] may be outstanding at any given time.
|
||||
/// - neither [invalidate] nor [invalidateAll] may be called while an invocation
|
||||
/// of [computeDelta] is outstanding.
|
||||
///
|
||||
/// Not intended to be implemented or extended by clients.
|
||||
abstract class IncrementalKernelGenerator {
|
||||
/// Creates an [IncrementalKernelGenerator] which is prepared to generate
|
||||
/// kernel representations of the program whose main library is in the given
|
||||
/// [source].
|
||||
///
|
||||
/// No file system access is performed by this constructor; the initial
|
||||
/// "previous program state" is an empty program containing no code, and the
|
||||
/// initial set of valid sources is empty. To obtain a kernel representation
|
||||
/// of the program, call [computeDelta].
|
||||
factory IncrementalKernelGenerator(Uri source, CompilerOptions options) =>
|
||||
new IncrementalKernelGeneratorImpl(source, new ProcessedOptions(options));
|
||||
|
||||
/// Generates a kernel representation of the changes to the program, assuming
|
||||
/// that all valid sources are unchanged since the last call to
|
||||
/// [computeDelta].
|
||||
///
|
||||
/// Source files in the set of valid sources are guaranteed not to be re-read
|
||||
/// from disk; they are assumed to be unchanged regardless of the state of the
|
||||
/// filesystem.
|
||||
///
|
||||
/// If the future completes successfully, the previous file state is updated
|
||||
/// and the set of valid sources is set to the set of all sources in the
|
||||
/// program.
|
||||
///
|
||||
/// If the future completes with an error (due to errors in the compiled
|
||||
/// source code), the caller may consider the previous file state and the set
|
||||
/// of valid sources to be unchanged; this means that once the user fixes the
|
||||
/// errors, it is safe to call [computeDelta] again.
|
||||
Future<DeltaProgram> computeDelta();
|
||||
|
||||
/// Remove any source file(s) associated with the given file path from the set
|
||||
/// of valid sources. This guarantees that those files will be re-read on the
|
||||
/// next call to [computeDelta]).
|
||||
void invalidate(String path);
|
||||
|
||||
/// Remove all source files from the set of valid sources. This guarantees
|
||||
/// that all files will be re-read on the next call to [computeDelta].
|
||||
///
|
||||
/// Note that this does not erase the previous program state; the next time
|
||||
/// [computeDelta] is called, if parts of the program are discovered to be
|
||||
/// unchanged, parts of the previous program state will still be re-used to
|
||||
/// speed up compilation.
|
||||
void invalidateAll();
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2017, 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 'package:front_end/src/base/processed_options.dart';
|
||||
import 'package:front_end/src/incremental_resolved_ast_generator_impl.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
|
||||
import 'compiler_options.dart';
|
||||
|
||||
/// Represents the difference between "old" and "new" states of a program.
|
||||
///
|
||||
/// Not intended to be implemented or extended by clients.
|
||||
class DeltaLibraries {
|
||||
/// The new state of the program, as a map from Uri to [ResolvedLibrary].
|
||||
///
|
||||
/// Libraries whose resolved AST is known to be unchanged since the last
|
||||
/// [DeltaLibraries] are not included.
|
||||
final Map<Uri, ResolvedLibrary> newState;
|
||||
|
||||
DeltaLibraries(this.newState);
|
||||
|
||||
/// TODO(paulberry): add information about libraries that were removed.
|
||||
}
|
||||
|
||||
/// Represents the resolved ASTs for all the compilation units in a single
|
||||
/// library.
|
||||
///
|
||||
/// Not intended to be implemented or extended by clients.
|
||||
class ResolvedLibrary {
|
||||
final CompilationUnit definingCompilationUnit;
|
||||
|
||||
ResolvedLibrary(this.definingCompilationUnit);
|
||||
|
||||
// TODO(paulberry): add support for parts.
|
||||
}
|
||||
|
||||
/// Interface for generating an initial resolved representation of a program and
|
||||
/// keeping it up to date as incremental changes are made.
|
||||
///
|
||||
/// This class maintains an internal "previous program state"; each
|
||||
/// time [computeDelta] is called, it updates the previous program state and
|
||||
/// produces a representation of what has changed. When there are few changes,
|
||||
/// a call to [computeDelta] should be much faster than compiling the whole
|
||||
/// program from scratch.
|
||||
///
|
||||
/// This class also maintains a set of "valid sources", which is a (possibly
|
||||
/// empty) subset of the sources constituting the previous program state. Files
|
||||
/// in this set are assumed to be unchanged since the last call to
|
||||
/// [computeDelta].
|
||||
///
|
||||
/// Behavior is undefined if the client does not obey the following concurrency
|
||||
/// restrictions:
|
||||
/// - no two invocations of [computeDelta] may be outstanding at any given time.
|
||||
/// - neither [invalidate] nor [invalidateAll] may be called while an invocation
|
||||
/// of [computeDelta] is outstanding.
|
||||
///
|
||||
/// Not intended to be implemented or extended by clients.
|
||||
abstract class IncrementalResolvedAstGenerator {
|
||||
/// Creates an [IncrementalResolvedAstGenerator] which is prepared to generate
|
||||
/// resolved ASTs for the program whose main library is in the given
|
||||
/// [source].
|
||||
///
|
||||
/// No file system access is performed by this constructor; the initial
|
||||
/// "previous program state" is an empty program containing no code, and the
|
||||
/// initial set of valid sources is empty. To obtain a resolved AST
|
||||
/// representation of the program, call [computeDelta].
|
||||
factory IncrementalResolvedAstGenerator(Uri source, CompilerOptions options) =>
|
||||
new IncrementalResolvedAstGeneratorImpl(source, new ProcessedOptions(options));
|
||||
|
||||
/// Generates a resolved AST representation of the changes to the program,
|
||||
/// assuming that all valid sources are unchanged since the last call to
|
||||
/// [computeDelta].
|
||||
///
|
||||
/// Source files in the set of valid sources are guaranteed not to be re-read
|
||||
/// from disk; they are assumed to be unchanged regardless of the state of the
|
||||
/// filesystem.
|
||||
///
|
||||
/// If the future completes successfully, the previous file state is updated
|
||||
/// and the set of valid sources is set to the set of all sources in the
|
||||
/// program.
|
||||
///
|
||||
/// If the future completes with an error (due to errors in the compiled
|
||||
/// source code), the caller may consider the previous file state and the set
|
||||
/// of valid sources to be unchanged; this means that once the user fixes the
|
||||
/// errors, it is safe to call [computeDelta] again.
|
||||
Future<DeltaLibraries> computeDelta();
|
||||
|
||||
/// Remove any source file(s) associated with the given file path from the set
|
||||
/// of valid sources. This guarantees that those files will be re-read on the
|
||||
/// next call to [computeDelta]).
|
||||
void invalidate(String path);
|
||||
|
||||
/// Remove all source files from the set of valid sources. This guarantees
|
||||
/// that all files will be re-read on the next call to [computeDelta].
|
||||
///
|
||||
/// Note that this does not erase the previous program state; the next time
|
||||
/// [computeDelta] is called, if parts of the program are discovered to be
|
||||
/// unchanged, parts of the previous program state will still be re-used to
|
||||
/// speed up compilation.
|
||||
void invalidateAll();
|
||||
}
|
||||
@@ -5,6 +5,33 @@
|
||||
import 'package:front_end/src/base/analysis_target.dart';
|
||||
import 'package:front_end/src/base/timestamped_data.dart';
|
||||
import 'package:front_end/src/base/uri_kind.dart';
|
||||
import 'package:path/path.dart' as pathos;
|
||||
|
||||
/// Base class providing implementations for the methods in [Source] that don't
|
||||
/// require filesystem access.
|
||||
abstract class BasicSource extends Source {
|
||||
final Uri uri;
|
||||
|
||||
BasicSource(this.uri);
|
||||
|
||||
@override
|
||||
String get encoding => uri.toString();
|
||||
|
||||
@override
|
||||
String get fullName => encoding;
|
||||
|
||||
@override
|
||||
int get hashCode => uri.hashCode;
|
||||
|
||||
@override
|
||||
bool get isInSystemLibrary => uri.scheme == 'dart';
|
||||
|
||||
@override
|
||||
String get shortName => pathos.basename(fullName);
|
||||
|
||||
@override
|
||||
bool operator ==(Object object) => object is Source && object.uri == uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* The interface `Source` defines the behavior of objects representing source code that can be
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2017, 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 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/standard_resolution_map.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/error/error.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
import 'package:front_end/incremental_kernel_generator.dart';
|
||||
import 'package:front_end/incremental_resolved_ast_generator.dart';
|
||||
import 'package:front_end/src/base/processed_options.dart';
|
||||
import 'package:front_end/src/base/source.dart';
|
||||
import 'package:front_end/src/incremental_resolved_ast_generator_impl.dart';
|
||||
import 'package:kernel/analyzer/loader.dart';
|
||||
import 'package:kernel/kernel.dart' hide Source;
|
||||
import 'package:kernel/repository.dart';
|
||||
|
||||
dynamic unimplemented() {
|
||||
// TODO(paulberry): get rid of this.
|
||||
throw new UnimplementedError();
|
||||
}
|
||||
|
||||
DartOptions _convertOptions(ProcessedOptions options) {
|
||||
// TODO(paulberry): make sure options.compileSdk is handled correctly.
|
||||
return new DartOptions(
|
||||
strongMode: true, // TODO(paulberry): options.strongMode,
|
||||
sdk: null, // TODO(paulberry): _uriToPath(options.sdkRoot, options),
|
||||
sdkSummary:
|
||||
null, // TODO(paulberry): options.compileSdk ? null : _uriToPath(options.sdkSummary, options),
|
||||
packagePath:
|
||||
null, // TODO(paulberry): _uriToPath(options.packagesFileUri, options),
|
||||
declaredVariables: null // TODO(paulberry): options.declaredVariables
|
||||
);
|
||||
}
|
||||
|
||||
/// Implementation of [IncrementalKernelGenerator].
|
||||
///
|
||||
/// Theory of operation: an instance of [IncrementalResolvedAstGenerator] is
|
||||
/// used to obtain resolved ASTs, and these are fed into kernel code generation
|
||||
/// logic.
|
||||
///
|
||||
/// Note that the kernel doesn't expect to take resolved ASTs as a direct input;
|
||||
/// it expects to request resolved ASTs from an [AnalysisContext]. To deal with
|
||||
/// this, we create [_AnalysisContextProxy] which returns the resolved ASTs when
|
||||
/// requested. TODO(paulberry): Make this unnecessary.
|
||||
class IncrementalKernelGeneratorImpl implements IncrementalKernelGenerator {
|
||||
final IncrementalResolvedAstGenerator _resolvedAstGenerator;
|
||||
final ProcessedOptions _options;
|
||||
|
||||
IncrementalKernelGeneratorImpl(Uri source, ProcessedOptions options)
|
||||
: _resolvedAstGenerator =
|
||||
new IncrementalResolvedAstGeneratorImpl(source, options),
|
||||
_options = options;
|
||||
|
||||
@override
|
||||
Future<DeltaProgram> computeDelta() async {
|
||||
var deltaLibraries = await _resolvedAstGenerator.computeDelta();
|
||||
var kernelOptions = _convertOptions(_options);
|
||||
var packages = null; // TODO(paulberry)
|
||||
var kernels = <Uri, Program>{};
|
||||
deltaLibraries.newState.forEach((uri, resolvedLibrary) {
|
||||
// The kernel generation code doesn't currently support building a kernel
|
||||
// directly from resolved ASTs--it wants to query an analysis context. So
|
||||
// we provide it with a proxy analysis context that feeds it the resolved
|
||||
// ASTs.
|
||||
var strongMode = true; // TODO(paulberry): set this correctly
|
||||
var analysisOptions = new _AnalysisOptionsProxy(strongMode);
|
||||
var context =
|
||||
new _AnalysisContextProxy(deltaLibraries.newState, analysisOptions);
|
||||
var repository = new Repository();
|
||||
var loader =
|
||||
new DartLoader(repository, kernelOptions, packages, context: context);
|
||||
loader.loadLibrary(uri);
|
||||
kernels[uri] = new Program(repository.libraries);
|
||||
});
|
||||
return new DeltaProgram(kernels);
|
||||
}
|
||||
|
||||
@override
|
||||
void invalidate(String path) => _resolvedAstGenerator.invalidate(path);
|
||||
|
||||
@override
|
||||
void invalidateAll() => _resolvedAstGenerator.invalidateAll();
|
||||
}
|
||||
|
||||
class _AnalysisContextProxy implements AnalysisContext {
|
||||
final Map<Uri, ResolvedLibrary> _resolvedLibraries;
|
||||
|
||||
@override
|
||||
final _SourceFactoryProxy sourceFactory = new _SourceFactoryProxy();
|
||||
|
||||
@override
|
||||
final AnalysisOptions analysisOptions;
|
||||
|
||||
_AnalysisContextProxy(this._resolvedLibraries, this.analysisOptions);
|
||||
|
||||
List<AnalysisError> computeErrors(Source source) {
|
||||
// TODO(paulberry): do we need to return errors sometimes?
|
||||
return [];
|
||||
}
|
||||
|
||||
LibraryElement computeLibraryElement(Source source) {
|
||||
assert(_resolvedLibraries.containsKey(source.uri));
|
||||
return resolutionMap
|
||||
.elementDeclaredByCompilationUnit(
|
||||
_resolvedLibraries[source.uri].definingCompilationUnit)
|
||||
.library;
|
||||
}
|
||||
|
||||
noSuchMethod(Invocation invocation) => unimplemented();
|
||||
|
||||
CompilationUnit resolveCompilationUnit(
|
||||
Source unitSource, LibraryElement library) {
|
||||
assert(_resolvedLibraries.containsKey(library.source.uri));
|
||||
// TODO(paulberry): support parts.
|
||||
assert(unitSource == library.source);
|
||||
return _resolvedLibraries[library.source.uri].definingCompilationUnit;
|
||||
}
|
||||
}
|
||||
|
||||
class _AnalysisOptionsProxy implements AnalysisOptions {
|
||||
final bool strongMode;
|
||||
|
||||
_AnalysisOptionsProxy(this.strongMode);
|
||||
|
||||
noSuchMethod(Invocation invocation) => unimplemented();
|
||||
}
|
||||
|
||||
class _SourceFactoryProxy implements SourceFactory {
|
||||
Source forUri2(Uri absoluteUri) => new _SourceProxy(absoluteUri);
|
||||
|
||||
noSuchMethod(Invocation invocation) => unimplemented();
|
||||
}
|
||||
|
||||
class _SourceProxy extends BasicSource {
|
||||
_SourceProxy(Uri uri) : super(uri);
|
||||
|
||||
noSuchMethod(Invocation invocation) => unimplemented();
|
||||
}
|
||||
@@ -0,0 +1,279 @@
|
||||
// Copyright (c) 2017, 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 'package:analyzer/file_system/file_system.dart';
|
||||
import 'package:analyzer/src/context/context.dart';
|
||||
import 'package:analyzer/src/dart/analysis/byte_store.dart';
|
||||
import 'package:analyzer/src/dart/analysis/driver.dart' as driver;
|
||||
import 'package:analyzer/src/dart/analysis/file_state.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart';
|
||||
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/summary_sdk.dart';
|
||||
import 'package:analyzer/src/util/absolute_path.dart';
|
||||
import 'package:front_end/incremental_resolved_ast_generator.dart';
|
||||
import 'package:front_end/src/base/processed_options.dart';
|
||||
import 'package:front_end/src/base/source.dart';
|
||||
import 'package:front_end/src/dependency_grapher_impl.dart';
|
||||
import 'package:path/src/context.dart';
|
||||
|
||||
dynamic unimplemented() {
|
||||
// TODO(paulberry): get rid of this.
|
||||
throw new UnimplementedError();
|
||||
}
|
||||
|
||||
/// Implementation of [IncrementalKernelGenerator].
|
||||
///
|
||||
/// Theory of operation: this class is a thin wrapper around
|
||||
/// [driver.AnalysisDriver]. When the client requests a new delta, we forward
|
||||
/// the request to the analysis driver. When the client calls an invalidate
|
||||
/// method, we ensure that the proper files will be re-read next time a delta is
|
||||
/// requested.
|
||||
///
|
||||
/// Note that the analysis driver expects to be able to read file contents
|
||||
/// synchronously based on filesystem path rather than asynchronously based on
|
||||
/// URI, so the file contents are first read into memory using the asynchronous
|
||||
/// FileSystem API, and then these are fed into the analysis driver using a
|
||||
/// proxy implementation of [ResourceProvider]. TODO(paulberry): make this (and
|
||||
/// other proxies in this file) unnecessary.
|
||||
class IncrementalResolvedAstGeneratorImpl
|
||||
implements IncrementalResolvedAstGenerator {
|
||||
driver.AnalysisDriverScheduler _scheduler;
|
||||
final _pathToUriMap = <String, Uri>{};
|
||||
final _uriToPathMap = <Uri, String>{};
|
||||
final _fileContents = <String, String>{};
|
||||
_ResourceProviderProxy _resourceProvider;
|
||||
driver.AnalysisDriver _driver;
|
||||
bool _isInitialized = false;
|
||||
final ProcessedOptions _options;
|
||||
final Uri _source;
|
||||
|
||||
IncrementalResolvedAstGeneratorImpl(this._source, this._options);
|
||||
|
||||
@override
|
||||
Future<DeltaLibraries> computeDelta() async {
|
||||
if (!_isInitialized) {
|
||||
await init();
|
||||
}
|
||||
// The analysis driver doesn't currently support an asynchronous file API,
|
||||
// so we have to find all the files first to read their contents.
|
||||
// TODO(paulberry): this is an unnecessary source of duplicate work and
|
||||
// should be eliminated ASAP.
|
||||
var graph = await graphForProgram([_source], _options);
|
||||
var libraries = <Uri, ResolvedLibrary>{};
|
||||
// TODO(paulberry): it should be possible to seed the driver using a URI,
|
||||
// not a file path.
|
||||
// TODO(paulberry): only start the scheduler the first time.
|
||||
_scheduler.start();
|
||||
_driver.addFile(_source.path);
|
||||
for (var libraryCycle in graph.topologicallySortedCycles) {
|
||||
for (var uri in libraryCycle.libraries.keys) {
|
||||
var contents =
|
||||
await _options.fileSystem.entityForUri(uri).readAsString();
|
||||
_storeVirtualFile(uri, uri.path, contents);
|
||||
}
|
||||
// The driver will request files from dart:, even though it actually uses
|
||||
// the data from the summary. TODO(paulberry): fix this.
|
||||
_storeVirtualFile(_DartSdkProxy._dartCoreSource.uri, 'core.dart', '');
|
||||
for (var uri in libraryCycle.libraries.keys) {
|
||||
var result = await _driver.getResult(uri.path);
|
||||
// TODO(paulberry): handle errors.
|
||||
libraries[uri] = new ResolvedLibrary(result.unit);
|
||||
}
|
||||
}
|
||||
// TODO(paulberry): stop the scheduler
|
||||
return new DeltaLibraries(libraries);
|
||||
}
|
||||
|
||||
Future<Null> init() async {
|
||||
// TODO(paulberry): can we just use null?
|
||||
var performanceLog = new driver.PerformanceLog(new _NullStringSink());
|
||||
_scheduler = new driver.AnalysisDriverScheduler(performanceLog);
|
||||
_resourceProvider =
|
||||
new _ResourceProviderProxy(_fileContents, _pathToUriMap);
|
||||
// TODO(paulberry): MemoryByteStore leaks memory (it never discards data).
|
||||
// Do something better here.
|
||||
var byteStore = new MemoryByteStore();
|
||||
// TODO(paulberry): can we just use null?
|
||||
var fileContentOverlay = new FileContentOverlay();
|
||||
var sdkContext = new AnalysisContextImpl();
|
||||
var dartSdk = new _DartSdkProxy(await _options.getSdkSummary(), sdkContext);
|
||||
sdkContext.sourceFactory =
|
||||
new SourceFactory([new DartUriResolver(dartSdk)]);
|
||||
bool strongMode = true; // TODO(paulberry): support strong mode flag.
|
||||
sdkContext.resultProvider = new SdkSummaryResultProvider(
|
||||
sdkContext, await _options.getSdkSummary(), strongMode);
|
||||
|
||||
var sourceFactory =
|
||||
new _SourceFactoryProxy(dartSdk, _pathToUriMap, _uriToPathMap);
|
||||
var analysisOptions = new AnalysisOptionsImpl();
|
||||
_driver = new driver.AnalysisDriver(
|
||||
_scheduler,
|
||||
performanceLog,
|
||||
_resourceProvider,
|
||||
byteStore,
|
||||
fileContentOverlay,
|
||||
sourceFactory,
|
||||
analysisOptions);
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
@override
|
||||
void invalidate(String path) {
|
||||
throw new UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
void invalidateAll() {
|
||||
throw new UnimplementedError();
|
||||
}
|
||||
|
||||
void _storeVirtualFile(Uri uri, String path, String contents) {
|
||||
_pathToUriMap[path] = uri;
|
||||
_uriToPathMap[uri] = path;
|
||||
_fileContents[path] = contents;
|
||||
}
|
||||
}
|
||||
|
||||
class _DartSdkProxy implements DartSdk {
|
||||
static final _dartCoreSource =
|
||||
new _SourceProxy(Uri.parse('dart:core'), 'core.dart');
|
||||
|
||||
final PackageBundle summary;
|
||||
|
||||
final AnalysisContext context;
|
||||
|
||||
_DartSdkProxy(this.summary, this.context);
|
||||
|
||||
@override
|
||||
PackageBundle getLinkedBundle() => summary;
|
||||
|
||||
@override
|
||||
Source mapDartUri(String uri) {
|
||||
// TODO(paulberry): this seems hacky.
|
||||
return new _SourceProxy(Uri.parse(uri), '$uri.dart');
|
||||
}
|
||||
|
||||
noSuchMethod(Invocation invocation) => unimplemented();
|
||||
}
|
||||
|
||||
class _FileProxy implements File {
|
||||
final _SourceProxy _source;
|
||||
|
||||
final _ResourceProviderProxy _resourceProvider;
|
||||
|
||||
_FileProxy(this._source, this._resourceProvider);
|
||||
|
||||
@override
|
||||
String get path => _source.fullName;
|
||||
|
||||
@override
|
||||
String get shortName => path;
|
||||
|
||||
@override
|
||||
Source createSource([Uri uri]) {
|
||||
assert(uri == null);
|
||||
return _source;
|
||||
}
|
||||
|
||||
noSuchMethod(Invocation invocation) => unimplemented();
|
||||
|
||||
@override
|
||||
String readAsStringSync() {
|
||||
assert(_resourceProvider.fileContents.containsKey(path));
|
||||
return _resourceProvider.fileContents[path];
|
||||
}
|
||||
}
|
||||
|
||||
/// A string sink that ignores everything written to it.
|
||||
class _NullStringSink implements StringSink {
|
||||
void write(Object obj) {}
|
||||
void writeAll(Iterable objects, [String separator = ""]) {}
|
||||
void writeCharCode(int charCode) {}
|
||||
void writeln([Object obj = ""]) {}
|
||||
}
|
||||
|
||||
class _ResourceProviderProxy implements ResourceProvider {
|
||||
final Map<String, String> fileContents;
|
||||
final Map<String, Uri> pathToUriMap;
|
||||
|
||||
_ResourceProviderProxy(this.fileContents, this.pathToUriMap);
|
||||
|
||||
@override
|
||||
AbsolutePathContext get absolutePathContext => throw new UnimplementedError();
|
||||
|
||||
@override
|
||||
Context get pathContext => throw new UnimplementedError();
|
||||
|
||||
@override
|
||||
File getFile(String path) {
|
||||
assert(fileContents.containsKey(path));
|
||||
assert(pathToUriMap.containsKey(path));
|
||||
return new _FileProxy(new _SourceProxy(pathToUriMap[path], path), this);
|
||||
}
|
||||
|
||||
@override
|
||||
Folder getFolder(String path) => throw new UnimplementedError();
|
||||
|
||||
@override
|
||||
Future<List<int>> getModificationTimes(List<Source> sources) =>
|
||||
throw new UnimplementedError();
|
||||
|
||||
@override
|
||||
Resource getResource(String path) => throw new UnimplementedError();
|
||||
|
||||
@override
|
||||
Folder getStateLocation(String pluginId) => throw new UnimplementedError();
|
||||
}
|
||||
|
||||
class _SourceFactoryProxy implements SourceFactory {
|
||||
@override
|
||||
final DartSdk dartSdk;
|
||||
|
||||
final Map<String, Uri> pathToUriMap;
|
||||
|
||||
final Map<Uri, String> uriToPathMap;
|
||||
|
||||
@override
|
||||
AnalysisContext context;
|
||||
|
||||
_SourceFactoryProxy(this.dartSdk, this.pathToUriMap, this.uriToPathMap);
|
||||
|
||||
@override
|
||||
SourceFactory clone() => this;
|
||||
|
||||
@override
|
||||
Source forUri(String absoluteUri) {
|
||||
if (absoluteUri == 'dart:core') return _DartSdkProxy._dartCoreSource;
|
||||
Uri uri = Uri.parse(absoluteUri);
|
||||
assert(uriToPathMap.containsKey(uri));
|
||||
return new _SourceProxy(uri, uriToPathMap[uri]);
|
||||
}
|
||||
|
||||
noSuchMethod(Invocation invocation) => unimplemented();
|
||||
|
||||
Source resolveUri(Source containingSource, String containedUri) {
|
||||
// TODO(paulberry): re-use code from dependency_grapher_impl, and support
|
||||
// SDK URI resolution logic.
|
||||
var absoluteUri = containingSource.uri.resolve(containedUri);
|
||||
return forUri(absoluteUri.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Uri restoreUri(Source source) => source.uri;
|
||||
}
|
||||
|
||||
class _SourceProxy extends BasicSource {
|
||||
@override
|
||||
final String fullName;
|
||||
|
||||
_SourceProxy(Uri uri, this.fullName) : super(uri);
|
||||
|
||||
int get modificationStamp => 0;
|
||||
|
||||
noSuchMethod(Invocation invocation) => unimplemented();
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright (c) 2017, 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 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:analyzer/src/dart/sdk/sdk.dart';
|
||||
import 'package:front_end/compiler_options.dart';
|
||||
import 'package:front_end/incremental_kernel_generator.dart';
|
||||
import 'package:front_end/memory_file_system.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:path/path.dart' as pathos;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(IncrementalKernelGeneratorTest);
|
||||
});
|
||||
}
|
||||
|
||||
final _sdkSummary = _readSdkSummary();
|
||||
|
||||
List<int> _readSdkSummary() {
|
||||
var resourceProvider = PhysicalResourceProvider.INSTANCE;
|
||||
var sdk = new FolderBasedDartSdk(resourceProvider,
|
||||
FolderBasedDartSdk.defaultSdkDirectory(resourceProvider))
|
||||
..useSummary = true;
|
||||
var path = resourceProvider.pathContext
|
||||
.join(sdk.directory.path, 'lib', '_internal', 'strong.sum');
|
||||
return resourceProvider.getFile(path).readAsBytesSync();
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class IncrementalKernelGeneratorTest {
|
||||
static final sdkSummaryUri = Uri.parse('special:sdk_summary');
|
||||
|
||||
/// Virtual filesystem for testing.
|
||||
final fileSystem = new MemoryFileSystem(pathos.posix, Uri.parse('file:///'));
|
||||
|
||||
/// The object under test.
|
||||
IncrementalKernelGenerator incrementalKernelGenerator;
|
||||
|
||||
Future<Map<Uri, Program>> getInitialState(Uri startingUri) async {
|
||||
fileSystem.entityForUri(sdkSummaryUri).writeAsBytesSync(_sdkSummary);
|
||||
incrementalKernelGenerator = new IncrementalKernelGenerator(
|
||||
startingUri,
|
||||
new CompilerOptions()
|
||||
..fileSystem = fileSystem
|
||||
..chaseDependencies = true
|
||||
..sdkSummary = sdkSummaryUri
|
||||
..packagesFileUri = new Uri());
|
||||
return (await incrementalKernelGenerator.computeDelta()).newState;
|
||||
}
|
||||
|
||||
test_emptyProgram() async {
|
||||
writeFiles({'/foo.dart': 'main() {}'});
|
||||
var fileUri = Uri.parse('file:///foo.dart');
|
||||
var initialState = await getInitialState(fileUri);
|
||||
expect(initialState.keys, unorderedEquals([fileUri]));
|
||||
var program = initialState[fileUri];
|
||||
expect(program.libraries, hasLength(1));
|
||||
var library = program.libraries[0];
|
||||
expect(library.importUri, fileUri);
|
||||
expect(library.classes, isEmpty);
|
||||
expect(library.procedures, hasLength(1));
|
||||
expect(library.procedures[0].name.name, 'main');
|
||||
var body = library.procedures[0].function.body;
|
||||
expect(body, new isInstanceOf<Block>());
|
||||
var block = body as Block;
|
||||
expect(block.statements, isEmpty);
|
||||
}
|
||||
|
||||
/// Write the given file contents to the virtual filesystem.
|
||||
void writeFiles(Map<String, String> contents) {
|
||||
contents.forEach((path, text) {
|
||||
fileSystem
|
||||
.entityForUri(Uri.parse('file://$path'))
|
||||
.writeAsStringSync(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2017, 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 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/file_system/physical_file_system.dart';
|
||||
import 'package:analyzer/src/dart/sdk/sdk.dart';
|
||||
import 'package:front_end/compiler_options.dart';
|
||||
import 'package:front_end/incremental_resolved_ast_generator.dart';
|
||||
import 'package:front_end/memory_file_system.dart';
|
||||
import 'package:path/path.dart' as pathos;
|
||||
import 'package:test/test.dart';
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
defineReflectiveTests(IncrementalResolvedAstGeneratorTest);
|
||||
});
|
||||
}
|
||||
|
||||
final _sdkSummary = _readSdkSummary();
|
||||
|
||||
List<int> _readSdkSummary() {
|
||||
var resourceProvider = PhysicalResourceProvider.INSTANCE;
|
||||
var sdk = new FolderBasedDartSdk(resourceProvider,
|
||||
FolderBasedDartSdk.defaultSdkDirectory(resourceProvider))
|
||||
..useSummary = true;
|
||||
var path = resourceProvider.pathContext
|
||||
.join(sdk.directory.path, 'lib', '_internal', 'strong.sum');
|
||||
return resourceProvider.getFile(path).readAsBytesSync();
|
||||
}
|
||||
|
||||
@reflectiveTest
|
||||
class IncrementalResolvedAstGeneratorTest {
|
||||
static final sdkSummaryUri = Uri.parse('special:sdk_summary');
|
||||
|
||||
/// Virtual filesystem for testing.
|
||||
final fileSystem = new MemoryFileSystem(pathos.posix, Uri.parse('file:///'));
|
||||
|
||||
/// The object under test.
|
||||
IncrementalResolvedAstGenerator incrementalResolvedAstGenerator;
|
||||
|
||||
Future<Map<Uri, ResolvedLibrary>> getInitialProgram(Uri startingUri) async {
|
||||
fileSystem.entityForUri(sdkSummaryUri).writeAsBytesSync(_sdkSummary);
|
||||
incrementalResolvedAstGenerator = new IncrementalResolvedAstGenerator(
|
||||
startingUri,
|
||||
new CompilerOptions()
|
||||
..fileSystem = fileSystem
|
||||
..chaseDependencies = true
|
||||
..sdkSummary = sdkSummaryUri
|
||||
..packagesFileUri = new Uri());
|
||||
return (await incrementalResolvedAstGenerator.computeDelta()).newState;
|
||||
}
|
||||
|
||||
test_emptyProgram() async {
|
||||
writeFiles({'/foo.dart': 'main() {}'});
|
||||
var fooUri = Uri.parse('file:///foo.dart');
|
||||
var initialProgram = await getInitialProgram(fooUri);
|
||||
expect(initialProgram.keys, unorderedEquals([fooUri]));
|
||||
var unit = initialProgram[fooUri].definingCompilationUnit;
|
||||
expect(unit.declarations, hasLength(1));
|
||||
expect(unit.declarations[0], new isInstanceOf<FunctionDeclaration>());
|
||||
var main = unit.declarations[0] as FunctionDeclaration;
|
||||
expect(main.name.name, 'main');
|
||||
// TODO(paulberry): test that stuff is actually resolved.
|
||||
// TODO(paulberry): test parts.
|
||||
}
|
||||
|
||||
/// Write the given file contents to the virtual filesystem.
|
||||
void writeFiles(Map<String, String> contents) {
|
||||
contents.forEach((path, text) {
|
||||
fileSystem
|
||||
.entityForUri(Uri.parse('file://$path'))
|
||||
.writeAsStringSync(text);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -108,8 +108,8 @@ class DartLoader implements ReferenceLevelLoader {
|
||||
bool get strongMode => context.analysisOptions.strongMode;
|
||||
|
||||
DartLoader(this.repository, DartOptions options, Packages packages,
|
||||
{DartSdk dartSdk})
|
||||
: this.context = createContext(options, packages, dartSdk: dartSdk),
|
||||
{DartSdk dartSdk, AnalysisContext context})
|
||||
: this.context = context ?? createContext(options, packages, dartSdk: dartSdk),
|
||||
this.applicationRoot = options.applicationRoot;
|
||||
|
||||
String getLibraryName(LibraryElement element) {
|
||||
|
||||
Reference in New Issue
Block a user