A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR 2using System; 3using System.Collections.Generic; 4using System.IO; 5using System.Linq; 6using System.Reflection; 7using System.Text; 8using UnityEngine.InputSystem.LowLevel; 9using UnityEditor.Build; 10using UnityEditor.Build.Reporting; 11using UnityEditor.Compilation; 12using UnityEditor.UnityLinker; 13using UnityEngine.InputSystem.Layouts; 14 15namespace UnityEngine.InputSystem.Editor 16{ 17 /// <summary> 18 /// Input system uses runtime reflection to instantiate and discover some capabilities like layouts, processors, interactions, etc. 19 /// Managed linker on high stripping modes is very keen on removing parts of classes or whole classes. 20 /// One way to preserve the classes is to put [Preserve] on class itself and every field/property we're interested in, 21 /// this was proven to be error prone as it's easy to forget an attribute and tedious as everything needs an attribute now. 22 /// 23 /// Instead this LinkFileGenerator inspects all types in the domain, and if they could be used via reflection, 24 /// we preserve them in all entirety. 25 /// 26 /// In a long run we would like to remove usage of reflection all together, and then this mechanism will be gone too. 27 /// 28 /// Beware, this uses "AppDomain.CurrentDomain.GetAssemblies" which returns editor assemblies, 29 /// but not all classes are available on all platforms, most of platform specific code is wrapped into defines like 30 /// "#if UNITY_EDITOR || UNITY_IOS || PACKAGE_DOCS_GENERATION", and when compiling for Android, 31 /// that particular class wouldn't be available in the final executable, though our link.xml here would still specify it, 32 /// potentially creating linker warnings that we need to later ignore. 33 /// </summary> 34 internal class LinkFileGenerator : IUnityLinkerProcessor 35 { 36 public int callbackOrder => 0; 37 38 public string GenerateAdditionalLinkXmlFile(BuildReport report, UnityLinkerBuildPipelineData data) 39 { 40 var currentAssemblyName = typeof(UnityEngine.InputSystem.InputSystem).Assembly.GetName().Name; 41 42 var typesByAssemblies = new Dictionary<System.Reflection.Assembly, Type[]>(); 43 var assemblies = AppDomain.CurrentDomain.GetAssemblies(); 44 foreach (var assembly in assemblies) 45 { 46 try 47 { 48 // Skip any assembly that doesn't reference the input system assembly. 49 if (assembly.GetName().Name != currentAssemblyName && !assembly 50 .GetReferencedAssemblies().Any(x => x.Name == currentAssemblyName)) 51 continue; 52 53 var types = assembly.GetTypes().Where(ShouldPreserveType).ToArray(); 54 if (types.Length > 0) 55 typesByAssemblies.Add(assembly, types); 56 } 57 catch (ReflectionTypeLoadException) 58 { 59 Debug.LogWarning($"Couldn't load types from assembly: {assembly.FullName}"); 60 } 61 } 62 63 var sb = new StringBuilder(); 64 sb.AppendLine("<linker>"); 65 66 foreach (var assembly in typesByAssemblies.Keys.OrderBy(a => a.GetName().Name)) 67 { 68 sb.AppendLine($" <assembly fullname=\"{assembly.GetName().Name}\">"); 69 70 var types = typesByAssemblies[assembly]; 71 foreach (var type in types.OrderBy(t => t.FullName)) 72 sb.AppendLine( 73 $" <type fullname=\"{FormatForXml(ToCecilName(type.FullName))}\" preserve=\"all\"/>"); 74 75 sb.AppendLine(" </assembly>"); 76 } 77 78 sb.AppendLine("</linker>"); 79 80 var filePathName = Path.Combine(Application.dataPath, "..", "Temp", "InputSystemLink.xml"); 81 File.WriteAllText(filePathName, sb.ToString()); 82 return filePathName; 83 } 84 85 static bool IsTypeUsedViaReflectionByInputSystem(Type type) 86 { 87 return type.IsSubclassOf(typeof(InputControl)) || 88 typeof(IInputStateTypeInfo).IsAssignableFrom(type) || 89 typeof(IInputInteraction).IsAssignableFrom(type) || 90 typeof(InputProcessor).IsAssignableFrom(type) || 91 typeof(InputBindingComposite).IsAssignableFrom(type) || 92 type.GetCustomAttributes<InputControlAttribute>().Any(); 93 } 94 95 static bool IsFieldRelatedToControlLayouts(FieldInfo field) 96 { 97 return IsTypeUsedViaReflectionByInputSystem(field.GetType()) || 98 field.GetCustomAttributes<InputControlAttribute>().Any(); 99 } 100 101 static bool IsPropertyRelatedToControlLayouts(PropertyInfo property) 102 { 103 return IsTypeUsedViaReflectionByInputSystem(property.GetType()) || 104 property.GetCustomAttributes<InputControlAttribute>().Any(); 105 } 106 107 static bool ShouldPreserveType(Type type) 108 { 109 if (IsTypeUsedViaReflectionByInputSystem(type)) 110 return true; 111 112 foreach (var field in type.GetFields()) 113 if (IsFieldRelatedToControlLayouts(field)) 114 return true; 115 116 foreach (var property in type.GetProperties()) 117 if (IsPropertyRelatedToControlLayouts(property)) 118 return true; 119 120 return false; 121 } 122 123 static string ToCecilName(string fullTypeName) 124 { 125 return fullTypeName.Replace('+', '/'); 126 } 127 128 static string FormatForXml(string value) 129 { 130 return value.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;"); 131 } 132 133 public void OnBeforeRun(BuildReport report, UnityLinkerBuildPipelineData data) 134 { 135 } 136 137 public void OnAfterRun(BuildReport report, UnityLinkerBuildPipelineData data) 138 { 139 } 140 } 141} 142#endif