Files
ft/Client/Packages/io.github.hatayama.uloopmcp/Editor/Compilation/ExternalCompilerPathResolver.cs

315 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
namespace io.github.hatayama.uLoopMCP
{
internal static class ExternalCompilerPathResolver
{
private const string NetCoreRuntimeDirectoryName = "NetCoreRuntime";
private const string DotNetSdkRoslynDirectoryName = "DotNetSdkRoslyn";
private const string DotNetSdkDirectoryName = "DotNetSdk";
private const string DotNetSdkSdkDirectoryName = "sdk";
private const string RoslynDirectoryName = "Roslyn";
private const string CompilerBincoreDirectoryName = "bincore";
private const string CompilerDllFileName = "csc.dll";
private const string CompilerRuntimeConfigFileName = "csc.runtimeconfig.json";
private const string CompilerDepsFileName = "csc.deps.json";
private const string CodeAnalysisDllFileName = "Microsoft.CodeAnalysis.dll";
private const string CodeAnalysisCSharpDllFileName = "Microsoft.CodeAnalysis.CSharp.dll";
private const string NetCoreRuntimeSharedDirectoryName = "shared";
private const string NetCoreRuntimeSharedFrameworkName = "Microsoft.NETCore.App";
public static ExternalCompilerPaths Resolve()
{
string editorPath = EditorApplication.applicationPath;
if (string.IsNullOrEmpty(editorPath))
{
return null;
}
string contentsPath = ResolveEditorContentsPath(editorPath);
if (string.IsNullOrEmpty(contentsPath))
{
return null;
}
string scriptingRootPath = ResolveScriptingRootPath(contentsPath);
string dotnetHostFileName = UnityEngine.Application.platform == UnityEngine.RuntimePlatform.WindowsEditor
? "dotnet.exe"
: "dotnet";
string effectiveScriptingRootPath = scriptingRootPath ?? contentsPath;
string compilerDirectoryPath = ResolveCompilerDirectoryPath(effectiveScriptingRootPath);
List<string> missingComponents = new List<string>();
if (string.IsNullOrEmpty(scriptingRootPath))
{
missingComponents.Add(Path.Combine(contentsPath, NetCoreRuntimeDirectoryName));
missingComponents.Add(Path.Combine(contentsPath, DotNetSdkRoslynDirectoryName));
missingComponents.Add(Path.Combine(contentsPath, DotNetSdkDirectoryName, DotNetSdkSdkDirectoryName, "*", RoslynDirectoryName, CompilerBincoreDirectoryName));
missingComponents.Add(Path.Combine(contentsPath, "Resources", "Scripting", NetCoreRuntimeDirectoryName));
missingComponents.Add(Path.Combine(contentsPath, "Resources", "Scripting", DotNetSdkRoslynDirectoryName));
missingComponents.Add(Path.Combine(contentsPath, "Resources", "Scripting", DotNetSdkDirectoryName, DotNetSdkSdkDirectoryName, "*", RoslynDirectoryName, CompilerBincoreDirectoryName));
}
if (string.IsNullOrEmpty(compilerDirectoryPath))
{
compilerDirectoryPath = Path.Combine(effectiveScriptingRootPath, DotNetSdkRoslynDirectoryName);
missingComponents.Add(compilerDirectoryPath);
missingComponents.Add(Path.Combine(effectiveScriptingRootPath, DotNetSdkDirectoryName, DotNetSdkSdkDirectoryName, "*", RoslynDirectoryName, CompilerBincoreDirectoryName));
}
string dotnetHostPath = Path.Combine(effectiveScriptingRootPath, NetCoreRuntimeDirectoryName, dotnetHostFileName);
string compilerDllPath = Path.Combine(compilerDirectoryPath, CompilerDllFileName);
string compilerRuntimeConfigPath = Path.Combine(compilerDirectoryPath, CompilerRuntimeConfigFileName);
string compilerDepsFilePath = Path.Combine(compilerDirectoryPath, CompilerDepsFileName);
string codeAnalysisDllPath = Path.Combine(compilerDirectoryPath, CodeAnalysisDllFileName);
string codeAnalysisCSharpDllPath = Path.Combine(compilerDirectoryPath, CodeAnalysisCSharpDllFileName);
string netCoreRuntimeSharedRootPath = Path.Combine(effectiveScriptingRootPath, NetCoreRuntimeDirectoryName, NetCoreRuntimeSharedDirectoryName, NetCoreRuntimeSharedFrameworkName);
string netCoreRuntimeSharedDirectoryPath = ResolveNetCoreRuntimeSharedDirectoryPath(netCoreRuntimeSharedRootPath);
if (!File.Exists(dotnetHostPath))
{
missingComponents.Add(dotnetHostPath);
}
if (!File.Exists(compilerDllPath))
{
missingComponents.Add(compilerDllPath);
}
if (!File.Exists(compilerRuntimeConfigPath))
{
missingComponents.Add(compilerRuntimeConfigPath);
}
if (!File.Exists(compilerDepsFilePath))
{
missingComponents.Add(compilerDepsFilePath);
}
if (!File.Exists(codeAnalysisDllPath))
{
missingComponents.Add(codeAnalysisDllPath);
}
if (!File.Exists(codeAnalysisCSharpDllPath))
{
missingComponents.Add(codeAnalysisCSharpDllPath);
}
if (string.IsNullOrEmpty(netCoreRuntimeSharedDirectoryPath))
{
missingComponents.Add(netCoreRuntimeSharedRootPath);
}
if (missingComponents.Count > 0)
{
DynamicCompilationHealthMonitor.ReportFastPathUnavailable(
editorPath,
contentsPath,
missingComponents);
return null;
}
return new ExternalCompilerPaths(
contentsPath,
scriptingRootPath,
dotnetHostPath,
compilerDllPath,
compilerRuntimeConfigPath,
compilerDepsFilePath,
codeAnalysisDllPath,
codeAnalysisCSharpDllPath,
netCoreRuntimeSharedDirectoryPath);
}
internal static string ResolveScriptingRootPath(string contentsPath)
{
if (string.IsNullOrEmpty(contentsPath))
{
return null;
}
string resourcesScriptingRootPath = Path.Combine(contentsPath, "Resources", "Scripting");
if (ContainsExternalCompilerLayout(resourcesScriptingRootPath))
{
return resourcesScriptingRootPath;
}
if (ContainsExternalCompilerLayout(contentsPath))
{
return contentsPath;
}
return ResolveScriptingRootPathByScan(contentsPath);
}
internal static string ResolveCompilerDirectoryPath(string scriptingRootPath)
{
if (string.IsNullOrEmpty(scriptingRootPath))
{
return null;
}
string legacyCompilerDirectoryPath = Path.Combine(scriptingRootPath, DotNetSdkRoslynDirectoryName);
if (Directory.Exists(legacyCompilerDirectoryPath))
{
return legacyCompilerDirectoryPath;
}
return ResolveDotNetSdkCompilerDirectoryPath(scriptingRootPath);
}
internal static string ResolveNetCoreRuntimeSharedDirectoryPath(string netCoreRuntimeSharedRootPath)
{
if (!Directory.Exists(netCoreRuntimeSharedRootPath))
{
return null;
}
string[] runtimeDirectories = Directory.GetDirectories(netCoreRuntimeSharedRootPath);
if (runtimeDirectories.Length == 0)
{
return null;
}
string highestVersionDirectoryPath = runtimeDirectories
.Select(runtimeDirectoryPath => new
{
Path = runtimeDirectoryPath,
VersionText = Path.GetFileName(runtimeDirectoryPath)
})
.Where(candidate => Version.TryParse(candidate.VersionText, out _))
.OrderByDescending(candidate => new Version(candidate.VersionText))
.ThenByDescending(candidate => candidate.VersionText, StringComparer.Ordinal)
.Select(candidate => candidate.Path)
.FirstOrDefault();
if (!string.IsNullOrEmpty(highestVersionDirectoryPath))
{
return highestVersionDirectoryPath;
}
return runtimeDirectories
.OrderByDescending(Path.GetFileName, StringComparer.Ordinal)
.First();
}
private static bool ContainsExternalCompilerLayout(string rootPath)
{
return Directory.Exists(Path.Combine(rootPath, NetCoreRuntimeDirectoryName))
&& !string.IsNullOrEmpty(ResolveCompilerDirectoryPath(rootPath));
}
private static string ResolveDotNetSdkCompilerDirectoryPath(string scriptingRootPath)
{
string sdkRootPath = Path.Combine(scriptingRootPath, DotNetSdkDirectoryName, DotNetSdkSdkDirectoryName);
if (!Directory.Exists(sdkRootPath))
{
return null;
}
List<string> sdkDirectoryPaths = Directory.GetDirectories(sdkRootPath).ToList();
sdkDirectoryPaths.Sort(CompareSdkDirectoryPathsDescending);
foreach (string sdkDirectoryPath in sdkDirectoryPaths)
{
string compilerDirectoryPath = Path.Combine(sdkDirectoryPath, RoslynDirectoryName, CompilerBincoreDirectoryName);
if (Directory.Exists(compilerDirectoryPath))
{
return compilerDirectoryPath;
}
}
return null;
}
private static int CompareSdkDirectoryPathsDescending(string leftPath, string rightPath)
{
string leftVersionText = Path.GetFileName(leftPath);
string rightVersionText = Path.GetFileName(rightPath);
bool leftIsVersion = Version.TryParse(leftVersionText, out Version leftVersion);
bool rightIsVersion = Version.TryParse(rightVersionText, out Version rightVersion);
if (leftIsVersion && rightIsVersion)
{
int versionComparison = rightVersion.CompareTo(leftVersion);
if (versionComparison != 0)
{
return versionComparison;
}
}
else if (leftIsVersion)
{
return -1;
}
else if (rightIsVersion)
{
return 1;
}
return string.Compare(rightVersionText, leftVersionText, StringComparison.Ordinal);
}
private static string ResolveScriptingRootPathByScan(string contentsPath)
{
if (!Directory.Exists(contentsPath))
{
return null;
}
Queue<(string Path, int Depth)> pendingDirectories = new Queue<(string Path, int Depth)>();
pendingDirectories.Enqueue((contentsPath, 0));
while (pendingDirectories.Count > 0)
{
(string currentPath, int depth) = pendingDirectories.Dequeue();
if (ContainsExternalCompilerLayout(currentPath))
{
return currentPath;
}
if (depth >= 4)
{
continue;
}
foreach (string childDirectoryPath in Directory.GetDirectories(currentPath).OrderBy(path => path, StringComparer.Ordinal))
{
pendingDirectories.Enqueue((childDirectoryPath, depth + 1));
}
}
return null;
}
private static string ResolveEditorContentsPath(string editorPath)
{
if (editorPath.EndsWith(".app", StringComparison.OrdinalIgnoreCase))
{
return Path.Combine(editorPath, "Contents");
}
string editorDirectoryPath = Path.GetDirectoryName(editorPath);
if (string.IsNullOrEmpty(editorDirectoryPath))
{
return null;
}
string dataDirectoryPath = Path.Combine(editorDirectoryPath, "Data");
if (Directory.Exists(dataDirectoryPath))
{
return dataDirectoryPath;
}
string installRootPath = Path.GetDirectoryName(editorDirectoryPath);
return string.IsNullOrEmpty(installRootPath)
? null
: installRootPath;
}
}
}