Skip to content

Commit

Permalink
Merge pull request #72494 from CyrusNajmabadi/runSGOnBuild
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi authored Apr 5, 2024
2 parents c27bd24 + 466eedb commit 8ca49e5
Show file tree
Hide file tree
Showing 50 changed files with 2,053 additions and 84 deletions.
3 changes: 3 additions & 0 deletions eng/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@ function Deploy-VsixViaTool() {
# Disable text spell checker to avoid spurious warnings in the error list
&$vsRegEdit set "$vsDir" $hive HKCU "FeatureFlags\Editor\EnableSpellChecker" Value dword 0

# Run source generators automatically during integration tests.
&$vsRegEdit set "$vsDir" $hive HKCU "FeatureFlags\Roslyn\SourceGeneratorExecutionBalanced" Value dword 0

# Configure LSP
$lspRegistryValue = [int]$lspEditor.ToBool()
&$vsRegEdit set "$vsDir" $hive HKCU "FeatureFlags\Roslyn\LSP\Editor" Value dword $lspRegistryValue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#nullable disable

using Microsoft.CodeAnalysis.Host;
using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion;

namespace Microsoft.CodeAnalysis.Editor;
Expand Down Expand Up @@ -132,6 +133,12 @@ internal static class PredefinedCommandHandlerNames
/// </summary>
public const string Rename = "Rename Command Handler";

/// <summary>
/// Command handler for detecting user save commands, and using that to issue a request to run source generators
/// (when in <see cref="SourceGeneratorExecutionPreference.Balanced"/> mode).
/// </summary>
public const string SourceGeneratorSave = "Source Generator Save Command Handler";

/// <summary>
/// Command handler name for Rename Tracking cancellation.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// 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;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Storage;

Expand All @@ -13,9 +12,14 @@ internal static class WorkspaceConfigurationOptionsStorage
public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(this IGlobalOptionService globalOptions)
=> new(
CacheStorage: globalOptions.GetOption(Database),
EnableOpeningSourceGeneratedFiles: globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspace) ??
globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag),
EnableOpeningSourceGeneratedFiles:
globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspace) ??
globalOptions.GetOption(EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag),
DisableRecoverableText: globalOptions.GetOption(DisableRecoverableText),
SourceGeneratorExecution:
globalOptions.GetOption(SourceGeneratorExecution) ??
(globalOptions.GetOption(SourceGeneratorExecutionBalancedFeatureFlag) ? SourceGeneratorExecutionPreference.Balanced : SourceGeneratorExecutionPreference.Automatic),

ValidateCompilationTrackerStates: globalOptions.GetOption(ValidateCompilationTrackerStates));

