升级XR插件版本

This commit is contained in:
Sora丶kong
2026-03-02 17:56:21 +08:00
parent 8962657674
commit 60f512a9bc
1317 changed files with 110305 additions and 48249 deletions

View File

@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Dependencies.PlatformDetectors;
using MCPForUnity.Editor.Helpers;
using UnityEditor;
using UnityEngine;
namespace MCPForUnity.Editor.Dependencies
{
/// <summary>
/// Main orchestrator for dependency validation and management
/// </summary>
public static class DependencyManager
{
private static readonly List<IPlatformDetector> _detectors = new List<IPlatformDetector>
{
new WindowsPlatformDetector(),
new MacOSPlatformDetector(),
new LinuxPlatformDetector()
};
private static IPlatformDetector _currentDetector;
/// <summary>
/// Get the platform detector for the current operating system
/// </summary>
public static IPlatformDetector GetCurrentPlatformDetector()
{
if (_currentDetector == null)
{
_currentDetector = _detectors.FirstOrDefault(d => d.CanDetect);
if (_currentDetector == null)
{
throw new PlatformNotSupportedException($"No detector available for current platform: {RuntimeInformation.OSDescription}");
}
}
return _currentDetector;
}
/// <summary>
/// Perform a comprehensive dependency check
/// </summary>
public static DependencyCheckResult CheckAllDependencies()
{
var result = new DependencyCheckResult();
try
{
var detector = GetCurrentPlatformDetector();
McpLog.Info($"Checking dependencies on {detector.PlatformName}...", always: false);
// Check Python
var pythonStatus = detector.DetectPython();
result.Dependencies.Add(pythonStatus);
// Check uv
var uvStatus = detector.DetectUv();
result.Dependencies.Add(uvStatus);
// Generate summary and recommendations
result.GenerateSummary();
GenerateRecommendations(result, detector);
McpLog.Info($"Dependency check completed. System ready: {result.IsSystemReady}", always: false);
}
catch (Exception ex)
{
McpLog.Error($"Error during dependency check: {ex.Message}");
result.Summary = $"Dependency check failed: {ex.Message}";
result.IsSystemReady = false;
}
return result;
}
/// <summary>
/// Get installation recommendations for the current platform
/// </summary>
public static string GetInstallationRecommendations()
{
try
{
var detector = GetCurrentPlatformDetector();
return detector.GetInstallationRecommendations();
}
catch (Exception ex)
{
return $"Error getting installation recommendations: {ex.Message}";
}
}
/// <summary>
/// Get platform-specific installation URLs
/// </summary>
public static (string pythonUrl, string uvUrl) GetInstallationUrls()
{
try
{
var detector = GetCurrentPlatformDetector();
return (detector.GetPythonInstallUrl(), detector.GetUvInstallUrl());
}
catch
{
return ("https://python.org/downloads/", "https://docs.astral.sh/uv/getting-started/installation/");
}
}
private static void GenerateRecommendations(DependencyCheckResult result, IPlatformDetector detector)
{
var missing = result.GetMissingDependencies();
if (missing.Count == 0)
{
result.RecommendedActions.Add("All dependencies are available. You can start using MCP for Unity.");
return;
}
foreach (var dep in missing)
{
if (dep.Name == "Python")
{
result.RecommendedActions.Add($"Install Python 3.10+ from: {detector.GetPythonInstallUrl()}");
}
else if (dep.Name == "uv Package Manager")
{
result.RecommendedActions.Add($"Install uv package manager from: {detector.GetUvInstallUrl()}");
}
else if (dep.Name == "MCP Server")
{
result.RecommendedActions.Add("MCP Server will be installed automatically when needed.");
}
}
if (result.GetMissingRequired().Count > 0)
{
result.RecommendedActions.Add("Use the Setup Window (Window > MCP for Unity > Local Setup Window) for guided installation.");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4a6d2236d370b4f1db4d0e3d3ce0dcac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4c0f2e87395b4c6c9df8c21b6d0fae13
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MCPForUnity.Editor.Dependencies.Models
{
/// <summary>
/// Result of a comprehensive dependency check
/// </summary>
[Serializable]
public class DependencyCheckResult
{
/// <summary>
/// List of all dependency statuses checked
/// </summary>
public List<DependencyStatus> Dependencies { get; set; }
/// <summary>
/// Overall system readiness for MCP operations
/// </summary>
public bool IsSystemReady { get; set; }
/// <summary>
/// Whether all required dependencies are available
/// </summary>
public bool AllRequiredAvailable => Dependencies?.Where(d => d.IsRequired).All(d => d.IsAvailable) ?? false;
/// <summary>
/// Whether any optional dependencies are missing
/// </summary>
public bool HasMissingOptional => Dependencies?.Where(d => !d.IsRequired).Any(d => !d.IsAvailable) ?? false;
/// <summary>
/// Summary message about the dependency state
/// </summary>
public string Summary { get; set; }
/// <summary>
/// Recommended next steps for the user
/// </summary>
public List<string> RecommendedActions { get; set; }
/// <summary>
/// Timestamp when this check was performed
/// </summary>
public DateTime CheckedAt { get; set; }
public DependencyCheckResult()
{
Dependencies = new List<DependencyStatus>();
RecommendedActions = new List<string>();
CheckedAt = DateTime.UtcNow;
}
/// <summary>
/// Get dependencies by availability status
/// </summary>
public List<DependencyStatus> GetMissingDependencies()
{
return Dependencies?.Where(d => !d.IsAvailable).ToList() ?? new List<DependencyStatus>();
}
/// <summary>
/// Get missing required dependencies
/// </summary>
public List<DependencyStatus> GetMissingRequired()
{
return Dependencies?.Where(d => d.IsRequired && !d.IsAvailable).ToList() ?? new List<DependencyStatus>();
}
/// <summary>
/// Generate a user-friendly summary of the dependency state
/// </summary>
public void GenerateSummary()
{
var missing = GetMissingDependencies();
var missingRequired = GetMissingRequired();
if (missing.Count == 0)
{
Summary = "All dependencies are available and ready.";
IsSystemReady = true;
}
else if (missingRequired.Count == 0)
{
Summary = $"System is ready. {missing.Count} optional dependencies are missing.";
IsSystemReady = true;
}
else
{
Summary = $"System is not ready. {missingRequired.Count} required dependencies are missing.";
IsSystemReady = false;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f6df82faa423f4e9ebb13a3dcee8ba19
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,65 @@
using System;
namespace MCPForUnity.Editor.Dependencies.Models
{
/// <summary>
/// Represents the status of a dependency check
/// </summary>
[Serializable]
public class DependencyStatus
{
/// <summary>
/// Name of the dependency being checked
/// </summary>
public string Name { get; set; }
/// <summary>
/// Whether the dependency is available and functional
/// </summary>
public bool IsAvailable { get; set; }
/// <summary>
/// Version information if available
/// </summary>
public string Version { get; set; }
/// <summary>
/// Path to the dependency executable/installation
/// </summary>
public string Path { get; set; }
/// <summary>
/// Additional details about the dependency status
/// </summary>
public string Details { get; set; }
/// <summary>
/// Error message if dependency check failed
/// </summary>
public string ErrorMessage { get; set; }
/// <summary>
/// Whether this dependency is required for basic functionality
/// </summary>
public bool IsRequired { get; set; }
/// <summary>
/// Suggested installation method or URL
/// </summary>
public string InstallationHint { get; set; }
public DependencyStatus(string name, bool isRequired = true)
{
Name = name;
IsRequired = isRequired;
IsAvailable = false;
}
public override string ToString()
{
var status = IsAvailable ? "✓" : "✗";
var version = !string.IsNullOrEmpty(Version) ? $" ({Version})" : "";
return $"{status} {Name}{version}";
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ddeeeca2f876f4083a84417404175199
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bdbaced669d14798a4ceeebfbff2b22c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
using MCPForUnity.Editor.Dependencies.Models;
namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
/// <summary>
/// Interface for platform-specific dependency detection
/// </summary>
public interface IPlatformDetector
{
/// <summary>
/// Platform name this detector handles
/// </summary>
string PlatformName { get; }
/// <summary>
/// Whether this detector can run on the current platform
/// </summary>
bool CanDetect { get; }
/// <summary>
/// Detect Python installation on this platform
/// </summary>
DependencyStatus DetectPython();
/// <summary>
/// Detect uv package manager on this platform
/// </summary>
DependencyStatus DetectUv();
/// <summary>
/// Get platform-specific installation recommendations
/// </summary>
string GetInstallationRecommendations();
/// <summary>
/// Get platform-specific Python installation URL
/// </summary>
string GetPythonInstallUrl();
/// <summary>
/// Get platform-specific uv installation URL
/// </summary>
string GetUvInstallUrl();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 67d73d0e8caef4e60942f4419c6b76bf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,207 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services;
namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
/// <summary>
/// Linux-specific dependency detection
/// </summary>
public class LinuxPlatformDetector : PlatformDetectorBase
{
public override string PlatformName => "Linux";
public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
public override DependencyStatus DetectPython()
{
var status = new DependencyStatus("Python", isRequired: true)
{
InstallationHint = GetPythonInstallUrl()
};
try
{
// Try running python directly first
if (TryValidatePython("python3", out string version, out string fullPath) ||
TryValidatePython("python", out version, out fullPath))
{
status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found Python {version} in PATH";
return status;
}
// Fallback: try 'which' command
if (TryFindInPath("python3", out string pathResult) ||
TryFindInPath("python", out pathResult))
{
if (TryValidatePython(pathResult, out version, out fullPath))
{
status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found Python {version} in PATH";
return status;
}
}
status.ErrorMessage = "Python not found in PATH";
status.Details = "Install Python 3.10+ and ensure it's added to PATH.";
}
catch (Exception ex)
{
status.ErrorMessage = $"Error detecting Python: {ex.Message}";
}
return status;
}
public override string GetPythonInstallUrl()
{
return "https://www.python.org/downloads/source/";
}
public override string GetUvInstallUrl()
{
return "https://docs.astral.sh/uv/getting-started/installation/#linux";
}
public override string GetInstallationRecommendations()
{
return @"Linux Installation Recommendations:
1. Python: Install via package manager or pyenv
- Ubuntu/Debian: sudo apt install python3 python3-pip
- Fedora/RHEL: sudo dnf install python3 python3-pip
- Arch: sudo pacman -S python python-pip
- Or use pyenv: https://github.com/pyenv/pyenv
2. uv Package Manager: Install via curl
- Run: curl -LsSf https://astral.sh/uv/install.sh | sh
- Or download from: https://github.com/astral-sh/uv/releases
3. MCP Server: Will be installed automatically by MCP for Unity
Note: Make sure ~/.local/bin is in your PATH for user-local installations.";
}
public override DependencyStatus DetectUv()
{
// First, honor overrides and cross-platform resolution via the base implementation
var status = base.DetectUv();
if (status.IsAvailable)
{
return status;
}
// If the user configured an override path but fallback was not used, keep the base result
// (failure typically means the override path is invalid and no system fallback found)
if (MCPServiceLocator.Paths.HasUvxPathOverride && !MCPServiceLocator.Paths.HasUvxPathFallback)
{
return status;
}
try
{
string augmentedPath = BuildAugmentedPath();
// Try uv first, then uvx, using ExecPath.TryRun for proper timeout handling
if (TryValidateUvWithPath("uv", augmentedPath, out string version, out string fullPath) ||
TryValidateUvWithPath("uvx", augmentedPath, out version, out fullPath))
{
status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found uv {version} in PATH";
status.ErrorMessage = null;
return status;
}
status.ErrorMessage = "uv not found in PATH";
status.Details = "Install uv package manager and ensure it's added to PATH.";
}
catch (Exception ex)
{
status.ErrorMessage = $"Error detecting uv: {ex.Message}";
}
return status;
}
private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
{
version = null;
fullPath = null;
try
{
string augmentedPath = BuildAugmentedPath();
// First, try to resolve the absolute path for better UI/logging display
string commandToRun = pythonPath;
if (TryFindInPath(pythonPath, out string resolvedPath))
{
commandToRun = resolvedPath;
}
if (!ExecPath.TryRun(commandToRun, "--version", null, out string stdout, out string stderr,
5000, augmentedPath))
return false;
// Check stdout first, then stderr (some Python distributions output to stderr)
string output = !string.IsNullOrWhiteSpace(stdout) ? stdout.Trim() : stderr.Trim();
if (output.StartsWith("Python "))
{
version = output.Substring(7);
fullPath = commandToRun;
if (TryParseVersion(version, out var major, out var minor))
{
return major > 3 || (major == 3 && minor >= 10);
}
}
}
catch
{
// Ignore validation errors
}
return false;
}
protected string BuildAugmentedPath()
{
var additions = GetPathAdditions();
if (additions.Length == 0) return null;
// Only return the additions - ExecPath.TryRun will prepend to existing PATH
return string.Join(Path.PathSeparator, additions);
}
private string[] GetPathAdditions()
{
var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
return new[]
{
"/usr/local/bin",
"/usr/bin",
"/bin",
"/snap/bin",
Path.Combine(homeDir, ".local", "bin")
};
}
protected override bool TryFindInPath(string executable, out string fullPath)
{
fullPath = ExecPath.FindInPath(executable, BuildAugmentedPath());
return !string.IsNullOrEmpty(fullPath);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b682b492eb80d4ed6834b76f72c9f0f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,205 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services;
namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
/// <summary>
/// macOS-specific dependency detection
/// </summary>
public class MacOSPlatformDetector : PlatformDetectorBase
{
public override string PlatformName => "macOS";
public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
public override DependencyStatus DetectPython()
{
var status = new DependencyStatus("Python", isRequired: true)
{
InstallationHint = GetPythonInstallUrl()
};
try
{
// 1. Try 'which' command with augmented PATH (prioritizing Homebrew)
if (TryFindInPath("python3", out string pathResult) ||
TryFindInPath("python", out pathResult))
{
if (TryValidatePython(pathResult, out string version, out string fullPath))
{
status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found Python {version} at {fullPath}";
return status;
}
}
// 2. Fallback: Try running python directly from PATH
if (TryValidatePython("python3", out string v, out string p) ||
TryValidatePython("python", out v, out p))
{
status.IsAvailable = true;
status.Version = v;
status.Path = p;
status.Details = $"Found Python {v} in PATH";
return status;
}
status.ErrorMessage = "Python not found in PATH or standard locations";
status.Details = "Install Python 3.10+ via Homebrew ('brew install python3') and ensure it's in your PATH.";
}
catch (Exception ex)
{
status.ErrorMessage = $"Error detecting Python: {ex.Message}";
}
return status;
}
public override string GetPythonInstallUrl()
{
return "https://www.python.org/downloads/macos/";
}
public override string GetUvInstallUrl()
{
return "https://docs.astral.sh/uv/getting-started/installation/#macos";
}
public override string GetInstallationRecommendations()
{
return @"macOS Installation Recommendations:
1. Python: Install via Homebrew (recommended) or python.org
- Homebrew: brew install python3
- Direct download: https://python.org/downloads/macos/
2. uv Package Manager: Install via curl or Homebrew
- Curl: curl -LsSf https://astral.sh/uv/install.sh | sh
- Homebrew: brew install uv
3. MCP Server: Will be installed automatically by MCP for Unity Bridge
Note: If using Homebrew, make sure /opt/homebrew/bin is in your PATH.";
}
public override DependencyStatus DetectUv()
{
// First, honor overrides and cross-platform resolution via the base implementation
var status = base.DetectUv();
if (status.IsAvailable)
{
return status;
}
// If the user configured an override path but fallback was not used, keep the base result
// (failure typically means the override path is invalid and no system fallback found)
if (MCPServiceLocator.Paths.HasUvxPathOverride && !MCPServiceLocator.Paths.HasUvxPathFallback)
{
return status;
}
try
{
string augmentedPath = BuildAugmentedPath();
// Try uv first, then uvx, using ExecPath.TryRun for proper timeout handling
if (TryValidateUvWithPath("uv", augmentedPath, out string version, out string fullPath) ||
TryValidateUvWithPath("uvx", augmentedPath, out version, out fullPath))
{
status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found uv {version} in PATH";
status.ErrorMessage = null;
return status;
}
status.ErrorMessage = "uv not found in PATH";
status.Details = "Install uv package manager and ensure it's added to PATH.";
}
catch (Exception ex)
{
status.ErrorMessage = $"Error detecting uv: {ex.Message}";
}
return status;
}
private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
{
version = null;
fullPath = null;
try
{
string augmentedPath = BuildAugmentedPath();
// First, try to resolve the absolute path for better UI/logging display
string commandToRun = pythonPath;
if (TryFindInPath(pythonPath, out string resolvedPath))
{
commandToRun = resolvedPath;
}
if (!ExecPath.TryRun(commandToRun, "--version", null, out string stdout, out string stderr,
5000, augmentedPath))
return false;
// Check stdout first, then stderr (some Python distributions output to stderr)
string output = !string.IsNullOrWhiteSpace(stdout) ? stdout.Trim() : stderr.Trim();
if (output.StartsWith("Python "))
{
version = output.Substring(7);
fullPath = commandToRun;
if (TryParseVersion(version, out var major, out var minor))
{
return major > 3 || (major == 3 && minor >= 10);
}
}
}
catch
{
// Ignore validation errors
}
return false;
}
protected string BuildAugmentedPath()
{
var additions = GetPathAdditions();
if (additions.Length == 0) return null;
// Only return the additions - ExecPath.TryRun will prepend to existing PATH
return string.Join(Path.PathSeparator, additions);
}
private string[] GetPathAdditions()
{
var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
return new[]
{
"/opt/homebrew/bin",
"/usr/local/bin",
"/usr/bin",
"/bin",
Path.Combine(homeDir, ".local", "bin")
};
}
protected override bool TryFindInPath(string executable, out string fullPath)
{
fullPath = ExecPath.FindInPath(executable, BuildAugmentedPath());
return !string.IsNullOrEmpty(fullPath);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c6f602b0a8ca848859197f9a949a7a5d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,137 @@
using System;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services;
namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
/// <summary>
/// Base class for platform-specific dependency detection
/// </summary>
public abstract class PlatformDetectorBase : IPlatformDetector
{
public abstract string PlatformName { get; }
public abstract bool CanDetect { get; }
public abstract DependencyStatus DetectPython();
public abstract string GetPythonInstallUrl();
public abstract string GetUvInstallUrl();
public abstract string GetInstallationRecommendations();
public virtual DependencyStatus DetectUv()
{
var status = new DependencyStatus("uv Package Manager", isRequired: true)
{
InstallationHint = GetUvInstallUrl()
};
try
{
// Get uv path from PathResolverService (respects override)
string uvxPath = MCPServiceLocator.Paths.GetUvxPath();
// Verify uv executable and get version
if (MCPServiceLocator.Paths.TryValidateUvxExecutable(uvxPath, out string version))
{
status.IsAvailable = true;
status.Version = version;
status.Path = uvxPath;
// Check if we used fallback from override to system path
if (MCPServiceLocator.Paths.HasUvxPathFallback)
{
status.Details = $"Found uv {version} (fallback to system path)";
status.ErrorMessage = "Override path not found, using system path";
}
else
{
status.Details = MCPServiceLocator.Paths.HasUvxPathOverride
? $"Found uv {version} (override path)"
: $"Found uv {version} in system path";
}
return status;
}
status.ErrorMessage = "uvx not found";
status.Details = "Install uv package manager or configure path override in Advanced Settings.";
}
catch (Exception ex)
{
status.ErrorMessage = $"Error detecting uvx: {ex.Message}";
}
return status;
}
protected bool TryParseVersion(string version, out int major, out int minor)
{
major = 0;
minor = 0;
try
{
var parts = version.Split('.');
if (parts.Length >= 2)
{
return int.TryParse(parts[0], out major) && int.TryParse(parts[1], out minor);
}
}
catch
{
// Ignore parsing errors
}
return false;
}
// In PlatformDetectorBase.cs
protected bool TryValidateUvWithPath(string command, string augmentedPath, out string version, out string fullPath)
{
version = null;
fullPath = null;
try
{
string commandToRun = command;
if (TryFindInPath(command, out string resolvedPath))
{
commandToRun = resolvedPath;
}
if (!ExecPath.TryRun(commandToRun, "--version", null, out string stdout, out string stderr,
5000, augmentedPath))
return false;
string output = string.IsNullOrWhiteSpace(stdout) ? stderr.Trim() : stdout.Trim();
if (output.StartsWith("uvx ") || output.StartsWith("uv "))
{
int spaceIndex = output.IndexOf(' ');
if (spaceIndex >= 0)
{
var remainder = output.Substring(spaceIndex + 1).Trim();
int nextSpace = remainder.IndexOf(' ');
int parenIndex = remainder.IndexOf('(');
int endIndex = Math.Min(
nextSpace >= 0 ? nextSpace : int.MaxValue,
parenIndex >= 0 ? parenIndex : int.MaxValue
);
version = endIndex < int.MaxValue ? remainder.Substring(0, endIndex).Trim() : remainder;
fullPath = commandToRun;
return true;
}
}
}
catch
{
// Ignore validation errors
}
return false;
}
// Add abstract method for subclasses to implement
protected abstract bool TryFindInPath(string executable, out string fullPath);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 44d715aedea2b8b41bf914433bbb2c49
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,297 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services;
namespace MCPForUnity.Editor.Dependencies.PlatformDetectors
{
/// <summary>
/// Windows-specific dependency detection
/// </summary>
public class WindowsPlatformDetector : PlatformDetectorBase
{
public override string PlatformName => "Windows";
public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
public override DependencyStatus DetectPython()
{
var status = new DependencyStatus("Python", isRequired: true)
{
InstallationHint = GetPythonInstallUrl()
};
try
{
// Try running python directly first (works with Windows App Execution Aliases)
if (TryValidatePython("python3.exe", out string version, out string fullPath) ||
TryValidatePython("python.exe", out version, out fullPath))
{
status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found Python {version} in PATH";
return status;
}
// Fallback: try 'where' command
if (TryFindInPath("python3.exe", out string pathResult) ||
TryFindInPath("python.exe", out pathResult))
{
if (TryValidatePython(pathResult, out version, out fullPath))
{
status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found Python {version} in PATH";
return status;
}
}
// Fallback: try to find python via uv
if (TryFindPythonViaUv(out version, out fullPath))
{
status.IsAvailable = true;
status.Version = version;
status.Path = fullPath;
status.Details = $"Found Python {version} via uv";
return status;
}
status.ErrorMessage = "Python not found in PATH";
status.Details = "Install Python 3.10+ and ensure it's added to PATH.";
}
catch (Exception ex)
{
status.ErrorMessage = $"Error detecting Python: {ex.Message}";
}
return status;
}
public override string GetPythonInstallUrl()
{
return "https://apps.microsoft.com/store/detail/python-313/9NCVDN91XZQP";
}
public override string GetUvInstallUrl()
{
return "https://docs.astral.sh/uv/getting-started/installation/#windows";
}
public override string GetInstallationRecommendations()
{
return @"Windows Installation Recommendations:
1. Python: Install from Microsoft Store or python.org
- Microsoft Store: Search for 'Python 3.10' or higher
- Direct download: https://python.org/downloads/windows/
2. uv Package Manager: Install via PowerShell
- Run: powershell -ExecutionPolicy ByPass -c ""irm https://astral.sh/uv/install.ps1 | iex""
- Or download from: https://github.com/astral-sh/uv/releases
3. MCP Server: Will be installed automatically by MCP for Unity Bridge";
}
public override DependencyStatus DetectUv()
{
// First, honor overrides and cross-platform resolution via the base implementation
var status = base.DetectUv();
if (status.IsAvailable)
{
return status;
}
// If the user configured an override path but fallback was not used, keep the base result
// (failure typically means the override path is invalid and no system fallback found)
if (MCPServiceLocator.Paths.HasUvxPathOverride && !MCPServiceLocator.Paths.HasUvxPathFallback)
{
return status;
}
try
{
string augmentedPath = BuildAugmentedPath();
// try to find uv
if (TryValidateUvWithPath("uv.exe", augmentedPath, out string uvVersion, out string uvPath))
{
status.IsAvailable = true;
status.Version = uvVersion;
status.Path = uvPath;
status.Details = $"Found uv {uvVersion} at {uvPath}";
return status;
}
// try to find uvx
if (TryValidateUvWithPath("uvx.exe", augmentedPath, out string uvxVersion, out string uvxPath))
{
status.IsAvailable = true;
status.Version = uvxVersion;
status.Path = uvxPath;
status.Details = $"Found uvx {uvxVersion} at {uvxPath} (fallback)";
return status;
}
status.ErrorMessage = "uv not found in PATH";
status.Details = "Install uv package manager and ensure it's added to PATH.";
}
catch (Exception ex)
{
status.ErrorMessage = $"Error detecting uv: {ex.Message}";
}
return status;
}
private bool TryFindPythonViaUv(out string version, out string fullPath)
{
version = null;
fullPath = null;
try
{
string augmentedPath = BuildAugmentedPath();
// Try to list installed python versions via uvx
if (!ExecPath.TryRun("uv", "python list", null, out string stdout, out string stderr, 5000, augmentedPath))
return false;
var lines = stdout.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
if (line.Contains("<download available>")) continue;
var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length >= 2)
{
string potentialPath = parts[parts.Length - 1];
if (File.Exists(potentialPath) &&
(potentialPath.EndsWith("python.exe") || potentialPath.EndsWith("python3.exe")))
{
if (TryValidatePython(potentialPath, out version, out fullPath))
{
return true;
}
}
}
}
}
catch
{
// Ignore errors if uv is not installed or fails
}
return false;
}
private bool TryValidatePython(string pythonPath, out string version, out string fullPath)
{
version = null;
fullPath = null;
try
{
string augmentedPath = BuildAugmentedPath();
// First, try to resolve the absolute path for better UI/logging display
string commandToRun = pythonPath;
if (TryFindInPath(pythonPath, out string resolvedPath))
{
commandToRun = resolvedPath;
}
// Run 'python --version' to get the version
if (!ExecPath.TryRun(commandToRun, "--version", null, out string stdout, out string stderr, 5000, augmentedPath))
return false;
// Check stdout first, then stderr (some Python distributions output to stderr)
string output = !string.IsNullOrWhiteSpace(stdout) ? stdout.Trim() : stderr.Trim();
if (output.StartsWith("Python "))
{
version = output.Substring(7);
fullPath = commandToRun;
if (TryParseVersion(version, out var major, out var minor))
{
return major > 3 || (major == 3 && minor >= 10);
}
}
}
catch
{
// Ignore validation errors
}
return false;
}
protected override bool TryFindInPath(string executable, out string fullPath)
{
fullPath = ExecPath.FindInPath(executable, BuildAugmentedPath());
return !string.IsNullOrEmpty(fullPath);
}
protected string BuildAugmentedPath()
{
var additions = GetPathAdditions();
if (additions.Length == 0) return null;
// Only return the additions - ExecPath.TryRun will prepend to existing PATH
return string.Join(Path.PathSeparator, additions);
}
private string[] GetPathAdditions()
{
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var homeDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
var additions = new List<string>();
// uv common installation paths
if (!string.IsNullOrEmpty(localAppData))
additions.Add(Path.Combine(localAppData, "Programs", "uv"));
if (!string.IsNullOrEmpty(programFiles))
additions.Add(Path.Combine(programFiles, "uv"));
// npm global paths
if (!string.IsNullOrEmpty(appData))
additions.Add(Path.Combine(appData, "npm"));
if (!string.IsNullOrEmpty(localAppData))
additions.Add(Path.Combine(localAppData, "npm"));
// Python common paths
if (!string.IsNullOrEmpty(localAppData))
additions.Add(Path.Combine(localAppData, "Programs", "Python"));
// Instead of hardcoded versions, enumerate existing directories
if (!string.IsNullOrEmpty(programFiles))
{
try
{
var pythonDirs = Directory.GetDirectories(programFiles, "Python3*")
.OrderByDescending(d => d); // Newest first
foreach (var dir in pythonDirs)
{
additions.Add(dir);
}
}
catch { /* Ignore if directory doesn't exist */ }
}
// User scripts
if (!string.IsNullOrEmpty(homeDir))
additions.Add(Path.Combine(homeDir, ".local", "bin"));
return additions.ToArray();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1aedc29caa5704c07b487d20a27e9334
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: