Using Grpc.Tools with Protoc plug-in to generate additional C# files - c#

I am using Grpc.Tools (2.38.1) to generate C# types and gRPC stubs from a Test.proto file containing some service definitions.
To do this I have the following in my project's .csproj file:
<ItemGroup>
<Protobuf Include="**/*.proto" />
</ItemGroup>
This is all working fine: my Test.proto gets compiled to Test.cs and TestGrpc.cs in the obj/Debug folder of my project. The types within them can be referenced from within other types in the project.
But I need to create a WCF interface for the service too, so I thought I could generate this using a custom Protoc plug-in. So I wrote a simple Protoc plug-in that writes out a TestWcf.cs file containing an interface. I then placed this plug-in executable on my path named protoc-gen-blah.exe and updated the entry in the .csproj file to this:
<ItemGroup>
<Protobuf Include="**/*.proto" AdditionalProtocArguments="--blah_out=obj\Debug" />
</ItemGroup>
This correctly creates the C# file, TestWcf.cs, with my interface in: fantastic.
The problem is that my interface within TestWcf.cs cannot be referenced from other types in the project unless I manually include the generated file in the project: something I do not have to do with the other generated files.
Whilst none of the files are included in the project by default―I have to enable 'Show All Files' to see them―Test.cs and TestGrpc.cs have arrows beside them in the Solution Explorer that allow them to be expanded to reveal the types inside. TestWcf.cs does not have this arrow. So Visual Studio is somehow aware that Test.cs and TestGrpc.cs are source code files.
Does anyone know what I need to do for my generated file to be automatically recognised by Visual Studio like the other two files are?
I suspect it has something to do with this part of the Grpc.Tools build target, as I noticed my TestWcf.cs file is not included in the files deleted by the Grpc.Tools clean either, but I can't see why it does not consider my generated file to be C#.
When I build, this is the Protoc call:
D:\...\Src\packages\Grpc.Tools.2.38.1\tools\windows_x86\protoc.exe --csharp_out=obj\Debug ⤶
--plugin=protoc-gen-grpc=D:\...\Src\packages\Grpc.Tools.2.38.1\tools\windows_x86\grpc_csharp_plugin.exe ⤶
--grpc_out=obj\Debug --proto_path=D:\...\Src\packages\Grpc.Tools.2.38.1\build\native\include ⤶
--proto_path=. --dependency_out=obj\Debug\xxxx_Test.protodep --error_format=msvs --blah_out=obj\Debug ⤶
Test.proto
The dependency file looks like this:
obj\Debug/Test.cs \
obj\Debug/TestGrpc.cs \
obj\Debug/TestWcf.cs: Test.proto
Thanks.

I believe the problem is caused some logic in Grpc.Tools that informs MSBuild of the files that have been generated:
public override string[] GetPossibleOutputs(ITaskItem protoItem)
{
...
var outputs = new string[doGrpc ? 2 : 1];
...
outputs[0] = Path.Combine(outdir, filename) + ".cs";
if (doGrpc)
{
...
outputs[1] = Path.Combine(grpcdir, filename) + "Grpc.cs";
}
return outputs;
}
This code only caters for two files being generated from a Protocol Buffer source (name.proto): the Protocol Buffers code generation (name.cs) and the gRPC code generation (nameGrpc.cs). It is not picking up the additional file and informing MSBuild that it exists, hence Visual Studio does not consider it to be code.
There is no away around this short of changing the Grpc.Tools code.

Related

copy App.config when compiling with CodeAnalysis.Compilation.Emit

