CompilationFinishedHook.cs 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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 whether 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. EditorUtility.RequestScriptReload();
  52. }
  53. static Assembly FindCompilationPipelineAssembly(string assemblyName) =>
  54. CompilationPipeline.GetAssemblies().First(assembly => assembly.name == assemblyName);
  55. static bool CompilerMessagesContainError(CompilerMessage[] messages) =>
  56. messages.Any(msg => msg.type == CompilerMessageType.Error);
  57. public static void OnCompilationFinished(string assemblyPath, CompilerMessage[] messages)
  58. {
  59. // Do nothing if there were compile errors on the target
  60. if (CompilerMessagesContainError(messages))
  61. {
  62. Debug.Log("Weaver: stop because compile errors on target");
  63. return;
  64. }
  65. // Should not run on the editor only assemblies (test ones still need to be weaved)
  66. if (assemblyPath.Contains("-Editor") ||
  67. (assemblyPath.Contains(".Editor") && !assemblyPath.Contains(".Tests")))
  68. {
  69. return;
  70. }
  71. // skip Mirror.dll because CompilationFinishedHook can't weave itself.
  72. // this would cause a sharing violation.
  73. // skip Mirror.Weaver.dll too.
  74. string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
  75. if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
  76. {
  77. return;
  78. }
  79. // find Mirror.dll
  80. Assembly mirrorAssembly = FindCompilationPipelineAssembly(MirrorRuntimeAssemblyName);
  81. if (mirrorAssembly == null)
  82. {
  83. Debug.LogError("Failed to find Mirror runtime assembly");
  84. return;
  85. }
  86. string mirrorRuntimeDll = mirrorAssembly.outputPath;
  87. if (!File.Exists(mirrorRuntimeDll))
  88. {
  89. // this is normal, it happens with any assembly that is built before mirror
  90. // such as unity packages or your own assemblies
  91. // those don't need to be weaved
  92. // if any assembly depends on mirror, then it will be built after
  93. return;
  94. }
  95. // find UnityEngine.CoreModule.dll
  96. string unityEngineCoreModuleDLL = UnityEditorInternal.InternalEditorUtility.GetEngineCoreModuleAssemblyPath();
  97. if (string.IsNullOrEmpty(unityEngineCoreModuleDLL))
  98. {
  99. Debug.LogError("Failed to find UnityEngine assembly");
  100. return;
  101. }
  102. HashSet<string> dependencyPaths = GetDependencyPaths(assemblyPath);
  103. dependencyPaths.Add(Path.GetDirectoryName(mirrorRuntimeDll));
  104. dependencyPaths.Add(Path.GetDirectoryName(unityEngineCoreModuleDLL));
  105. if (!WeaveFromFile(assemblyPath, dependencyPaths.ToArray()))
  106. {
  107. // Set false...will be checked in \Editor\EnterPlayModeSettingsCheck.CheckSuccessfulWeave()
  108. SessionState.SetBool("MIRROR_WEAVE_SUCCESS", false);
  109. if (UnityLogEnabled) Debug.LogError($"Weaving failed for {assemblyPath}");
  110. }
  111. }
  112. static HashSet<string> GetDependencyPaths(string assemblyPath)
  113. {
  114. // build directory list for later asm/symbol resolving using CompilationPipeline refs
  115. HashSet<string> dependencyPaths = new HashSet<string>
  116. {
  117. Path.GetDirectoryName(assemblyPath)
  118. };
  119. foreach (Assembly assembly in CompilationPipeline.GetAssemblies())
  120. {
  121. if (assembly.outputPath == assemblyPath)
  122. {
  123. foreach (string reference in assembly.compiledAssemblyReferences)
  124. {
  125. dependencyPaths.Add(Path.GetDirectoryName(reference));
  126. }
  127. }
  128. }
  129. return dependencyPaths;
  130. }
  131. // helper function to invoke Weaver with an AssemblyDefinition from a
  132. // file path, with dependencies added.
  133. static bool WeaveFromFile(string assemblyPath, string[] dependencies)
  134. {
  135. // resolve assembly from stream
  136. using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
  137. using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters{ ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
  138. {
  139. // add this assembly's path and unity's assembly path
  140. asmResolver.AddSearchDirectory(Path.GetDirectoryName(assemblyPath));
  141. asmResolver.AddSearchDirectory(Helpers.UnityEngineDllDirectoryName());
  142. // add dependencies
  143. if (dependencies != null)
  144. {
  145. foreach (string path in dependencies)
  146. {
  147. asmResolver.AddSearchDirectory(path);
  148. }
  149. }
  150. // create weaver with logger
  151. weaver = new Weaver(new CompilationFinishedLogger());
  152. if (weaver.Weave(assembly, asmResolver, out bool modified))
  153. {
  154. // write changes to file if modified
  155. if (modified)
  156. assembly.Write(new WriterParameters{WriteSymbols = true});
  157. return true;
  158. }
  159. return false;
  160. }
  161. }
  162. }
  163. }
  164. #endif