How to show Analyzer errors/warnings during msbuild in VS Dev Cmd & using MSBuildWorkspace - visual-studio

I'll explain the situation with an example.
Suppose I have created a Roslyn Analyzer which throws Error when Class name is TestClass. Analyzer code is as below:
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(Method, SyntaxKind.ClassDeclaration);
}
private static void Method(SyntaxNodeAnalysisContext context)
{
var node = (ClassDeclarationSyntax)context.Node;
var name = node.TryGetInferredMemberName();
if(name == "TestClass")
{
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation()));
}
}
So i install the Analyzer nupkg in some ConsoleApp project. Console project has following code in Program.cs file
using System;
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
class TestClass
{
static void test()
{
Console.WriteLine("TestClass");
}
}
}
Now if i build the ConsoleApp project in Visual Studio then i get Error as "TestClass name not to be used" which is fine.
But when i try to build the same project using msbuild command in Developer Command Prompt for VS 2017 i don't see any error from Analyzer. I want that all the errors shown in Error list in VS should be shown in Dev Cmd.
My end goal is to create a stand-alone code analysis tool project and then use MSBuildWorkspace to compile ConsoleApp project and get the analyzer errors/warnings. Part of code is as below:
var filePath = #"C:\Users\user\repos\ConsoleApp\ConsoleApp.sln";
var msbws = MSBuildWorkspace.Create();
var soln = await msbws.OpenSolutionAsync(filePath);
var errors = new List<Diagnostic>();
foreach (var proj in soln.Projects)
{
var name = proj.Name;
var compilation = await proj.GetCompilationAsync();
errors.AddRange(compilation.GetDiagnostics().Where(n => n.Severity == DiagnosticSeverity.Error).ToList());
}
var count = errors.Count();
Above code does not show errors/warnings from analyzer.
How can i achieve this?
Thanks in Advance.

To show analyzer errors/warnings during msbuild in VS Dev Cmd, you just have to pass rebuild switch for example
msbuild Tempsolution.sln /t:rebuild
And for MSBuidlWorkspace, this code worked for me. We have to manually specify the analyzer to use by using compilation.WithAnalyzer(ImmutableArray<DiagnosticAnalyzer>);.
MSBuildLocator.RegisterDefaults();
var filePath = #"C:\Users\user\repos\ConsoleApp\ConsoleApp.sln";
var msbws = MSBuildWorkspace.Create();
var soln = await msbws.OpenSolutionAsync(filePath);
var errors = new List<Diagnostic>();
foreach (var proj in soln.Projects)
{
var analyzer = proj.AnalyzerReferences.Where(alz => alz.Display.ToLower() == "Your analyzer name").FirstOrDefault();
var compilation = await proj.GetCompilationAsync();
var compWithAnalyzer = compilation.WithAnalyzers(analyzer.GetAnalyzersForAllLanguages());
var res = compWithAnalyzer.GetAllDiagnosticsAsync().Result;
errors.AddRange(res.Where(r => r.Severity == DiagnosticSeverity.Error).ToList());
}
var count = errors.Count();

How to show Analyzer errors/warnings during msbuild in VS Dev Cmd &
using MSBuildWorkspace
Actually, these warnings are from Code analysis mechanism rather than MSBuild warnings(like MSBxxx). And I think the TestClass name not to be used is just a warning(yellow mark) not an error.
In VS IDE, its environment integrates the MSBuild tool(Developer Command Prompt for VS) and Code Analyzer. Because of this, you can get the warnings in VS IDE.
However, when you use Developer Command Prompt, which is essentially a separate compilation tool for MSBuild, it doesnot have an integrated code analyzer, so you don't have this type of warning except for MSBuild warnings and errors(MSBxxx). This is also the limitation of the tool. Warning by itself does not affect the entire program.
Test
You can test it by input this in an empty console project: int a=1;(It is a code analyzer warning) and I am sure that the warning can be showed in output window in VS IDE and will not be listed in Developer Command Prompt for VS.
Suggestion
As a suggestion, you can try to treat these warnings as errors and Code Analyzer passes these warnings to the msbuild and specifies them as errors so that you can get the error in DEV.
Add these in your xxx.csproj file:
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
Although this approach breaks the build process, it is reliable and practical. And this method is very commonly used, generally used in the final production stage of the project, to exclude all errors and warnings for large projects, so as to prevent subsequent errors that may occur and be foolproof.
Then, you can use your code to build the project.

