Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ jobs:
fail-fast: false
matrix:
configuration: [CSharp, VisualBasic]
project: [Analyzers, CodeFixers, Extensions, HighPerformance, SourceGenerators, DynamicCast, Swagger]
project: [Analyzers, CodeFixers, Extensions, HighPerformance, SourceGenerators, DynamicCast, Kiota]
exclude:
- configuration: VisualBasic
project: DynamicCast
- configuration: VisualBasic
project: Swagger
project: Kiota

env:
PROJECT: ${{ matrix.project }}
Expand Down
4 changes: 2 additions & 2 deletions CompilerPlatform.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<BuildType Project="CSharp" />
<Build Solution="VisualBasic|*" Project="false" />
</Project>
<Project Path="src/features/Riverside.CompilerPlatform.Features.Swagger/Riverside.CompilerPlatform.Features.Swagger.csproj" Id="ba9bb2cd-6a93-4e05-9e46-4917360831a2">
<Project Path="src/features/Riverside.CompilerPlatform.Features.Kiota/Riverside.CompilerPlatform.Features.Kiota.csproj">
<BuildType Project="CSharp" />
<Build Solution="VisualBasic|*" Project="false" />
</Project>
Expand All @@ -26,7 +26,7 @@
<BuildType Project="CSharp" />
<Build Solution="VisualBasic|*" Project="false" />
</Project>
<Project Path="tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj" Id="198c5333-faf0-4f00-b4ed-f714bb5309c9" />
<Project Path="tests/Riverside.CompilerPlatform.Features.Tests/Riverside.CompilerPlatform.Features.Tests.csproj" />
<Project Path="tests/Riverside.CompilerPlatform.VisualBasic.Tests/Riverside.CompilerPlatform.VisualBasic.Tests.vbproj">
<BuildType Project="VisualBasic" />
<Build Solution="CSharp|*" Project="false" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace Riverside.CompilerPlatform.Features.Swagger;
namespace Riverside.CompilerPlatform.Features.Kiota;

