File size: 5,914 Bytes
18a519f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | #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
|