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("&", "&").Replace("<", "<").Replace(">", ">");
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