| File: HotReload\ProjectUpdatesBuilder.StaticWebAssets.cs | Web Access |
| Project: src\sdk\src\Dotnet.Watch\Watch\Microsoft.DotNet.HotReload.Watch.csproj (Microsoft.DotNet.HotReload.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 System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.Build.Execution; using Microsoft.Build.Graph; using Microsoft.CodeAnalysis; using Microsoft.DotNet.HotReload; using Microsoft.Extensions.Logging; namespace Microsoft.DotNet.Watch; internal sealed partial class ProjectUpdatesBuilder { private static readonly ImmutableArray<string> s_targets = [TargetNames.GenerateComputedBuildStaticWebAssets, TargetNames.ResolveReferencedProjectsStaticWebAssets]; private sealed class AssetsBuilder(ProjectUpdatesBuilder projectUpdatesBuilder, EvaluationResult evaluationResult) : StaticWebAssetUpdateBuilder { protected override bool TryGetManifest(ProjectInstanceId id, [NotNullWhen(true)] out StaticWebAssetsManifest? manifest) => evaluationResult.StaticWebAssetsManifests.TryGetValue(id, out manifest); protected override IEnumerable<ProjectInstanceInfo> GetProjectInstances(string projectPath) => evaluationResult.ProjectGraph.GetProjectNodes(projectPath).Select(GetInfo); protected override IEnumerable<(ProjectInstanceInfo info, ILogger logger)> GetApplicationProjectAncestors(ProjectInstanceId projectInstanceId) => from node in evaluationResult.ProjectGraph.GetProjectNode(projectInstanceId).GetAncestorsAndSelf() // use any of the running projects associated with the ancestor: let runningProject = projectUpdatesBuilder.GetCorrespondingRunningProjects(node.ProjectInstance.GetId()).FirstOrDefault() where runningProject != null select (GetInfo(node), runningProject.ClientLogger); private static ProjectInstanceInfo GetInfo(ProjectGraphNode projectNode) => new() { Id = projectNode.ProjectInstance.GetId(), AssemblyName = projectNode.ProjectInstance.GetAssemblyName(), HasScopedCssTargets = HasScopedCssTargets(projectNode.ProjectInstance), }; private static bool HasScopedCssTargets(ProjectInstance projectInstance) => s_targets.All(projectInstance.Targets.ContainsKey); } public async ValueTask AddStaticAssetUpdatesAsync( IReadOnlyList<ChangedFile> files, EvaluationResult evaluationResult, Stopwatch stopwatch, CancellationToken cancellationToken) { var builder = new AssetsBuilder(this, evaluationResult); foreach (var file in files) { builder.AddAssets(file.Item.FilePath, file.Item.ContainingProjectPaths, file.Item.StaticWebAssetRelativeUrl); } if (builder.Assets.Count == 0) { return; } HashSet<ProjectInstanceId>? failedApplicationProjectInstances = null; if (builder.ProjectInstancesToRegenerate.Count > 0) { Logger.LogDebug("Regenerating scoped CSS bundles."); // Deep copy instances so that we don't pollute the project graph: var buildRequests = builder.ProjectInstancesToRegenerate .Select(instanceId => BuildRequest.Create(evaluationResult.RestoredProjectInstances[instanceId].DeepCopy(), s_targets)) .ToArray(); _ = await evaluationResult.BuildManager.BuildAsync( buildRequests, onFailure: failedInstance => { Logger.LogWarning("[{ProjectName}] Failed to regenerate scoped CSS bundle.", failedInstance.GetDisplayName()); failedApplicationProjectInstances ??= []; failedApplicationProjectInstances.Add(failedInstance.GetId()); // continue build return true; }, operationName: "ScopedCss", cancellationToken); } foreach (var (applicationProjectInstance, instanceAssets) in builder.Assets) { if (failedApplicationProjectInstances?.Contains(applicationProjectInstance) == true) { continue; } foreach (var runningProject in GetCorrespondingRunningProjects(applicationProjectInstance)) { if (!_staticAssetUpdates.TryGetValue(runningProject, out var updatesPerRunningProject)) { _staticAssetUpdates.Add(runningProject, updatesPerRunningProject = []); } if (!runningProject.Clients.UseRefreshServerToApplyStaticAssets && !runningProject.Clients.IsManagedAgentSupported) { // Static assets are applied via managed Hot Reload agent (e.g. in MAUI Blazor app), but managed Hot Reload is not supported (e.g. startup hooks are disabled). _projectsToRebuild.Add(runningProject.ProjectNode.ProjectInstance.FullPath); _projectsToRestart.Add(runningProject); } else { updatesPerRunningProject.AddRange(instanceAssets.Values); } } } } private IEnumerable<RunningProject> GetCorrespondingRunningProjects(ProjectInstanceId project) { if (!RunningProjects.TryGetValue(project.ProjectPath, out var projectsWithPath)) { return []; } return projectsWithPath.Where(p => string.Equals(p.GetTargetFramework(), project.TargetFramework, StringComparison.OrdinalIgnoreCase)); } }