partial class KiotaEngine
{
public enum Accessibility

Check warning on line 5 in src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs

View workflow job for this annotation

GitHub Actions / build (CSharp, Kiota)

Missing XML comment for publicly visible type or member 'KiotaEngine.Accessibility'

Check warning on line 5 in src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs

View workflow job for this annotation

GitHub Actions / build (CSharp, Kiota)

Missing XML comment for publicly visible type or member 'KiotaEngine.Accessibility'
{
Internal,

Check warning on line 7 in src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs

View workflow job for this annotation

GitHub Actions / build (CSharp, Kiota)

Missing XML comment for publicly visible type or member 'KiotaEngine.Accessibility.Internal'

Check warning on line 7 in src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs

View workflow job for this annotation

GitHub Actions / build (CSharp, Kiota)

Missing XML comment for publicly visible type or member 'KiotaEngine.Accessibility.Internal'
Private,

Check warning on line 8 in src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs

View workflow job for this annotation

GitHub Actions / build (CSharp, Kiota)

Missing XML comment for publicly visible type or member 'KiotaEngine.Accessibility.Private'

Check warning on line 8 in src/features/Riverside.CompilerPlatform.Features.Kiota/KiotaEngine.Enums.cs

View workflow job for this annotation

GitHub Actions / build (CSharp, Kiota)

Missing XML comment for publicly visible type or member 'KiotaEngine.Accessibility.Private'
Protected,
Public,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Text;
using Riverside.CompilerPlatform.Helpers;
using System.Text;

namespace Riverside.CompilerPlatform.Features.Swagger;
namespace Riverside.CompilerPlatform.Features.Kiota;

partial class KiotaEngine
{
Expand All @@ -13,19 +14,19 @@ partial class KiotaEngine
/// </returns>
public override string ToString()
{
var command = new StringBuilder().Append("kiota generate");
var command = new StringBuilder().Append("generate");

if (!string.IsNullOrWhiteSpace(Path))
{
command.Append($" --openapi {Path}");
command.Append($" --openapi {SanitizationHelpers.EscapeArg(Path!)}");
}
if (!string.IsNullOrWhiteSpace(Manifest))
{
command.Append($" --manifest {Manifest}");
command.Append($" --manifest {SanitizationHelpers.EscapeArg(Manifest!)}");
}
if (!string.IsNullOrWhiteSpace(Output))
{
command.Append($" --output {Output}");
command.Append($" --output {SanitizationHelpers.EscapeArg(Output!)}");
}
command.Append($" --language {Language}");
if (!string.IsNullOrWhiteSpace(ClassName))
Expand Down Expand Up @@ -56,68 +57,68 @@ public override string ToString()
{
command.Append($" --additional-data {AdditionalData}");
}
if (Serializer is not null)
if (Serializer is not null && Serializer.Length > 0)
{
var serializers = new StringBuilder().Append(" --serializer ");
foreach (var serializer in Serializer)
{
serializers.Append(serializer + "|");
}
serializers.Remove(serializers.Length, 1); // remove final '|' char
serializers.Remove(serializers.Length - 1, 1); // remove final '|' char
command.Append(serializers.ToString());
}
if (Deserializer is not null)
if (Deserializer is not null && Deserializer.Length > 0)
{
var deserializers = new StringBuilder().Append(" --deserializer ");
foreach (var deserializer in Deserializer)
{
deserializers.Append(deserializer + "|");
}
deserializers.Remove(deserializers.Length, 1); // remove final '|' char
deserializers.Remove(deserializers.Length - 1, 1); // remove final '|' char
command.Append(deserializers.ToString());
}
if (CleanOutput is not null)
{
command.Append($" --clean-output {CleanOutput}");
}
if (StructuredMimeTypes is not null)
if (StructuredMimeTypes is not null && StructuredMimeTypes.Length > 0)
{
var structuredMimeTypes = new StringBuilder().Append(" --structured-mime-types ");
foreach (var structuredMimeType in StructuredMimeTypes)
{
structuredMimeTypes.Append(structuredMimeType + "|");
}
structuredMimeTypes.Remove(structuredMimeTypes.Length, 1); // remove final '|' char
structuredMimeTypes.Remove(structuredMimeTypes.Length - 1, 1); // remove final '|' char
command.Append(structuredMimeTypes.ToString());
}
if (IncludePath is not null)
if (IncludePath is not null && IncludePath.Length > 0)
{
var includePaths = new StringBuilder().Append(" --include-path ");
foreach (var includePath in IncludePath)
{
includePaths.Append(includePath + "|");
}
includePaths.Remove(includePaths.Length, 1); // remove final '|' char
includePaths.Remove(includePaths.Length - 1, 1); // remove final '|' char
command.Append(includePaths.ToString());
}
if (ExcludePath is not null)
if (ExcludePath is not null && ExcludePath.Length > 0)
{
var excludePaths = new StringBuilder().Append(" --exclude-path ");
foreach (var excludePath in ExcludePath)
{
excludePaths.Append(excludePath + "|");
}
excludePaths.Remove(excludePaths.Length, 1); // remove final '|' char
excludePaths.Remove(excludePaths.Length - 1, 1); // remove final '|' char
command.Append(excludePaths.ToString());
}
if (DisableValidationRules is not null)
if (DisableValidationRules is not null && DisableValidationRules.Length > 0)
{
var disableValidationRules = new StringBuilder().Append(" --disable-validation-rules ");
foreach (var disableValidationRule in DisableValidationRules)
{
disableValidationRules.Append(disableValidationRule + "|");
}
disableValidationRules.Remove(disableValidationRules.Length, 1); // remove final '|' char
disableValidationRules.Remove(disableValidationRules.Length - 1, 1); // remove final '|' char
command.Append(disableValidationRules.ToString());
}
if (ClearCache is not null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Text;
using System.Diagnostics.CodeAnalysis;

namespace Riverside.CompilerPlatform.Features.Swagger;
namespace Riverside.CompilerPlatform.Features.Kiota;

/// <summary>
/// Represents the configuration and options for generating code using the Kiota engine.
Expand Down Expand Up @@ -128,6 +128,7 @@ public partial class KiotaEngine
/// <param name="l">The target programming language for code generation.</param>
/// <param name="c">The name of the root class to be generated. Can be null to use a default class name.</param>
/// <param name="tam">The access modifier to apply to generated types. Can be null to use the default accessibility.</param>
/// <param name="n">The namespace for the generated client class. Can be null to use the default namespace.</param>
/// <param name="ll">The log level to use for diagnostic output during generation. Can be null to use the default log level.</param>
/// <param name="b">Indicates whether to use a backing store for generated models. If null, the default behavior is used.</param>
/// <param name="ebc">Indicates whether to exclude backward compatible code from the output. If null, the default behavior is used.</param>
Expand All @@ -141,14 +142,16 @@ public partial class KiotaEngine
/// <param name="dvr">An array of validation rules to disable during generation. Can be null to enable all rules.</param>
/// <param name="cc">Indicates whether to clear the internal cache before generation. If null, the default behavior is used.</param>
/// <param name="dsv">Indicates whether to disable SSL validation for network operations. If null, the default behavior is used.</param>
public KiotaEngine(string? d, string? a, string? o, GenerationLanguage l, string? c, Accessibility? tam, ConsoleLogLevel? ll, bool? b, bool? ebc, bool? ad, string[]? s, string[]? ds, bool? co, string[]? m, string[]? i, string[]? e, ValidationRules[]? dvr, bool? cc, bool? dsv)
[SetsRequiredMembers]
public KiotaEngine(string? d, string? a, string? o, GenerationLanguage l, string? c, Accessibility? tam, string? n, ConsoleLogLevel? ll, bool? b, bool? ebc, bool? ad, string[]? s, string[]? ds, bool? co, string[]? m, string[]? i, string[]? e, ValidationRules[]? dvr, bool? cc, bool? dsv)
{
Path = d;
Manifest = a;
Output = o;
Language = l;
ClassName = c;
TypeAccessModifier = tam;
NamespaceName = n;
LogLevel = ll;
BackingStore = b;
ExcludeBackwardCompatible = ebc;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
using Riverside.CompilerPlatform.SourceGenerators;
using Riverside.CompilerPlatform.Extensions;
using Riverside.CompilerPlatform.Helpers;
using System;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;

namespace Riverside.CompilerPlatform.Features.Kiota;

/// <summary>
/// Generates source code from OpenAPI specification files as part of the build process.
/// </summary>
[Generator]
public partial class KiotaGenerator : IncrementalGenerator
{
private const string VersionProperty = "build_property.Kiota_Version";
private const string LanguageProperty = "build_property.KiotaGenerator_Language";
private const string ClassNameProperty = "build_property.KiotaGenerator_ClassName";
private const string NamespaceNameProperty = "build_property.KiotaGenerator_NamespaceName";
private const string TypeAccessModifierProperty = "build_property.KiotaGenerator_TypeAccessModifier";
private const string LogLevelProperty = "build_property.KiotaGenerator_LogLevel";
private const string BackingStoreProperty = "build_property.KiotaGenerator_BackingStore";
private const string ExcludeBackwardCompatibleProperty = "build_property.KiotaGenerator_ExcludeBackwardCompatible";
private const string AdditionalDataProperty = "build_property.KiotaGenerator_AdditionalData";
private const string SerializerProperty = "build_property.KiotaGenerator_Serializer";
private const string DeserializerProperty = "build_property.KiotaGenerator_Deserializer";
private const string CleanOutputProperty = "build_property.KiotaGenerator_CleanOutput";
private const string StructuredMimeTypesProperty = "build_property.KiotaGenerator_StructuredMimeTypes";
private const string IncludePathProperty = "build_property.KiotaGenerator_IncludePath";
private const string ExcludePathProperty = "build_property.KiotaGenerator_ExcludePath";
private const string DisableValidationRulesProperty = "build_property.KiotaGenerator_DisableValidationRules";
private const string ClearCacheProperty = "build_property.KiotaGenerator_ClearCache";
private const string DisableSSLValidationProperty = "build_property.KiotaGenerator_DisableSSLValidation";

private static readonly string ToolDirectory = Path.Combine(
Path.GetTempPath(), "Roslyn", "Advanced Compiler Services for .NET", "KiotaGenerator");

/// <inheritdoc/>
protected override void OnBeforeGeneration(GeneratorContext context, CancellationToken cancellationToken)
{
var options = context.AnalyzerConfigOptions.GlobalOptions;

var specs = context.AdditionalTexts
.Where(at => at.Path.EndsWith(".yaml", StringComparison.OrdinalIgnoreCase)
|| at.Path.EndsWith(".yml", StringComparison.OrdinalIgnoreCase)
|| at.Path.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
.ToImmutableArray();

if (specs.IsEmpty)
return;

var version = options.GetString(VersionProperty);

// Kiota engine args
var language = options.GetNullableEnum<KiotaEngine.GenerationLanguage>(LanguageProperty)
?? KiotaEngine.GenerationLanguage.CSharp;
var className = options.GetString(ClassNameProperty);
var namespaceName = options.GetString(NamespaceNameProperty);
var typeAccessModifier = options.GetNullableEnum<KiotaEngine.Accessibility>(TypeAccessModifierProperty);
var logLevel = options.GetNullableEnum<KiotaEngine.ConsoleLogLevel>(LogLevelProperty);
var backingStore = options.GetNullableBool(BackingStoreProperty);
var excludeBackwardCompatible = options.GetNullableBool(ExcludeBackwardCompatibleProperty);
var additionalData = options.GetNullableBool(AdditionalDataProperty);
var serializers = options.GetPipeSeparatedArray(SerializerProperty);
var deserializers = options.GetPipeSeparatedArray(DeserializerProperty);
var cleanOutput = options.GetNullableBool(CleanOutputProperty);
var structuredMimeTypes = options.GetPipeSeparatedArray(StructuredMimeTypesProperty);
var includePaths = options.GetPipeSeparatedArray(IncludePathProperty);
var excludePaths = options.GetPipeSeparatedArray(ExcludePathProperty);
var disableValidationRules = options.GetPipeSeparatedEnumArray<KiotaEngine.ValidationRules>(DisableValidationRulesProperty);
var clearCache = options.GetNullableBool(ClearCacheProperty);
var disableSSLValidation = options.GetNullableBool(DisableSSLValidationProperty);

string toolExecutable;
try
{
var (installed, installError) = NETCoreToolHelpers
.EnsureToolAsync("Microsoft.OpenApi.Kiota", ToolDirectory, version)
.GetAwaiter().GetResult();

if (!installed)
{
CreateDiagnostic(
"KG0000",
"Microsoft Kiota installation failed",
installError ?? "Failed to install or locate the Microsoft Kiota tool.").Report(context);
return;
}

toolExecutable = NETCoreToolHelpers.GetExecutablePath(ToolDirectory, "kiota");
}
catch (Exception ex)
{
CreateDiagnostic("KG0000", "Microsoft Kiota installation failed", ex.Message).Report(context);
return;
}

foreach (var spec in specs)
{
cancellationToken.ThrowIfCancellationRequested();

var specPath = spec.Path;
if (!File.Exists(specPath))
continue;

var specFileName = Path.GetFileNameWithoutExtension(specPath);
var effectiveNamespace = namespaceName ?? SanitizationHelpers.Sanitize(specFileName);

var tempOut = DirectoryHelpers.CreateTemporary(
Path.Combine(Path.GetTempPath(), "Roslyn", "Advanced Compiler Services for .NET"));

try
{
var engine = new KiotaEngine(
d: specPath,
a: null,
o: tempOut,
l: language,
c: className,
n: effectiveNamespace,
tam: typeAccessModifier,
ll: logLevel,
b: backingStore,
ebc: excludeBackwardCompatible,
ad: additionalData,
s: serializers,
ds: deserializers,
co: cleanOutput,
m: structuredMimeTypes,
i: includePaths,
e: excludePaths,
dvr: disableValidationRules,
cc: clearCache,
dsv: disableSSLValidation);

var runResult = ProcessHelpers
.RunProcess(toolExecutable, engine.ToString(), TimeSpan.FromMinutes(2))
.GetAwaiter().GetResult();

if (runResult.ExitCode != 0)
{
CreateDiagnostic(
"KG0001",
"OpenAPI generation failed",
$"Microsoft Kiota failed for spec '{specPath}' with exit code {runResult.ExitCode}: {runResult.StandardError.ReplaceLineEndings(" ")}").Report(context);
DirectoryHelpers.TryDelete(tempOut);
continue;
}

var csFiles = Directory.EnumerateFiles(tempOut, "*.cs", SearchOption.AllDirectories).ToArray();
if (csFiles.Length == 0)
{
CreateDiagnostic(
"KG0002",
"No C# files generated",
$"Microsoft Kiota produced no C# files for spec '{specPath}'").Report(context);
DirectoryHelpers.TryDelete(tempOut);
continue;
}

foreach (var cs in csFiles)
{
try
{
var content = File.ReadAllText(cs, Encoding.UTF8);
var rel = Path.GetRelativePath(tempOut, cs)
.Replace(Path.DirectorySeparatorChar, '.')
.Replace(Path.AltDirectorySeparatorChar, '.');
var hintName = $"{SanitizationHelpers.Sanitize(engine.NamespaceName!)}.{SanitizationHelpers.Sanitize(rel)}";
AddSource(hintName, content);
}
catch (Exception ex)
{
CreateDiagnostic(
"KG0003",
"Failed to add generated file",
$"Failed to add '{cs}': {ex.Message}").Report(context);
}
}

DirectoryHelpers.TryDelete(tempOut);
}
catch (Exception ex)
{
CreateDiagnostic("KG9999", "OpenAPI generator exception", ex.ToString()).Report(context);
DirectoryHelpers.TryDelete(tempOut);
}
}
}
}
Loading
Loading