private ConstructScheduleResult ConstructSchedule()

in Public/Src/Engine/Dll/Engine.cs [2779:3176]


        private ConstructScheduleResult ConstructSchedule(
            LoggingContext loggingContext,
            int maxDegreeOfParallelism,
            CacheInitializationTask cacheInitializationTask,
            JournalState journalState,
            EngineState engineState,
            out EngineSchedule engineSchedule,
            out RootFilter rootFilter)
        {
            Contract.Requires(maxDegreeOfParallelism > 0, "maxDegreeOfParallelism > 0");

            engineSchedule = null;
            rootFilter = null;
            bool enablePartialEvaluation = Configuration.FrontEnd.UsePartialEvaluation();

            bool reusedGraph = false;

            if (Configuration.Engine.Phase == EnginePhases.None)
            {
                EndPhase(EnginePhases.None);
                return ConstructScheduleResult.None;
            }

            var cachedGraphIdToLoad = Configuration.Cache.CachedGraphIdToLoad;
            GraphFingerprint graphFingerprint;
            if (!string.IsNullOrEmpty(cachedGraphIdToLoad))
            {
                var parsed = Fingerprint.TryParse(cachedGraphIdToLoad, out var fingerprint);
                Contract.Assert(parsed);

                // the id is already validated by argument parsing.
                var compositeFingerprint = CompositeGraphFingerprint.Zero;
                compositeFingerprint.OverallFingerprint = new ContentFingerprint(fingerprint);

                graphFingerprint = new GraphFingerprint(compositeFingerprint, compositeFingerprint);
            }
            else
            {
                // The fingerprint needs to include the values that get evaluated.
                EvaluationFilter partialEvaluationData = EvaluationFilter.Empty;
                if (enablePartialEvaluation &&
                    !EngineSchedule.TryGetEvaluationFilter(
                        loggingContext,
                        Context,
                        m_initialCommandLineConfiguration,
                        Configuration,
                        out partialEvaluationData))
                {
                    Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged during graph scheduling.");
                    return ConstructScheduleResult.Failure;
                }

                graphFingerprint = Configuration.Distribution.BuildRole != DistributedBuildRoles.Worker
                    ? GraphFingerprinter.TryComputeFingerprint(
                        loggingContext,
                        m_initialCommandLineConfiguration.Startup,
                        Configuration,
                        Context.PathTable,
                        // Get deserialized version of evaluation filter so that it no longer depends on path table.
                        partialEvaluationData.GetDeserializedFilter(),
                        FileContentTable,
                        m_commitId,
                        TestHooks)
                    : null;
            }

            GraphReuseResult reuseResult = null;
            var phase = Configuration.Engine.Phase;
            if (phase.HasFlag(EnginePhases.Schedule)
                &&
                ((IsGraphCacheConsumptionAllowed() && graphFingerprint != null) ||
                 Configuration.Distribution.BuildRole == DistributedBuildRoles.Worker))
            {
                reuseResult = AttemptToReuseGraph(
                    loggingContext,
                    maxDegreeOfParallelism,
                    graphFingerprint,
                    m_initialCommandLineConfiguration.Startup.Properties,
                    cacheInitializationTask,
                    journalState,
                    engineState);

                if (TestHooks != null)
                {
                    TestHooks.GraphReuseResult = reuseResult;
                }

                if (reuseResult.IsFullReuse)
                {
                    engineSchedule = reuseResult.EngineSchedule;
                    reusedGraph = true;
                }

                if (engineSchedule == null)
                {
                    if (Configuration.Distribution.BuildRole == DistributedBuildRoles.Worker)
                    {
                        Logger.Log.DistributionWorkerCouldNotLoadGraph(loggingContext);
                        return ConstructScheduleResult.Failure;
                    }

                    var cacheConfiguration = Configuration.Cache;
                    if (HasExplicitlyLoadedGraph(cacheConfiguration))
                    {
                        if (cacheConfiguration.CachedGraphPathToLoad.IsValid)
                        {
                            Logger.Log.PipGraphByPathFailure(loggingContext, cacheConfiguration.CachedGraphPathToLoad.ToString(Context.PathTable));
                            return ConstructScheduleResult.Failure;
                        }

                        if (!string.IsNullOrEmpty(cacheConfiguration.CachedGraphIdToLoad))
                        {
                            Logger.Log.PipGraphByIdFailure(loggingContext, cacheConfiguration.CachedGraphIdToLoad);
                            return ConstructScheduleResult.Failure;
                        }

                        Contract.Assert(false, "Unhandled explicit graph load");
                    }
                }

                m_enginePerformanceInfo.CacheInitializationDurationMs = (long)cacheInitializationTask.InitializationTime.TotalMilliseconds;
            }

            if (Configuration.Engine.ExitOnNewGraph && !reusedGraph)
            {
                Logger.Log.ExitOnNewGraph(loggingContext);
                return ConstructScheduleResult.ExitOnNewGraph;
            }

            m_buildViewModel.SetContext(Context);

            try
            {
                if (engineSchedule == null)
                {
                    // We have established a graph cache miss, and may choose to save a graph for next time.
                    // We need an input tracker to accumulate all spec, config, assembly, etc. inputs which
                    // we use as part of constructing a new graph; the resulting assertions are
                    // used to validate re-use of this new graph on subsequent runs.
                    InputTracker inputTrackerForGraphConstruction;
                    if (graphFingerprint != null)
                    {
                        if (reuseResult?.InputChanges != null)
                        {
                            inputTrackerForGraphConstruction = InputTracker.ContinueExistingTrackerWithInputChanges(
                                loggingContext,
                                FileContentTable,
                                reuseResult.InputChanges,
                                graphFingerprint.ExactFingerprint);

                            // For instance, a spec is changed and it uses 'glob' instead of 'globR' and we should remove some tracked directories.
                            // Instead of modifying the set of directory fingerprints, let's retrack the directories by starting with an empty set.
                            inputTrackerForGraphConstruction.ClearDirectoryFingerprints();
                        }
                        else
                        {
                            inputTrackerForGraphConstruction = InputTracker.Create(
                                loggingContext,
                                FileContentTable,
                                journalState,
                                graphFingerprint.ExactFingerprint);
                        }
                    }
                    else
                    {
                        inputTrackerForGraphConstruction = InputTracker.CreateDisabledTracker(loggingContext);
                    }

                    // must not use previously created 'mountsTable' (in 'DoRun') because the context
                    // and its path table might have been invalidated/changed in the meantime
                    var mountsTable = MountsTable.CreateAndRegister(loggingContext, Context, Configuration, m_initialCommandLineConfiguration.Startup.Properties);

                    using (var frontEndEngineAbstraction = new FrontEndEngineImplementation(
                        loggingContext,
                        Context.PathTable,
                        Configuration,
                        m_initialCommandLineConfiguration.Startup,
                        mountsTable,
                        inputTrackerForGraphConstruction,
                        m_snapshotCollector,
                        m_directoryTranslator,
                        () => FileContentTable,
                        Configuration.Logging.GetTimerUpdatePeriodInMs(),
                        reuseResult?.IsPartialReuse == true,
                        FrontEndController.RegisteredFrontEnds))
                    {
                        PipGraph newlyEvaluatedGraph;
                        if (TestHooks?.FrontEndEngineAbstraction != null)
                        {
                            TestHooks.FrontEndEngineAbstraction.Value = frontEndEngineAbstraction;
                        }

                        var sw = Stopwatch.StartNew();
                        // Create the evaluation filter
                        EvaluationFilter evaluationFilter;
                        if (enablePartialEvaluation)
                        {
                            if (!EngineSchedule.TryGetEvaluationFilter(
                                loggingContext,
                                Context,
                                m_initialCommandLineConfiguration,
                                Configuration,
                                out evaluationFilter))
                            {
                                Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged during graph scheduling.");
                                return ConstructScheduleResult.Failure;
                            }
                        }
                        else
                        {
                            evaluationFilter = EvaluationFilter.Empty;
                        }

                        if (!ConstructAndEvaluateGraph(
                            loggingContext,
                            frontEndEngineAbstraction,
                            cacheInitializationTask,
                            mountsTable,
                            evaluationFilter,
                            reuseResult,
                            out newlyEvaluatedGraph))
                        {
                            Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged during DScript graph construction.");
                            return ConstructScheduleResult.Failure;
                        }

                        m_enginePerformanceInfo.GraphConstructionDurationMs = sw.ElapsedMilliseconds;

                        if (Configuration.Engine.LogStatistics)
                        {
                            BuildXL.Tracing.Logger.Log.Statistic(
                                loggingContext,
                                new BuildXL.Tracing.Statistic
                                {
                                    Name = "GraphConstruction.DurationMs",
                                    Value = m_enginePerformanceInfo.GraphConstructionDurationMs
                                });
                        }

                        if (m_snapshotCollector != null)
                        {
                            if (newlyEvaluatedGraph != null)
                            {
                                m_snapshotCollector.SetPipGraph(newlyEvaluatedGraph);
                            }

                            // Collect all mounts.
                            foreach (var mount in mountsTable.AllMounts)
                            {
                                m_snapshotCollector.RecordMount(mount);
                            }
                        }

                        if (!phase.HasFlag(EnginePhases.Schedule))
                        {
                            return reusedGraph ? ConstructScheduleResult.ReusedExistingGraph : ConstructScheduleResult.ConstructedNewGraph;
                        }

                        Contract.Assert(newlyEvaluatedGraph != null);

                        // TODO: Shouldn't need to force the cache until the execution phase; this can be fixed when BuildXLScheduler is constructed only in the Execute phase.
                        Possible<CacheInitializer> possibleCacheInitializer = cacheInitializationTask.GetAwaiter().GetResult();
                        if (!possibleCacheInitializer.Succeeded)
                        {
                            // StorageCacheStartupError has been logged by CacheInitializer
                            return ConstructScheduleResult.Failure;
                        }

                        CacheInitializer cacheInitializerForGraphConstruction = possibleCacheInitializer.Result;

                        engineSchedule = EngineSchedule.Create(
                            loggingContext,
                            context: Context,
                            cacheInitializer: cacheInitializerForGraphConstruction,
                            configuration: Configuration,
                            fileContentTable: FileContentTable,
                            pipGraph: newlyEvaluatedGraph,
                            journalState: journalState,
                            mountPathExpander: mountsTable.MountPathExpander,
                            directoryMembershipFingerprinterRules: new DirectoryMembershipFingerprinterRuleSet(Configuration, Context.StringTable),
                            performanceCollector: m_collector,
                            directoryTranslator: m_directoryTranslator,
                            maxDegreeOfParallelism: Configuration.FrontEnd.MaxFrontEndConcurrency(),
                            tempCleaner: m_tempCleaner,
                            buildEngineFingerprint: graphFingerprint?.ExactFingerprint.BuildEngineHash.ToString(),
                            detoursListener: TestHooks?.DetoursListener);

                        if (engineSchedule == null)
                        {
                            Contract.Assert(loggingContext.ErrorWasLogged, "An error should have been logged during graph scheduling.");
                            return ConstructScheduleResult.Failure;
                        }

                        // Dispose engine state if and only if a new engine schedule is constructed from scratch.
                        engineState?.Dispose();

                        Contract.Assert(
                            !EngineState.IsUsable(engineState),
                            "Previous engine state must be unusable if a new engine schedule is constructed from scratch.");

                        var envVarsImpactingBuild = frontEndEngineAbstraction.ComputeEnvironmentVariablesImpactingBuild();
                        var mountsImpactingBuild = frontEndEngineAbstraction.ComputeEffectiveMounts();
                        var availableEnvVars = frontEndEngineAbstraction.GetAllEnvironmentVariables();

                        // Disable caching graph in the unit tests which do not set the app deployment.
                        if (TestHooks == null || TestHooks.AppDeployment != null)
                        {
                            m_graphCacheContentCachePut = CacheEngineScheduleStateAsync(
                                loggingContext,
                                graphFingerprint,
                                engineSchedule,
                                inputTrackerForGraphConstruction,
                                envVarsImpactingBuild,
                                mountsImpactingBuild,
                                availableEnvVars,
                                mountsTable.MountsByName);
                        }

                        if (IsDistributedOrchestrator)
                        {
                            // In a distributed build, we must synchronously wait for the graph to be placed in the content cache.
                            if (!m_graphCacheContentCachePut.GetAwaiter().GetResult())
                            {
                                Logger.Log.ErrorUnableToCacheGraphDistributedBuild(loggingContext);
                                return ConstructScheduleResult.Failure;
                            }
                        }
                    }
                }

                Contract.Assert(engineSchedule != null);

                // When constructing the graph above, we exit early if the schedule phase is not requested. But if we
                // didn't have to construct the engineSchedue, we wouldn't have early returned and still may not
                // have the schedule phase.
                if (!phase.HasFlag(EnginePhases.Schedule))
                {
                    return reusedGraph ? ConstructScheduleResult.ReusedExistingGraph : ConstructScheduleResult.ConstructedNewGraph;
                }

                // If the graph cache is a hit and engineState is not null and disposed, use the previous state to speed the scheduling process.
                SchedulerState previousSchedulerState = EngineState.IsUsable(engineState) ? engineState.SchedulerState : null;

                // Bail out if the build was canceled during graph construction
                if (engineSchedule.IsTerminating)
                {
                    return ConstructScheduleResult.Failure;
                }

                if (TestHooks != null)
                {
                    TestHooks.TempCleanerTempDirectory = m_tempCleaner.TempDirectory;
                }

                // Now that graph is constructed and saved, workers can be attached
                if (IsDistributedOrchestrator && phase.HasFlag(EnginePhases.Execute))
                {
                    m_orchestratorService.EnableDistribution(engineSchedule);
                }

                if (!engineSchedule.PrepareForBuild(
                    loggingContext,
                    m_initialCommandLineConfiguration,
                    Configuration,
                    previousSchedulerState,
                    ref rootFilter,
                    GetNonScrubbablePaths(),
                    m_enginePerformanceInfo))
                {
                    MakeScheduleInfoAvailableToViewer(engineSchedule);
                    Contract.Assume(loggingContext.ErrorWasLogged, "An error should have been logged during graph scheduling.");
                    return ConstructScheduleResult.Failure;
                }
            }
            finally
            {
                if (phase.HasFlag(EnginePhases.Schedule))
                {
                    EndPhase(EnginePhases.Schedule);
                }
            }

            MakeScheduleInfoAvailableToViewer(engineSchedule);

            if (TestHooks?.Scheduler != null)
            {
                TestHooks.Scheduler.Value = engineSchedule.Scheduler;
            }

            // Need to thread through the process start time checking it from the currently running process is not
            // valid for server mode builds.
            engineSchedule.Scheduler.SetProcessStartTime(m_processStartTimeUtc);

            // We are done scheduling. Log configuration statistics used for scheduling.
            Logger.Log.ScheduleConstructedWithConfiguration(loggingContext, Configuration.GetStatistics().ResolverKinds);

            return reusedGraph ? ConstructScheduleResult.ReusedExistingGraph : ConstructScheduleResult.ConstructedNewGraph;
        }