123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142 |
- #if UNITY_EDITOR
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using System.Reflection;
- using System.Text;
- using UnityEngine.InputSystem.LowLevel;
- using UnityEditor.Build;
- using UnityEditor.Build.Reporting;
- using UnityEditor.Compilation;
- using UnityEditor.UnityLinker;
- using UnityEngine.InputSystem.Layouts;
-
- namespace UnityEngine.InputSystem.Editor
- {
- /// <summary>
- /// Input system uses runtime reflection to instantiate and discover some capabilities like layouts, processors, interactions, etc.
- /// Managed linker on high stripping modes is very keen on removing parts of classes or whole classes.
- /// One way to preserve the classes is to put [Preserve] on class itself and every field/property we're interested in,
- /// this was proven to be error prone as it's easy to forget an attribute and tedious as everything needs an attribute now.
- ///
- /// Instead this LinkFileGenerator inspects all types in the domain, and if they could be used via reflection,
- /// we preserve them in all entirety.
- ///
- /// In a long run we would like to remove usage of reflection all together, and then this mechanism will be gone too.
- ///
- /// Beware, this uses "AppDomain.CurrentDomain.GetAssemblies" which returns editor assemblies,
- /// but not all classes are available on all platforms, most of platform specific code is wrapped into defines like
- /// "#if UNITY_EDITOR || UNITY_IOS || PACKAGE_DOCS_GENERATION", and when compiling for Android,
- /// that particular class wouldn't be available in the final executable, though our link.xml here would still specify it,
- /// potentially creating linker warnings that we need to later ignore.
- /// </summary>
- internal class LinkFileGenerator : IUnityLinkerProcessor
- {
- public int callbackOrder => 0;
-
- public string GenerateAdditionalLinkXmlFile(BuildReport report, UnityLinkerBuildPipelineData data)
- {
- var currentAssemblyName = typeof(UnityEngine.InputSystem.InputSystem).Assembly.GetName().Name;
-
- var typesByAssemblies = new Dictionary<System.Reflection.Assembly, Type[]>();
- var assemblies = AppDomain.CurrentDomain.GetAssemblies();
- foreach (var assembly in assemblies)
- {
- try
- {
- // Skip any assembly that doesn't reference the input system assembly.
- if (assembly.GetName().Name != currentAssemblyName && !assembly
- .GetReferencedAssemblies().Any(x => x.Name == currentAssemblyName))
- continue;
-
- var types = assembly.GetTypes().Where(ShouldPreserveType).ToArray();
- if (types.Length > 0)
- typesByAssemblies.Add(assembly, types);
- }
- catch (ReflectionTypeLoadException)
- {
- Debug.LogWarning($"Couldn't load types from assembly: {assembly.FullName}");
- }
- }
-
- var sb = new StringBuilder();
- sb.AppendLine("<linker>");
-
- foreach (var assembly in typesByAssemblies.Keys.OrderBy(a => a.GetName().Name))
- {
- sb.AppendLine($" <assembly fullname=\"{assembly.GetName().Name}\">");
-
- var types = typesByAssemblies[assembly];
- foreach (var type in types.OrderBy(t => t.FullName))
- sb.AppendLine(
- $" <type fullname=\"{FormatForXml(ToCecilName(type.FullName))}\" preserve=\"all\"/>");
-
- sb.AppendLine(" </assembly>");
- }
-
- sb.AppendLine("</linker>");
-
- var filePathName = Path.Combine(Application.dataPath, "..", "Temp", "InputSystemLink.xml");
- File.WriteAllText(filePathName, sb.ToString());
- return filePathName;
- }
-
- static bool IsTypeUsedViaReflectionByInputSystem(Type type)
- {
- return type.IsSubclassOf(typeof(InputControl)) ||
- typeof(IInputStateTypeInfo).IsAssignableFrom(type) ||
- typeof(IInputInteraction).IsAssignableFrom(type) ||
- typeof(InputProcessor).IsAssignableFrom(type) ||
- typeof(InputBindingComposite).IsAssignableFrom(type) ||
- type.GetCustomAttributes<InputControlAttribute>().Any();
- }
-
- static bool IsFieldRelatedToControlLayouts(FieldInfo field)
- {
- return IsTypeUsedViaReflectionByInputSystem(field.GetType()) ||
- field.GetCustomAttributes<InputControlAttribute>().Any();
- }
-
- static bool IsPropertyRelatedToControlLayouts(PropertyInfo property)
- {
- return IsTypeUsedViaReflectionByInputSystem(property.GetType()) ||
- property.GetCustomAttributes<InputControlAttribute>().Any();
- }
-
- static bool ShouldPreserveType(Type type)
- {
- if (IsTypeUsedViaReflectionByInputSystem(type))
- return true;
-
- foreach (var field in type.GetFields())
- if (IsFieldRelatedToControlLayouts(field))
- return true;
-
- foreach (var property in type.GetProperties())
- if (IsPropertyRelatedToControlLayouts(property))
- return true;
-
- return false;
- }
-
- static string ToCecilName(string fullTypeName)
- {
- return fullTypeName.Replace('+', '/');
- }
-
- static string FormatForXml(string value)
- {
- return value.Replace("&", "&").Replace("<", "<").Replace(">", ">");
- }
-
- public void OnBeforeRun(BuildReport report, UnityLinkerBuildPipelineData data)
- {
- }
-
- public void OnAfterRun(BuildReport report, UnityLinkerBuildPipelineData data)
- {
- }
- }
- }
- #endif
|