Related

Resolve actual Reference path using Microsoft.Build.Evaluation

I'm doing some introspection and analysis of csproj files using the Microsoft.Build.Evaluation tools in a small C# console app. I want to locate the actual location of Reference items, using the same heuristics as MSBuild itself ie the locations described here. I'm heading towards auto conversion of build artifacts into packages, similar to what's outlined on the JetBrains blog here
The only examples I can find expect the HintPath to be correct, for example this project, and I know there are some HintPaths that are not currently correct, I don't want to trust them. This project very close what I'm trying to do, with the added complication that I want to use real resolution behaviour to find dependencies.
I have an instance of a Microsoft.Build.Evaluation.Project object for my csproj, and I can't see any methods available on it that could exersize the resolution for me. I think what I'm hoping for is a magic Resolve() method for a Reference or a ProjectItem, a bit like this method.
I can probably find an alternative by constraining my own search to a set of limited output paths used by this build system, but I'd like to hook into MSBuild if I can.
The reference resolution is one of the trickiest parts of MSBuild. The logic of how assemblies are located is implemented inside the a standard set of tasks:
ResolveAssemblyReference, ResolveNativeReference, etc. The logic is how this works is very complicated, you can see that just by looking at number of possible parameters to those tasks.
However you don't need to know the exact logic to find the location of referenced files. There are standard targets called "ResolveAssemblyReferences", "ResolveProjectReferences" and some others more specialized for native references, COM references. Those targets are executed as part of the normal build. If you just execute those targets separately, you can find out the return values, which is exactly what you need. The same mechanism is used by IDE to get location of refereces, for Intellisense, introspection, etc.
Here is how you can do it in code:
using Microsoft.Build.BuildEngine;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using System;
using System.Collections.Generic;
class Program
{
static int Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: GetReferences.exe <projectFileName>");
return -1;
}
string projectFileName = args[0];
ConsoleLogger logger = new ConsoleLogger(LoggerVerbosity.Normal);
BuildManager manager = BuildManager.DefaultBuildManager;
ProjectInstance projectInstance = new ProjectInstance(projectFileName);
var result = manager.Build(
new BuildParameters()
{
DetailedSummary = true,
Loggers = new List<ILogger>() { logger }
},
new BuildRequestData(projectInstance, new string[]
{
"ResolveProjectReferences",
"ResolveAssemblyReferences"
}));
PrintResultItems(result, "ResolveProjectReferences");
PrintResultItems(result, "ResolveAssemblyReferences");
return 0;
}
private static void PrintResultItems(BuildResult result, string targetName)
{
var buildResult = result.ResultsByTarget[targetName];
var buildResultItems = buildResult.Items;
if (buildResultItems.Length == 0)
{
Console.WriteLine("No refereces detected in target {0}.", targetName);
return;
}
foreach (var item in buildResultItems)
{
Console.WriteLine("{0} reference: {1}", targetName, item.ItemSpec);
}
}
}
Notice, the engine is called to invoke specific targets in the project. Your project usually does not build, but some targets might be invoked by pre-requisite targets.
Just compile it and will print a sub-set of all dependencies. There might be more dependencies if you use COM references or native dependencies for your project. It should be easy to modify the sample to get those as well.

Cannot suppress certain warning globally

I have VS2013 with Code Analysis turned on. I want to suppress one type of message globally, so I have following code in GlobalSuppressions.cs
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores", Scope = "namespace", Target = "MyProject.MyNamespace")]
But my function name contains underscore still got warned/errored by VS.
[TestMethod()]
public void Do_Something_Successfully_Test()
{
}
Any idea why?

SolutionContext.ConfigurationName set returns E_FAIL

