Skip to content

Commit 4d1edfb

Browse files
Add patching for Unity.SourceGenerators
1 parent 8b9922c commit 4d1edfb

File tree

2 files changed

+115
-14
lines changed

2 files changed

+115
-14
lines changed

src/ModuleDefinitionExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,20 @@ public static class ModuleDefinitionExtensions
1818

1919
return null;
2020
}
21+
22+
public static AssemblyReference? GetAssemblyReferenceByName(this ModuleDefinition module, string name)
23+
{
24+
ArgumentNullException.ThrowIfNull(module);
25+
ArgumentNullException.ThrowIfNull(name);
26+
27+
foreach (AssemblyReference reference in module.AssemblyReferences)
28+
{
29+
if (reference.Name == name)
30+
{
31+
return reference;
32+
}
33+
}
34+
35+
return null;
36+
}
2137
}

src/Program.cs

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using UnityRoslynUpdater;
33
using AsmResolver.DotNet;
44
using AsmResolver.PE.DotNet.Cil;
5+
using AsmResolver.DotNet.Signatures.Types;
6+
using AsmResolver.DotNet.Signatures;
57

68
if (args.Length < 1)
79
{
@@ -42,15 +44,8 @@ you wish to link to a newer .NET SDK version.
4244
// This approach is preferred because it does not require modifying anything else, all of
4345
// the IDE support packages should automatically pick up this change without further edits.
4446
//
45-
var corePath = Path.Combine(dataPath, "Managed", "UnityEngine", "UnityEditor.CoreModule.dll");
46-
var assembly = AssemblyDefinition.FromFile(corePath);
4747

48-
// We're going to find a call to set_LanguageVersion in the default constructor, and then
49-
// change the value it passes in to whatever the latest C# language version is at this time.
50-
var target = assembly.Modules[0].GetTypeByFullName("UnityEditor.Compilation.ScriptCompilerOptions");
51-
var version = sdk.ComputeLatestCSharpLangVersion();
52-
53-
if (target is null || !TryPatchLangVersion(target, version))
48+
if (!TryPatchCompilerOptions())
5449
{
5550
Console.Error.WriteLine(
5651
"""
@@ -62,10 +57,24 @@ the changes applied by this patch. Aborting process...
6257
return;
6358
}
6459

65-
bool TryPatchLangVersion(TypeDefinition type, string version)
60+
bool TryPatchCompilerOptions()
6661
{
67-
var method = type.GetDefaultConstructor();
68-
var setter = type.GetMethodByName("set_LanguageVersion");
62+
var dllPath = Path.Combine(dataPath, "Managed", "UnityEngine", "UnityEditor.CoreModule.dll");
63+
64+
if (!File.Exists(dllPath))
65+
return false;
66+
67+
// We're going to find a call to set_LanguageVersion in the default constructor, and then
68+
// change the value it passes in to whatever the latest C# language version is at this time.
69+
var assembly = AssemblyDefinition.FromFile(dllPath);
70+
var target = assembly.Modules[0].GetTypeByFullName("UnityEditor.Compilation.ScriptCompilerOptions");
71+
var version = sdk.ComputeLatestCSharpLangVersion();
72+
73+
if (target is null)
74+
return false;
75+
76+
var method = target.GetDefaultConstructor();
77+
var setter = target.GetMethodByName("set_LanguageVersion");
6978

7079
if (method is null || setter is null)
7180
return false;
@@ -79,13 +88,92 @@ bool TryPatchLangVersion(TypeDefinition type, string version)
7988
{
8089
Debug.Assert(instructions[i - 1].OpCode == CilOpCodes.Ldstr);
8190
instructions[i - 1].Operand = version;
91+
92+
// Instruction has been patched, now save our changes and move on.
93+
assembly.Write(dllPath);
94+
Console.WriteLine($"Updated language version to {version}.");
8295
return true;
8396
}
8497
}
8598

8699
return false;
87100
}
88101

102+
//
103+
// Starting with Unity 2022, Unity.SourceGenerators.dll is used by Unity to process
104+
// .cs files and discover MonoBehaviour implementations in them. However, currently
105+
// it uses the NamespaceDeclarationSyntax class, not BaseNamespaceDeclarationSyntax
106+
// which causes it to fail on FileScopedNamespaceDeclarationSyntax.
107+
//
108+
// We patch the TypeNameHelper.GetTypeInformation function to replace all references
109+
// to NamespaceDeclarationSyntax with BaseNamespaceDeclarationSyntax, which fixes it.
110+
//
111+
112+
TryPatchSourceGenerator(Path.Combine(dataPath, "Tools", "Unity.SourceGenerators", "Unity.SourceGenerators.dll"));
113+
TryPatchSourceGenerator(Path.Combine(dataPath, "Tools", "Compilation", "Unity.SourceGenerators", "Unity.SourceGenerators.dll"));
114+
115+
bool TryPatchSourceGenerator(string dllPath)
116+
{
117+
const string NamespaceDeclarationSyntax = "Microsoft.CodeAnalysis.CSharp.Syntax.NamespaceDeclarationSyntax";
118+
119+
if (!File.Exists(dllPath))
120+
return false;
121+
122+
var assembly = AssemblyDefinition.FromFile(dllPath);
123+
var module = assembly.Modules[0];
124+
var target = module.GetTypeByFullName("Unity.MonoScriptGenerator.TypeNameHelper");
125+
var method = target?.GetMethodByName("GetTypeInformation");
126+
var patches = 0;
127+
128+
if (target is null || method is null)
129+
return false;
130+
131+
if (method.CilMethodBody is not { Instructions: var instructions } body)
132+
return false;
133+
134+
var baseNamespaceDeclarationSyntax = module.DefaultImporter.ImportType(
135+
new TypeReference(
136+
module.GetAssemblyReferenceByName("Microsoft.CodeAnalysis.CSharp"),
137+
"Microsoft.CodeAnalysis.CSharp.Syntax",
138+
"BaseNamespaceDeclarationSyntax"));
139+
140+
foreach (var local in body.LocalVariables)
141+
{
142+
if (local.VariableType is TypeDefOrRefSignature { FullName: NamespaceDeclarationSyntax } type)
143+
{
144+
local.VariableType = new TypeDefOrRefSignature(baseNamespaceDeclarationSyntax);
145+
patches++;
146+
}
147+
}
148+
149+
for (int i = 0; i < instructions.Count; i++)
150+
{
151+
if (instructions[i].Operand is TypeReference { FullName: NamespaceDeclarationSyntax } type)
152+
{
153+
instructions[i].Operand = baseNamespaceDeclarationSyntax;
154+
patches++;
155+
}
156+
else if (instructions[i].Operand is MemberReference
157+
{
158+
IsMethod: true,
159+
Parent: TypeReference { FullName: NamespaceDeclarationSyntax } parent
160+
} member)
161+
{
162+
var reference = new MemberReference(baseNamespaceDeclarationSyntax, member.Name, member.Signature as MemberSignature);
163+
instructions[i].Operand = module.DefaultImporter.ImportMethod(reference);
164+
patches++;
165+
}
166+
}
167+
168+
if (patches > 0)
169+
{
170+
assembly.Write(dllPath);
171+
Console.WriteLine($"Patched source generator assembly at {Path.GetRelativePath(editorPath, dllPath)}.");
172+
}
173+
174+
return true;
175+
}
176+
89177
//
90178
// We need to redirect two important directories:
91179
// * Editor/Data/NetCoreRuntime
@@ -117,9 +205,6 @@ void ProcessBuiltInSdkDirectory(string path)
117205
Directory.Delete(path, recursive: false);
118206
}
119207

120-
assembly.Write(corePath);
121-
Console.WriteLine($"Updated language version to {version}.");
122-
123208
Directory.CreateSymbolicLink(Path.Combine(dataPath, "NetCoreRuntime"), DotNetInstallation.Current.Location);
124209
Directory.CreateSymbolicLink(Path.Combine(dataPath, "DotNetSdkRoslyn"), sdk.RoslynLocation);
125210
Console.WriteLine($"Linked to .NET SDK at {sdk.Location}");

0 commit comments

Comments
 (0)