fix(ivy): always re-analyze the program during incremental rebuilds (#33862)

Previously, the ngtsc compiler attempted to reuse analysis work from the
previous program during an incremental build. To do this, it had to prove
that the work was safe to reuse - that no changes made to the new program
would invalidate the previous analysis.

The implementation of this had a significant design flaw: if the previous
program had errors, the previous analysis would be missing significant
information, and the dependency graph extracted from it would not be
sufficient to determine which files should be re-analyzed to fill in the
gaps. This often meant that the build output after an error was resolved
would be wholly incorrect.

This commit switches ngtsc to take a simpler approach to incremental
rebuilds. Instead of attempting to reuse prior analysis work, the entire
program is re-analyzed with each compilation. This is actually not as
expensive as one might imagine - analysis is a fairly small part of overall
compilation time.

Based on the dependency graph extracted during this analysis, the compiler
then can make accurate decisions on whether to emit specific files. A new
suite of tests is added to validate behavior in the presence of source code
level errors.

This new approach is dramatically simpler than the previous algorithm, and
should always produce correct results for a semantically correct program.s

Fixes #32388
Fixes #32214

PR Close #33862
This commit is contained in:
Alex Rickabaugh
2019-11-12 13:38:51 -08:00
parent cf9aa4fd14
commit 4be8929844
8 changed files with 340 additions and 169 deletions

View File

@ -28,7 +28,7 @@ import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
import {TypeScriptReflectionHost} from './reflection';
import {HostResourceLoader} from './resource_loader';
import {NgModuleRouteAnalyzer, entryPointKeyFor} from './routing';
import {CompoundComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
import {ComponentScopeReader, CompoundComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, TypeCheckShimGenerator, generatedFactoryTransform} from './shims';
import {ivySwitchTransform} from './switch';
import {IvyCompilation, declarationTransformFactory, ivyTransformFactory} from './transform';
@ -186,8 +186,7 @@ export class NgtscProgram implements api.Program {
this.incrementalState = IncrementalState.fresh();
} else {
this.incrementalState = IncrementalState.reconcile(
oldProgram.incrementalState, oldProgram.reuseTsProgram, this.tsProgram,
this.modifiedResourceFiles);
oldProgram.reuseTsProgram, this.tsProgram, this.modifiedResourceFiles);
}
}
@ -309,13 +308,11 @@ export class NgtscProgram implements api.Program {
if (this.compilation === undefined) {
const analyzeSpan = this.perfRecorder.start('analyze');
this.compilation = this.makeCompilation();
this.tsProgram.getSourceFiles()
.filter(file => !file.fileName.endsWith('.d.ts'))
.forEach(file => {
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', file);
this.compilation !.analyzeSync(file);
this.perfRecorder.stop(analyzeFileSpan);
});
this.tsProgram.getSourceFiles().filter(file => !file.isDeclarationFile).forEach(file => {
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', file);
this.compilation !.analyzeSync(file);
this.perfRecorder.stop(analyzeFileSpan);
});
this.perfRecorder.stop(analyzeSpan);
this.compilation.resolve();
}
@ -576,13 +573,12 @@ export class NgtscProgram implements api.Program {
const evaluator = new PartialEvaluator(this.reflector, checker, this.incrementalState);
const dtsReader = new DtsMetadataReader(checker, this.reflector);
const localMetaRegistry = new LocalMetadataRegistry();
const localMetaReader = new CompoundMetadataReader([localMetaRegistry, this.incrementalState]);
const localMetaReader: MetadataReader = localMetaRegistry;
const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, this.aliasingHost);
const scopeRegistry = new LocalModuleScopeRegistry(
localMetaReader, depScopeReader, this.refEmitter, this.aliasingHost, this.incrementalState);
const scopeReader = new CompoundComponentScopeReader([scopeRegistry, this.incrementalState]);
const metaRegistry =
new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry, this.incrementalState]);
localMetaReader, depScopeReader, this.refEmitter, this.aliasingHost);
const scopeReader: ComponentScopeReader = scopeRegistry;
const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry]);
this.metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]);