CompilationFinishedHook.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // for Unity 2020+ we use ILPostProcessor.
  2. // only automatically invoke it for older versions.
  3. #if !UNITY_2020_3_OR_NEWER
  4. using System;
  5. using System.Collections.Generic;
  6. using System.IO;
  7. using System.Linq;
  8. using Mono.CecilX;
  9. using UnityEditor;
  10. using UnityEditor.Compilation;
  11. using UnityEngine;
  12. using UnityAssembly = UnityEditor.Compilation.Assembly;
  13. namespace Mirror.Weaver
  14. {
  15. public static class CompilationFinishedHook
  16. {
  17. // needs to be the same as Weaver.MirrorAssemblyName!
  18. const string MirrorRuntimeAssemblyName = "Mirror";
  19. const string MirrorWeaverAssemblyName = "Mirror.Weaver";
  20. // global weaver define so that tests can use it
  21. internal static Weaver weaver;
  22. // delegate for subscription to Weaver warning messages
  23. public static Action<string> OnWeaverWarning;
  24. // delete for subscription to Weaver error messages
  25. public static Action<string> OnWeaverError;
  26. // controls weather Weaver errors are reported direct to the Unity console (tests enable this)
  27. public static bool UnityLogEnabled = true;
  28. [InitializeOnLoadMethod]
  29. public static void OnInitializeOnLoad()
  30. {
  31. CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
  32. // We only need to run this once per session
  33. // after that, all assemblies will be weaved by the event
  34. if (!SessionState.GetBool("MIRROR_WEAVED", false))
  35. {
  36. // reset session flag
  37. SessionState.SetBool("MIRROR_WEAVED", true);
  38. SessionState.SetBool("MIRROR_WEAVE_SUCCESS", true);
  39. WeaveExistingAssemblies();
  40. }
  41. }
  42. public static void WeaveExistingAssemblies()
  43. {
  44. foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
  45. {
  46. if (File.Exists(assembly.outputPath))
  47. {
  48. OnCompilationFinished(assembly.outputPath, new CompilerMessage[0]);
  49. }
  50. }
  51. #if UNITY_2019_3_OR_NEWER
  52. EditorUtility.RequestScriptReload();
  53. #else
  54. UnityEditorInternal.InternalEditorUtility.RequestScriptReload();
  55. #endif
  56. }
  57. static Assembly FindCompilationPipelineAssembly(string assemblyName) =>
  58. CompilationPipeline.GetAssemblies().First(assembly => assembly.name == assemblyName);
  59. static bool CompilerMessagesContainError(CompilerMessage[] messages) =>
  60. messages.Any(msg => msg.type == CompilerMessageType.Error);
  61. public static void OnCompilationFinished(string assemblyPath, CompilerMessage[] messages)
  62. {
  63. // Do nothing if there were compile errors on the target
  64. if (CompilerMessagesContainError(messages))
  65. {
  66. Debug.Log("Weaver: stop because compile errors on target");
  67. return;
  68. }
  69. // Should not run on the editor only assemblies
  70. if (assemblyPath.Contains("-Editor") || assemblyPath.Contains(".Editor"))
  71. {
  72. return;
  73. }
  74. // don't weave mirror files
  75. string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
  76. if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
  77. {
  78. return;
  79. }
  80. // find Mirror.dll
  81. Assembly mirrorAssembly = FindCompilationPipelineAssembly(MirrorRuntimeAssemblyName);
  82. if (mirrorAssembly == null)
  83. {
  84. Debug.LogError("Failed to find Mirror runtime assembly");
  85. return;
  86. }
  87. string mirrorRuntimeDll = mirrorAssembly.outputPath;
  88. if (!File.Exists(mirrorRuntimeDll))
  89. {
  90. // this is normal, it happens with any assembly that is built before mirror
  91. // such as unity packages or your own assemblies
  92. // those don't need to be weaved
  93. // if any assembly depends on mirror, then it will be built after
  94. return;
  95. }
  96. // find UnityEngine.CoreModule.dll
  97. string unityEngineCoreModuleDLL = UnityEditorInternal.InternalEditorUtility.GetEngineCoreModuleAssemblyPath();
  98. if (string.IsNullOrEmpty(unityEngineCoreModuleDLL))
  99. {
  100. Debug.LogError("Failed to find UnityEngine assembly");
  101. return;
  102. }
  103. HashSet<string> dependencyPaths = GetDependencyPaths(assemblyPath);
  104. dependencyPaths.Add(Path.GetDirectoryName(mirrorRuntimeDll));
  105. dependencyPaths.Add(Path.GetDirectoryName(unityEngineCoreModuleDLL));
  106. if (!WeaveFromFile(assemblyPath, dependencyPaths.ToArray()))
  107. {
  108. // Set false...will be checked in \Editor\EnterPlayModeSettingsCheck.CheckSuccessfulWeave()
  109. SessionState.SetBool("MIRROR_WEAVE_SUCCESS", false);
  110. if (UnityLogEnabled) Debug.LogError($"Weaving failed for {assemblyPath}");
  111. }
  112. }
  113. static HashSet<string> GetDependencyPaths(string assemblyPath)
  114. {
  115. // build directory list for later asm/symbol resolving using CompilationPipeline refs
  116. HashSet<string> dependencyPaths = new HashSet<string>
  117. {
  118. Path.GetDirectoryName(assemblyPath)
  119. };
  120. foreach (Assembly assembly in CompilationPipeline.GetAssemblies())
  121. {
  122. if (assembly.outputPath == assemblyPath)
  123. {
  124. foreach (string reference in assembly.compiledAssemblyReferences)
  125. {
  126. dependencyPaths.Add(Path.GetDirectoryName(reference));
  127. }
  128. }
  129. }
  130. return dependencyPaths;
  131. }
  132. // helper function to invoke Weaver with an AssemblyDefinition from a
  133. // file path, with dependencies added.
  134. static bool WeaveFromFile(string assemblyPath, string[] dependencies)
  135. {
  136. // resolve assembly from stream
  137. using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
  138. using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters{ ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
  139. {
  140. // add this assembly's path and unity's assembly path
  141. asmResolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath));
  142. asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName());
  143. // add dependencies
  144. if (dependencies != null)
  145. {
  146. foreach (string path in dependencies)
  147. {
  148. asmResolver.AddSearchDirectory(path);
  149. }
  150. }
  151. // create weaver with logger
  152. weaver = new Weaver(new CompilationFinishedLogger());
  153. if (weaver.Weave(assembly, asmResolver, out bool modified))
  154. {
  155. // write changes to file if modified
  156. if (modified)
  157. assembly.Write(new WriterParameters{WriteSymbols = true});
  158. return true;
  159. }
  160. return false;
  161. }
  162. }
  163. }
  164. }
  165. #endif