feat: add Godot C# compatibility and shared codegen weaving
Validate ShrinkSDK Workspace / unity (push) Failing after 3m18s

This commit is contained in:
2026-09-05 02:37:43 +08:00
parent 60cf457230
commit 88ba3cdc48
95 changed files with 2712 additions and 19 deletions
@@ -0,0 +1,9 @@
## Release 0.1.0
### New Rules
Rule ID | Category | Severity | Notes
--------|----------|----------|-------
SHRINK001 | ShrinkSDK.CodeGen | Error | Invalid event subscriber signature
SHRINK002 | ShrinkSDK.CodeGen | Error | Duplicate network opcode and route
SHRINK003 | ShrinkSDK.CodeGen | Error | Invalid application installer
@@ -0,0 +1,75 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
namespace ShrinkSDK.CodeGen.Analyzers;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class ShrinkCodeGenAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor InvalidEventHandler = new(
"SHRINK001", "Invalid event subscriber signature",
"Method '{0}' must use void(TEvent), UniTask(TEvent), or UniTask(TEvent, CancellationToken)",
"ShrinkSDK.CodeGen", DiagnosticSeverity.Error, true);
private static readonly DiagnosticDescriptor DuplicateNetworkContract = new(
"SHRINK002", "Duplicate network opcode and route",
"Network opcode/route '{0}' is already declared by '{1}'",
"ShrinkSDK.CodeGen", DiagnosticSeverity.Error, true);
private static readonly DiagnosticDescriptor InvalidInstaller = new(
"SHRINK003", "Invalid application installer",
"Type '{0}' has ShrinkAppModuleInstaller but does not implement IShrinkAppModuleInstaller",
"ShrinkSDK.CodeGen", DiagnosticSeverity.Error, true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
ImmutableArray.Create(InvalidEventHandler, DuplicateNetworkContract, InvalidInstaller);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterCompilationStartAction(start =>
{
var networkContracts = new ConcurrentDictionary<string, INamedTypeSymbol>(StringComparer.Ordinal);
start.RegisterSymbolAction(symbolContext => AnalyzeMethod(symbolContext), SymbolKind.Method);
start.RegisterSymbolAction(symbolContext => AnalyzeType(symbolContext, networkContracts), SymbolKind.NamedType);
});
}
private static void AnalyzeMethod(SymbolAnalysisContext context)
{
var method = (IMethodSymbol)context.Symbol;
if (!HasAttribute(method, "ShrinkEventBus.ShrinkSubscribeAttribute")) return;
var parametersValid = method.Parameters.Length == 1 ||
method.Parameters.Length == 2 && method.Parameters[1].Type.ToDisplayString() == "System.Threading.CancellationToken";
var returnName = method.ReturnType.ToDisplayString();
var returnValid = method.Parameters.Length == 1 && (method.ReturnsVoid || returnName == "Cysharp.Threading.Tasks.UniTask") ||
method.Parameters.Length == 2 && returnName == "Cysharp.Threading.Tasks.UniTask";
if (!parametersValid || !returnValid)
context.ReportDiagnostic(Diagnostic.Create(InvalidEventHandler, method.Locations.FirstOrDefault(), method.Name));
}
private static void AnalyzeType(SymbolAnalysisContext context, ConcurrentDictionary<string, INamedTypeSymbol> networkContracts)
{
var type = (INamedTypeSymbol)context.Symbol;
if (HasAttribute(type, "ShrinkApp.ShrinkAppModuleInstallerAttribute") &&
!type.AllInterfaces.Any(item => item.ToDisplayString() == "ShrinkApp.IShrinkAppModuleInstaller"))
context.ReportDiagnostic(Diagnostic.Create(InvalidInstaller, type.Locations.FirstOrDefault(), type.Name));
var network = type.GetAttributes().FirstOrDefault(attribute =>
attribute.AttributeClass?.ToDisplayString() == "ShrinkNetwork.ShrinkNetworkMessageAttribute");
if (network == null || network.ConstructorArguments.Length == 0) return;
var opcode = network.ConstructorArguments[0].Value?.ToString() ?? string.Empty;
var route = network.ConstructorArguments.Length > 1 ? network.ConstructorArguments[1].Value as string ?? string.Empty : string.Empty;
var key = opcode + ":" + route;
if (!networkContracts.TryAdd(key, type) && networkContracts.TryGetValue(key, out var previous))
context.ReportDiagnostic(Diagnostic.Create(DuplicateNetworkContract, type.Locations.FirstOrDefault(), key, previous.Name));
}
private static bool HasAttribute(ISymbol symbol, string fullName) =>
symbol.GetAttributes().Any(attribute => attribute.AttributeClass?.ToDisplayString() == fullName);
}
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>ShrinkSDK.CodeGen.Analyzers</AssemblyName>
<RootNamespace>ShrinkSDK.CodeGen.Analyzers</RootNamespace>
<IsPackable>false</IsPackable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" />
</ItemGroup>
</Project>
@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.CodeGen.Core</AssemblyName>
<RootNamespace>ShrinkSDK.CodeGen</RootNamespace>
<PackageId>ShrinkSDK.CodeGen.Core</PackageId>
<Version>0.1.0</Version>
<IsPackable>true</IsPackable>
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Assets\Modules\ShrinkShared.CodeGen\Editor\Core\*.cs" />
<PackageReference Include="Mono.Cecil" Version="0.11.6" />
</ItemGroup>
</Project>
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<AssemblyName>ShrinkSDK.CodeGen.Task</AssemblyName>
<RootNamespace>ShrinkSDK.CodeGen</RootNamespace>
<PackageId>ShrinkSDK.CodeGen</PackageId>
<Version>0.1.0</Version>
<Description>MSBuild integration for ShrinkSDK Cecil weaving.</Description>
<IsPackable>true</IsPackable>
<BuildOutputTargetFolder>tools</BuildOutputTargetFolder>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);IncludeShrinkCodeGenDependencies</TargetsForTfmSpecificBuildOutput>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\ShrinkSDK.CodeGen.Core\ShrinkSDK.CodeGen.Core.csproj" />
<ProjectReference Include="..\ShrinkSDK.CodeGen.Analyzers\ShrinkSDK.CodeGen.Analyzers.csproj" ReferenceOutputAssembly="false" />
<PackageReference Include="Mono.Cecil" Version="0.11.6" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Framework" Version="17.14.28" PrivateAssets="all" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.14.28" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Include="buildTransitive\ShrinkSDK.CodeGen.props" Pack="true" PackagePath="buildTransitive\ShrinkSDK.CodeGen.props" />
<None Include="buildTransitive\ShrinkSDK.CodeGen.targets" Pack="true" PackagePath="buildTransitive\ShrinkSDK.CodeGen.targets" />
<None Include="..\ShrinkSDK.CodeGen.Analyzers\bin\$(Configuration)\netstandard2.0\ShrinkSDK.CodeGen.Analyzers.dll"
Pack="true" PackagePath="analyzers\dotnet\cs\ShrinkSDK.CodeGen.Analyzers.dll" />
</ItemGroup>
<Target Name="IncludeShrinkCodeGenDependencies" DependsOnTargets="Build">
<ItemGroup>
<BuildOutputInPackage Include="$(OutputPath)ShrinkSDK.CodeGen.Core.dll" />
<BuildOutputInPackage Include="$(OutputPath)Mono.Cecil.dll" />
</ItemGroup>
</Target>
</Project>
@@ -0,0 +1,98 @@
#nullable enable
using System;
using System.IO;
using System.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace ShrinkSDK.CodeGen;
public sealed class ShrinkWeaveTask : Task
{
[Required]
public string AssemblyPath { get; set; } = string.Empty;
public string? PdbPath { get; set; }
public ITaskItem[] ReferencePaths { get; set; } = Array.Empty<ITaskItem>();
public string? StrongNameKeyFile { get; set; }
[Output] public bool Changed { get; private set; }
[Output] public int InstanceSubscriberCount { get; private set; }
[Output] public int StaticSubscriberCount { get; private set; }
[Output] public int RegistryEntryCount { get; private set; }
public override bool Execute()
{
if (!File.Exists(AssemblyPath))
{
Log.LogError($"[ShrinkSDK.CodeGen] Intermediate assembly does not exist: {AssemblyPath}");
return false;
}
var assemblyDirectory = Path.GetDirectoryName(Path.GetFullPath(AssemblyPath))!;
var temporaryAssembly = Path.Combine(assemblyDirectory, $".{Path.GetFileName(AssemblyPath)}.{Guid.NewGuid():N}.tmp");
var hasPdb = !string.IsNullOrWhiteSpace(PdbPath) && File.Exists(PdbPath);
var temporaryPdb = hasPdb
? Path.Combine(assemblyDirectory, $".{Path.GetFileName(PdbPath)}.{Guid.NewGuid():N}.tmp")
: null;
try
{
var result = ShrinkAssemblyWeaver.Weave(
AssemblyPath,
hasPdb ? PdbPath : null,
ReferencePaths.Select(item => item.ItemSpec),
temporaryAssembly,
temporaryPdb,
ShrinkCodeGenPlatform.EngineNeutral,
StrongNameKeyFile);
foreach (var diagnostic in result.Diagnostics)
{
switch (diagnostic.Severity)
{
case ShrinkCodeGenDiagnosticSeverity.Warning:
Log.LogWarning($"[ShrinkSDK.CodeGen] {diagnostic.Message}");
break;
case ShrinkCodeGenDiagnosticSeverity.Error:
Log.LogError($"[ShrinkSDK.CodeGen] {diagnostic.Message}");
break;
default:
Log.LogMessage(MessageImportance.Low, $"[ShrinkSDK.CodeGen] {diagnostic.Message}");
break;
}
}
if (!result.Succeeded)
return false;
Changed = result.Changed;
InstanceSubscriberCount = result.InstanceSubscribers;
StaticSubscriberCount = result.StaticSubscribers;
RegistryEntryCount = result.RegistryEntries;
if (Changed)
{
File.Move(temporaryAssembly, AssemblyPath, true);
if (hasPdb && temporaryPdb != null)
File.Move(temporaryPdb, PdbPath!, true);
}
Log.LogMessage(MessageImportance.High,
$"[ShrinkSDK.CodeGen] {Path.GetFileName(AssemblyPath)} woven: instance={InstanceSubscriberCount}, static={StaticSubscriberCount}, registry={RegistryEntryCount}.");
return true;
}
catch (Exception exception)
{
Log.LogErrorFromException(exception, true);
return false;
}
finally
{
TryDelete(temporaryAssembly);
if (temporaryPdb != null) TryDelete(temporaryPdb);
}
}
private static void TryDelete(string path)
{
try { if (File.Exists(path)) File.Delete(path); }
catch { }
}
}
@@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<ShrinkCodeGenEnabled Condition="'$(ShrinkCodeGenEnabled)' == ''">true</ShrinkCodeGenEnabled>
</PropertyGroup>
</Project>
@@ -0,0 +1,18 @@
<Project>
<PropertyGroup>
<_ShrinkCodeGenTaskAssembly Condition="'$(_ShrinkCodeGenTaskAssembly)' == ''">$(MSBuildThisFileDirectory)..\tools\net8.0\ShrinkSDK.CodeGen.Task.dll</_ShrinkCodeGenTaskAssembly>
</PropertyGroup>
<UsingTask TaskName="ShrinkSDK.CodeGen.ShrinkWeaveTask" AssemblyFile="$(_ShrinkCodeGenTaskAssembly)" />
<Target Name="ShrinkCodeGenWeave"
AfterTargets="CoreCompile"
BeforeTargets="CopyFilesToOutputDirectory"
Condition="'$(ShrinkCodeGenEnabled)' == 'true' and '$(DesignTimeBuild)' != 'true'">
<PropertyGroup>
<_ShrinkCodeGenIntermediateAssembly>$(IntermediateOutputPath)$(TargetName).dll</_ShrinkCodeGenIntermediateAssembly>
</PropertyGroup>
<ShrinkWeaveTask AssemblyPath="$(_ShrinkCodeGenIntermediateAssembly)"
PdbPath="$(IntermediateOutputPath)$(TargetName).pdb"
ReferencePaths="@(ReferencePath)"
StrongNameKeyFile="$(ShrinkCodeGenStrongNameKeyFile)" />
</Target>
</Project>