chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking;
|
||||
|
||||
namespace MCPForUnity.Editor.Setup
|
||||
{
|
||||
public static class RoslynInstaller
|
||||
{
|
||||
private const string PluginsRelPath = "Plugins/Roslyn";
|
||||
|
||||
private static readonly (string packageId, string version, string dllPath, string dllName)[] NuGetEntries =
|
||||
{
|
||||
("microsoft.codeanalysis.common", "4.12.0", "lib/netstandard2.0/Microsoft.CodeAnalysis.dll", "Microsoft.CodeAnalysis.dll"),
|
||||
("microsoft.codeanalysis.csharp", "4.12.0", "lib/netstandard2.0/Microsoft.CodeAnalysis.CSharp.dll", "Microsoft.CodeAnalysis.CSharp.dll"),
|
||||
("system.collections.immutable", "8.0.0", "lib/netstandard2.0/System.Collections.Immutable.dll", "System.Collections.Immutable.dll"),
|
||||
("system.reflection.metadata", "8.0.0", "lib/netstandard2.0/System.Reflection.Metadata.dll", "System.Reflection.Metadata.dll"),
|
||||
// Transitive dep of Microsoft.CodeAnalysis.* on netstandard2.0. Without it, Roslyn's StringTable
|
||||
// static cctor throws FileNotFoundException for v6.0.0.0 and every Roslyn entry point fails to
|
||||
// initialize. Unity ships a v4.x of this assembly which does NOT satisfy the v6 reference.
|
||||
("system.runtime.compilerservices.unsafe","6.0.0", "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll", "System.Runtime.CompilerServices.Unsafe.dll"),
|
||||
};
|
||||
|
||||
public static bool IsInstalled()
|
||||
{
|
||||
string folder = Path.Combine(Application.dataPath, PluginsRelPath);
|
||||
foreach (var entry in NuGetEntries)
|
||||
{
|
||||
string path = Path.Combine(folder, entry.dllName);
|
||||
if (!File.Exists(path))
|
||||
return false;
|
||||
|
||||
// Defense-in-depth: a stale DLL whose assembly version is older than what
|
||||
// Roslyn references (e.g. a v4.x System.Runtime.CompilerServices.Unsafe
|
||||
// shadowing the v6 we actually need) would still satisfy file-existence but
|
||||
// leave Roslyn unable to load. Compare the on-disk assembly version against
|
||||
// each entry's declared NuGet version, treating "older or unreadable" as not
|
||||
// installed so Install() can rewrite it.
|
||||
if (Version.TryParse(entry.version, out var requiredVersion))
|
||||
{
|
||||
try
|
||||
{
|
||||
var actual = AssemblyName.GetAssemblyName(path).Version;
|
||||
if (actual == null || actual < requiredVersion)
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void Install(bool interactive = true)
|
||||
{
|
||||
if (IsInstalled() && interactive)
|
||||
{
|
||||
if (!EditorUtility.DisplayDialog(
|
||||
"Roslyn Already Installed",
|
||||
$"Roslyn DLLs are already present in Assets/{PluginsRelPath}.\nReinstall?",
|
||||
"Reinstall", "Cancel"))
|
||||
return;
|
||||
}
|
||||
|
||||
string destFolder = Path.Combine(Application.dataPath, PluginsRelPath);
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(destFolder);
|
||||
|
||||
for (int i = 0; i < NuGetEntries.Length; i++)
|
||||
{
|
||||
var (packageId, pkgVersion, dllPathInZip, dllName) = NuGetEntries[i];
|
||||
|
||||
if (interactive)
|
||||
{
|
||||
EditorUtility.DisplayProgressBar(
|
||||
"Installing Roslyn",
|
||||
$"Downloading {packageId} v{pkgVersion}...",
|
||||
(float)i / NuGetEntries.Length);
|
||||
}
|
||||
|
||||
string url =
|
||||
$"https://api.nuget.org/v3-flatcontainer/{packageId}/{pkgVersion}/{packageId}.{pkgVersion}.nupkg";
|
||||
|
||||
using (var request = UnityWebRequest.Get(url))
|
||||
{
|
||||
request.timeout = 30;
|
||||
request.SendWebRequest();
|
||||
while (!request.isDone)
|
||||
System.Threading.Thread.Sleep(50);
|
||||
|
||||
if (request.result != UnityWebRequest.Result.Success)
|
||||
throw new Exception($"Failed to download {packageId}: {request.error}");
|
||||
|
||||
byte[] nupkgBytes = request.downloadHandler.data;
|
||||
byte[] dllBytes = ExtractFileFromZip(nupkgBytes, dllPathInZip);
|
||||
|
||||
if (dllBytes == null)
|
||||
{
|
||||
Debug.LogError($"[MCP] Could not find {dllPathInZip} in {packageId}.{pkgVersion}.nupkg");
|
||||
continue;
|
||||
}
|
||||
|
||||
string destPath = Path.Combine(destFolder, dllName);
|
||||
File.WriteAllBytes(destPath, dllBytes);
|
||||
Debug.Log($"[MCP] Extracted {dllName} ({dllBytes.Length / 1024}KB) → Assets/{PluginsRelPath}/{dllName}");
|
||||
}
|
||||
}
|
||||
|
||||
if (interactive)
|
||||
EditorUtility.DisplayProgressBar("Installing Roslyn", "Refreshing assets...", 0.95f);
|
||||
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceSynchronousImport);
|
||||
|
||||
if (interactive)
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
EditorUtility.DisplayDialog(
|
||||
"Roslyn Installed",
|
||||
$"Roslyn DLLs and dependencies installed to Assets/{PluginsRelPath}/.\n\n" +
|
||||
"The runtime_compilation tool is now available via MCP.",
|
||||
"OK");
|
||||
}
|
||||
|
||||
Debug.Log($"[MCP] Roslyn installation complete ({NuGetEntries.Length} DLLs). runtime_compilation is now available.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (interactive) EditorUtility.ClearProgressBar();
|
||||
Debug.LogError($"[MCP] Failed to install Roslyn: {e}");
|
||||
|
||||
if (interactive)
|
||||
{
|
||||
EditorUtility.DisplayDialog(
|
||||
"Installation Failed",
|
||||
$"Could not download Roslyn DLLs:\n{e.Message}\n\n" +
|
||||
"You can manually download Microsoft.CodeAnalysis.CSharp from NuGet " +
|
||||
"and place the DLLs in Assets/Plugins/Roslyn/.",
|
||||
"OK");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] ExtractFileFromZip(byte[] zipBytes, string entryPath)
|
||||
{
|
||||
entryPath = entryPath.Replace('\\', '/');
|
||||
|
||||
using (var stream = new MemoryStream(zipBytes))
|
||||
using (var archive = new ZipArchive(stream, ZipArchiveMode.Read))
|
||||
{
|
||||
foreach (var entry in archive.Entries)
|
||||
{
|
||||
if (entry.FullName.Replace('\\', '/').Equals(entryPath, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
using (var entryStream = entry.Open())
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
entryStream.CopyTo(output);
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user