Skip to content

Enable "Run Code Analysis" commands to run FxCop for managed projects #1230

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,8 @@
</GuidSymbol>
</Symbols>

<UsedCommands>
<UsedCommand guid="guidVSStd2K" id="ECMD_RUNFXCOPSEL" />
<UsedCommand guid="guidVSStd2K" id="ECMD_RUNFXCOPPROJCTX" />
</UsedCommands>
</CommandTable>
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@
<Compile Include="ProjectSystem\VS\Editor\TempFileTextBufferManager.cs" />
<Compile Include="ProjectSystem\VS\EditAndContinue\EditAndContinueProvider.cs" />
<Compile Include="ProjectSystem\VS\ExportProjectNodeComServiceAttribute.cs" />
<Compile Include="ProjectSystem\VS\Input\Commands\AbstractRunCodeAnalysisCommand.cs" />
<Compile Include="ProjectSystem\VS\Input\Commands\RunCodeAnalysisTopLevelBuildMenuCommand.cs" />
<Compile Include="ProjectSystem\VS\Input\Commands\RunCodeAnalysisProjectContextMenuCommand.cs" />
<Compile Include="ProjectSystem\VS\Input\Commands\GenerateNuGetPackageTopLevelBuildMenuCommand.cs" />
<Compile Include="ProjectSystem\VS\Input\Commands\GenerateNuGetPackageProjectContextMenuCommand.cs" />
<Compile Include="ProjectSystem\VS\Input\Commands\AbstractGenerateNuGetPackageCommand.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ internal class ManagedProjectSystemPackage : AsyncPackage
ProjectCapability.HandlesOwnReload + "; " +
ProjectCapability.OpenProjectFile;

public const long RunCodeAnalysisTopLevelBuildMenuCmdId = 0x066f;
public const long RunCodeAnalysisProjectContextMenuCmdId = 0x0670;
public const string CodeAnalysisPackageGuid = "B20604B0-72BC-4953-BB92-95BF26D30CFA";
public const string VSStd2KCommandSet = "1496A755-94DE-11D0-8C3F-00C04FC2AAE2";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


