Skip to content

Commit 40f4d57

Browse files
committed
feat: support Unity 2021.1 or later
1 parent fb5ac33 commit 40f4d57

File tree

5 files changed

+234
-0
lines changed

5 files changed

+234
-0
lines changed

Plugins/CSharpCompilerSettings/Core.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ private static bool IsDevelopAssembly
2525

2626
private static readonly ICustomCompiler[] customCompilers = new ICustomCompiler[]
2727
{
28+
CustomCompiler_2021.instance,
2829
CustomCompiler_Legacy.instance,
2930
};
3031

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
using System;
2+
using System.Collections;
3+
using System.Diagnostics;
4+
using System.IO;
5+
using System.Linq;
6+
using UnityEngine;
7+
using UnityEditor.Compilation;
8+
using System.Collections.Generic;
9+
using Debug = UnityEngine.Debug;
10+
using UnityEditor;
11+
12+
namespace Coffee.CSharpCompilerSettings
13+
{
14+
internal class CustomCompiler_2021 : ScriptableSingleton<CustomCompiler_2021>, ICustomCompiler
15+
{
16+
private static readonly int logIdentifier = typeof(Core).FullName.GetHashCode();
17+
private static readonly object cscOutputParser = Type.GetType("UnityEditor.Scripting.Compilers.MicrosoftCSharpCompilerOutputParser, UnityEditor").New();
18+
private object[] assemblies;
19+
private string dagName;
20+
[SerializeField] private bool hasCompileError;
21+
[SerializeField] private bool isInitialized;
22+
23+
public bool IsValid()
24+
{
25+
var unityVersions = Application.unityVersion.Split('.');
26+
return 2021 <= int.Parse(unityVersions[0]);
27+
}
28+
29+
public void Dispose()
30+
{
31+
typeof(CompilationPipeline).RemoveEvent<object>("compilationStarted", OnCompilationStarted);
32+
typeof(CompilationPipeline).RemoveEvent<object>("compilationFinished", OnCompilationFinished);
33+
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
34+
CompilationPipeline.assemblyCompilationFinished -= OnAssemblyCompilationFinished;
35+
}
36+
37+
public void Register()
38+
{
39+
typeof(CompilationPipeline).AddEvent<object>("compilationStarted", OnCompilationStarted);
40+
typeof(CompilationPipeline).AddEvent<object>("compilationFinished", OnCompilationFinished);
41+
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
42+
CompilationPipeline.assemblyCompilationFinished += OnAssemblyCompilationFinished;
43+
44+
// Request recompilation at once.
45+
if (!isInitialized)
46+
{
47+
Logger.LogInfo("This is first compilation. Request script compilation again.");
48+
Utils.RequestCompilation();
49+
}
50+
}
51+
52+
public void OnPlayModeStateChanged(PlayModeStateChange state)
53+
{
54+
if (hasCompileError && state == PlayModeStateChange.ExitingEditMode)
55+
{
56+
EditorApplication.isPlaying = false;
57+
typeof(SceneView).Call("ShowCompileErrorNotification");
58+
}
59+
}
60+
61+
public CompilerInfo GetDefaultCompiler()
62+
{
63+
var ext = (Application.platform == RuntimePlatform.WindowsEditor) ? ".exe" : "";
64+
var appContents = EditorApplication.applicationContentsPath;
65+
if (Directory.Exists(Utils.PathCombine(appContents, "DotNetSdkRoslyn")))
66+
{
67+
var dotnet = Utils.PathCombine(appContents, "NetCoreRuntime", "dotnet" + ext);
68+
var csc = Utils.PathCombine(appContents, "DotNetSdkRoslyn", "csc.dll");
69+
return new CompilerInfo("", csc, dotnet);
70+
}
71+
else if (Directory.Exists(Utils.PathCombine(appContents, "Tools", "Roslyn")))
72+
{
73+
var dotnet = Utils.PathCombine(appContents, "Tools", "netcorerun", "netcorerun" + ext);
74+
var csc = Utils.PathCombine(appContents, "Tools", "Roslyn", "csc.dll");
75+
return new CompilerInfo("", csc, dotnet);
76+
}
77+
78+
throw new FileNotFoundException("Default CompilerInfo is not found.");
79+
}
80+
private void DebugRemoveLogEntriesByIdentifier()
81+
{
82+
hasCompileError = false;
83+
typeof(Debug).Call("RemoveLogEntriesByIdentifier", logIdentifier);
84+
}
85+
86+
private void DebugLogCompilerMessage(string outputs, bool hasError)
87+
{
88+
var parsedMessages = cscOutputParser.Call("Parse", outputs.Split('\r', '\n'), hasError) as IList;
89+
foreach (var m in parsedMessages)
90+
{
91+
var isError = (int)m.Get("type") == 0;
92+
typeof(Debug).Call("LogCompilerMessage", m.Get("message"), m.Get("file"), m.Get("line"), m.Get("column"), true, isError, logIdentifier);
93+
}
94+
}
95+
96+
private string GetOutDir(string dagName)
97+
{
98+
if (dagName.EndsWith("E")) return "Library/ScriptAssemblies";
99+
100+
var unityVersions = Application.unityVersion.Split('.');
101+
if (int.Parse(unityVersions[0]) == 2021)
102+
return "Library/PlayerScriptAssemblies";
103+
else
104+
return "Library/Bee/PlayerScriptAssemblies";
105+
}
106+
107+
private void Recompile(object assembly, string dagName)
108+
{
109+
var assemblyName = assembly.Get("name") as string;
110+
var asmdefPath = assembly.Get("asmdef") as string;
111+
112+
// Check.
113+
if (!Core.ShouldToRecompile(assemblyName, asmdefPath)) return;
114+
115+
// Check compiler.
116+
var cscSetting = Core.GetSettings();
117+
var compilerInfo = cscSetting.UseDefaultCompiler
118+
? GetDefaultCompiler()
119+
: CompilerInfo.GetInstalledInfo(cscSetting.CompilerPackage.PackageId);
120+
if (!compilerInfo.IsValid)
121+
{
122+
Logger.LogWarning(" <color=#bbbb44><Skipped> C# compiler '{0}' is not installed. Use default compiler instead.</color>", compilerInfo.Path);
123+
compilerInfo = GetDefaultCompiler();
124+
}
125+
126+
// Modify response file.
127+
var responseFile = $"Library/Bee/artifacts/{dagName}.dag/{assemblyName}.rsp";
128+
var modResponseFile = $"{responseFile}.mod";
129+
var text = File.ReadAllText(responseFile);
130+
text = Core.ModifyResponseFile(cscSetting, assemblyName, asmdefPath, text);
131+
text = text.Replace($"/out:\"Library/Bee/artifacts/{dagName}.dag/", $"/out:\"{GetOutDir(dagName)}/");
132+
File.WriteAllText(modResponseFile, text);
133+
134+
// Setup recompile process.
135+
var psi = new ProcessStartInfo();
136+
compilerInfo.Setup(psi, modResponseFile, Application.platform);
137+
138+
using (var program = Type.GetType("UnityEditor.Utils.Program, UnityEditor").New(psi) as IDisposable)
139+
{
140+
// Start recompile.
141+
Logger.LogDebug("<color=#22aa22><b>Recompile ({0})</b></color> Start command: {1} {2}\n\nResponse file:\n{3}", assemblyName, psi.FileName, psi.Arguments, text);
142+
program.Call("Start");
143+
program.Call("WaitForExit");
144+
145+
// Check outputs.
146+
var outputs = program.Call("GetAllOutput") as string;
147+
var exitCode = (int)program.Get("ExitCode");
148+
hasCompileError |= exitCode != 0;
149+
150+
// Log compiler messages.
151+
program.Call("LogProcessStartInfo");
152+
Logger.LogDebug("<color=#22aa22><b>Recompile ({0})</b></color> Finished with code {1}\n\nOutputs:\n{2}", assemblyName, exitCode, outputs);
153+
154+
DebugLogCompilerMessage(outputs, exitCode != 0);
155+
}
156+
}
157+
158+
private void OnCompilationStarted(object state)
159+
{
160+
DebugRemoveLogEntriesByIdentifier();
161+
162+
// Store assembly infos.
163+
var driver = state.Get("Driver");
164+
dagName = driver.Get("m_DagName") as string;
165+
var mdata = driver.Get("DataForBuildProgram").Get("m_Data") as Dictionary<string, object>;
166+
var cdata = mdata.Values.FirstOrDefault(x => x.GetType().FullName.EndsWith("ScriptCompilationData"));
167+
assemblies = cdata.Get("assemblies") as object[];
168+
}
169+
170+
private void OnAssemblyCompilationFinished(string dllPath, CompilerMessage[] messages)
171+
{
172+
if (messages.Any(x => x.type == CompilerMessageType.Error)) return;
173+
var assemblyName = Path.GetFileNameWithoutExtension(dllPath);
174+
175+
try
176+
{
177+
var assembly = assemblies.First(x => (string)x.Get("name") == assemblyName);
178+
Recompile(assembly, dagName);
179+
}
180+
catch (Exception e)
181+
{
182+
hasCompileError = true;
183+
Debug.LogException(e);
184+
}
185+
}
186+
187+
private void OnCompilationFinished(object state)
188+
{
189+
if (!isInitialized)
190+
{
191+
isInitialized = true;
192+
193+
foreach (var assembly in assemblies)
194+
{
195+
try
196+
{
197+
Recompile(assembly, dagName);
198+
}
199+
catch (Exception e)
200+
{
201+
Debug.LogException(e);
202+
}
203+
}
204+
}
205+
206+
if (Application.isBatchMode && hasCompileError)
207+
{
208+
Console.WriteLine("::compilation error:: exit 1");
209+
Application.Quit(1);
210+
}
211+
}
212+
}
213+
}

Plugins/CSharpCompilerSettings/CustomCompiler_2021.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Plugins/CSharpCompilerSettings/Utils.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Net;
77
using System.Threading;
88
using UnityEditor;
9+
using UnityEditor.Compilation;
910
using UnityEngine;
1011

1112
namespace Coffee.CSharpCompilerSettings
@@ -41,6 +42,13 @@ public static string[] ModifySymbols(IEnumerable<string> defines, string modifyS
4142

4243
public static void RequestCompilation(string assemblyName = null)
4344
{
45+
var unityVersions = Application.unityVersion.Split('.');
46+
if(2021 <= int.Parse(unityVersions[0]))
47+
{
48+
typeof(CompilationPipeline).Call("RequestScriptCompilation");
49+
return;
50+
}
51+
4452
var editorCompilation = Type.GetType("UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface, UnityEditor")
4553
.Get("Instance");
4654

Plugins/Recompiler/CSharpCompilerSettings.rsp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"${PLUGIN_SOURCE}/AssemblyInfo.cs"
1919
"${PLUGIN_SOURCE}/Core.cs"
2020
"${PLUGIN_SOURCE}/CustomCompiler_Legacy.cs"
21+
"${PLUGIN_SOURCE}/CustomCompiler_2021.cs"
2122
"${PLUGIN_SOURCE}/CscSettingsAsset.cs"
2223
"${PLUGIN_SOURCE}/CSharpLanguageVersion.cs"
2324
"${PLUGIN_SOURCE}/ReflectionExtensions.cs"

0 commit comments

Comments
 (0)