I have the following code in a custom project based on MPF for Projects - Visual Studio 2010:
EnvDTE.Project dteProj = CurrentProject();
dteProj.ConfigurationManager.AddConfigurationRow("MyCustomConfig", "Debug", false);
var solution = dteProj.DTE.Solution as EnvDTE90.Solution3;
foreach (EnvDTE80.SolutionConfiguration2 solConfig in solution.SolutionBuild.SolutionConfigurations)
{
foreach (EnvDTE.SolutionContext solContext in solConfig.SolutionContexts)
{
if (dteProj.UniqueName != solContext.ProjectName)
continue;
//Returns E_FAIL
solContext.ConfigurationName = "MyCustomConfig";
}
}
As you can see everything is pretty straight forward. I create a new configuration for my project and want to use it in a solution context. Setting the configuration name returns E_FAIL.
Why is the assignment failing? What is the correct programmatic equivalent for selecting a project configuration for a project from the drop down in the Configuration Manager dialog box?
Thanks

how to disable T4 template auto-run in visual studio (2012)?

I have some T4 templates in my project. Whenever I make changes and save the tt file, it auto update the generated files. This is a template that loops all tables in a database and generates about 100+ files. So visual studio hangs for a few seconds every time I save my template and this is annoying. Is there a way to disable to "auto-refresh" function and I can manually run the template through the context menu.
Thanks!
You could delete TextTemplatingFileGenerator under "Custom Tool" in the file's Properties while you are editing it, and then put it back when you are finished.
I had a similiar issue. I found a quick work around by creating a ttinclude file (actually this was already a standard include file containing utility functions for my templates) and including it in all of my T4 templates. Then I simply created a compiler error in the include file. Thus when the generator attempted to run it would simply fail on the compile. Then when I'm ready to actually generate, I get rid of the offending code and then generate.
e.g. To cause a failure:
<#+
#
#>
To disable the failure:
<#+
//#
#>
You can also use this trick in the T4 template itself if you just want to disable the one you're working on.
Hopefully future VS versions will allow you to simply disable the auto-transform.
Since the TT is always executed (still), I found a different way to control the output when the TT is executed.
/********SET THIS TO REGENERATE THE FILE (OR NOT) ********/
var _RegenerateFile = true;
/********COS VS ALWAYS REGENERATES ON SAVE ***************/
// Also, T4VSHostProcess.exe may lock files.
// Kill it from task manager if you get "cannot copy file in use by another process"
var _CurrentFolder = new FileInfo(Host.ResolvePath(Host.TemplateFile)).DirectoryName;
var _AssemblyLoadFolder = Path.Combine(_CurrentFolder, "bin\\Debug");
Directory.SetCurrentDirectory(_CurrentFolder);
Debug.WriteLine($"Using working folder {_CurrentFolder}");
if (_RegenerateFile == false)
{
Debug.WriteLine($"Not Regenerating File");
var existingFileName = Path.ChangeExtension(Host.TemplateFile, "cs");
var fileContent = File.ReadAllText(existingFileName);
return fileContent;
}
Debug.WriteLine($"Regenerating File"); //put the rest of your usual template
Another way (what I eventually settled on) is based on reading a conditional compilation symbol that sets a property on one of the the classes that is providing the data for the T4. This gives the benefit of skipping all that preparation (and IDE lag) unless you add the REGEN_CODE_FILES conditional compilation symbol. (I guess this could also be made into a new solution configuration too. yes, this does work and removes the need for the class change below)
An example of the class i am calling in the same assembly..
public class MetadataProvider
{
public bool RegenCodeFile { get; set; }
public MetadataProvider()
{
#if REGEN_CODE_FILES
RegenCodeFile = true; //try to get this to set the property
#endif
if (RegenCodeFile == false)
{
return;
}
//code that does some degree of preparation and c...
}
}
In the TT file...
var _MetaProvider = new MetadataProvider();
var _RegenerateFile = _MetaProvider.RegenCodeFile;
// T4VSHostProcess.exe may lock files.
// Kill it from task manager if you get "cannot copy file in use by another process"
var _CurrentFolder = new FileInfo(Host.ResolvePath(Host.TemplateFile)).DirectoryName;
var _AssemblyLoadFolder = Path.Combine(_CurrentFolder, "bin\\Debug");
Directory.SetCurrentDirectory(_CurrentFolder);
Debug.WriteLine($"Using working folder {_CurrentFolder}");
if (_RegenerateFile == false)
{
Debug.WriteLine($"Not Regenerating File");
var existingFileName = Path.ChangeExtension(Host.TemplateFile, "cs");
var fileContent = File.ReadAllText(existingFileName);
return fileContent;
}
Debug.WriteLine($"Regenerating File");

Display a message in Visual Studio's output window when not debug mode?

