/*
+------------------------------------------------------------------+
| Author: Ivan Murzak (https://github.com/IvanMurzak) |
| Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) |
| Copyright (c) 2025 Ivan Murzak |
| Licensed under the Apache License, Version 2.0. |
| See the LICENSE file in the project root for more information. |
+------------------------------------------------------------------+
*/
#nullable enable
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Compilation;
namespace com.IvanMurzak.Unity.MCP.Editor.DependencyResolver
{
///
/// Detects assemblies that Unity already provides (built-in BCL, engine modules, other packages).
/// Uses the same approach as NuGetForUnity's UnityPreImportedLibraryResolver:
/// 1. Get all player assembly references via CompilationPipeline
/// 2. Skip references that resolve from our NuGet install folder (those are our own duplicates)
/// 3. Result = assemblies Unity provides — these should NOT be installed from NuGet
///
/// This prevents conflicts like CS1705 (version mismatch) and "multiple assemblies" errors.
///
static class UnityAssemblyResolver
{
static HashSet? cachedUnityAssemblies;
///
/// Returns true if the given assembly name is already provided by Unity
/// (either as a BCL assembly or from another installed package).
///
public static bool IsAlreadyImported(string assemblyName)
{
return GetUnityProvidedAssemblies().Contains(assemblyName);
}
///
/// Invalidates the cached set of Unity-provided assemblies.
/// Call this after installing or removing packages.
///
public static void InvalidateCache()
{
cachedUnityAssemblies = null;
}
///
/// Gets the set of assembly names that Unity already provides.
/// Caches the result for performance (invalidated between domain reloads).
///
public static IReadOnlyCollection GetUnityProvidedAssemblies()
{
if (cachedUnityAssemblies != null)
return cachedUnityAssemblies;
// Get all assemblies referenced by player builds (includes BCL, engine, other packages)
var playerAssemblies = CompilationPipeline.GetAssemblies(AssembliesType.PlayerWithoutTestAssemblies)
.Where(a => (a.flags & AssemblyFlags.EditorAssembly) == 0);
var unityProvided = new HashSet(System.StringComparer.OrdinalIgnoreCase);
// Canonicalize the install root to an absolute path once. `assembly.allReferences`
// paths are typically absolute, while NuGetConfig.InstallPath is project-relative,
// so a plain StartsWith comparison would never match for our own NuGet DLLs —
// we'd incorrectly record them as Unity-provided and later disable them.
var installRootAbs = Path.GetFullPath(NuGetConfig.InstallPath).Replace('\\', '/');
if (!installRootAbs.EndsWith("/"))
installRootAbs += "/";
foreach (var assembly in playerAssemblies)
{
foreach (var reference in assembly.allReferences)
{
var name = Path.GetFileNameWithoutExtension(reference);
// Resolve references whose path is outside our NuGet install folder
// (these are genuinely Unity-provided and would conflict with NuGet duplicates).
// If the reference path points into our NuGet install folder we skip it here —
// but we still want to detect the *name* as Unity-provided if any other
// reference resolves the same name outside our install folder.
var fullRef = Path.GetFullPath(reference).Replace('\\', '/');
if (fullRef.StartsWith(installRootAbs, System.StringComparison.OrdinalIgnoreCase))
continue;
unityProvided.Add(name);
}
}
// Hard-coded additions (following NuGetForUnity's pattern)
var compatLevel = PlayerSettings.GetApiCompatibilityLevel(
UnityEditor.Build.NamedBuildTarget.FromBuildTargetGroup(
EditorUserBuildSettings.selectedBuildTargetGroup));
if (compatLevel == ApiCompatibilityLevel.NET_Standard_2_0)
{
unityProvided.Add("NETStandard.Library");
unityProvided.Add("Microsoft.NETCore.Platforms");
}
unityProvided.Add("Microsoft.CSharp");
cachedUnityAssemblies = unityProvided;
return unityProvided;
}
}
}