Ejemplo n.º 1
0
        /// <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());
        }
Ejemplo n.º 2
0
 /// <inheritdoc />
 public Task <Possible <ISourceFile>[]> ParseAndBindSpecsAsync(SpecWithOwningModule[] specs)
 {
     return(m_decoratee.ParseAndBindSpecsAsync(specs));
 }