Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .yamato/project.metafile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Editors where tests will happen. The first entry of this array is also used
# for validation
test_editors:
- 2019.4
- 2020.2
- trunk

Expand Down
10 changes: 9 additions & 1 deletion com.unity.multiplayer.mlapi/Editor/CodeGen/CodeGenHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
using Unity.CompilationPipeline.Common.ILPostProcessing;
using UnityEngine;

#if UNITY_2021_1_OR_NEWER
#warning Built-in Unity ILPP needs to be re-enabled after fixing self-referencing assembly reload issue on 2021.1+ and trunk
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to highlight this and explain here a little further.

As @wackoisgod mentioned in the PR description, we want to re-enable Unity's built-in ILPP instead of this workaround:

The goal is that we can re-enable the unity version once self-references are fixed and other issues are resolved.

However, this workaround is still needed for 2019.4 target but 2020.x(LTS)+ will be OK without the workaround implementation and just work fine with Unity ILPP.
Also to overstate a little, we expect reloading self-referencing assemblies to be fixed on 2021.x+ and trunk in the future, hence this compiler warning on 2021.1+

#endif

#if !UNITY_2019_4_OR_NEWER
#error MLAPI requires Unity 2019.4 or newer
#endif

namespace MLAPI.Editor.CodeGen
{
internal static class CodeGenHelpers
Expand Down Expand Up @@ -193,4 +201,4 @@ public static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compile
return assemblyDefinition;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#if !UNITY_2020_2_OR_NEWER || UNITY_2021_1_OR_NEWER
using System.IO;
using Unity.CompilationPipeline.Common.ILPostProcessing;

namespace MLAPI.Editor.CodeGen
{
internal class ILPostProcessCompiledAssembly : ICompiledAssembly
{
private readonly string m_AssemblyFilename;
private readonly string m_OutputPath;
private InMemoryAssembly m_InMemoryAssembly;

public ILPostProcessCompiledAssembly(string asmName, string[] refs, string[] defines, string outputPath)
{
m_AssemblyFilename = asmName;
Name = Path.GetFileNameWithoutExtension(m_AssemblyFilename);
References = refs;
Defines = defines;
m_OutputPath = outputPath;
}

public string Name { get; }
Comment thread
NoelStephensUnity marked this conversation as resolved.
public string[] References { get; }
public string[] Defines { get; }

public InMemoryAssembly InMemoryAssembly
{
get
{
if (m_InMemoryAssembly == null)
{
m_InMemoryAssembly = new InMemoryAssembly(
File.ReadAllBytes(Path.Combine(m_OutputPath, m_AssemblyFilename)),
File.ReadAllBytes(Path.Combine(m_OutputPath, $"{Path.GetFileNameWithoutExtension(m_AssemblyFilename)}.pdb")));
}

return m_InMemoryAssembly;
}
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions com.unity.multiplayer.mlapi/Editor/CodeGen/ILPostProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#if !UNITY_2020_2_OR_NEWER || UNITY_2021_1_OR_NEWER
using Unity.CompilationPipeline.Common.ILPostProcessing;

namespace MLAPI.Editor.CodeGen
{
public abstract class ILPostProcessor
{
public abstract bool WillProcess(ICompiledAssembly compiledAssembly);
public abstract ILPostProcessResult Process(ICompiledAssembly compiledAssembly);
public abstract ILPostProcessor GetInstance();
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

217 changes: 217 additions & 0 deletions com.unity.multiplayer.mlapi/Editor/CodeGen/ILPostProcessorProgram.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#if !UNITY_2020_2_OR_NEWER || UNITY_2021_1_OR_NEWER
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;

using Assembly = System.Reflection.Assembly;

namespace MLAPI.Editor.CodeGen
{
internal static class ILPostProcessProgram
{
private static ILPostProcessor[] ILPostProcessors { get; set; }
Comment thread
NoelStephensUnity marked this conversation as resolved.
Outdated

[InitializeOnLoadMethod]
private static void OnInitializeOnLoad()
{
CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
ILPostProcessors = FindAllPostProcessors();
}

private static ILPostProcessor[] FindAllPostProcessors()
{
var typesDerivedFrom = TypeCache.GetTypesDerivedFrom<ILPostProcessor>();
var localILPostProcessors = new List<ILPostProcessor>(typesDerivedFrom.Count);

foreach (var typeCollection in typesDerivedFrom)
{
try
{
localILPostProcessors.Add((ILPostProcessor)Activator.CreateInstance(typeCollection));
}
catch (Exception exception)
{
Debug.LogError($"Could not create ILPostProcessor ({typeCollection.FullName}):{Environment.NewLine}{exception.StackTrace}");
}
}

// Default sort by type full name
localILPostProcessors.Sort((left, right) => string.Compare(left.GetType().FullName, right.GetType().FullName, StringComparison.Ordinal));

return localILPostProcessors.ToArray();
}

private static void OnCompilationFinished(string targetAssembly, CompilerMessage[] messages)
{
if (messages.Length > 0)
{
if (messages.Any(msg => msg.type == CompilerMessageType.Error))
{
return;
}
}

// Should not run on the editor only assemblies
if (targetAssembly.Contains("-Editor") || targetAssembly.Contains(".Editor"))
{
return;
}

// Should not run on Unity Engine modules but we can run on the MLAPI Runtime DLL
if ((targetAssembly.Contains("com.unity") || Path.GetFileName(targetAssembly).StartsWith("Unity")) && !targetAssembly.Contains("Unity.Multiplayer."))
{
return;
}

// Debug.Log($"Running MLAPI ILPP on {targetAssembly}");

var outputDirectory = $"{Application.dataPath}/../{Path.GetDirectoryName(targetAssembly)}";
var unityEngine = string.Empty;
var mlapiRuntimeAssemblyPath = string.Empty;
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
var usesMLAPI = false;
var foundThisAssembly = false;

var depenencyPaths = new List<string>();
foreach (var assembly in assemblies)
{
// Find the assembly currently being compiled from domain assembly list and check if it's using unet
if (assembly.GetName().Name == Path.GetFileNameWithoutExtension(targetAssembly))
{
foundThisAssembly = true;
foreach (var dependency in assembly.GetReferencedAssemblies())
{
// Since this assembly is already loaded in the domain this is a no-op and returns the
// already loaded assembly
depenencyPaths.Add(Assembly.Load(dependency).Location);
if (dependency.Name.Contains(CodeGenHelpers.RuntimeAssemblyName))
{
usesMLAPI = true;
}
}
}

try
{
if (assembly.Location.Contains("UnityEngine.CoreModule"))
{
unityEngine = assembly.Location;
}

if (assembly.Location.Contains(CodeGenHelpers.RuntimeAssemblyName))
{
mlapiRuntimeAssemblyPath = assembly.Location;
}
}
catch (NotSupportedException)
{
// in memory assembly, can't get location
}
}

if (!foundThisAssembly)
{
// Target assembly not found in current domain, trying to load it to check references
// will lead to trouble in the build pipeline, so lets assume it should go to weaver.
// Add all assemblies in current domain to dependency list since there could be a
// dependency lurking there (there might be generated assemblies so ignore file not found exceptions).
// (can happen in runtime test framework on editor platform and when doing full library reimport)
foreach (var assembly in assemblies)
{
try
{
if (!(assembly.ManifestModule is System.Reflection.Emit.ModuleBuilder))
{
depenencyPaths.Add(Assembly.Load(assembly.GetName().Name).Location);
}
}
catch (FileNotFoundException)
{
}
}

usesMLAPI = true;
}

// We check if we are the MLAPI!
if (!usesMLAPI)
{
// we shall also check and see if it we are ourself
usesMLAPI = targetAssembly.Contains(CodeGenHelpers.RuntimeAssemblyName);
}

if (!usesMLAPI)
{
return;
}

if (string.IsNullOrEmpty(unityEngine))
{
Debug.LogError("Failed to find UnityEngine assembly");
return;
}

if (string.IsNullOrEmpty(mlapiRuntimeAssemblyPath))
{
Debug.LogError("Failed to find mlapi runtime assembly");
return;
}

var assemblyPathName = Path.GetFileName(targetAssembly);

var targetCompiledAssembly = new ILPostProcessCompiledAssembly(assemblyPathName, depenencyPaths.ToArray(), null, outputDirectory);

void WriteAssembly(InMemoryAssembly inMemoryAssembly, string outputPath, string assName)
{
if (inMemoryAssembly == null)
{
throw new ArgumentException("InMemoryAssembly has never been accessed or modified");
}

var asmPath = Path.Combine(outputPath, assName);
var pdbFileName = $"{Path.GetFileNameWithoutExtension(assName)}.pdb";
var pdbPath = Path.Combine(outputPath, pdbFileName);

File.WriteAllBytes(asmPath, inMemoryAssembly.PeData);
File.WriteAllBytes(pdbPath, inMemoryAssembly.PdbData);
}

foreach (var i in ILPostProcessors)
{
var result = i.Process(targetCompiledAssembly);
if (result == null)
continue;

if (result.Diagnostics.Count > 0)
{
Debug.LogError($"ILPostProcessor - {i.GetType().Name} failed to run on {targetCompiledAssembly.Name}");

foreach (var message in result.Diagnostics)
{
switch (message.DiagnosticType)
{
case DiagnosticType.Error:
Debug.LogError($"ILPostProcessor Error - {message.MessageData} {message.File} {message.Line} {message.Column}");
break;
case DiagnosticType.Warning:
Debug.LogWarning($"ILPostProcessor Warning - {message.MessageData} {message.File} {message.Line} {message.Column}");
break;
}
}

continue;
}

// we now need to write out the result?
WriteAssembly(result.InMemoryAssembly, outputDirectory, assemblyPathName);
}
}
}
}
#endif

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading