Skip to content

Commit d7acc1b

Browse files
committed
Enable "Run Code Analysis" commands to run FxCop for managed projects
1. Mimic the behavior of native project system to load stancore package for code analysis, if installed. 2. Turn on the CodeAnalysis specific global build properties for enabling FxCop when build is invoked from Run Code Analysis. Fixes dotnet#988
1 parent 1ecb197 commit d7acc1b

13 files changed

+364
-2
lines changed

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/Menus.vsct

+4
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,8 @@
9898
</GuidSymbol>
9999
</Symbols>
100100

101+
<UsedCommands>
102+
<UsedCommand guid="guidVSStd2K" id="ECMD_RUNFXCOPSEL" />
103+
<UsedCommand guid="guidVSStd2K" id="ECMD_RUNFXCOPPROJCTX" />
104+
</UsedCommands>
101105
</CommandTable>

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/Microsoft.VisualStudio.ProjectSystem.Managed.VS.csproj

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@
4949
<Compile Include="ProjectSystem\VS\Editor\TempFileTextBufferManager.cs" />
5050
<Compile Include="ProjectSystem\VS\EditAndContinue\EditAndContinueProvider.cs" />
5151
<Compile Include="ProjectSystem\VS\ExportProjectNodeComServiceAttribute.cs" />
52+
<Compile Include="ProjectSystem\VS\Input\Commands\AbstractRunCodeAnalysisCommand.cs" />
53+
<Compile Include="ProjectSystem\VS\Input\Commands\RunCodeAnalysisTopLevelBuildMenuCommand.cs" />
54+
<Compile Include="ProjectSystem\VS\Input\Commands\RunCodeAnalysisProjectContextMenuCommand.cs" />
5255
<Compile Include="ProjectSystem\VS\Input\Commands\GenerateNuGetPackageTopLevelBuildMenuCommand.cs" />
5356
<Compile Include="ProjectSystem\VS\Input\Commands\GenerateNuGetPackageProjectContextMenuCommand.cs" />
5457
<Compile Include="ProjectSystem\VS\Input\Commands\AbstractGenerateNuGetPackageCommand.cs" />

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/Packaging/ManagedProjectSystemPackage.cs

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ internal class ManagedProjectSystemPackage : AsyncPackage
2121
ProjectCapability.HandlesOwnReload + "; " +
2222
ProjectCapability.OpenProjectFile;
2323

24+
public const long RunCodeAnalysisTopLevelBuildMenuCmdId = 0x066f;
25+
public const long RunCodeAnalysisProjectContextMenuCmdId = 0x0670;
26+
public const string CodeAnalysisPackageGuid = "B20604B0-72BC-4953-BB92-95BF26D30CFA";
27+
public const string VSStd2KCommandSet = "1496A755-94DE-11D0-8C3F-00C04FC2AAE2";
28+
2429
public ManagedProjectSystemPackage()
2530
{
2631
}

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Input/Commands/AbstractGenerateNuGetPackageCommand.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

