unity-mcp/UnityMcpBridge/Editor/Helpers/ServerInstaller.cs

251 lines
9.6 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using UnityEngine;
namespace UnityMcpBridge.Editor.Helpers
{
public static class ServerInstaller
{
private const string PackageName = "unity-mcp-server";
private const string BranchName = "feature/install-overhaul";
private const string GitUrl =
"git+https://github.com/justinpbarnett/unity-mcp.git@"
+ BranchName
+ "#subdirectory=UnityMcpServer";
private const string PyprojectUrl =
"https://raw.githubusercontent.com/justinpbarnett/unity-mcp/"
+ BranchName
+ "/UnityMcpServer/pyproject.toml";
// Typical uv installation paths per OS
private static readonly string[] WindowsUvPaths = new[]
{
@"C:\Users\$USER$\.local\bin\uv.exe",
@"C:\Program Files\uv\uv.exe",
@"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",
};
public static void EnsureServerInstalled()
{
try
{
string uvPath = FindUvExecutable();
if (string.IsNullOrEmpty(uvPath))
{
throw new Exception(
"Could not find 'uv' executable. Please ensure it is installed."
);
}
// Check if the package is installed
System.Diagnostics.Process process = new();
process.StartInfo.FileName = uvPath;
process.StartInfo.Arguments = "pip show " + PackageName;
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)
{
// Package is installed, check version
string installedVersion = GetVersionFromPipShow(output);
string latestVersion = GetLatestVersionFromGitHub();
if (new Version(installedVersion) < new Version(latestVersion))
{
Debug.Log(
$"Updating {PackageName} from {installedVersion} to {latestVersion}..."
);
RunCommand(uvPath, "pip install --upgrade " + GitUrl);
Debug.Log($"{PackageName} updated successfully.");
}
else
{
Debug.Log($"{PackageName} is up to date (version {installedVersion}).");
}
}
else if (process.ExitCode == 1 && output.Contains("Package(s) not found"))
{
// Package not found, install it from GitHub
Debug.Log("Installing " + PackageName + "...");
RunCommand(uvPath, "pip install " + GitUrl);
Debug.Log(PackageName + " installed successfully.");
}
else
{
throw new Exception(
$"Command 'uv pip show {PackageName}' failed with exit code {process.ExitCode}. Output: {output} Error: {error}"
);
}
}
catch (Exception ex)
{
Debug.LogError($"Failed to ensure {PackageName} is installed: {ex.Message}");
Debug.LogWarning(
"Please install "
+ PackageName
+ " manually using 'uv pip install "
+ GitUrl
+ "'."
);
}
}
private static void RunCommand(string uvPath, string arguments)
{
System.Diagnostics.Process installProcess = new();
installProcess.StartInfo.FileName = uvPath;
installProcess.StartInfo.Arguments = arguments;
installProcess.StartInfo.UseShellExecute = false;
installProcess.StartInfo.RedirectStandardOutput = true;
installProcess.StartInfo.RedirectStandardError = true;
installProcess.Start();
string installOutput = installProcess.StandardOutput.ReadToEnd();
string installError = installProcess.StandardError.ReadToEnd();
installProcess.WaitForExit();
if (installProcess.ExitCode != 0)
{
throw new Exception(
$"Command '{uvPath} {arguments}' failed. Output: {installOutput} Error: {installError}"
);
}
}
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;
}
}
return null; // Not found
}
private static string GetVersionFromPipShow(string output)
{
string[] lines = output.Split('\n');
foreach (string line in lines)
{
if (line.StartsWith("Version:"))
{
return line["Version:".Length..].Trim();
}
}
throw new Exception("Version not found in pip show output");
}
private static string GetLatestVersionFromGitHub()
{
using HttpClient client = new();
client.DefaultRequestHeaders.Add("User-Agent", "UnityMcpBridge");
string content = client.GetStringAsync(PyprojectUrl).Result;
string pattern = @"version\s*=\s*""(.*?)""";
Match match = Regex.Match(content, pattern);
return match.Success
? 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()
{
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.";
}
}
}