2025-04-08 19:22:24 +08:00
|
|
|
using System;
|
2025-04-08 19:52:44 +08:00
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
2025-04-08 19:22:24 +08:00
|
|
|
using System.Net.Http;
|
2025-04-08 19:52:44 +08:00
|
|
|
using System.Runtime.InteropServices;
|
2025-04-08 19:22:24 +08:00
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
namespace UnityMcpBridge.Editor.Helpers
|
|
|
|
|
{
|
|
|
|
|
public static class ServerInstaller
|
|
|
|
|
{
|
|
|
|
|
private const string PackageName = "unity-mcp-server";
|
2025-04-08 19:52:44 +08:00
|
|
|
private const string GitUrl =
|
|
|
|
|
"git+https://github.com/justinpbarnett/unity-mcp.git#subdirectory=UnityMcpServer";
|
|
|
|
|
private const string PyprojectUrl =
|
|
|
|
|
"https://raw.githubusercontent.com/justinpbarnett/unity-mcp/master/UnityMcpServer/pyproject.toml";
|
|
|
|
|
|
|
|
|
|
// Typical uv installation paths per OS
|
|
|
|
|
private static readonly string[] WindowsUvPaths = new[]
|
|
|
|
|
{
|
|
|
|
|
@"C:\Users\$USER$\.local\bin\uv.exe", // Default uv install path
|
|
|
|
|
@"C:\Program Files\uv\uv.exe", // Possible system-wide install
|
|
|
|
|
@"C:\Users\$USER$\AppData\Local\Programs\uv\uv.exe",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static readonly string[] LinuxUvPaths = new[]
|
|
|
|
|
{
|
|
|
|
|
"/home/$USER$/.local/bin/uv",
|
|
|
|
|
"/usr/local/bin/uv",
|
|
|
|
|
"/usr/bin/uv",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private static readonly string[] MacUvPaths = new[]
|
|
|
|
|
{
|
|
|
|
|
"/Users/$USER$/.local/bin/uv",
|
|
|
|
|
"/usr/local/bin/uv",
|
|
|
|
|
"/opt/homebrew/bin/uv", // Homebrew on Apple Silicon
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
public static void EnsureServerInstalled()
|
2025-04-08 19:22:24 +08:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
2025-04-08 19:52:44 +08:00
|
|
|
string uvPath = FindUvExecutable();
|
|
|
|
|
if (string.IsNullOrEmpty(uvPath))
|
|
|
|
|
{
|
|
|
|
|
throw new Exception(GetUvNotFoundMessage());
|
|
|
|
|
}
|
2025-04-08 19:22:24 +08:00
|
|
|
|
2025-04-08 19:52:44 +08:00
|
|
|
// Check if unity-mcp-server is installed
|
|
|
|
|
string output = RunCommand(uvPath, $"pip show {PackageName}");
|
2025-04-08 19:22:24 +08:00
|
|
|
if (output.Contains("WARNING: Package(s) not found"))
|
|
|
|
|
{
|
2025-04-08 19:52:44 +08:00
|
|
|
Debug.Log($"Installing {PackageName}...");
|
|
|
|
|
RunCommand(uvPath, $"pip install {GitUrl}");
|
2025-04-08 19:22:24 +08:00
|
|
|
Debug.Log($"{PackageName} installed successfully.");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
string installedVersion = GetVersionFromPipShow(output);
|
2025-04-08 19:52:44 +08:00
|
|
|
string latestVersion = GetLatestVersionFromGitHub();
|
2025-04-08 19:22:24 +08:00
|
|
|
if (new Version(installedVersion) < new Version(latestVersion))
|
|
|
|
|
{
|
|
|
|
|
Debug.Log(
|
2025-04-08 19:52:44 +08:00
|
|
|
$"Updating {PackageName} from {installedVersion} to {latestVersion}..."
|
2025-04-08 19:22:24 +08:00
|
|
|
);
|
2025-04-08 19:52:44 +08:00
|
|
|
RunCommand(uvPath, $"pip install --upgrade {GitUrl}");
|
2025-04-08 19:22:24 +08:00
|
|
|
Debug.Log($"{PackageName} updated successfully.");
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Debug.Log($"{PackageName} is up to date (version {installedVersion}).");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Debug.LogError($"Failed to ensure {PackageName} is installed: {ex.Message}");
|
2025-04-08 19:52:44 +08:00
|
|
|
Debug.LogWarning(GetInstallInstructions());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string FindUvExecutable()
|
|
|
|
|
{
|
|
|
|
|
string username = Environment.UserName;
|
|
|
|
|
string[] uvPaths;
|
|
|
|
|
|
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
|
|
|
{
|
|
|
|
|
uvPaths = WindowsUvPaths.Select(p => p.Replace("$USER$", username)).ToArray();
|
|
|
|
|
}
|
|
|
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
|
|
|
{
|
|
|
|
|
uvPaths = LinuxUvPaths.Select(p => p.Replace("$USER$", username)).ToArray();
|
|
|
|
|
}
|
|
|
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
|
|
|
{
|
|
|
|
|
uvPaths = MacUvPaths.Select(p => p.Replace("$USER$", username)).ToArray();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw new PlatformNotSupportedException("Unsupported operating system.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// First, try 'uv' directly from PATH
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
System.Diagnostics.Process process = new();
|
|
|
|
|
process.StartInfo.FileName = "uv";
|
|
|
|
|
process.StartInfo.Arguments = "--version";
|
|
|
|
|
process.StartInfo.UseShellExecute = false;
|
|
|
|
|
process.StartInfo.RedirectStandardOutput = true;
|
|
|
|
|
process.StartInfo.RedirectStandardError = true;
|
|
|
|
|
process.Start();
|
|
|
|
|
process.WaitForExit(2000); // Wait up to 2 seconds
|
|
|
|
|
if (process.ExitCode == 0)
|
|
|
|
|
{
|
|
|
|
|
return "uv"; // Found in PATH
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
// Not in PATH, proceed to check specific locations
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check specific paths
|
|
|
|
|
foreach (string path in uvPaths)
|
|
|
|
|
{
|
|
|
|
|
if (File.Exists(path))
|
|
|
|
|
{
|
|
|
|
|
return path;
|
|
|
|
|
}
|
2025-04-08 19:22:24 +08:00
|
|
|
}
|
2025-04-08 19:52:44 +08:00
|
|
|
|
|
|
|
|
return null; // Not found
|
2025-04-08 19:22:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string RunCommand(string fileName, string arguments)
|
|
|
|
|
{
|
|
|
|
|
System.Diagnostics.Process process = new();
|
|
|
|
|
process.StartInfo.FileName = fileName;
|
2025-04-08 19:52:44 +08:00
|
|
|
|
|
|
|
|
// On non-Windows, might need to adjust for shell execution
|
|
|
|
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
|
|
|
{
|
|
|
|
|
process.StartInfo.FileName = "/bin/sh";
|
|
|
|
|
process.StartInfo.Arguments = $"-c \"{fileName} {arguments}\"";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
process.StartInfo.Arguments = arguments;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-08 19:22:24 +08:00
|
|
|
process.StartInfo.UseShellExecute = false;
|
|
|
|
|
process.StartInfo.RedirectStandardOutput = true;
|
|
|
|
|
process.StartInfo.RedirectStandardError = true;
|
|
|
|
|
process.Start();
|
|
|
|
|
string output = process.StandardOutput.ReadToEnd();
|
|
|
|
|
string error = process.StandardError.ReadToEnd();
|
|
|
|
|
process.WaitForExit();
|
|
|
|
|
if (process.ExitCode != 0)
|
|
|
|
|
{
|
|
|
|
|
throw new Exception(
|
|
|
|
|
$"Command '{fileName} {arguments}' failed with exit code {process.ExitCode}: {error}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return output;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetVersionFromPipShow(string output)
|
|
|
|
|
{
|
2025-04-08 19:52:44 +08:00
|
|
|
var lines = output.Split('\n');
|
|
|
|
|
foreach (var line in lines)
|
2025-04-08 19:22:24 +08:00
|
|
|
{
|
|
|
|
|
if (line.StartsWith("Version:"))
|
|
|
|
|
{
|
2025-04-08 19:52:44 +08:00
|
|
|
return line.Substring("Version:".Length).Trim();
|
2025-04-08 19:22:24 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
throw new Exception("Version not found in pip show output");
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-08 19:52:44 +08:00
|
|
|
private static string GetLatestVersionFromGitHub()
|
|
|
|
|
{
|
|
|
|
|
using (HttpClient client = new HttpClient())
|
|
|
|
|
{
|
|
|
|
|
client.DefaultRequestHeaders.Add("User-Agent", "UnityMcpBridge");
|
|
|
|
|
string content = client.GetStringAsync(PyprojectUrl).Result;
|
|
|
|
|
string pattern = @"version\s*=\s*""(.*?)""";
|
|
|
|
|
Match match = Regex.Match(content, pattern);
|
|
|
|
|
if (match.Success)
|
|
|
|
|
{
|
|
|
|
|
return match.Groups[1].Value;
|
|
|
|
|
}
|
|
|
|
|
throw new Exception("Could not find version in pyproject.toml");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetUvNotFoundMessage()
|
|
|
|
|
{
|
|
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
|
|
|
{
|
|
|
|
|
return "uv not found in PATH or typical Windows locations.";
|
|
|
|
|
}
|
|
|
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
|
|
|
{
|
|
|
|
|
return "uv not found in PATH or typical Linux locations.";
|
|
|
|
|
}
|
|
|
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
|
|
|
{
|
|
|
|
|
return "uv not found in PATH or typical macOS locations.";
|
|
|
|
|
}
|
|
|
|
|
return "uv not found on this platform.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string GetInstallInstructions()
|
2025-04-08 19:22:24 +08:00
|
|
|
{
|
2025-04-08 19:52:44 +08:00
|
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
|
|
|
{
|
|
|
|
|
return "Install uv with: powershell -c \"irm https://astral.sh/uv/install.ps1 | iex\" and ensure it's in your PATH.";
|
|
|
|
|
}
|
|
|
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
|
|
|
|
{
|
|
|
|
|
return "Install uv with: curl -LsSf https://astral.sh/uv/install.sh | sh and ensure it's in your PATH.";
|
|
|
|
|
}
|
|
|
|
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
|
|
|
|
{
|
|
|
|
|
return "Install uv with: brew install uv or curl -LsSf https://astral.sh/uv/install.sh | sh and ensure it's in your PATH.";
|
|
|
|
|
}
|
|
|
|
|
return "Install uv following platform-specific instructions and add it to your PATH.";
|
2025-04-08 19:22:24 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|