33
using System;
4-
using System.Threading;
54
using System.Threading.Tasks;
65
using Microsoft.VisualStudio.ProjectSystem.Build;
76
using Microsoft.VisualStudio.ProjectSystem.Input;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System;
4+
using System.Threading.Tasks;
5+
using Microsoft.VisualStudio.Packaging;
6+
using Microsoft.VisualStudio.ProjectSystem.Build;
7+
using Microsoft.VisualStudio.ProjectSystem.Input;
8+
using Microsoft.VisualStudio.Shell;
9+
using Microsoft.VisualStudio.Shell.Interop;
10+
using IOleCommandTarget = Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget;
11+
using Task = System.Threading.Tasks.Task;
12+
13+
namespace Microsoft.VisualStudio.ProjectSystem.VS.Input.Commands
14+
{
15+
internal abstract class AbstractRunCodeAnalysisCommand : AbstractSingleNodeProjectCommand, IVsUpdateSolutionEvents, IDisposable
16+
{
17+
private readonly IProjectThreadingService _threadingService;
18+
private readonly IServiceProvider _serviceProvider;
19+
private readonly RunCodeAnalysisBuildPropertyProvider _runCodeAnalysisBuildPropertyProvider;
20+
21+
private IVsSolutionBuildManager2 _buildManager;
22+
private uint _solutionEventsCookie;
23+
private IVsShell _vsShell;
24+
private bool? _isCodeAnalysisPackageInstalled;
25+
private bool _codeAnalysisPackageLoadAttempted;
26+
private IOleCommandTarget _codeAnalysisCommandTarget;
27+
28+
protected AbstractRunCodeAnalysisCommand(
29+
UnconfiguredProject unconfiguredProject,
30+
IProjectThreadingService threadingService,
31+
SVsServiceProvider serviceProvider,
32+
RunCodeAnalysisBuildPropertyProvider runCodeAnalysisBuildPropertyProvider)
33+
{
34+
Requires.NotNull(unconfiguredProject, nameof(unconfiguredProject));
35+
Requires.NotNull(threadingService, nameof(threadingService));
36+
Requires.NotNull(serviceProvider, nameof(serviceProvider));
37+
Requires.NotNull(serviceProvider, nameof(runCodeAnalysisBuildPropertyProvider));
38+
39+
UnconfiguredProject = unconfiguredProject;
40+
_threadingService = threadingService;
41+
_serviceProvider = serviceProvider;
42+
_runCodeAnalysisBuildPropertyProvider = runCodeAnalysisBuildPropertyProvider;
43+
}
44+
45+
protected UnconfiguredProject UnconfiguredProject { get; }
46+
47+
protected abstract string GetCommandText();
48+
protected abstract bool ShouldHandle(IProjectTree node);
49+
protected abstract long CommandId { get; }
50+
51+
protected async override Task<CommandStatusResult> GetCommandStatusAsync(IProjectTree node, bool focused, string commandText, CommandStatus progressiveStatus)
52+
{
53+
if (ShouldHandle(node))
54+
{
55+
// Enable the command if code analysis is enabled and the build manager is ready to build.
56+
if (await IsCodeAnalysisPackageInstalledAsync().ConfigureAwait(false))
57+
{
58+
var commandStatus = await IsReadyToBuildAsync().ConfigureAwait(false) ? CommandStatus.Enabled : CommandStatus.Supported;
59+
return await GetCommandStatusResult.Handled(GetCommandText(), commandStatus).ConfigureAwait(false);
60+
}
61+
}
62+
63+
return CommandStatusResult.Unhandled;
64+
}
65+
66+
private async Task<bool> IsCodeAnalysisPackageInstalledAsync()
67+
{
68+
if (!_isCodeAnalysisPackageInstalled.HasValue)
69+
{
70+
// Switch to UI thread for querying the build manager service.
71+
await _threadingService.SwitchToUIThread();
72+
73+
if (_vsShell == null)
74+
{
75+
_vsShell = _serviceProvider.GetService<IVsShell, SVsShell>();
76+
}
77+
78+
var codeAnalysisGuid = new Guid(ManagedProjectSystemPackage.CodeAnalysisPackageGuid);
79+
ErrorHandler.ThrowOnFailure(_vsShell.IsPackageInstalled(ref codeAnalysisGuid, out int packageInstalled));
80+
_isCodeAnalysisPackageInstalled = packageInstalled != 0;
81+
}
82+
83+
return _isCodeAnalysisPackageInstalled.Value;
84+
}
85+
86+
private async Task EnsureCodeAnalysisPackageLoadedAsync()
87+
{
88+
if (!_codeAnalysisPackageLoadAttempted)
89+
{
90+
// Switch to UI thread for querying the build manager service.
91+
await _threadingService.SwitchToUIThread();
92+
93+
if (_vsShell == null)
94+
{
95+
_vsShell = _serviceProvider.GetService<IVsShell, SVsShell>();
96+
}
97+
98+
var codeAnalysisGuid = new Guid(ManagedProjectSystemPackage.CodeAnalysisPackageGuid);
99+
_vsShell.IsPackageLoaded(ref codeAnalysisGuid, out IVsPackage package);
100+
if (package == null)
101+
{
102+
_vsShell.LoadPackage(ref codeAnalysisGuid, out package);
103+
}
104+
105+
_codeAnalysisCommandTarget = package as IOleCommandTarget;
106+
_codeAnalysisPackageLoadAttempted = true;
107+
}
108+
}
109+
110+
private async Task<bool> IsReadyToBuildAsync()
111+
{
112+
// Ensure build manager is initialized.
113+
await EnsureBuildManagerInitializedAsync().ConfigureAwait(true);
114+
115+
ErrorHandler.ThrowOnFailure(_buildManager.QueryBuildManagerBusy(out int busy));
116+
return busy == 0;
117+
}
118+
119+
private async Task EnsureBuildManagerInitializedAsync()
120+
{
121+
// Switch to UI thread for querying the build manager service.
122+
await _threadingService.SwitchToUIThread();
123+
124+
if (_buildManager == null)
125+
{
126+
_buildManager = _serviceProvider.GetService<IVsSolutionBuildManager2, SVsSolutionBuildManager>();
127+
128+
// Register for solution build events.
129+
_buildManager.AdviseUpdateSolutionEvents(this, out _solutionEventsCookie);
130+
}
131+
}
132+
133+
protected override async Task<bool> TryHandleCommandAsync(IProjectTree node, bool focused, long commandExecuteOptions, IntPtr variantArgIn, IntPtr variantArgOut)
134+
{
135+
if (!ShouldHandle(node))
136+
{
137+
return false;
138+
}
139+
140+
if (await IsReadyToBuildAsync().ConfigureAwait(false))
141+
{
142+
// Build manager APIs require UI thread access.
143+
await _threadingService.SwitchToUIThread();
144+
145+
await EnsureCodeAnalysisPackageLoadedAsync().ConfigureAwait(false);
146+
147+
// Enable RunCodeAnalysisOnce on this project.
148+
_runCodeAnalysisBuildPropertyProvider.EnableRunCodeAnalysisOnBuild(UnconfiguredProject.FullPath);
149+
150+
_codeAnalysisCommandTarget?.Exec(VSConstants.VSStd2K, (uint)CommandId, (uint)commandExecuteOptions, variantArgIn, variantArgOut);
151+
}
152+
153+
return true;
154+
}
155+
156+
#region IVsUpdateSolutionEvents members
157+
public int UpdateSolution_Begin(ref int pfCancelUpdate)
158+
{
159+
return VSConstants.S_OK;
160+
}
161+
162+
public int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand)
163+
{
164+
_runCodeAnalysisBuildPropertyProvider.DisableRunCodeAnalysisOnBuild();
165+
return VSConstants.S_OK;
166+
}
167+
168+
public int UpdateSolution_Cancel()
169+
{
170+
_runCodeAnalysisBuildPropertyProvider.DisableRunCodeAnalysisOnBuild();
171+
return VSConstants.S_OK;
172+
}
173+
174+
public int UpdateSolution_StartUpdate(ref int pfCancelUpdate)
175+
{
176+
return VSConstants.S_OK;
177+
}
178+
179+
public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy)
180+
{
181+
return VSConstants.S_OK;
182+
}
183+
#endregion
184+
185+
#region IDisposable
186+
public void Dispose()
187+
{
188+
// Build manager APIs require UI thread access.
189+
_threadingService.ExecuteSynchronously(async () =>
190+
{
191+
await _threadingService.SwitchToUIThread();
192+
193+
if (_buildManager != null)
194+
{
195+
// Unregister solution build events.
196+
_buildManager.UnadviseUpdateSolutionEvents(_solutionEventsCookie);
197+
_buildManager = null;
198+
}
199+
});
200+
}
201+
#endregion
202+
}
203+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System.ComponentModel.Composition;
4+
using Microsoft.VisualStudio.Packaging;
5+
using Microsoft.VisualStudio.ProjectSystem.Build;
6+
using Microsoft.VisualStudio.ProjectSystem.Input;
7+
using Microsoft.VisualStudio.Shell;
8+
9+
namespace Microsoft.VisualStudio.ProjectSystem.VS.Input.Commands
10+
{
11+
[ProjectCommand(ManagedProjectSystemPackage.VSStd2KCommandSet, ManagedProjectSystemPackage.RunCodeAnalysisProjectContextMenuCmdId)]
12+
[AppliesTo(ProjectCapability.CodeAnalysis)]
13+
internal class RunCodeAnalysisProjectContextMenuCommand : AbstractRunCodeAnalysisCommand
14+
{
15+
[ImportingConstructor]
16+
public RunCodeAnalysisProjectContextMenuCommand(
17+
UnconfiguredProject unconfiguredProject,
18+
IProjectThreadingService threadingService,
19+
SVsServiceProvider serviceProvider,
20+
RunCodeAnalysisBuildPropertyProvider runCodeAnalysisBuildPropertyProvider)
21+
: base(unconfiguredProject, threadingService, serviceProvider, runCodeAnalysisBuildPropertyProvider)
22+
{
23+
}
24+
25+
protected override bool ShouldHandle(IProjectTree node) => node.IsRoot();
26+
protected override string GetCommandText() => VSResources.RunCodeAnalysisProjectContextMenuCommand;
27+
protected override long CommandId => ManagedProjectSystemPackage.RunCodeAnalysisProjectContextMenuCmdId;
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System.ComponentModel.Composition;
4+
using System.IO;
5+
using Microsoft.VisualStudio.Packaging;
6+
using Microsoft.VisualStudio.ProjectSystem.Build;
7+
using Microsoft.VisualStudio.ProjectSystem.Input;
8+
using Microsoft.VisualStudio.Shell;
9+
10+
namespace Microsoft.VisualStudio.ProjectSystem.VS.Input.Commands
11+
{
12+
[ProjectCommand(ManagedProjectSystemPackage.VSStd2KCommandSet, ManagedProjectSystemPackage.RunCodeAnalysisTopLevelBuildMenuCmdId)]
13+
[AppliesTo(ProjectCapability.CodeAnalysis)]
14+
internal class RunCodeAnalysisTopLevelBuildMenuCommand : AbstractRunCodeAnalysisCommand
15+
{
16+
[ImportingConstructor]
17+
public RunCodeAnalysisTopLevelBuildMenuCommand(
18+
UnconfiguredProject unconfiguredProject,
19+
IProjectThreadingService threadingService,
20+
SVsServiceProvider serviceProvider,
21+
RunCodeAnalysisBuildPropertyProvider runCodeAnalysisBuildPropertyProvider)
22+
: base(unconfiguredProject, threadingService, serviceProvider, runCodeAnalysisBuildPropertyProvider)
23+
{
24+
}
25+
26+
protected override bool ShouldHandle(IProjectTree node) => node.IsRoot();
27+
protected override string GetCommandText() =>string.Format(VSResources.RunCodeAnalysisTopLevelBuildMenuCommand, Path.GetFileNameWithoutExtension(UnconfiguredProject.FullPath));
28+
protected override long CommandId => ManagedProjectSystemPackage.RunCodeAnalysisTopLevelBuildMenuCmdId;
29+
}
30+
}

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/VSResources.Designer.cs

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/VSResources.resx

+6
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,10 @@ In order to debug this project, add an executable project to this solution which
276276
<data name="XprojMigrationGeneralFailure" xml:space="preserve">
277277
<value>Failed to migrate XProj project {0}. '{1}' exited with error code {2}.</value>
278278
</data>
279+
<data name="RunCodeAnalysisProjectContextMenuCommand" xml:space="preserve">
280+
<value>Run C&amp;ode Analysis</value>
281+
</data>
282+
<data name="RunCodeAnalysisTopLevelBuildMenuCommand" xml:space="preserve">
283+
<value>Run Code &amp;Analysis on {0}</value>
284+
</data>
279285
</root>

src/Microsoft.VisualStudio.ProjectSystem.Managed/Microsoft.VisualStudio.ProjectSystem.Managed.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<Compile Include="CommonConstants.cs" />
3939
<Compile Include="ProjectSystem\Build\CommandLineDesignTimeBuildPropertiesProvider.cs" />
4040
<Compile Include="ProjectSystem\Build\GeneratePackageOnBuildDesignTimeBuildPropertyProvider.cs" />
41+
<Compile Include="ProjectSystem\Build\RunCodeAnalysisBuildPropertyProvider.cs" />
4142
<Compile Include="ProjectSystem\Build\GeneratePackageOnBuildPropertyProvider.cs" />
4243
<Compile Include="ProjectSystem\Build\TargetFrameworkGlobalBuildPropertyProvider.cs" />
4344
<Compile Include="ProjectSystem\ContractNames.cs" />

0 commit comments

Comments
 (0)