public static readonly Option2<StorageDatabase> Database = new(
Expand All @@ -36,4 +40,15 @@ public static WorkspaceConfigurationOptions GetWorkspaceConfigurationOptions(thi

public static readonly Option2<bool> EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag = new(
"dotnet_enable_opening_source_generated_files_in_workspace_feature_flag", WorkspaceConfigurationOptions.Default.EnableOpeningSourceGeneratedFiles);

public static readonly Option2<SourceGeneratorExecutionPreference?> SourceGeneratorExecution = new(
"dotnet_source_generator_execution",
defaultValue: null,
isEditorConfigOption: true,
serializer: new EditorConfigValueSerializer<SourceGeneratorExecutionPreference?>(
s => SourceGeneratorExecutionPreferenceUtilities.Parse(s),
SourceGeneratorExecutionPreferenceUtilities.GetEditorConfigString));

public static readonly Option2<bool> SourceGeneratorExecutionBalancedFeatureFlag = new(
"dotnet_source_generator_execution_balanced_feature_flag", false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@
<CheckBox x:Name="Run_code_analysis_in_separate_process"
Content="{x:Static local:AdvancedOptionPageStrings.Option_run_code_analysis_in_separate_process}" />

<CheckBox x:Name="Analyze_source_generated_files"
Content="{x:Static local:AdvancedOptionPageStrings.Option_analyze_source_generated_files}" />

<CheckBox x:Name="Show_Remove_Unused_References_command_in_Solution_Explorer_experimental"
Content="{x:Static local:AdvancedOptionPageStrings.Option_Show_Remove_Unused_References_command_in_Solution_Explorer_experimental}" />

Expand All @@ -68,7 +65,34 @@
Content="{x:Static local:AdvancedOptionPageStrings.Option_Skip_analyzers_for_implicitly_triggered_builds}" />
</StackPanel>
</GroupBox>


<GroupBox x:Uid="SourceGeneratorsGroupBox"
Header="{x:Static local:AdvancedOptionPageStrings.Option_Source_Generators}">
<StackPanel>

<StackPanel Margin="0, -5, 0, 5">
<Label x:Name="Run_source_generators_label" Content="{x:Static local:AdvancedOptionPageStrings.Option_Source_generator_execution_requires_restart}"/>

<StackPanel Margin="15, 0, 0, 0">
<RadioButton GroupName="Run_source_generators_location"
x:Name="Automatic_Run_generators_after_any_change"
Content="{x:Static local:AdvancedOptionPageStrings.Option_Automatic_Run_generators_after_any_change}"/>

<RadioButton GroupName="Run_source_generators_location"
x:Name="Balanced_Run_generators_after_saving_or_building"
Content="{x:Static local:AdvancedOptionPageStrings.Option_Balanced_Run_generators_after_saving_or_building}"/>
</StackPanel>
</StackPanel>

<CheckBox x:Name="Analyze_source_generated_files"
Content="{x:Static local:AdvancedOptionPageStrings.Option_analyze_source_generated_files}" />

<CheckBox x:Name="Enable_all_features_in_opened_files_from_source_generators"
Content="{x:Static local:AdvancedOptionPageStrings.Enable_all_features_in_opened_files_from_source_generators_experimental}" />

</StackPanel>
</GroupBox>

<GroupBox x:Uid="GoToDefinitionGroupBox"
Header="{x:Static local:AdvancedOptionPageStrings.Option_Go_To_Definition}">
<StackPanel>
Expand Down Expand Up @@ -216,8 +240,6 @@
Content="{x:Static local:AdvancedOptionPageStrings.Option_Underline_reassigned_variables}" />
<CheckBox x:Name="Strike_out_obsolete_symbols"
Content="{x:Static local:AdvancedOptionPageStrings.Option_Strike_out_obsolete_symbols}" />
<CheckBox x:Name="Enable_all_features_in_opened_files_from_source_generators"
Content="{x:Static local:AdvancedOptionPageStrings.Enable_all_features_in_opened_files_from_source_generators_experimental}" />
</StackPanel>
</GroupBox>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,6 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon

BindToOption(Run_code_analysis_in_separate_process, RemoteHostOptionsStorage.OOP64Bit);

BindToOption(Analyze_source_generated_files, SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, () =>
{
// If the option has not been set by the user, check if the option is enabled from experimentation. If so, default to that.
return optionStore.GetOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFilesFeatureFlag);
});

BindToOption(Enable_file_logging_for_diagnostics, VisualStudioLoggingOptionsStorage.EnableFileLoggingForDiagnostics);
BindToOption(Skip_analyzers_for_implicitly_triggered_builds, FeatureOnOffOptions.SkipAnalyzersForImplicitlyTriggeredBuilds);
BindToOption(Show_Remove_Unused_References_command_in_Solution_Explorer_experimental, FeatureOnOffOptions.OfferRemoveUnusedReferences, () =>
Expand All @@ -83,6 +77,31 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon
return optionStore.GetOption(FeatureOnOffOptions.OfferRemoveUnusedReferencesFeatureFlag);
});

// Source Generators
BindToOption(Automatic_Run_generators_after_any_change, WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Automatic, () =>
{
// If the option hasn't been set by the user, then check the feature flag. If the feature flag has set
// us to only run when builds complete, then we're not in automatic mode. So we `!` the result.
return !optionStore.GetOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecutionBalancedFeatureFlag);
});
BindToOption(Balanced_Run_generators_after_saving_or_building, WorkspaceConfigurationOptionsStorage.SourceGeneratorExecution, SourceGeneratorExecutionPreference.Balanced, () =>
{
// If the option hasn't been set by the user, then check the feature flag. If the feature flag has set
// us to only run when builds complete, then we're in `Balanced_Run_generators_after_saving_or_building` mode and directly return it.
return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.SourceGeneratorExecutionBalancedFeatureFlag);
});
BindToOption(Analyze_source_generated_files, SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFiles, () =>
{
// If the option has not been set by the user, check if the option is enabled from experimentation. If so, default to that.
return optionStore.GetOption(SolutionCrawlerOptionsStorage.EnableDiagnosticsInSourceGeneratedFilesFeatureFlag);
});
BindToOption(Enable_all_features_in_opened_files_from_source_generators, WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, () =>
{
// If the option has not been set by the user, check if the option is enabled from experimentation.
// If so, default to that.
return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag);
});

// Go To Definition
BindToOption(Enable_navigation_to_sourcelink_and_embedded_sources, MetadataAsSourceOptionsStorage.NavigateToSourceLinkAndEmbeddedSources);
BindToOption(Enable_navigation_to_decompiled_sources, MetadataAsSourceOptionsStorage.NavigateToDecompiledSources);
Expand Down Expand Up @@ -138,12 +157,6 @@ public AdvancedOptionPageControl(OptionStore optionStore, IComponentModel compon
BindToOption(Report_invalid_placeholders_in_string_dot_format_calls, IdeAnalyzerOptionsStorage.ReportInvalidPlaceholdersInStringDotFormatCalls, LanguageNames.CSharp);
BindToOption(Underline_reassigned_variables, ClassificationOptionsStorage.ClassifyReassignedVariables, LanguageNames.CSharp);
BindToOption(Strike_out_obsolete_symbols, ClassificationOptionsStorage.ClassifyObsoleteSymbols, LanguageNames.CSharp);
BindToOption(Enable_all_features_in_opened_files_from_source_generators, WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspace, () =>
{
// If the option has not been set by the user, check if the option is enabled from experimentation.
// If so, default to that.
return optionStore.GetOption(WorkspaceConfigurationOptionsStorage.EnableOpeningSourceGeneratedFilesInWorkspaceFeatureFlag);
});

// Regular Expressions
BindToOption(Colorize_regular_expressions, ClassificationOptionsStorage.ColorizeRegexPatterns, LanguageNames.CSharp);
Expand Down
12 changes: 12 additions & 0 deletions src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageStrings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -404,5 +404,17 @@ public static string Document_Outline

public static string Option_Enable_document_outline_experimental_requires_restart
=> ServicesVSResources.Enable_document_outline_experimental_requires_restart;

public static string Option_Source_Generators
=> ServicesVSResources.Source_Generators;

public static string Option_Source_generator_execution_requires_restart
=> ServicesVSResources.Source_generator_execution_requires_restart;

public static string Option_Automatic_Run_generators_after_any_change
=> ServicesVSResources.Automatic_Run_generators_after_any_change;

public static string Option_Balanced_Run_generators_after_saving_or_building
=> ServicesVSResources.Balanced_Run_generators_after_saving_or_building;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@ public bool TryFetch(LocalUserRegistryOptionPersister persister, OptionKey2 opti
{"dotnet_enable_diagnostics_in_source_generated_files_feature_flag", new FeatureFlagStorage(@"Roslyn.EnableDiagnosticsInSourceGeneratedFiles")},
{"dotnet_enable_opening_source_generated_files_in_workspace", new RoamingProfileStorage("TextEditor.Roslyn.Specific.EnableOpeningSourceGeneratedFilesInWorkspaceExperiment")},
{"dotnet_enable_opening_source_generated_files_in_workspace_feature_flag", new FeatureFlagStorage(@"Roslyn.SourceGeneratorsEnableOpeningInWorkspace")},
{"dotnet_source_generator_execution", new RoamingProfileStorage("TextEditor.Roslyn.Specific.SourceGeneratorExecution")},
{"dotnet_source_generator_execution_balanced_feature_flag", new FeatureFlagStorage(@"Roslyn.SourceGeneratorExecutionBalanced")},
{"xaml_enable_lsp_intellisense", new FeatureFlagStorage(@"Xaml.EnableLspIntelliSense")},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public async Task<ProjectSystemProject> CreateAndAddToWorkspaceAsync(
_visualStudioWorkspaceImpl.Services.GetRequiredService<VisualStudioMetadataReferenceManager>();

_visualStudioWorkspaceImpl.SubscribeExternalErrorDiagnosticUpdateSourceToSolutionBuildEvents();
_visualStudioWorkspaceImpl.SubscribeToSourceGeneratorImpactingEvents();

#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task
// Since we're on the UI thread here anyways, use that as an opportunity to grab the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;

internal abstract partial class VisualStudioWorkspaceImpl
{
private bool _isSubscribedToSourceGeneratorImpactingEvents;

public void SubscribeToSourceGeneratorImpactingEvents()
{
_foregroundObject.AssertIsForeground();
if (_isSubscribedToSourceGeneratorImpactingEvents)
return;

// UIContextImpl requires IVsMonitorSelection service:
if (ServiceProvider.GlobalProvider.GetService(typeof(IVsMonitorSelection)) == null)
return;

_isSubscribedToSourceGeneratorImpactingEvents = true;

// This pattern ensures that we are called whenever the build starts/completes even if it is already in progress.
KnownUIContexts.SolutionBuildingContext.WhenActivated(() =>
{
KnownUIContexts.SolutionBuildingContext.UIContextChanged += (_, e) =>
{
if (!e.Activated)
{
// After a build occurs, transition the solution to a new source generator version. This will
// ensure that any cached SG documents will be re-generated.
this.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false);
}
};
});

KnownUIContexts.SolutionExistsAndFullyLoadedContext.WhenActivated(() =>
{
KnownUIContexts.SolutionExistsAndFullyLoadedContext.UIContextChanged += (_, e) =>
{
if (e.Activated)
{
// After the solution fully loads, transition the solution to a new source generator version. This
// will ensure that we'll now produce correct SG docs with fully knowledge of all the user's state.
this.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false);
}
};
});

// Whenever the workspace status changes, go attempt to update generators.
var workspaceStatusService = this.Services.GetRequiredService<IWorkspaceStatusService>();
workspaceStatusService.StatusChanged += (_, _) => EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false);

// Now kick off at least the initial work to run generators.
this.EnqueueUpdateSourceGeneratorVersion(projectId: null, forceRegeneration: false);
}

[Export(typeof(ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[ContentType(ContentTypeNames.XamlContentType)]
[Name(PredefinedCommandHandlerNames.SourceGeneratorSave)]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class SaveCommandHandler() : IChainedCommandHandler<SaveCommandArgs>
{
public string DisplayName => ServicesVSResources.Roslyn_save_command_handler;

public CommandState GetCommandState(SaveCommandArgs args, Func<CommandState> nextCommandHandler)
=> nextCommandHandler();

public void ExecuteCommand(SaveCommandArgs args, Action nextCommandHandler, CommandExecutionContext executionContext)
{
nextCommandHandler();

// After a save happens, enqueue a request to run generators on the projects impacted by the save.
foreach (var projectGroup in args.SubjectBuffer.GetRelatedDocuments().GroupBy(d => d.Project))
{
if (projectGroup.Key.Solution.Workspace is VisualStudioWorkspaceImpl visualStudioWorkspace)
{
visualStudioWorkspace.EnqueueUpdateSourceGeneratorVersion(projectGroup.Key.Id, forceRegeneration: false);
}
}
}
}
}
Loading

0 comments on commit 8ca49e5

Please sign in to comment.