diff --git a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs index c528798034735..f9ffb0681ab50 100644 --- a/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs +++ b/src/VisualStudio/CSharp/Impl/ProjectSystemShim/CSharpProjectShim.OptionsProcessor.cs @@ -11,13 +11,14 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; using Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim.Interop; +using Microsoft.VisualStudio.LanguageServices.ProjectSystem.Legacy; using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.CSharp.ProjectSystemShim; internal partial class CSharpProjectShim { - private sealed class OptionsProcessor : ProjectSystemProjectOptionsProcessor + private sealed class OptionsProcessor : AbstractLegacyProjectSystemProjectOptionsProcessor { private readonly ProjectSystemProject _projectSystemProject; diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs index 4618fc78423ca..98232c926e590 100644 --- a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProject.cs @@ -18,6 +18,7 @@ using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel; using Microsoft.VisualStudio.LanguageServices.Implementation.TaskList; using Microsoft.VisualStudio.LanguageServices.ProjectSystem; +using Microsoft.VisualStudio.LanguageServices.ProjectSystem.Legacy; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; @@ -32,7 +33,7 @@ internal abstract partial class AbstractLegacyProject { public IVsHierarchy Hierarchy { get; } protected ProjectSystemProject ProjectSystemProject { get; } - internal ProjectSystemProjectOptionsProcessor ProjectSystemProjectOptionsProcessor { get; set; } + internal AbstractLegacyProjectSystemProjectOptionsProcessor ProjectSystemProjectOptionsProcessor { get; set; } protected IProjectCodeModel ProjectCodeModel { get; set; } protected VisualStudioWorkspace Workspace { get; } diff --git a/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProjectSystemProjectOptionsProcessor.cs b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProjectSystemProjectOptionsProcessor.cs new file mode 100644 index 0000000000000..c288e6bf021eb --- /dev/null +++ b/src/VisualStudio/Core/Def/ProjectSystem/Legacy/AbstractLegacyProjectSystemProjectOptionsProcessor.cs @@ -0,0 +1,61 @@ +// 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 file in the project root for more information. + +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Workspaces.ProjectSystem; + +namespace Microsoft.VisualStudio.LanguageServices.ProjectSystem.Legacy; + +internal abstract class AbstractLegacyProjectSystemProjectOptionsProcessor : ProjectSystemProjectOptionsProcessor +{ + public AbstractLegacyProjectSystemProjectOptionsProcessor( + ProjectSystemProject project, + SolutionServices workspaceServices) + : base(project, workspaceServices) + { + } + + public string? ExplicitRuleSetFilePath + { + get; + set + { + lock (_gate) + { + if (field == value) + { + return; + } + + field = value; + + UpdateProjectOptions_NoLock(); + } + } + } + + protected override string? GetEffectiveRulesetFilePath() + => ExplicitRuleSetFilePath ?? base.GetEffectiveRulesetFilePath(); + + protected override bool ShouldSaveCommandLine(ImmutableArray arguments) + { + // Legacy projects require this to be kept as it may be needed if ExplicitRuleSetFilePath is changed + return true; + } + + /// + /// Called by a derived class to notify that we need to update the settings in the project system for something that will be provided + /// by either + /// or . + /// + protected void UpdateProjectForNewHostValues() + { + lock (_gate) + { + UpdateProjectOptions_NoLock(); + } + } +} diff --git a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb index f5240c7a3e687..d2351a187f297 100644 --- a/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb +++ b/src/VisualStudio/VisualBasic/Impl/ProjectSystemShim/VisualBasicProject.OptionsProcessor.vb @@ -10,7 +10,7 @@ Imports Microsoft.CodeAnalysis Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.VisualBasic Imports Microsoft.CodeAnalysis.Workspaces.ProjectSystem -Imports Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem +Imports Microsoft.VisualStudio.LanguageServices.ProjectSystem.Legacy Imports Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim.Interop Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim @@ -20,7 +20,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic.ProjectSystemShim ''' Partial Friend NotInheritable Class VisualBasicProject Friend NotInheritable Class OptionsProcessor - Inherits ProjectSystemProjectOptionsProcessor + Inherits AbstractLegacyProjectSystemProjectOptionsProcessor Private _rawOptions As VBCompilerOptions Private ReadOnly _imports As New List(Of GlobalImport) diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs index dd48638b65ad9..8f2a9fb62c212 100644 --- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs +++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectOptionsProcessor.cs @@ -3,10 +3,9 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.IO; -using System.Threading; using Microsoft.CodeAnalysis.Host; using Roslyn.Utilities; @@ -17,13 +16,6 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable private readonly ProjectSystemProject _project; private readonly SolutionServices _workspaceServices; private readonly ICommandLineParserService _commandLineParserService; - private readonly ITemporaryStorageServiceInternal _temporaryStorageService; - - /// - /// Gate to guard all mutable fields in this class. - /// The lock hierarchy means you are allowed to call out of this class and into while holding the lock. - /// - private readonly object _gate = new(); /// /// A hashed checksum of the last command line we were set to. We use this @@ -32,17 +24,19 @@ internal class ProjectSystemProjectOptionsProcessor : IDisposable /// private Checksum? _commandLineChecksum; + private CommandLineArguments _commandLineArgumentsForCommandLine; + private IReferenceCountedDisposable>? _ruleSetFile = null; + /// - /// To save space in the managed heap, we dump the entire command-line string to our - /// temp-storage-service. This is helpful as compiler command-lines can grow extremely large - /// (especially in cases with many references). + /// To save space in the managed heap, we only cache the command line if we have a ruleset or if we are in a legacy project. /// - /// Note: this will be null in the case that the command line is an empty array. - private ITemporaryStorageStreamHandle? _commandLineStorageHandle; + private ImmutableArray _commandLine; - private CommandLineArguments _commandLineArgumentsForCommandLine; - private string? _explicitRuleSetFilePath; - private IReferenceCountedDisposable>? _ruleSetFile = null; + /// + /// Gate to guard all mutable fields in this class. + /// The lock hierarchy means you are allowed to call out of this class and into while holding the lock. + /// + protected readonly object _gate = new(); public ProjectSystemProjectOptionsProcessor( ProjectSystemProject project, @@ -51,7 +45,6 @@ public ProjectSystemProjectOptionsProcessor( _project = project ?? throw new ArgumentNullException(nameof(project)); _workspaceServices = workspaceServices; _commandLineParserService = workspaceServices.GetLanguageServices(project.Language).GetRequiredService(); - _temporaryStorageService = workspaceServices.GetRequiredService(); // Set up _commandLineArgumentsForCommandLine to a default. No lock taken since we're in // the constructor so nothing can race. @@ -70,26 +63,19 @@ private bool ReparseCommandLineIfChanged_NoLock(ImmutableArray arguments _commandLineChecksum = checksum; - // Dispose the existing stored command-line and then persist the new one so we can - // recover it later. Only bother persisting things if we have a non-empty string. - - _commandLineStorageHandle = null; - if (!arguments.IsEmpty) - { - using var stream = SerializableBytes.CreateWritableStream(); - using var writer = new StreamWriter(stream); - - foreach (var value in arguments) - writer.WriteLine(value); - - writer.Flush(); - _commandLineStorageHandle = _temporaryStorageService.WriteToTemporaryStorage(stream, CancellationToken.None); - } - ReparseCommandLine_NoLock(arguments); + _commandLine = ShouldSaveCommandLine(arguments) ? arguments : default; + return true; } + protected virtual bool ShouldSaveCommandLine(ImmutableArray arguments) + { + // Only bother storing the command line if there is an effective ruleset, as that may + // require a later reparse using it. + return GetEffectiveRulesetFilePath() != null; + } + public void SetCommandLine(string commandLine) { if (commandLine == null) @@ -113,26 +99,6 @@ public void SetCommandLine(ImmutableArray arguments) } } - public string? ExplicitRuleSetFilePath - { - get => _explicitRuleSetFilePath; - - set - { - lock (_gate) - { - if (_explicitRuleSetFilePath == value) - { - return; - } - - _explicitRuleSetFilePath = value; - - UpdateProjectOptions_NoLock(); - } - } - } - /// /// Returns the active path to the rule set file that is being used by this project, or null if there isn't a rule set file. /// @@ -161,6 +127,9 @@ private void DisposeOfRuleSetFile_NoLock() private void ReparseCommandLine_NoLock(ImmutableArray arguments) { + // If arguments isn't set, we somehow lost the command line + Debug.Assert(!arguments.IsDefault); + _commandLineArgumentsForCommandLine = _commandLineParserService.Parse(arguments, Path.GetDirectoryName(_project.FilePath), isInteractive: false, sdkDirectory: null); } @@ -173,9 +142,12 @@ public CommandLineArguments GetParsedCommandLineArguments() return _commandLineArgumentsForCommandLine; } - private void UpdateProjectOptions_NoLock() + protected virtual string? GetEffectiveRulesetFilePath() + => _commandLineArgumentsForCommandLine.RuleSetPath; + + protected void UpdateProjectOptions_NoLock() { - var effectiveRuleSetPath = ExplicitRuleSetFilePath ?? _commandLineArgumentsForCommandLine.RuleSetPath; + var effectiveRuleSetPath = GetEffectiveRulesetFilePath(); if (_ruleSetFile?.Target.Value.FilePath != effectiveRuleSetPath) { @@ -245,24 +217,10 @@ private void RuleSetFile_UpdatedOnDisk(object? sender, EventArgs e) // effective values was potentially done by the act of parsing the command line. Even though the command line didn't change textually, // the effective result did. Then we call UpdateProjectOptions_NoLock to reapply any values; that will also re-acquire the new ruleset // includes in the IDE so we can be watching for changes again. - var commandLine = _commandLineStorageHandle == null - ? [] - : EnumerateLines(_commandLineStorageHandle).ToImmutableArray(); - DisposeOfRuleSetFile_NoLock(); - ReparseCommandLine_NoLock(commandLine); + ReparseCommandLine_NoLock(_commandLine); UpdateProjectOptions_NoLock(); } - - static IEnumerable EnumerateLines( - ITemporaryStorageStreamHandle storageHandle) - { - using var stream = storageHandle.ReadFromTemporaryStorage(); - using var reader = new StreamReader(stream); - - while (reader.ReadLine() is string line) - yield return line; - } } /// @@ -279,18 +237,6 @@ protected virtual CompilationOptions ComputeCompilationOptionsWithHostValues(Com protected virtual ParseOptions ComputeParseOptionsWithHostValues(ParseOptions parseOptions) => parseOptions; - /// - /// Called by a derived class to notify that we need to update the settings in the project system for something that will be provided - /// by either or . - /// - protected void UpdateProjectForNewHostValues() - { - lock (_gate) - { - UpdateProjectOptions_NoLock(); - } - } - public void Dispose() { lock (_gate)