File: Build\ProjectGraphFactory.cs
Web Access
Project: ..\..\..\src\BuiltInTools\dotnet-watch\dotnet-watch.csproj (dotnet-watch)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Immutable;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Graph;
using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;
 
namespace Microsoft.DotNet.Watch;
 
internal sealed class ProjectGraphFactory(ImmutableDictionary<string, string> globalOptions)
{
    /// <summary>
    /// Reuse <see cref="ProjectCollection"/> with XML element caching to improve performance.
    ///
    /// The cache is automatically updated when build files change.
    /// https://github.com/dotnet/msbuild/blob/b6f853defccd64ae1e9c7cf140e7e4de68bff07c/src/Build/Definition/ProjectCollection.cs#L343-L354
    /// </summary>
    private readonly ProjectCollection _collection = new(
        globalProperties: globalOptions,
        loggers: [],
        remoteLoggers: [],
        ToolsetDefinitionLocations.Default,
        maxNodeCount: 1,
        onlyLogCriticalEvents: false,
        loadProjectsReadOnly: false,
        useAsynchronousLogging: false,
        reuseProjectRootElementCache: true);
 
    /// <summary>
    /// Tries to create a project graph by running the build evaluation phase on the <see cref="rootProjectFile"/>.
    /// </summary>
    public ProjectGraph? TryLoadProjectGraph(
        string rootProjectFile,
        ILogger logger,
        bool projectGraphRequired,
        CancellationToken cancellationToken)
    {
        var entryPoint = new ProjectGraphEntryPoint(rootProjectFile, globalOptions);
        try
        {
            return new ProjectGraph([entryPoint], _collection, projectInstanceFactory: null, cancellationToken);
        }
        catch (Exception e) when (e is not OperationCanceledException)
        {
            // ProejctGraph aggregates OperationCanceledException exception,
            // throw here to propagate the cancellation.
            cancellationToken.ThrowIfCancellationRequested();
 
            logger.LogDebug("Failed to load project graph.");
 
            if (e is AggregateException { InnerExceptions: var innerExceptions })
            {
                foreach (var inner in innerExceptions)
                {
                    Report(inner);
                }
            }
            else
            {
                Report(e);
            }
 
            void Report(Exception e)
            {
                if (projectGraphRequired)
                {
                    logger.LogError(e.Message);
                }
                else
                {
                    logger.LogWarning(e.Message);
                }
            }
        }
 
        return null;
    }
}