diff --git a/eng/pipelines/templates/build-official-release.yml b/eng/pipelines/templates/build-official-release.yml index 5b533112bd5..18762e00c80 100644 --- a/eng/pipelines/templates/build-official-release.yml +++ b/eng/pipelines/templates/build-official-release.yml @@ -194,7 +194,7 @@ jobs: inputs: bootstrapperCoreVersion: latest vsMajorVersion: ${{ parameters.VisualStudioMinimumVersion }} - channelName: int.main + channelName: int.d18.0 manifests: $(Build.SourcesDirectory)/artifacts/$(BuildConfiguration)/VSSetup/Insertion/SetupManifest.vsman # Outputting to the Insertion folder allows the bootstrapper to be published to the Products drop, along with our insertion files. # The merged .vsman (OverlaidInstallerManifest.vsman) created by the bootstrapper assumes the bootstrapper will be output to the same drop (Products) as the insertion files. diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/HotReload/VisualStudioBrowserRefreshServer.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/HotReload/VisualStudioBrowserRefreshServer.cs index d4ee1abdc4c..7aa125d4580 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/HotReload/VisualStudioBrowserRefreshServer.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/HotReload/VisualStudioBrowserRefreshServer.cs @@ -61,7 +61,7 @@ async Task ListenAsync(CancellationToken cancellationToken) _ = OnBrowserConnected(webSocketContext.WebSocket, webSocketContext.SecWebSocketProtocols.FirstOrDefault()); } - catch (Exception e) + catch (Exception e) when (e is not (OperationCanceledException or ObjectDisposedException)) { Logger.LogError("Accepting web socket exception: {Message}", e.Message); @@ -70,13 +70,13 @@ async Task ListenAsync(CancellationToken cancellationToken) } } } - catch (OperationCanceledException) + catch (Exception e) when (e is OperationCanceledException or ObjectDisposedException) { - // nop + // expected during shutdown } catch (Exception e) { - Logger.LogError("HttpListener exception: {Message}", e.Message); + Logger.LogError("Unexpected HttpListener exception: {Message}", e.Message); } } } diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Retargeting/DotNetReleasesProvider.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Retargeting/DotNetReleasesProvider.cs index 4a3411f5fd2..9493705a9e2 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Retargeting/DotNetReleasesProvider.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Retargeting/DotNetReleasesProvider.cs @@ -133,7 +133,7 @@ private Task CreateDefaultFileIfNotExistAsync(string path, string resource, bool { // Find the product that matches the major/minor version of the SDK Product matchingProduct = products.FirstOrDefault(p => p.LatestSdkVersion.Major == parsedSdkVersion.Major && - p.LatestSdkVersion.Minor == parsedSdkVersion.Minor && p.LatestSdkVersion.IsLaterThan(sdkVersion)); + p.LatestSdkVersion.Minor == parsedSdkVersion.Minor && p.LatestSdkVersion.IsLaterThan(parsedSdkVersion)); if (matchingProduct is not null) { diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Retargeting/ProjectRetargetHandler.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Retargeting/ProjectRetargetHandler.cs index fe9ad193f78..5f2f2f24b1f 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Retargeting/ProjectRetargetHandler.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Retargeting/ProjectRetargetHandler.cs @@ -13,28 +13,28 @@ namespace Microsoft.VisualStudio.ProjectSystem.VS.Retargeting; [Order(Order.Default)] internal sealed partial class ProjectRetargetHandler : IProjectRetargetHandler, IDisposable { - private readonly UnconfiguredProject _unconfiguredProject; private readonly Lazy _releasesProvider; private readonly IFileSystem _fileSystem; private readonly IProjectThreadingService _projectThreadingService; private readonly IVsService _projectRetargetingService; + private readonly IVsService _solutionService; private Guid _currentSdkDescriptionId = Guid.Empty; private Guid _sdkRetargetId = Guid.Empty; [ImportingConstructor] public ProjectRetargetHandler( - UnconfiguredProject unconfiguredProject, Lazy releasesProvider, IFileSystem fileSystem, IProjectThreadingService projectThreadingService, - IVsService projectRetargetingService) + IVsService projectRetargetingService, + IVsService solutionService) { - _unconfiguredProject = unconfiguredProject; _releasesProvider = releasesProvider; _fileSystem = fileSystem; _projectThreadingService = projectThreadingService; _projectRetargetingService = projectRetargetingService; + _solutionService = solutionService; } public Task CheckForRetargetAsync(RetargetCheckOptions options) @@ -133,11 +133,11 @@ public Task RetargetAsync(TextWriter outputLogger, RetargetOptions options, IPro private async Task GetSdkVersionForProjectAsync() { - string projectDirectory = _unconfiguredProject.GetProjectDirectory(); + string? solutionDirectory = await GetSolutionDirectoryAsync(); - if (!string.IsNullOrEmpty(projectDirectory)) + if (!string.IsNullOrEmpty(solutionDirectory)) { - string? globalJsonPath = FindGlobalJsonPath(projectDirectory); + string? globalJsonPath = FindGlobalJsonPath(solutionDirectory!); if (globalJsonPath is not null) { try @@ -159,6 +159,22 @@ public Task RetargetAsync(TextWriter outputLogger, RetargetOptions options, IPro return null; } + private async Task GetSolutionDirectoryAsync() + { + IVsSolution? solution = await _solutionService.GetValueOrNullAsync(); + + if (solution is not null) + { + int hr = solution.GetSolutionInfo(out string solutionDirectory, out string _, out string _); + if (hr == HResult.OK && !string.IsNullOrEmpty(solutionDirectory)) + { + return solutionDirectory; + } + } + + return null; + } + public void Dispose() { if (_currentSdkDescriptionId != Guid.Empty || _sdkRetargetId != Guid.Empty) diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/HotReloadLogger.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/HotReloadLogger.cs index f8ee1a7a414..bb3fde395e3 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/HotReloadLogger.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/HotReloadLogger.cs @@ -10,7 +10,8 @@ namespace Microsoft.VisualStudio.ProjectSystem.HotReload; internal sealed class HotReloadLogger(IHotReloadDiagnosticOutputService service, string projectName, string variant, int sessionInstanceId, string categoryName) : ILogger { public bool IsEnabled(LogLevel logLevel) - => true; + // Do not enable Trace level logging as it affects the application output + => logLevel >= LogLevel.Debug; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { diff --git a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/ProjectHotReloadSession.cs b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/ProjectHotReloadSession.cs index 32267d1f420..1664fc8ee8f 100644 --- a/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/ProjectHotReloadSession.cs +++ b/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/HotReload/ProjectHotReloadSession.cs @@ -1,6 +1,5 @@ // Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Diagnostics.CodeAnalysis; using System.Runtime.Versioning; using Microsoft.DotNet.HotReload; using Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue; @@ -72,16 +71,6 @@ internal static string GetInjectedAssemblyPath(string targetFramework, string as public IDeltaApplier? DeltaApplier => _lazyDeltaApplier; - [MemberNotNull(nameof(_lazyDeltaApplier))] - private void RequireActiveSession() - { - if (!_sessionActive) - throw new InvalidOperationException($"Hot Reload session has not started"); - - if (_lazyDeltaApplier is null) - throw new InvalidOperationException(); - } - public async Task ApplyChangesAsync(CancellationToken cancellationToken) { if (_sessionActive) @@ -199,19 +188,29 @@ public async Task StartSessionAsync(CancellationToken cancellationToken) public async Task StopSessionAsync(CancellationToken cancellationToken) { - RequireActiveSession(); - - _sessionActive = false; - _lazyDeltaApplier.Dispose(); - _lazyDeltaApplier = null; + if (_sessionActive && _lazyDeltaApplier is not null) + { + _sessionActive = false; + _lazyDeltaApplier.Dispose(); + _lazyDeltaApplier = null; - await _hotReloadAgentManagerClient.Value.AgentTerminatedAsync(this, cancellationToken); - WriteToOutputWindow(Resources.HotReloadStopSession, default); + await _hotReloadAgentManagerClient.Value.AgentTerminatedAsync(this, cancellationToken); + WriteToOutputWindow(Resources.HotReloadStopSession, default); + } } public async ValueTask ApplyUpdatesAsync(ImmutableArray updates, CancellationToken cancellationToken) { - RequireActiveSession(); + // A stricter check for session active could be done here, like raise an exception when not active session or no delta applier + // But sometimes debugger would call ApplyUpdatesAsync even when there is not active session + // e.g. when user restarts on rude edits + // We need to talk to debugger team to see if we can avoid such calls in the future + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2581474 + if (_sessionActive is false || _lazyDeltaApplier is null) + { + DebugTrace($"{nameof(ApplyUpdatesAsync)} called but the session is not active."); + return; + } try { @@ -248,11 +247,15 @@ private bool LogAndPropagate(Exception e, CancellationToken cancellationToken) return false; } - public ValueTask> GetCapabilitiesAsync(CancellationToken cancellationToken) + public async ValueTask> GetCapabilitiesAsync(CancellationToken cancellationToken) { - RequireActiveSession(); + // Delegate to the delta applier for the session + if (_lazyDeltaApplier is not null) + { + return await _lazyDeltaApplier.GetCapabilitiesAsync(cancellationToken); + } - return _lazyDeltaApplier.GetCapabilitiesAsync(cancellationToken); + return []; } public ValueTask ReportDiagnosticsAsync(ImmutableArray diagnostics, CancellationToken cancellationToken) diff --git a/tests/Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests/ProjectSystem/HotReload/ProjectHotReloadSessionTests.cs b/tests/Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests/ProjectSystem/HotReload/ProjectHotReloadSessionTests.cs index 9e9400b91a3..62d770f2e90 100644 --- a/tests/Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests/ProjectSystem/HotReload/ProjectHotReloadSessionTests.cs +++ b/tests/Microsoft.VisualStudio.ProjectSystem.Managed.UnitTests/ProjectSystem/HotReload/ProjectHotReloadSessionTests.cs @@ -325,7 +325,7 @@ public async Task StopSessionAsync_WhenSessionNotActive_DoesNotCallAgentTerminat // Session is not started/active // Act - await Assert.ThrowsAsync(() => session.StopSessionAsync(CancellationToken.None)); + await session.StopSessionAsync(CancellationToken.None); // Assert hotReloadAgentManagerClient.Verify( @@ -381,7 +381,7 @@ public async Task ApplyUpdatesAsync_WhenSessionNotActive_DoesNotCallDeltaApplier var updates = ImmutableArray.Create(); // Act - await Assert.ThrowsAsync(() => session.ApplyUpdatesAsync(updates, CancellationToken.None).AsTask()); + await session.ApplyUpdatesAsync(updates, CancellationToken.None); // Assert deltaApplier.Verify(