In Java, you can use System.out.println(message) to print a message to the output window.
What's the equivalent in Visual Studio ?
I know when I'm in debug mode I can use this to see the message in the output window:
Debug.WriteLine("Debug : User_Id = "+Session["User_Id"]);
System.Diagnostics.Trace.WriteLine("Debug : User_Id = "+Session["User_Id"]);
How can this be done without debugging in Visual Studio?
The results are not in the Output window but in the Test Results Detail (TestResult Pane at the bottom, right click on on Test Results and go to TestResultDetails).
This works with Debug.WriteLine and Console.WriteLine.
The Trace messages can occur in the output window as well, even if you're not in debug mode.
You just have to make sure the the TRACE compiler constant is defined.
The Trace.WriteLine method is a conditionally compiled method. That means that it will only be executed if the TRACE constant is defined when the code is compiled. By default in Visual Studio, TRACE is only defined in DEBUG mode.
Right Click on the Project and Select Properties. Go to the Compile tab. Select Release mode and add TRACE to the defined preprocessor constants. That should fix the issue for you.
This whole thread confused the h#$l out of me until I realized you have to be running the debugger to see ANY trace or debug output. I needed a debug output (outside of the debugger) because my WebApp runs fine when I debug it but not when the debugger isn't running (SqlDataSource is instantiated correctly when running through the debugger).
Just because debug output can be seen when you're running in release mode doesn't mean you'll see anything if you're not running the debugger. Careful reading of Writing to output window of Visual Studio? gave me DebugView as an alternative. Extremely useful!
Hopefully this helps anyone else confused by this.
For me this was the fact that debug.writeline shows in the Immediate window, not the Output. My installation of VS2013 by default doesn't even show an option to open the Immediate window, so you have to do the following:
Select Tools -> Customize
Commands Tab
View | Other Windows menu bar dropdown
Add Command...
The Immediate option is in the Debug section.
Once you have Ok'd that, you can go to View -> Other Windows and select the Immediate Window and hey presto all of the debug output can be seen.
Unfortunately for me it also showed about 50 errors that I wasn't aware of in my project... maybe I'll just turn it off again :-)
To write in the Visual Studio output window I used IVsOutputWindow and IVsOutputWindowPane. I included as members in my OutputWindow class which look like this :
public class OutputWindow : TextWriter
{
#region Members
private static readonly Guid mPaneGuid = new Guid("AB9F45E4-2001-4197-BAF5-4B165222AF29");
private static IVsOutputWindow mOutputWindow = null;
private static IVsOutputWindowPane mOutputPane = null;
#endregion
#region Constructor
public OutputWindow(DTE2 aDte)
{
if( null == mOutputWindow )
{
IServiceProvider serviceProvider =
new ServiceProvider(aDte as Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
mOutputWindow = serviceProvider.GetService(typeof(SVsOutputWindow)) as IVsOutputWindow;
}
if (null == mOutputPane)
{
Guid generalPaneGuid = mPaneGuid;
mOutputWindow.GetPane(ref generalPaneGuid, out IVsOutputWindowPane pane);
if ( null == pane)
{
mOutputWindow.CreatePane(ref generalPaneGuid, "Your output window name", 0, 1);
mOutputWindow.GetPane(ref generalPaneGuid, out pane);
}
mOutputPane = pane;
}
}
#endregion
#region Properties
public override Encoding Encoding => System.Text.Encoding.Default;
#endregion
#region Public Methods
public override void Write(string aMessage) => mOutputPane.OutputString($"{aMessage}\n");
public override void Write(char aCharacter) => mOutputPane.OutputString(aCharacter.ToString());
public void Show(DTE2 aDte)
{
mOutputPane.Activate();
aDte.ExecuteCommand("View.Output", string.Empty);
}
public void Clear() => mOutputPane.Clear();
#endregion
}
If you have a big text to write in output window you usually don't want to freeze the UI. In this purpose you can use a Dispatcher. To write something in output window using this implementation now you can simple do this:
Dispatcher mDispatcher = HwndSource.FromHwnd((IntPtr)mDte.MainWindow.HWnd).RootVisual.Dispatcher;
using (OutputWindow outputWindow = new OutputWindow(mDte))
{
mDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
outputWindow.Write("Write what you want here");
}));
}

Resources