public ManagedProjectSystemPackage()
{
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.ProjectSystem.Build;
using Microsoft.VisualStudio.ProjectSystem.Input;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Packaging;
using Microsoft.VisualStudio.ProjectSystem.Build;
using Microsoft.VisualStudio.ProjectSystem.Input;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using IOleCommandTarget = Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget;
using Task = System.Threading.Tasks.Task;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Input.Commands
{
internal abstract class AbstractRunCodeAnalysisCommand : AbstractSingleNodeProjectCommand, IVsUpdateSolutionEvents, IDisposable
{
private readonly IProjectThreadingService _threadingService;
private readonly IServiceProvider _serviceProvider;
private readonly RunCodeAnalysisBuildPropertyProvider _runCodeAnalysisBuildPropertyProvider;

private IVsSolutionBuildManager2 _buildManager;
private uint _solutionEventsCookie;
private IVsShell _vsShell;
private bool? _isCodeAnalysisPackageInstalled;
private bool _codeAnalysisPackageLoadAttempted;
private IOleCommandTarget _codeAnalysisCommandTarget;

protected AbstractRunCodeAnalysisCommand(
UnconfiguredProject unconfiguredProject,
IProjectThreadingService threadingService,
SVsServiceProvider serviceProvider,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We normally do this as [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider

RunCodeAnalysisBuildPropertyProvider runCodeAnalysisBuildPropertyProvider)
{
Requires.NotNull(unconfiguredProject, nameof(unconfiguredProject));
Requires.NotNull(threadingService, nameof(threadingService));
Requires.NotNull(serviceProvider, nameof(serviceProvider));
Requires.NotNull(serviceProvider, nameof(runCodeAnalysisBuildPropertyProvider));

UnconfiguredProject = unconfiguredProject;
_threadingService = threadingService;
_serviceProvider = serviceProvider;
_runCodeAnalysisBuildPropertyProvider = runCodeAnalysisBuildPropertyProvider;
}

protected UnconfiguredProject UnconfiguredProject { get; }

protected abstract string GetCommandText();
protected abstract bool ShouldHandle(IProjectTree node);
protected abstract long CommandId { get; }

protected async override Task<CommandStatusResult> GetCommandStatusAsync(IProjectTree node, bool focused, string commandText, CommandStatus progressiveStatus)
{
if (ShouldHandle(node))
{
// Enable the command if code analysis is enabled and the build manager is ready to build.
if (await IsCodeAnalysisPackageInstalledAsync().ConfigureAwait(false))
{
var commandStatus = await IsReadyToBuildAsync().ConfigureAwait(false) ? CommandStatus.Enabled : CommandStatus.Supported;
return await GetCommandStatusResult.Handled(GetCommandText(), commandStatus).ConfigureAwait(false);
}
}

return CommandStatusResult.Unhandled;
}

private async Task<bool> IsCodeAnalysisPackageInstalledAsync()
{
if (!_isCodeAnalysisPackageInstalled.HasValue)
{
// Switch to UI thread for querying the build manager service.
await _threadingService.SwitchToUIThread();

if (_vsShell == null)
{
_vsShell = _serviceProvider.GetService<IVsShell, SVsShell>();
}

var codeAnalysisGuid = new Guid(ManagedProjectSystemPackage.CodeAnalysisPackageGuid);
ErrorHandler.ThrowOnFailure(_vsShell.IsPackageInstalled(ref codeAnalysisGuid, out int packageInstalled));
_isCodeAnalysisPackageInstalled = packageInstalled != 0;
}

return _isCodeAnalysisPackageInstalled.Value;
}

private async Task EnsureCodeAnalysisPackageLoadedAsync()
{
if (!_codeAnalysisPackageLoadAttempted)
{
// Switch to UI thread for querying the build manager service.
await _threadingService.SwitchToUIThread();

if (_vsShell == null)
{
_vsShell = _serviceProvider.GetService<IVsShell, SVsShell>();
}

var codeAnalysisGuid = new Guid(ManagedProjectSystemPackage.CodeAnalysisPackageGuid);
_vsShell.IsPackageLoaded(ref codeAnalysisGuid, out IVsPackage package);
if (package == null)
{
_vsShell.LoadPackage(ref codeAnalysisGuid, out package);
}

_codeAnalysisCommandTarget = package as IOleCommandTarget;
_codeAnalysisPackageLoadAttempted = true;
}
}

private async Task<bool> IsReadyToBuildAsync()
{
// Ensure build manager is initialized.
await EnsureBuildManagerInitializedAsync().ConfigureAwait(true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the ConfigureAwait(true) here? I don't believe you're coming from a specific thread on this.


ErrorHandler.ThrowOnFailure(_buildManager.QueryBuildManagerBusy(out int busy));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switch to UI thread.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps just switch above EnsureBuildManagerInitializedAsync(), then the ConfigureAwait will matter.

return busy == 0;
}

private async Task EnsureBuildManagerInitializedAsync()
{
// Switch to UI thread for querying the build manager service.
await _threadingService.SwitchToUIThread();

if (_buildManager == null)
{
_buildManager = _serviceProvider.GetService<IVsSolutionBuildManager2, SVsSolutionBuildManager>();

// Register for solution build events.
_buildManager.AdviseUpdateSolutionEvents(this, out _solutionEventsCookie);
}
}

protected override async Task<bool> TryHandleCommandAsync(IProjectTree node, bool focused, long commandExecuteOptions, IntPtr variantArgIn, IntPtr variantArgOut)
{
if (!ShouldHandle(node))
{
return false;
}

if (await IsReadyToBuildAsync().ConfigureAwait(false))
{
// Build manager APIs require UI thread access.
await _threadingService.SwitchToUIThread();

await EnsureCodeAnalysisPackageLoadedAsync().ConfigureAwait(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConfigureAwait(true)


// Enable RunCodeAnalysisOnce on this project.
_runCodeAnalysisBuildPropertyProvider.EnableRunCodeAnalysisOnBuild(UnconfiguredProject.FullPath);

_codeAnalysisCommandTarget?.Exec(VSConstants.VSStd2K, (uint)CommandId, (uint)commandExecuteOptions, variantArgIn, variantArgOut);
}

return true;
}

#region IVsUpdateSolutionEvents members
public int UpdateSolution_Begin(ref int pfCancelUpdate)
{
return VSConstants.S_OK;
}

public int UpdateSolution_Done(int fSucceeded, int fModified, int fCancelCommand)
{
_runCodeAnalysisBuildPropertyProvider.DisableRunCodeAnalysisOnBuild();
return VSConstants.S_OK;
}

public int UpdateSolution_Cancel()
{
_runCodeAnalysisBuildPropertyProvider.DisableRunCodeAnalysisOnBuild();
return VSConstants.S_OK;
}

public int UpdateSolution_StartUpdate(ref int pfCancelUpdate)
{
return VSConstants.S_OK;
}

public int OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy)
{
return VSConstants.S_OK;
}
#endregion

#region IDisposable
public void Dispose()
{
// Build manager APIs require UI thread access.
_threadingService.ExecuteSynchronously(async () =>
{
await _threadingService.SwitchToUIThread();

if (_buildManager != null)
{
// Unregister solution build events.
_buildManager.UnadviseUpdateSolutionEvents(_solutionEventsCookie);
_buildManager = null;
}
});
}
#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Packaging;
using Microsoft.VisualStudio.ProjectSystem.Build;
using Microsoft.VisualStudio.ProjectSystem.Input;
using Microsoft.VisualStudio.Shell;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Input.Commands
{
[ProjectCommand(ManagedProjectSystemPackage.VSStd2KCommandSet, ManagedProjectSystemPackage.RunCodeAnalysisProjectContextMenuCmdId)]
[AppliesTo(ProjectCapability.CodeAnalysis)]
internal class RunCodeAnalysisProjectContextMenuCommand : AbstractRunCodeAnalysisCommand
{
[ImportingConstructor]
public RunCodeAnalysisProjectContextMenuCommand(
UnconfiguredProject unconfiguredProject,
IProjectThreadingService threadingService,
SVsServiceProvider serviceProvider,
RunCodeAnalysisBuildPropertyProvider runCodeAnalysisBuildPropertyProvider)
: base(unconfiguredProject, threadingService, serviceProvider, runCodeAnalysisBuildPropertyProvider)
{
}

protected override bool ShouldHandle(IProjectTree node) => node.IsRoot();
protected override string GetCommandText() => VSResources.RunCodeAnalysisProjectContextMenuCommand;
protected override long CommandId => ManagedProjectSystemPackage.RunCodeAnalysisProjectContextMenuCmdId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.ComponentModel.Composition;
using System.IO;
using Microsoft.VisualStudio.Packaging;
using Microsoft.VisualStudio.ProjectSystem.Build;
using Microsoft.VisualStudio.ProjectSystem.Input;
using Microsoft.VisualStudio.Shell;

namespace Microsoft.VisualStudio.ProjectSystem.VS.Input.Commands
{
[ProjectCommand(ManagedProjectSystemPackage.VSStd2KCommandSet, ManagedProjectSystemPackage.RunCodeAnalysisTopLevelBuildMenuCmdId)]
[AppliesTo(ProjectCapability.CodeAnalysis)]
internal class RunCodeAnalysisTopLevelBuildMenuCommand : AbstractRunCodeAnalysisCommand
{
[ImportingConstructor]
public RunCodeAnalysisTopLevelBuildMenuCommand(
UnconfiguredProject unconfiguredProject,
IProjectThreadingService threadingService,
SVsServiceProvider serviceProvider,
RunCodeAnalysisBuildPropertyProvider runCodeAnalysisBuildPropertyProvider)
: base(unconfiguredProject, threadingService, serviceProvider, runCodeAnalysisBuildPropertyProvider)
{
}

protected override bool ShouldHandle(IProjectTree node) => node.IsRoot();
protected override string GetCommandText() =>string.Format(VSResources.RunCodeAnalysisTopLevelBuildMenuCommand, Path.GetFileNameWithoutExtension(UnconfiguredProject.FullPath));
protected override long CommandId => ManagedProjectSystemPackage.RunCodeAnalysisTopLevelBuildMenuCmdId;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,10 @@ In order to debug this project, add an executable project to this solution which
<data name="XprojMigrationGeneralFailure" xml:space="preserve">
<value>Failed to migrate XProj project {0}. '{1}' exited with error code {2}.</value>
</data>
<data name="RunCodeAnalysisProjectContextMenuCommand" xml:space="preserve">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not an existing command with existing text?

<value>Run C&amp;ode Analysis</value>
</data>
<data name="RunCodeAnalysisTopLevelBuildMenuCommand" xml:space="preserve">
<value>Run Code &amp;Analysis on {0}</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<Compile Include="CommonConstants.cs" />
<Compile Include="ProjectSystem\Build\CommandLineDesignTimeBuildPropertiesProvider.cs" />
<Compile Include="ProjectSystem\Build\GeneratePackageOnBuildDesignTimeBuildPropertyProvider.cs" />
<Compile Include="ProjectSystem\Build\RunCodeAnalysisBuildPropertyProvider.cs" />
<Compile Include="ProjectSystem\Build\GeneratePackageOnBuildPropertyProvider.cs" />
<Compile Include="ProjectSystem\Build\TargetFrameworkGlobalBuildPropertyProvider.cs" />
<Compile Include="ProjectSystem\ContractNames.cs" />
Expand Down
Loading