暫無描述
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LinkFileGenerator.cs 5.8KB

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