yes
This commit is contained in:
parent
ccfaa35c1d
commit
d2a4639c0e
@ -22,7 +22,9 @@
|
|||||||
"Bash(curl:*)",
|
"Bash(curl:*)",
|
||||||
"Bash(timeout 3 cmd:*)",
|
"Bash(timeout 3 cmd:*)",
|
||||||
"Bash(timeout:*)",
|
"Bash(timeout:*)",
|
||||||
"Bash(tasklist:*)"
|
"Bash(tasklist:*)",
|
||||||
|
"Bash(dotnet build:*)",
|
||||||
|
"Bash(dotnet --list-sdks:*)"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|||||||
@ -41,9 +41,10 @@ public class ProtoFileSourceGenerator : IIncrementalGenerator
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Get build properties for configuration
|
// Get the root namespace from the compilation - this matches what GrpcGenerator does
|
||||||
var packageName = GetBuildProperty(spc, "RootNamespace") ?? "cqrs";
|
var rootNamespace = compilation.AssemblyName ?? "Generated";
|
||||||
var csharpNamespace = GetBuildProperty(spc, "RootNamespace") ?? "Generated.Grpc";
|
var packageName = "cqrs";
|
||||||
|
var csharpNamespace = $"{rootNamespace}.Grpc";
|
||||||
|
|
||||||
// Generate the proto file content
|
// Generate the proto file content
|
||||||
var generator = new ProtoFileGenerator(compilation);
|
var generator = new ProtoFileGenerator(compilation);
|
||||||
|
|||||||
130
Svrnty.CQRS.Grpc.Generators/WriteProtoFileTask.cs
Normal file
130
Svrnty.CQRS.Grpc.Generators/WriteProtoFileTask.cs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
#pragma warning disable RS1035 // Do not use APIs banned for analyzers - This is an MSBuild task, not an analyzer
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.Build.Framework;
|
||||||
|
using Microsoft.Build.Utilities;
|
||||||
|
|
||||||
|
namespace Svrnty.CQRS.Grpc.Generators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MSBuild task that extracts the auto-generated proto file content from the source generator
|
||||||
|
/// output and writes it to disk so Grpc.Tools can process it
|
||||||
|
/// </summary>
|
||||||
|
public class WriteProtoFileTask : Task
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The project directory where we should look for generated files
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string ProjectDirectory { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The intermediate output path (typically obj/Debug/net10.0)
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string IntermediateOutputPath { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The output directory where the proto file should be written (typically Protos/)
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string OutputDirectory { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The name of the proto file to generate (typically cqrs_services.proto)
|
||||||
|
/// </summary>
|
||||||
|
[Required]
|
||||||
|
public string ProtoFileName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public override bool Execute()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log.LogMessage(MessageImportance.High,
|
||||||
|
"Svrnty.CQRS.Grpc: Extracting auto-generated proto file...");
|
||||||
|
|
||||||
|
// Look for the generated C# file containing the proto content
|
||||||
|
// Source generators output to obj/Generated, not IntermediateOutputPath/Generated
|
||||||
|
var generatedFilePath = Path.Combine(
|
||||||
|
ProjectDirectory,
|
||||||
|
"obj",
|
||||||
|
"Generated",
|
||||||
|
"Svrnty.CQRS.Grpc.Generators",
|
||||||
|
"Svrnty.CQRS.Grpc.Generators.ProtoFileSourceGenerator",
|
||||||
|
"GeneratedProtoFile.g.cs"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!File.Exists(generatedFilePath))
|
||||||
|
{
|
||||||
|
Log.LogWarning(
|
||||||
|
$"Generated proto file not found at {generatedFilePath}. " +
|
||||||
|
"The proto file may not have been generated yet. This is normal on first build.");
|
||||||
|
return true; // Don't fail the build, just skip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the generated C# file
|
||||||
|
var csContent = File.ReadAllText(generatedFilePath);
|
||||||
|
|
||||||
|
// Extract the proto content using a more robust approach
|
||||||
|
// Looking for: public const string Content = @"...";
|
||||||
|
var startMarker = "public const string Content = @\"";
|
||||||
|
var startIndex = csContent.IndexOf(startMarker);
|
||||||
|
|
||||||
|
if (startIndex < 0)
|
||||||
|
{
|
||||||
|
Log.LogError($"Could not find Content property in {generatedFilePath}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex += startMarker.Length;
|
||||||
|
|
||||||
|
// Find the closing "; - We need the LAST occurrence because the content contains escaped quotes
|
||||||
|
// The pattern is: Content = @"...content...";
|
||||||
|
// where content has "" for literal quotes
|
||||||
|
var endMarker = "\";";
|
||||||
|
|
||||||
|
// Find where the next field starts or class ends to limit our search
|
||||||
|
var nextFieldOrEnd = csContent.IndexOf("\n }", startIndex); // End of class
|
||||||
|
if (nextFieldOrEnd < 0)
|
||||||
|
{
|
||||||
|
nextFieldOrEnd = csContent.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endIndex = csContent.LastIndexOf(endMarker, nextFieldOrEnd, nextFieldOrEnd - startIndex);
|
||||||
|
|
||||||
|
if (endIndex < 0 || endIndex < startIndex)
|
||||||
|
{
|
||||||
|
Log.LogError($"Could not find end of Content property in {generatedFilePath}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract and unescape doubled quotes
|
||||||
|
var protoContent = csContent.Substring(startIndex, endIndex - startIndex);
|
||||||
|
protoContent = protoContent.Replace("\"\"", "\"");
|
||||||
|
|
||||||
|
Log.LogMessage(MessageImportance.High,
|
||||||
|
$"Extracted proto content length: {protoContent.Length} characters");
|
||||||
|
|
||||||
|
// Ensure output directory exists
|
||||||
|
var fullOutputPath = Path.Combine(ProjectDirectory, OutputDirectory);
|
||||||
|
Directory.CreateDirectory(fullOutputPath);
|
||||||
|
|
||||||
|
// Write the proto file
|
||||||
|
var protoFilePath = Path.Combine(fullOutputPath, ProtoFileName);
|
||||||
|
File.WriteAllText(protoFilePath, protoContent);
|
||||||
|
|
||||||
|
Log.LogMessage(MessageImportance.High,
|
||||||
|
$"Svrnty.CQRS.Grpc: Successfully generated proto file at {protoFilePath}");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.LogErrorFromException(ex, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,17 +6,32 @@
|
|||||||
<GeneratedProtoFileName Condition="'$(GeneratedProtoFileName)' == ''">cqrs_services.proto</GeneratedProtoFileName>
|
<GeneratedProtoFileName Condition="'$(GeneratedProtoFileName)' == ''">cqrs_services.proto</GeneratedProtoFileName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Target Name="SvrntyGenerateProtoInfo" BeforeTargets="CoreCompile">
|
<!-- Determine the assembly path (different for NuGet package vs project reference) -->
|
||||||
<Message Text="Svrnty.CQRS.Grpc.Generators: Proto file will be auto-generated to $(ProtoOutputDirectory)\$(GeneratedProtoFileName)" Importance="normal" />
|
<PropertyGroup>
|
||||||
</Target>
|
<_GeneratorsAssemblyPath Condition="Exists('$(MSBuildThisFileDirectory)\..\analyzers\dotnet\cs\Svrnty.CQRS.Grpc.Generators.dll')">$(MSBuildThisFileDirectory)\..\analyzers\dotnet\cs\Svrnty.CQRS.Grpc.Generators.dll</_GeneratorsAssemblyPath>
|
||||||
|
<_GeneratorsAssemblyPath Condition="'$(_GeneratorsAssemblyPath)' == '' AND Exists('$(MSBuildThisFileDirectory)\..\bin\Debug\netstandard2.0\Svrnty.CQRS.Grpc.Generators.dll')">$(MSBuildThisFileDirectory)\..\bin\Debug\netstandard2.0\Svrnty.CQRS.Grpc.Generators.dll</_GeneratorsAssemblyPath>
|
||||||
|
<_GeneratorsAssemblyPath Condition="'$(_GeneratorsAssemblyPath)' == '' AND Exists('$(MSBuildThisFileDirectory)\..\bin\Release\netstandard2.0\Svrnty.CQRS.Grpc.Generators.dll')">$(MSBuildThisFileDirectory)\..\bin\Release\netstandard2.0\Svrnty.CQRS.Grpc.Generators.dll</_GeneratorsAssemblyPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<!-- Load the WriteProtoFileTask from the generator assembly -->
|
||||||
|
<UsingTask TaskName="Svrnty.CQRS.Grpc.Generators.WriteProtoFileTask"
|
||||||
|
AssemblyFile="$(_GeneratorsAssemblyPath)"
|
||||||
|
Condition="'$(_GeneratorsAssemblyPath)' != ''" />
|
||||||
|
|
||||||
<!-- This target ensures the Protos directory exists before the generator runs -->
|
<!-- This target ensures the Protos directory exists before the generator runs -->
|
||||||
<Target Name="EnsureProtosDirectory" BeforeTargets="CoreCompile">
|
<Target Name="EnsureProtosDirectory" BeforeTargets="CoreCompile">
|
||||||
<MakeDir Directories="$(ProtoOutputDirectory)" Condition="!Exists('$(ProtoOutputDirectory)')" />
|
<MakeDir Directories="$(ProtoOutputDirectory)" Condition="!Exists('$(ProtoOutputDirectory)')" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<!-- Set environment variable so the source generator can find the project directory -->
|
<!-- Extract the proto file from the source generator output BEFORE Grpc.Tools processes protos -->
|
||||||
<PropertyGroup>
|
<!-- Runs before CoreCompile, after source generators have been executed -->
|
||||||
<MSBuildProjectDirectory>$(MSBuildProjectDirectory)</MSBuildProjectDirectory>
|
<Target Name="SvrntyExtractProtoFile" BeforeTargets="CoreCompile" AfterTargets="ResolveProjectReferences" DependsOnTargets="EnsureProtosDirectory" Condition="'$(GenerateProtoFile)' == 'true'">
|
||||||
</PropertyGroup>
|
<Message Text="Svrnty.CQRS.Grpc: Extracting auto-generated proto file to $(ProtoOutputDirectory)\$(GeneratedProtoFileName)" Importance="high" />
|
||||||
|
|
||||||
|
<WriteProtoFileTask
|
||||||
|
ProjectDirectory="$(MSBuildProjectDirectory)"
|
||||||
|
IntermediateOutputPath="$(IntermediateOutputPath)"
|
||||||
|
OutputDirectory="$(ProtoOutputDirectory)"
|
||||||
|
ProtoFileName="$(GeneratedProtoFileName)" />
|
||||||
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -6,46 +6,48 @@ package cqrs;
|
|||||||
|
|
||||||
// Command service for CQRS operations
|
// Command service for CQRS operations
|
||||||
service CommandService {
|
service CommandService {
|
||||||
// Adds a new user and returns the user ID
|
// AddUserCommand operation
|
||||||
rpc AddUser (AddUserCommandRequest) returns (AddUserCommandResponse);
|
rpc AddUser (AddUserCommandRequest) returns (AddUserCommandResponse);
|
||||||
|
|
||||||
// Removes a user
|
// RemoveUserCommand operation
|
||||||
rpc RemoveUser (RemoveUserCommandRequest) returns (RemoveUserCommandResponse);
|
rpc RemoveUser (RemoveUserCommandRequest) returns (RemoveUserCommandResponse);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query service for CQRS operations
|
// Query service for CQRS operations
|
||||||
service QueryService {
|
service QueryService {
|
||||||
// Fetches a user by ID
|
// FetchUserQuery operation
|
||||||
rpc FetchUser (FetchUserQueryRequest) returns (FetchUserQueryResponse);
|
rpc FetchUser (FetchUserQueryRequest) returns (FetchUserQueryResponse);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request message for adding a user
|
// Request message for AddUserCommand
|
||||||
message AddUserCommandRequest {
|
message AddUserCommandRequest {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
string email = 2;
|
string email = 2;
|
||||||
int32 age = 3;
|
int32 age = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response message containing the added user ID
|
// Response message for AddUserCommand
|
||||||
message AddUserCommandResponse {
|
message AddUserCommandResponse {
|
||||||
int32 result = 1;
|
int32 result = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request message for removing a user
|
// Request message for RemoveUserCommand
|
||||||
message RemoveUserCommandRequest {
|
message RemoveUserCommandRequest {
|
||||||
int32 user_id = 1;
|
int32 user_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response message for remove user (empty)
|
// Response message for RemoveUserCommand
|
||||||
message RemoveUserCommandResponse {
|
message RemoveUserCommandResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request message for fetching a user
|
// Request message for FetchUserQuery
|
||||||
message FetchUserQueryRequest {
|
message FetchUserQueryRequest {
|
||||||
int32 user_id = 1;
|
int32 user_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response message containing the user
|
// Response message for FetchUserQuery
|
||||||
message FetchUserQueryResponse {
|
message FetchUserQueryResponse {
|
||||||
User result = 1;
|
User result = 1;
|
||||||
}
|
}
|
||||||
@ -56,3 +58,4 @@ message User {
|
|||||||
string name = 2;
|
string name = 2;
|
||||||
string email = 3;
|
string email = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,4 +32,7 @@
|
|||||||
<ProjectReference Include="..\Svrnty.CQRS.MinimalApi\Svrnty.CQRS.MinimalApi.csproj" />
|
<ProjectReference Include="..\Svrnty.CQRS.MinimalApi\Svrnty.CQRS.MinimalApi.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Import the proto generation targets for testing (in production this would come from the NuGet package) -->
|
||||||
|
<Import Project="..\Svrnty.CQRS.Grpc.Generators\build\Svrnty.CQRS.Grpc.Generators.targets" />
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
BIN
Svrnty.CQRS.Grpc/.DS_Store
vendored
Normal file
BIN
Svrnty.CQRS.Grpc/.DS_Store
vendored
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user