#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 { /// /// 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. /// 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(); 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(""); foreach (var assembly in typesByAssemblies.Keys.OrderBy(a => a.GetName().Name)) { sb.AppendLine($" "); var types = typesByAssemblies[assembly]; foreach (var type in types.OrderBy(t => t.FullName)) sb.AppendLine( $" "); sb.AppendLine(" "); } sb.AppendLine(""); 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().Any(); } static bool IsFieldRelatedToControlLayouts(FieldInfo field) { return IsTypeUsedViaReflectionByInputSystem(field.GetType()) || field.GetCustomAttributes().Any(); } static bool IsPropertyRelatedToControlLayouts(PropertyInfo property) { return IsTypeUsedViaReflectionByInputSystem(property.GetType()) || property.GetCustomAttributes().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