/// <summary> /// Tries to filter a given workspace definition by reusing information from the previous BuildXL invocation. /// </summary> /// <returns> /// 1. Failure if the error occurred during parsing/binding one of the changed specs. /// 2. Result(null) when the filtering failed due to symbols mismatch or due to another reason. /// 3. Result(WorkspaceDefinition) when the filtering succeeded. /// </returns> /// <remarks> /// If the previous binding information can be reused, then the set of specs that are safe to use as public facades + serialized AST /// are identified as well /// </remarks> private async Task <FilteredWorkspaceDefinition> TryFilterWorkspaceDefinitionIncrementallyAsync( List <string> changedFiles, IWorkspaceProvider workspaceProvider, WorkspaceDefinition workspaceDefinition, EvaluationFilter evaluationFilter) { Logger.TryingToReuseFrontEndSnapshot(LoggingContext); // TODO: potentially, we could check the number of changes compared to the workspace definition size. // If the number of changes is too big, maybe we should go into the full parse mode. // But we need to check the perf implications before making this decision. var changedSpecs = changedFiles.Select( p => { var fullPath = AbsolutePath.Create(FrontEndContext.PathTable, p); var containingModule = workspaceDefinition.TryGetModuleDefinition(fullPath); return(new SpecWithOwningModule(fullPath, containingModule)); }).ToArray(); // Need to check if the spec does not belong to the current workspace // or the changed spec belongs to the prelude. foreach (var changedSpec in changedSpecs) { if (changedSpec.OwningModule == null) { Logger.FailToReuseFrontEndSnapshot( LoggingContext, I($"Changed spec file '{changedSpec.Path.ToString(FrontEndContext.PathTable)}' is not part of the computed workspace.")); return(FilteredWorkspaceDefinition.CanNotFilter()); } if (changedSpec.OwningModule.Descriptor == workspaceDefinition.PreludeModule.Descriptor) { Logger.FailToReuseFrontEndSnapshot( LoggingContext, I($"Changed spec file '{changedSpec.Path.ToString(FrontEndContext.PathTable)}' is part of the prelude.")); return(FilteredWorkspaceDefinition.CanNotFilter()); } } // Getting the snapshot from the previous run. // Binding snapshot contains all the specs as well as all the configuration files. // Need to adjust the count. var expectedNumberOfSpecs = workspaceDefinition.SpecCount + (workspaceProvider.GetConfigurationModule()?.Specs.Count ?? 0); var snapshot = FrontEndArtifactManager.TryLoadFrontEndSnapshot(expectedNumberOfSpecs); if (snapshot == null) { // The error message was already logged. return(FilteredWorkspaceDefinition.CanNotFilter()); } // Parsing and binding all the changed specs. var possibleParseResult = await workspaceProvider.ParseAndBindSpecsAsync(changedSpecs); var firstFailure = LogParseOrBindingErrorsIfAny(possibleParseResult); if (firstFailure != null) { // This is actual failure. // Instead of switching to the full mode, we can actually stop here. return(FilteredWorkspaceDefinition.Error(firstFailure)); } // Snapshot is valid and parse/binding is completed successfully. var snapshotState = GetSnapshotReuseState(possibleParseResult, snapshot); if (snapshotState.State == SnapshotState.NoMatch) { // NoMatch is returned if the snapshot is unavailable. if (snapshotState.SpecsWithIncompatiblePublicSurface.Count != 0) { Logger.FailToReuseFrontEndSnapshot( LoggingContext, I($"Spec file '{snapshotState.SpecsWithIncompatiblePublicSurface.First().Path.AbsolutePath}' changed its binding symbols.")); } return(FilteredWorkspaceDefinition.CanNotFilter()); } // Changed file could get different symbols. // Need to re-save it within the front-end snapshot. UpdateAndSaveSnapshot(possibleParseResult, snapshot); var snapshotProvider = new SnapshotBasedSpecProvider(snapshot); // Now we know exactly which are all the files that need to go through parsing/type checking/AST conversion. So we // inform that to the artifact manager so the public surface and AST serialization // can be resued for the rest, if available. // Observe these set of files are not reflecting a potential user filter, but that's fine. If there is a dirty spec // that is outside of the filter, that spec won't be requested by the workspace anyway NotifyDirtySpecsForPublicFacadeAndAstReuse( snapshotProvider, workspaceDefinition, changedSpecs.Select(f => f.Path).ToList()); // The fingerprints for all changed specs are still the same, // so we can filter the workspace definition provided that the filter allows it. if (snapshotState.State == SnapshotState.FullMatch) { var filter = new WorkspaceFilter(FrontEndContext.PathTable); var filteredWorkspace = evaluationFilter.CanPerformPartialEvaluationScript(PrimaryConfigFile) ? filter.FilterWorkspaceDefinition(workspaceDefinition, evaluationFilter, snapshotProvider) : workspaceDefinition.Modules; return(FilteredWorkspaceDefinition.Filter(new WorkspaceDefinition(filteredWorkspace, workspaceDefinition.PreludeModule))); } // Specs are not the same, but we would be able to load public facades for all unaffected specs. var dirtySpecNames = string.Join( ", ", snapshotState.SpecsWithTheSamePublicSurface.Take(10).Select(p => Path.GetFileName(p.Path.AbsolutePath))); Logger.FailedToFilterWorkspaceDefinition( LoggingContext, I($"{dirtySpecNames} changed one or more declarations.")); return(FilteredWorkspaceDefinition.CanNotFilter()); }
/// <inheritdoc /> public Task <Possible <ISourceFile>[]> ParseAndBindSpecsAsync(SpecWithOwningModule[] specs) { return(m_decoratee.ParseAndBindSpecsAsync(specs)); }