I am compiling a C# project using the Roslyn CodeAnalysis APIs, as in the following snipet:
...
EmitResult emitResult = null;
using (FileStream outputFileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
emitResult = compilation.Emit(outputFileStream, null);
}
...
This works perfectly. However, when I want to compile an executable project that contains an App.Config file, this file is not emitted in the output directory (similar to how it is emitted when compiling via Visual Studio), which is problematic if you want to parse some configs from that file at runtime.
I realize that it should not be emitted by default, but I would probably need to enable some option, or to somehow extract the App.Config path from the Project object and emit it myself? I cannot seem to find any information regarding this, and the Project class does not seem to contain any information regarding config files (unless I am missing something).
Is there some specific API that I need to use to achieve the above? Any pointers to existing code that does this available?
Thanks a lot!
Roslyn is not a build system, it's a compiler. It can only transform code into assemblies (and PDBs). When you compile a .csproj in Visual Studio, MSBuild reads all the properties and items in the project file and translates them into a series of steps that, among other things, call the Roslyn compiler, copies App.config files to the output directory, copies references, etc.
While it's true Roslyn can read MSBuild project files, it only uses this information for compilation purposes, such as locating references.
So, if you're dynamically compiling assemblies, you'll have to copy the App.config file yourself (note - VS doesn't emit this file - it just copies and renames it).

Update files in visual studio project from build script

Scenario
I am in a situation at the moment where one of the projects I am working on depends upon a 3rd party API, which exposes a swagger descriptor in JSON which we consume using auto rest to generate the C# files for use in the system.
The c# files are generated as part of the build script however there have been instances where the API is updated, and although we do not use any of the new changes it does change some of the c# code which is generated which in turn may depend upon new files which are output from the build script but VS does not know they exist so does not include them.
Issue
So I am trying to find a way to tell visual studio from a build script or some sane way (Without manually changing a *.csproj file) to automatically include everything within a folder in its project, so is there a way to do this?
Other Info
I have deliberately not mentioned build script technologies as it is not really important, so I don't mind if I have to use msbuild tasks or other command line stuff as long as it works I can probably find a way to hook it in.
Also I know some people will think "why do you need to keep re-generating the files?", and we don't really, and currently we try to keep to a specific version, however ignoring that the question still stands, i.e how do you update files VS knows about from outside VS.
You can use wild cards in .csproj file. You only need to do this once
since you didn't give the folder name, I am suggesting a generic solution for you.
To prevent the Visual studio from expanding the wild card once you modify the porject list in Visual studio, you have to add it as property.
In PropertyGroup , add a property with like this
<PropertyGroup>
<IncludeFolder>yourFolder\**</IncludeFolder>
</PropertyGroup>
Then in add the following line to ItemGroup
<ItemGroup>
<Content Include="$(IncludeFolder)" />
</ItemGroup>
This will add everything in that folder to your project.
Also this wild card will not be expanded by the visual studio once you modify them from Visual studio solution explorer. (A weird process from Visual Studio)

SonarQube with C# plugin with MSBuild Runner does not take exclusions

Currently I have an instance of SonarQube 5.1.2 with C# plugin and MSBuild runner in order to analyze a 1.200.000 LOC project. I intend to reduce the classes that are analyzed, I created a sonar.properties file with the line
sonar.exclusions=**/Databases/**/*.*
but after reading the log from the analysis, files inside the Databases folder were analyzed. following the instructions from Eric Starr, I set this simple exclusion rule in the call of the runner:
"C:\sonarqube-5.1.2\bin\MSBuild.SonarQube.Runner.exe" begin /k:MyProject /n:MyProject /v:2 /d:sonar.exclusions="file:C:\codesource\Databases/**/*.*" /d:sonar.scm.provider=tfvc /d:sonar.tfvc.username=************* /d:sonar.tfvc.password.secured={aes}*************************** "/d:sonar.cs.vscoveragexml.reportsPaths=C:\codesource\CodeCoverage\Results.coveragexml"
I found that the runner creates a sonar-project.properties file, and it contains a lot of files located in the databases folder:
BC78C8C4-8ECD-47CB-9781-F621AE109FE4.sonar.projectName=myDatabase
BC78C8C4-8ECD-47CB-9781-F621AE109FE4.sonar.projectBaseDir=BC78C8C4-8ECD-47CB-9781-F621AE109FE4.sonar.projectName=myDatabase
BC78C8C4-8ECD-47CB-9781-F621AE109FE4.sonar.projectBaseDir=C:\\codesource\\Databases\\myDatabase
BC78C8C4-8ECD-47CB-9781-F621AE109FE4.sonar.sources=\
C:\\codesource\\Databases\\myDatabase\\Scripts\\PreDeployment\\PATCH_20150527_01.sql,\
C:\\codesource\\Databases\\myDatabase\\Scripts\\PreDeployment\\ROCOMMON.DBVERSION.sql,\
,\.....
as I understood, there should be no files in the databases folder. Am I wrong?
You are using the SonarQube Scanner for MSBuild which is very different from the regular SonarQube Scanner used for all other languages.
The sonar.exclude line that you are trying to use would only work if you would use the regular SonarQube scanner, because that takes in the Sonar-project.properties file. The SonarQube Scanner for MSBuild only has a SonarQube.Analysis.Xml file that contains project-related settings that you can tweak.
You can use couple of overwriting strategies for the SonarQube.Analysis.Xml file:
A project-specific property defined in the MSBuild *.*proj file (corresponding to a SonarQube module) can override:
A property defined in the command line (/d:propertyName=value) has which can override:
A property defined in the SonarQube.Analysis.xml configuration file
A property defined in the SonarQube User Interface at project level which can override everything
A property defined in the SonarQube User Interface at global level which can't override anything
To exclude specific folders or extensions from your Solution:
You need to add the excludes into each individual projects' .csproj file. Here's the syntax which you should use within the main root node, called <Project...> and into one of the targets, preferably <Target Name="BeforeBuild">. Hope the syntax below is self-explanetory enough, but in case it isn't, please leave a comment under this answer and I'll update it right away.
<Target Name="BeforeBuild">
<ItemGroup>
<SonarQubeSetting Include="sonar.exclusions">
<Value>**/Databases/**/*</Value>
</SonarQubeSetting>
</ItemGroup>
</Target>
Hope it helps!
Source

Antlr4 C# Application Tutorial/Example

I want to use Antlr4 to parse some files in my C# application. I have been able to generate the parser and lexer files so far given my grammer. Now I would like to use read in the files and apply the parser and lexer to them. I have been searching for documentation on how to do that but I am coming up short. I have found some old examples using previous versions of Antlr but they don't appear to work for Antlr4. Any help would be appreciated. Thanks.
In Visual Studio, go to Tools -> Extensions and Updates and search the Online section for "ANTLR Language Support" by Sam Harwell. More information can be found on the GitHub project site
This does a few things:
Adds Templates for the combined grammars.
Adds Syntax Highlighting
Adds an MSBuild target for the grammar to generate the parser.
In your solution, set up your project structure like this:
Solution
GrammarProject
Calculator.g4
ImplementationProject
GeneratedFiles (All files in this folder are added as Links to files located in GrammarProject\obj\Debug)
CalculatorBaseListener.cs
CalculatorBaseVisitor.cs
CalculatorLexer.cs
CalculatorListener.cs
CalculatorParser.cs
CalculatorVistor.cs
MyCalcualtorImplementation.cs
Write and Compile your grammar.
In your folder for the Links to Generated Files, Right-Click the folder and click Add -> Existing Item
Browse to Grammar Project\obj\Debug and select all the generated parser files.
This next step is important. On the Add button there is a little drop-down arrow. Click the drop-down arrow and click "Add As Link".
This will add the generated files to the implementation project using a symbolic link instead of a direct copy.
This gives the added benefit of not having to remove and re-add the parser files if you have to change your grammar later.
Once you have gotten this far, then you can inherit from GrammarProject.CalculatorBaseListener or GrammarProject.CalculatorBaseParser depending on what development pattern you have decided to use.
As a side note, "The Definitive ANTLR 4 Reference" by Terence Parr is an excellent resource to understand how ANTLR4 works and the difference development patterns. All the examples are in java, but the concepts apply to both Java and C#.
try with
using (StreamReader fileStream = new StreamReader(fileName)) {
AntlrInputStream inputStream = new AntlrInputStream(fileStream);
SearchLexer lexer = new SearchLexer(inputStream);
CommonTokenStream commonTokenStream = new CommonTokenStream(lexer);
SearchParser parser = new SearchParser(commonTokenStream);
parser.RemoveErrorListeners();
parser.AddErrorListener(new ErrorListener()); // add ours
parser.root();
}
I am using Visual Studio 2019 Professional (latest version 16.7.3)
As for now ANTLR Language Support are not available for VS 2019. There is an unofficial version available https://github.com/tunnelvisionlabs/antlr4cs/issues/353 but it not complaint with VS2019 extension API
(more info here: https://devblogs.microsoft.com/visualstudio/updates-to-synchronous-autoload-of-extensions-in-visual-studio-2019/)
You may try the following (steps for .net standard library)
Install VS extension AntlrVSIX 8.0 (using the Extension Manager)
create a .NET Standard Library project (MyLib.Parser.Grammar)
Created a dummy (.cs) class - not sure if its still necessary, there were some issues in the past if the project contained only grammar files
Reference the following packages (using Nuget)
Antlr4.Runtime.Standard
Antlr4BuildTasks
Add grammar files (.g4) e.g. you can used the grammar repository available here
https://github.com/antlr/grammars-v4
Let's say you would like to parse TSQL (https://github.com/antlr/grammars-v4/tree/master/sql/tsql) - add TSqlParser.g4 and TSqllexer.g4 to your project
Edit the project file MyLib.Parser.Grammar.csproj, it should look something like
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Antlr4 Include="TSqlLexer.g4">
<Package>MyLib.Parser</Package>
<Visitor>true</Visitor>
<Error>false</Error>
<Listener>true</Listener>
</Antlr4>
<Antlr4 Include="TSqlParser.g4">
<Package>MyLib.Parser</Package>
<Visitor>true</Visitor>
<Error>false</Error>
<Listener>true</Listener>
</Antlr4>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Antlr4.Runtime.Standard" Version="4.8.0" />
<PackageReference Include="Antlr4BuildTasks" Version="8.3.0" />
</ItemGroup>
</Project>
At this point when you build the MyLib.Parser.Grammar project the Antlr4BuildTasks tools will create the parser .cs files, but they will be available in the project bin folder (e.g. \MyLib.Parser.Grammar\bin\Debug\netstandard2.1)
Create another library project MyLib.Parser
Create project dependency so MyLib.Parser.Grammar is build before MyLib.Parser
Direct the output files from MyLib.Parser.Grammar to MyLib.Parser project by using the AntOutDir attribute and relative paths in the project definition. Now the Antlr4 section in the project file should like something like:
<ItemGroup>
<Antlr4 Include="TSqlLexer.g4">
<Package>MyLib.Parser</Package>
<Visitor>true</Visitor>
<Error>false</Error>
<Listener>true</Listener>
<AntOutDir>..\MyLib.Parser</AntOutDir>
</Antlr4>
<Antlr4 Include="TSqlParser.g4">
<Package>MyLib.Parser</Package>
<Visitor>true</Visitor>
<Error>false</Error>
<Listener>true</Listener>
<AntOutDir>..\MyLib.Parser</AntOutDir>
</Antlr4>
</ItemGroup>
Now after rebuild, the generated .cs files should be added to the MyLib.Parser project automatically
here are a sample of ErrorListener
public class ErrorListener : BaseErrorListener
{
public void SyntaxError(IRecognizer recognizer, int offendingSymbol, int line, int charPositionInLine, string msg, RecognitionException e)
{
Console.WriteLine("{0}: line {1}/column {2} {3}", e, line, charPositionInLine, msg);
}
}

Code to add a C# class file to a project

Can anybody provide some code block to add an existing C# file to a project.
I have 2 projects in my solution. One project generates C# class files which will be use by second project. I have to incluse these generated files in the second project and build the project. It should be done through programatically. I know that to include these files I have to edit the C#project file (which is an XML) and make an entry that file. But I thought of
using existing code if anybody has it.
Thanks
You can ahve a look at the MsBuild.Engine namespace. It allows you to manipulate a csproj in a consistent way so you can oper the target project and add the reference programmatically.

Categories