1
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
using System.Linq;
|
||||
using FastScriptReload.Runtime;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class ConstructorRewriter : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
private readonly bool _adjustCtorOnlyForNonNestedTypes;
|
||||
|
||||
public ConstructorRewriter(bool adjustCtorOnlyForNonNestedTypes, bool writeRewriteReasonAsComment)
|
||||
: base(writeRewriteReasonAsComment)
|
||||
{
|
||||
_adjustCtorOnlyForNonNestedTypes = adjustCtorOnlyForNonNestedTypes;
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
|
||||
{
|
||||
if (_adjustCtorOnlyForNonNestedTypes)
|
||||
{
|
||||
var typeNestedLevel = node.Ancestors().Count(a => a is TypeDeclarationSyntax);
|
||||
if (typeNestedLevel == 1)
|
||||
{
|
||||
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
|
||||
}
|
||||
|
||||
return base.VisitConstructorDeclaration(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node)
|
||||
{
|
||||
if (_adjustCtorOnlyForNonNestedTypes)
|
||||
{
|
||||
var typeNestedLevel = node.Ancestors().Count(a => a is TypeDeclarationSyntax);
|
||||
if (typeNestedLevel == 1)
|
||||
{
|
||||
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
|
||||
}
|
||||
|
||||
return base.VisitDestructorDeclaration(node);
|
||||
}
|
||||
|
||||
private SyntaxNode AdjustCtorOrDestructorNameForTypeAdjustment(BaseMethodDeclarationSyntax node, SyntaxToken nodeIdentifier)
|
||||
{
|
||||
var typeName = (node.Ancestors().First(n => n is TypeDeclarationSyntax) as TypeDeclarationSyntax).Identifier.ToString();
|
||||
if (!nodeIdentifier.ToFullString().Contains(typeName))
|
||||
{
|
||||
//Used Roslyn version bug, some static methods are also interpreted as ctors, eg
|
||||
// public static void Method()
|
||||
// {
|
||||
// Bar(); //treated as Ctor declaration...
|
||||
// }
|
||||
//
|
||||
// private static void Bar()
|
||||
// {
|
||||
//
|
||||
// }
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!typeName.EndsWith(AssemblyChangesLoader.ClassnamePatchedPostfix))
|
||||
{
|
||||
typeName += AssemblyChangesLoader.ClassnamePatchedPostfix;
|
||||
}
|
||||
|
||||
return AddRewriteCommentIfNeeded(
|
||||
node.ReplaceToken(nodeIdentifier, SyntaxFactory.Identifier(typeName)),
|
||||
$"{nameof(ConstructorRewriter)}:{nameof(AdjustCtorOrDestructorNameForTypeAdjustment)}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,150 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class CreateNewFieldInitMethodRewriter: FastScriptReloadCodeRewriterBase {
|
||||
private readonly Dictionary<string, List<string>> _typeToNewFieldDeclarations;
|
||||
private static readonly string NewFieldsToCreateValueFnDictionaryFieldName = "__Patched_NewFieldNameToInitialValueFn";
|
||||
private static readonly string NewFieldsToGetTypeFnDictionaryFieldName = "__Patched_NewFieldsToGetTypeFnDictionaryFieldName";
|
||||
private static readonly string DictionaryFullNamespaceTypeName = "System.Collections.Generic.Dictionary";
|
||||
|
||||
public static Dictionary<string, Func<object>> ResolveNewFieldsToCreateValueFn(Type forType)
|
||||
{
|
||||
return (Dictionary<string, Func<object>>) forType.GetField(NewFieldsToCreateValueFnDictionaryFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
|
||||
}
|
||||
|
||||
public static Dictionary<string, Func<object>> ResolveNewFieldsToTypeFn(Type forType)
|
||||
{
|
||||
return (Dictionary<string, Func<object>>) forType.GetField(NewFieldsToGetTypeFnDictionaryFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
|
||||
}
|
||||
|
||||
public CreateNewFieldInitMethodRewriter(Dictionary<string, List<string>> typeToNewFieldDeclarations, bool writeRewriteReasonAsComment)
|
||||
:base(writeRewriteReasonAsComment)
|
||||
{
|
||||
_typeToNewFieldDeclarations = typeToNewFieldDeclarations;
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
|
||||
{
|
||||
var fullClassName = RoslynUtils.GetMemberFQDN(node, node.Identifier.ToString());
|
||||
if (!_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var newClassFields))
|
||||
{
|
||||
LoggerScoped.LogWarning($"Unable to find new-fields for type: {fullClassName}, this is not an issue if there are no new fields for that type.");
|
||||
}
|
||||
|
||||
Func<FieldDeclarationSyntax, ExpressionSyntax> getObjectFnSyntax = fieldDeclarationNode => fieldDeclarationNode.Declaration.Variables[0].Initializer?.Value //value captured from initializer
|
||||
?? SyntaxFactory.DefaultExpression(SyntaxFactory.IdentifierName(fieldDeclarationNode.Declaration.Type.ToString()));
|
||||
var withDictionaryFieldNameToInitFieldValue = CreateNewFieldNameToGetObjectFnDictionary(node, newClassFields, getObjectFnSyntax, NewFieldsToCreateValueFnDictionaryFieldName);
|
||||
|
||||
Func<FieldDeclarationSyntax, ExpressionSyntax> getObjectTypeFnSyntax = fieldDeclarationNode => SyntaxFactory.TypeOfExpression(fieldDeclarationNode.Declaration.Type);
|
||||
return CreateNewFieldNameToGetObjectFnDictionary(withDictionaryFieldNameToInitFieldValue, newClassFields, getObjectTypeFnSyntax, NewFieldsToGetTypeFnDictionaryFieldName);
|
||||
}
|
||||
|
||||
private ClassDeclarationSyntax CreateNewFieldNameToGetObjectFnDictionary(ClassDeclarationSyntax node,
|
||||
List<string> newClassFields, Func<FieldDeclarationSyntax, ExpressionSyntax> getObjectFnSyntax, string dictionaryFieldName)
|
||||
{
|
||||
var dictionaryKeyToInitValueNodes = newClassFields.SelectMany(fieldName =>
|
||||
{
|
||||
var fieldDeclarationNode = node.ChildNodes().OfType<FieldDeclarationSyntax>()
|
||||
.Single(f => f.Declaration.Variables.First().Identifier.ToString() == fieldName);
|
||||
|
||||
return new[]
|
||||
{
|
||||
(SyntaxNodeOrToken)SyntaxFactory.AssignmentExpression(
|
||||
SyntaxKind.SimpleAssignmentExpression,
|
||||
SyntaxFactory.ImplicitElementAccess()
|
||||
.WithArgumentList(
|
||||
SyntaxFactory.BracketedArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.LiteralExpression(
|
||||
SyntaxKind.StringLiteralExpression,
|
||||
SyntaxFactory.Literal(fieldDeclarationNode.Declaration.Variables.First()
|
||||
.Identifier.ToString())))))), //variable name
|
||||
SyntaxFactory.ParenthesizedLambdaExpression(getObjectFnSyntax(fieldDeclarationNode))),
|
||||
SyntaxFactory.Token(SyntaxKind.CommaToken) //comma, add for all
|
||||
};
|
||||
}).ToArray();
|
||||
|
||||
|
||||
var dictionaryInitializer =
|
||||
SyntaxFactory.InitializerExpression(
|
||||
SyntaxKind.ObjectInitializerExpression,
|
||||
SyntaxFactory.SeparatedList<ExpressionSyntax>(
|
||||
dictionaryKeyToInitValueNodes.ToArray()
|
||||
));
|
||||
|
||||
var withDictionaryFieldNameToInitFieldValue = node.AddMembers(
|
||||
SyntaxFactory.FieldDeclaration(
|
||||
SyntaxFactory.VariableDeclaration(
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier(DictionaryFullNamespaceTypeName))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SeparatedList<TypeSyntax>(
|
||||
new SyntaxNodeOrToken[]
|
||||
{
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.StringKeyword)),
|
||||
SyntaxFactory.Token(SyntaxKind.CommaToken),
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier("System.Func"))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<TypeSyntax>(
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.ObjectKeyword)))))
|
||||
}))))
|
||||
.WithVariables(
|
||||
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
|
||||
SyntaxFactory.VariableDeclarator(
|
||||
SyntaxFactory.Identifier(dictionaryFieldName))
|
||||
.WithInitializer(
|
||||
SyntaxFactory.EqualsValueClause(
|
||||
SyntaxFactory.ObjectCreationExpression(
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier(DictionaryFullNamespaceTypeName))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SeparatedList<TypeSyntax>(
|
||||
new SyntaxNodeOrToken[]
|
||||
{
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.StringKeyword)),
|
||||
SyntaxFactory.Token(SyntaxKind.CommaToken),
|
||||
SyntaxFactory.QualifiedName(
|
||||
SyntaxFactory.IdentifierName("System"),
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier("Func"))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory
|
||||
.SingletonSeparatedList<
|
||||
TypeSyntax>(
|
||||
SyntaxFactory
|
||||
.PredefinedType(
|
||||
SyntaxFactory.Token(
|
||||
SyntaxKind
|
||||
.ObjectKeyword))))))
|
||||
}))))
|
||||
.WithInitializer(dictionaryInitializer))))))
|
||||
.WithTriviaFrom(node)
|
||||
.WithModifiers(
|
||||
SyntaxFactory.TokenList(
|
||||
SyntaxFactory.Token(SyntaxKind.PrivateKeyword),
|
||||
SyntaxFactory.Token(SyntaxKind.StaticKeyword)))
|
||||
.NormalizeWhitespace()
|
||||
.WithLeadingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed))
|
||||
.WithTrailingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed, SyntaxFactory.ElasticCarriageReturnLineFeed))
|
||||
);
|
||||
return AddRewriteCommentIfNeeded(withDictionaryFieldNameToInitFieldValue, $"{nameof(CreateNewFieldInitMethodRewriter)}", true);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
public abstract class FastScriptReloadCodeRewriterBase : CSharpSyntaxRewriter
|
||||
{
|
||||
protected readonly bool _writeRewriteReasonAsComment;
|
||||
|
||||
protected FastScriptReloadCodeRewriterBase(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false) : base(visitIntoStructuredTrivia)
|
||||
{
|
||||
_writeRewriteReasonAsComment = writeRewriteReasonAsComment;
|
||||
}
|
||||
|
||||
protected SyntaxToken AddRewriteCommentIfNeeded(SyntaxToken syntaxToken, string commentText, bool append = false)
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(syntaxToken, commentText, _writeRewriteReasonAsComment, append);
|
||||
}
|
||||
|
||||
public static SyntaxToken AddRewriteCommentIfNeeded(SyntaxToken syntaxToken, string commentText, bool writeRewriteReasonAsComment, bool append)
|
||||
{
|
||||
if (writeRewriteReasonAsComment)
|
||||
{
|
||||
if (append)
|
||||
{
|
||||
return syntaxToken.WithLeadingTrivia(
|
||||
syntaxToken.LeadingTrivia.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
|
||||
}
|
||||
else
|
||||
{
|
||||
return syntaxToken.WithTrailingTrivia(
|
||||
syntaxToken.TrailingTrivia.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxToken;
|
||||
}
|
||||
|
||||
protected T AddRewriteCommentIfNeeded<T>(T syntaxNode, string commentText, bool append = false)
|
||||
where T : SyntaxNode
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(syntaxNode, commentText, _writeRewriteReasonAsComment, append);
|
||||
}
|
||||
|
||||
public static T AddRewriteCommentIfNeeded<T>(T syntaxNode, string commentText, bool writeRewriteReasonAsComment, bool append) where T : SyntaxNode
|
||||
{
|
||||
if (writeRewriteReasonAsComment)
|
||||
{
|
||||
if (append)
|
||||
{
|
||||
return syntaxNode.WithLeadingTrivia(syntaxNode.GetLeadingTrivia()
|
||||
.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
|
||||
}
|
||||
else
|
||||
{
|
||||
return syntaxNode.WithTrailingTrivia(syntaxNode.GetTrailingTrivia()
|
||||
.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxNode;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class FieldsWalker : CSharpSyntaxWalker {
|
||||
private readonly Dictionary<string, List<NewFieldDeclaration>> _typeNameToFieldDeclarations = new Dictionary<string, List<NewFieldDeclaration>>();
|
||||
|
||||
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
|
||||
{
|
||||
var className = node.Identifier;
|
||||
var fullClassName = RoslynUtils.GetMemberFQDN(node, className.ToString());
|
||||
if(!_typeNameToFieldDeclarations.ContainsKey(fullClassName)) {
|
||||
_typeNameToFieldDeclarations[fullClassName] = new List<NewFieldDeclaration>();
|
||||
}
|
||||
|
||||
base.VisitClassDeclaration(node);
|
||||
}
|
||||
|
||||
public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
|
||||
{
|
||||
var fieldName = node.Declaration.Variables.First().Identifier.ToString();
|
||||
var fullClassName = RoslynUtils.GetMemberFQDNWithoutMemberName(node);
|
||||
if(!_typeNameToFieldDeclarations.ContainsKey(fullClassName)) {
|
||||
_typeNameToFieldDeclarations[fullClassName] = new List<NewFieldDeclaration>();
|
||||
}
|
||||
|
||||
_typeNameToFieldDeclarations[fullClassName].Add(new NewFieldDeclaration(fieldName, node.Declaration.Type.ToString(), node));
|
||||
|
||||
base.VisitFieldDeclaration(node);
|
||||
}
|
||||
|
||||
public Dictionary<string, List<NewFieldDeclaration>> GetTypeToFieldDeclarations() {
|
||||
return _typeNameToFieldDeclarations;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using FastScriptReload.Runtime;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class HotReloadCompliantRewriter : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
public List<string> StrippedUsingDirectives = new List<string>();
|
||||
|
||||
public HotReloadCompliantRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
//if subclasses need to be adjusted, it's done via recursion.
|
||||
// foreach (var childNode in node.ChildNodes().OfType<ClassDeclarationSyntax>())
|
||||
// {
|
||||
// var changed = Visit(childNode);
|
||||
// node = node.ReplaceNode(childNode, changed);
|
||||
// }
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node)
|
||||
{
|
||||
if (node.Parent is CompilationUnitSyntax)
|
||||
{
|
||||
StrippedUsingDirectives.Add(node.ToFullString());
|
||||
return null;
|
||||
}
|
||||
|
||||
return base.VisitUsingDirective(node);
|
||||
}
|
||||
|
||||
private SyntaxNode AddPatchedPostfixToTopLevelDeclarations(CSharpSyntaxNode node, SyntaxToken identifier)
|
||||
{
|
||||
var newIdentifier = SyntaxFactory.Identifier(identifier + AssemblyChangesLoader.ClassnamePatchedPostfix);
|
||||
newIdentifier = AddRewriteCommentIfNeeded(newIdentifier, $"{nameof(HotReloadCompliantRewriter)}:{nameof(AddPatchedPostfixToTopLevelDeclarations)}");
|
||||
node = node.ReplaceToken(identifier, newIdentifier);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,127 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
public class ManualUserDefinedScriptOverridesRewriter : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
private readonly SyntaxNode _userDefinedOverridesRoot;
|
||||
|
||||
public ManualUserDefinedScriptOverridesRewriter(SyntaxNode userDefinedOverridesRoot, bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
_userDefinedOverridesRoot = userDefinedOverridesRoot;
|
||||
}
|
||||
|
||||
//TODO: refactor to use OverrideDeclarationWithMatchingUserDefinedIfExists
|
||||
public override SyntaxNode VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node)
|
||||
{
|
||||
var methodFQDN = RoslynUtils.GetMemberFQDN(node, "operator");
|
||||
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
|
||||
//implicit conversion operators do not have name, just parameter list
|
||||
.OfType<BaseMethodDeclarationSyntax>()
|
||||
.FirstOrDefault(m => m.ParameterList.ToString() == node.ParameterList.ToString() //parameter lists is type / order / names, all good for targetting if there's a proper match
|
||||
&& methodFQDN == RoslynUtils.GetMemberFQDN(m, "operator") //make sure same FQDN, even though there's no name there could be more implicit operators in file
|
||||
);
|
||||
|
||||
if (matchingInOverride != null)
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node), $"User defined custom conversion override", true);
|
||||
}
|
||||
else {
|
||||
return base.VisitConversionOperatorDeclaration(node);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: refactor to use OverrideDeclarationWithMatchingUserDefinedIfExists
|
||||
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
|
||||
{
|
||||
var methodName = node.Identifier.ValueText;
|
||||
var methodFQDN = RoslynUtils.GetMemberFQDN(node, node.Identifier.ToString());
|
||||
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
|
||||
.OfType<MethodDeclarationSyntax>()
|
||||
.FirstOrDefault(m => m.Identifier.ValueText == methodName
|
||||
&& m.ParameterList.Parameters.Count == node.ParameterList.Parameters.Count
|
||||
&& m.ParameterList.ToString() == node.ParameterList.ToString() //parameter lists is type / order / names, all good for targetting if there's a proper match
|
||||
&& m.TypeParameterList?.ToString() == node.TypeParameterList?.ToString() //typed paratemets are for generics, also check
|
||||
&& methodFQDN == RoslynUtils.GetMemberFQDN(m, m.Identifier.ToString()) //last check for mathod FQDN (potentially slower than others)
|
||||
);
|
||||
|
||||
if (matchingInOverride != null)
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node), $"User defined custom method override", true);
|
||||
}
|
||||
else {
|
||||
return base.VisitMethodDeclaration(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
|
||||
{
|
||||
return OverrideDeclarationWithMatchingUserDefinedIfExists(
|
||||
node,
|
||||
(d) => d.Identifier.ValueText,
|
||||
(d) => HasSameParametersPredicate(node.ParameterList)(d.ParameterList),
|
||||
(d) => base.VisitConstructorDeclaration(d)
|
||||
);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node)
|
||||
{
|
||||
return OverrideDeclarationWithMatchingUserDefinedIfExists(
|
||||
node,
|
||||
(d) => d.Identifier.ValueText,
|
||||
(d) => HasSameParametersPredicate(node.ParameterList)(d.ParameterList),
|
||||
(d) => base.VisitDestructorDeclaration(d)
|
||||
);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
|
||||
{
|
||||
return OverrideDeclarationWithMatchingUserDefinedIfExists(
|
||||
node,
|
||||
(d) => d.Identifier.ValueText,
|
||||
(d) => true,
|
||||
(d) => base.VisitPropertyDeclaration(d)
|
||||
);
|
||||
}
|
||||
|
||||
private SyntaxNode OverrideDeclarationWithMatchingUserDefinedIfExists<T>(T node, Func<T, string> getName,
|
||||
Func<T, bool> customFindMatchInOverridePredicate, Func<T, SyntaxNode> visitDefault)
|
||||
where T: MemberDeclarationSyntax
|
||||
{
|
||||
var name = getName(node);
|
||||
var fqdn = RoslynUtils.GetMemberFQDN(node, name);
|
||||
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
|
||||
.OfType<T>()
|
||||
.FirstOrDefault(d =>
|
||||
{
|
||||
var declarationName = getName(d);
|
||||
return declarationName == name
|
||||
&& customFindMatchInOverridePredicate(d)
|
||||
&& fqdn == RoslynUtils.GetMemberFQDN(d, declarationName); //last check for mathod FQDN (potentially slower than others)
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
if (matchingInOverride != null)
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node),
|
||||
$"User defined custom {typeof(T)} override", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return visitDefault(node);
|
||||
}
|
||||
}
|
||||
|
||||
private Func<ParameterListSyntax, bool> HasSameParametersPredicate(ParameterListSyntax parameters)
|
||||
{
|
||||
return (resolvedParams) => resolvedParams.Parameters.Count == parameters.Parameters.Count
|
||||
&& resolvedParams.ToString() == parameters.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
public class NewFieldDeclaration
|
||||
{
|
||||
public string FieldName { get; }
|
||||
public string TypeName { get; }
|
||||
public FieldDeclarationSyntax FieldDeclarationSyntax { get; } //TODO: PERF: will that block whole tree from being garbage collected
|
||||
|
||||
public NewFieldDeclaration(string fieldName, string typeName, FieldDeclarationSyntax fieldDeclarationSyntax)
|
||||
{
|
||||
FieldName = fieldName;
|
||||
TypeName = typeName;
|
||||
FieldDeclarationSyntax = fieldDeclarationSyntax;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,135 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FastScriptReload.Runtime;
|
||||
using FastScriptReload.Scripts.Runtime;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class NewFieldsRewriter : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
private readonly Dictionary<string, List<string>> _typeToNewFieldDeclarations;
|
||||
|
||||
public NewFieldsRewriter(Dictionary<string, List<string>> typeToNewFieldDeclarations, bool writeRewriteReasonAsComment)
|
||||
:base(writeRewriteReasonAsComment)
|
||||
{
|
||||
_typeToNewFieldDeclarations = typeToNewFieldDeclarations;
|
||||
}
|
||||
|
||||
public static List<MemberInfo> GetReplaceableMembers(Type type)
|
||||
{ //TODO: later other might need to be included? props?
|
||||
return type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Cast<MemberInfo>().ToList();
|
||||
}
|
||||
|
||||
|
||||
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
|
||||
{
|
||||
if (node.Expression.ToString() == "nameof")
|
||||
{
|
||||
var classNode = node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
||||
if (classNode != null)
|
||||
{
|
||||
var fullClassName = RoslynUtils.GetMemberFQDN(classNode, classNode.Identifier.ToString());
|
||||
if (!string.IsNullOrEmpty(fullClassName))
|
||||
{
|
||||
var nameofExpressionParts = node.ArgumentList.Arguments.First().ToFullString().Split('.'); //nameof could have multiple . like NewFieldCustomClass.FieldInThatClass
|
||||
var fieldName = nameofExpressionParts.First(); // should take first part only to determine if new field eg. 'NewFieldCustomClass'
|
||||
if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var allNewFieldNamesForClass))
|
||||
{
|
||||
if (allNewFieldNamesForClass.Contains(fieldName))
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(
|
||||
SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(nameofExpressionParts.Last())), // should take last part only to for actual string eg. 'FieldInThatClass'
|
||||
$"{nameof(NewFieldsRewriter)}:{nameof(VisitInvocationExpression)}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitInvocationExpression(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
|
||||
{
|
||||
var classNode = node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
||||
if (classNode != null)
|
||||
{
|
||||
var fullClassName = RoslynUtils.GetMemberFQDN(classNode, classNode.Identifier.ToString());
|
||||
if (!string.IsNullOrEmpty(fullClassName))
|
||||
{
|
||||
var fieldName = node.Identifier.ToString();
|
||||
if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var allNewFieldNamesForClass))
|
||||
{
|
||||
if (allNewFieldNamesForClass.Contains(fieldName))
|
||||
{
|
||||
var isNameOfExpression = node.Ancestors().OfType<InvocationExpressionSyntax>().Any(e => e.Expression.ToString() == "nameof");
|
||||
if (!isNameOfExpression) //nameof expression will be rewritten via VisitInvocationExpression
|
||||
{
|
||||
return
|
||||
AddRewriteCommentIfNeeded(
|
||||
SyntaxFactory.MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.InvocationExpression(
|
||||
SyntaxFactory.MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.IdentifierName(typeof(TemporaryNewFieldValues).FullName),
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier(nameof(TemporaryNewFieldValues.ResolvePatchedObject)))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<TypeSyntax>(
|
||||
SyntaxFactory.IdentifierName(fullClassName + AssemblyChangesLoader.ClassnamePatchedPostfix))))))
|
||||
.WithArgumentList(
|
||||
SyntaxFactory.ArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.ThisExpression())))),
|
||||
SyntaxFactory.IdentifierName(fieldName))
|
||||
.WithTriviaFrom(node),
|
||||
$"{nameof(NewFieldsRewriter)}:{nameof(VisitIdentifierName)}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerScoped.LogWarning($"Unable to find type: {fullClassName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitIdentifierName(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
|
||||
{
|
||||
var fieldName = node.Declaration.Variables.First().Identifier.ToString();
|
||||
var fullClassName = RoslynUtils.GetMemberFQDNWithoutMemberName(node);
|
||||
|
||||
if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var newFields))
|
||||
{
|
||||
if (newFields.Contains(fieldName))
|
||||
{
|
||||
var existingLeading = node.GetLeadingTrivia();
|
||||
var existingTrailing = node.GetTrailingTrivia();
|
||||
|
||||
return AddRewriteCommentIfNeeded(
|
||||
node
|
||||
.WithLeadingTrivia(existingLeading.Add(SyntaxFactory.Comment("/* ")))
|
||||
.WithTrailingTrivia(existingTrailing.Insert(0, SyntaxFactory.Comment(" */ //Auto-excluded to prevent exceptions - see docs"))),
|
||||
$"{nameof(NewFieldsRewriter)}:{nameof(VisitFieldDeclaration)}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitFieldDeclaration(node);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
public class RoslynUtils
|
||||
{
|
||||
public static string GetMemberFQDN(MemberDeclarationSyntax memberNode, string memberName) //TODO: try get rid of member name (needs to cast to whatever member it could be to get identifier)
|
||||
{
|
||||
var outer = GetMemberFQDNWithoutMemberName(memberNode);
|
||||
return !string.IsNullOrEmpty(outer)
|
||||
? $"{outer}.{memberName}"
|
||||
: memberName;
|
||||
}
|
||||
|
||||
public static string GetMemberFQDNWithoutMemberName(MemberDeclarationSyntax memberNode) //TODO: move out to helper class
|
||||
{
|
||||
var fullTypeContibutingAncestorNames = memberNode.Ancestors().OfType<MemberDeclarationSyntax>().Select(da =>
|
||||
{
|
||||
if (da is TypeDeclarationSyntax t) return t.Identifier.ToString();
|
||||
else if (da is NamespaceDeclarationSyntax n) return n.Name.ToString();
|
||||
else throw new Exception("Unable to resolve full field name");
|
||||
}).Reverse().ToList();
|
||||
|
||||
return string.Join(".", fullTypeContibutingAncestorNames);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class ThisAssignmentRewriter: ThisRewriterBase {
|
||||
public ThisAssignmentRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitThisExpression(ThisExpressionSyntax node)
|
||||
{
|
||||
if (node.Parent is AssignmentExpressionSyntax) {
|
||||
return CreateCastedThisExpression(node);
|
||||
}
|
||||
|
||||
return base.VisitThisExpression(node);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,22 +0,0 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class ThisCallRewriter : ThisRewriterBase
|
||||
{
|
||||
public ThisCallRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitThisExpression(ThisExpressionSyntax node)
|
||||
{
|
||||
if (node.Parent is ArgumentSyntax)
|
||||
{
|
||||
return CreateCastedThisExpression(node);
|
||||
}
|
||||
return base.VisitThisExpression(node);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
using System.Linq;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
abstract class ThisRewriterBase : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
protected ThisRewriterBase(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
}
|
||||
|
||||
protected SyntaxNode CreateCastedThisExpression(ThisExpressionSyntax node)
|
||||
{
|
||||
var ancestors = node.Ancestors().Where(n => n is TypeDeclarationSyntax).Cast<TypeDeclarationSyntax>().ToList();
|
||||
if (ancestors.Count() > 1)
|
||||
{
|
||||
LoggerScoped.LogWarning($"ThisRewriter: for class: '{ancestors.First().Identifier}' - 'this' call/assignment in nested class / struct. Dynamic cast will be used but this could cause issues in some cases:" +
|
||||
$"\r\n\r\n1) - If called method has multiple overrides, using dynamic will cause compiler issue as it'll no longer be able to pick correct one" +
|
||||
$"\r\n\r\n If you see any issues with that message, please look at 'Limitation' section in documentation as this outlines how to deal with it.");
|
||||
|
||||
//TODO: casting to dynamic seems to be best option (and one that doesn't fail for nested classes), what's the performance overhead?
|
||||
return SyntaxFactory.CastExpression(
|
||||
SyntaxFactory.ParseTypeName("dynamic"),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
var firstAncestor = ancestors.FirstOrDefault();
|
||||
if (firstAncestor == null)
|
||||
{
|
||||
LoggerScoped.LogWarning($"Unable to find first ancestor for node: {node.ToFullString()}, this rewrite will not be applied");
|
||||
return node;
|
||||
}
|
||||
|
||||
var methodInType = firstAncestor.Identifier.ToString();
|
||||
var resultNode = SyntaxFactory.CastExpression(
|
||||
SyntaxFactory.ParseTypeName(methodInType),
|
||||
SyntaxFactory.CastExpression(
|
||||
SyntaxFactory.ParseTypeName(typeof(object).FullName),
|
||||
node
|
||||
)
|
||||
);
|
||||
|
||||
return AddRewriteCommentIfNeeded(resultNode, $"{nameof(ThisRewriterBase)}:{nameof(CreateCastedThisExpression)}");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,264 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using FastScriptReload.Runtime;
|
||||
using ImmersiveVRTools.Editor.Common.Cache;
|
||||
using ImmersiveVRTools.Runtime.Common;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using UnityEditor;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class DotnetExeDynamicCompilation: DynamicCompilationBase
|
||||
{
|
||||
private static string _dotnetExePath;
|
||||
private static string _cscDll;
|
||||
private static string _tempFolder;
|
||||
|
||||
private static string ApplicationContentsPath = EditorApplication.applicationContentsPath;
|
||||
private static readonly List<string> _createdFilesToCleanUp = new List<string>();
|
||||
|
||||
static DotnetExeDynamicCompilation()
|
||||
{
|
||||
#if UNITY_EDITOR_WIN
|
||||
const string dotnetExecutablePath = "dotnet.exe";
|
||||
#else
|
||||
const string dotnetExecutablePath = "dotnet"; //mac and linux, no extension
|
||||
#endif
|
||||
|
||||
_dotnetExePath = FindFileOrThrow(dotnetExecutablePath);
|
||||
_cscDll = FindFileOrThrow("csc.dll"); //even on mac/linux need to find dll and use, not no extension one
|
||||
_tempFolder = Path.GetTempPath();
|
||||
|
||||
EditorApplication.playModeStateChanged += obj =>
|
||||
{
|
||||
if (obj == PlayModeStateChange.ExitingPlayMode && _createdFilesToCleanUp.Any())
|
||||
{
|
||||
LoggerScoped.LogDebug($"Removing temporary files: [{string.Join(",", _createdFilesToCleanUp)}]");
|
||||
|
||||
foreach (var fileToCleanup in _createdFilesToCleanUp)
|
||||
{
|
||||
File.Delete(fileToCleanup);
|
||||
}
|
||||
_createdFilesToCleanUp.Clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static string FindFileOrThrow(string fileName)
|
||||
{
|
||||
return SessionStateCache.GetOrCreateString($"FSR:FilePath_{fileName}", () =>
|
||||
{
|
||||
var foundFile = Directory
|
||||
.GetFiles(ApplicationContentsPath, fileName, SearchOption.AllDirectories)
|
||||
.FirstOrDefault();
|
||||
if (foundFile == null)
|
||||
{
|
||||
throw new Exception($"Unable to find '{fileName}', make sure Editor version supports it. You can also add preprocessor directive 'FastScriptReload_CompileViaMCS' which will use Mono compiler instead");
|
||||
}
|
||||
|
||||
return foundFile;
|
||||
});
|
||||
}
|
||||
|
||||
public static CompileResult Compile(List<string> filePathsWithSourceCode, UnityMainThreadDispatcher unityMainThreadDispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
var asmName = Guid.NewGuid().ToString().Replace("-", "");
|
||||
var rspFile = _tempFolder + $"{asmName}.rsp";
|
||||
var assemblyAttributeFilePath = _tempFolder + $"{asmName}.DynamicallyCreatedAssemblyAttribute.cs";
|
||||
var sourceCodeCombinedFilePath = _tempFolder + $"{asmName}.SourceCodeCombined.cs";
|
||||
var outLibraryPath = $"{_tempFolder}{asmName}.dll";
|
||||
|
||||
var sourceCodeCombined = CreateSourceCodeCombinedContents(filePathsWithSourceCode, ActiveScriptCompilationDefines.ToList());
|
||||
CreateFileAndTrackAsCleanup(sourceCodeCombinedFilePath, sourceCodeCombined, _createdFilesToCleanUp);
|
||||
#if UNITY_EDITOR
|
||||
unityMainThreadDispatcher.Enqueue(() =>
|
||||
{
|
||||
if ((bool)FastScriptReloadPreference.IsAutoOpenGeneratedSourceFileOnChangeEnabled.GetEditorPersistedValueOrDefault())
|
||||
{
|
||||
UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(sourceCodeCombinedFilePath, 0);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
var rspFileContent = GenerateCompilerArgsRspFileContents(outLibraryPath, _tempFolder, asmName, sourceCodeCombinedFilePath, assemblyAttributeFilePath);
|
||||
CreateFileAndTrackAsCleanup(rspFile, rspFileContent, _createdFilesToCleanUp);
|
||||
CreateFileAndTrackAsCleanup(assemblyAttributeFilePath, DynamicallyCreatedAssemblyAttributeSourceCode, _createdFilesToCleanUp);
|
||||
|
||||
var exitCode = ExecuteDotnetExeCompilation(_dotnetExePath, _cscDll, rspFile, outLibraryPath, out var outputMessages);
|
||||
|
||||
var compiledAssembly = Assembly.LoadFrom(outLibraryPath);
|
||||
return new CompileResult(outLibraryPath, outputMessages, exitCode, compiledAssembly, sourceCodeCombined, sourceCodeCombinedFilePath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
LoggerScoped.LogError($"Compilation error: temporary files were not removed so they can be inspected: "
|
||||
+ string.Join(", ", _createdFilesToCleanUp
|
||||
.Select(f => $"<a href=\"{f}\" line=\"1\">{f}</a>")));
|
||||
if (LogHowToFixMessageOnCompilationError)
|
||||
{
|
||||
LoggerScoped.LogWarning($@"HOW TO FIX - INSTRUCTIONS:
|
||||
|
||||
1) Open file that caused issue by looking at error log starting with: 'FSR: Compilation error: temporary files were not removed so they can be inspected: '. And click on file path to open.
|
||||
2) Look up other error in the console, which will be like 'Error When updating files:' - this one contains exact line that failed to compile (in XXX_SourceCodeGenerated.cs file). Those are same compilation errors as you see in Unity/IDE when developing.
|
||||
3) Read compiler error message as it'll help understand the issue
|
||||
|
||||
Error could be caused by a normal compilation issue that you created in source file (eg typo), in that case please fix and it'll recompile.
|
||||
|
||||
It's possible compilation fails due to existing limitation, in that case:
|
||||
|
||||
<b><color='orange'>You can quickly specify custom script rewrite override for part of code that's failing.</color></b>
|
||||
|
||||
Please use project panel to:
|
||||
1) Right-click on the original file that has compilation issue
|
||||
2) Click Fast Script Reload -> Add / Open User Script Rewrite Override
|
||||
3) Read top comment in opened file and it'll explain how to create overrides
|
||||
|
||||
I'm continuously working on mitigating limitations.
|
||||
|
||||
If you could please get in touch with me via 'support@immersivevrtools.com' and include error you see in the console as well as created files (from paths in previous error). This way I can get it fixed for you.
|
||||
|
||||
You can also:
|
||||
1) Look at 'limitation' section in the docs - which will explain bit more around limitations and workarounds
|
||||
2) Move some of the code that you want to work on to different file - compilation happens on whole file, if you have multiple types there it could increase the chance of issues
|
||||
3) Have a look at compilation error, it shows error line (in the '*.SourceCodeCombined.cs' file, it's going to be something that compiler does not accept, likely easy to spot. To workaround you can change that part of code in original file. It's specific patterns that'll break it.
|
||||
|
||||
*If you want to prevent that message from reappearing please go to Window -> Fast Script Reload -> Start Screen -> Logging -> tick off 'Log how to fix message on compilation error'*");
|
||||
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateFileAndTrackAsCleanup(string filePath, string contents, List<string> createdFilesToCleanUp)
|
||||
{
|
||||
File.WriteAllText(filePath, contents);
|
||||
createdFilesToCleanUp.Add(filePath);
|
||||
}
|
||||
|
||||
private static string GenerateCompilerArgsRspFileContents(string outLibraryPath, string tempFolder, string asmName,
|
||||
string sourceCodeCombinedFilePath, string assemblyAttributeFilePath)
|
||||
{
|
||||
var rspContents = new StringBuilder();
|
||||
rspContents.AppendLine("-target:library");
|
||||
rspContents.AppendLine($"-out:\"{outLibraryPath}\"");
|
||||
rspContents.AppendLine($"-refout:\"{tempFolder}{asmName}.ref.dll\""); //TODO: what's that?
|
||||
foreach (var symbol in ActiveScriptCompilationDefines)
|
||||
{
|
||||
rspContents.AppendLine($"-define:{symbol}");
|
||||
}
|
||||
|
||||
foreach (var referenceToAdd in ResolveReferencesToAdd(new List<string>()))
|
||||
{
|
||||
rspContents.AppendLine($"-r:\"{referenceToAdd}\"");
|
||||
}
|
||||
|
||||
rspContents.AppendLine($"\"{sourceCodeCombinedFilePath}\"");
|
||||
rspContents.AppendLine($"\"{assemblyAttributeFilePath}\"");
|
||||
|
||||
rspContents.AppendLine($"-langversion:latest");
|
||||
|
||||
rspContents.AppendLine("/deterministic");
|
||||
rspContents.AppendLine("/optimize-");
|
||||
rspContents.AppendLine("/debug:portable");
|
||||
rspContents.AppendLine("/nologo");
|
||||
rspContents.AppendLine("/RuntimeMetadataVersion:v4.0.30319");
|
||||
|
||||
rspContents.AppendLine("/nowarn:0169");
|
||||
rspContents.AppendLine("/nowarn:0649");
|
||||
rspContents.AppendLine("/nowarn:1701");
|
||||
rspContents.AppendLine("/nowarn:1702");
|
||||
rspContents.AppendLine("/utf8output");
|
||||
rspContents.AppendLine("/preferreduilang:en-US");
|
||||
|
||||
var rspContentsString = rspContents.ToString();
|
||||
return rspContentsString;
|
||||
}
|
||||
|
||||
private static int ExecuteDotnetExeCompilation(string dotnetExePath, string cscDll, string rspFile,
|
||||
string outLibraryPath, out List<string> outputMessages)
|
||||
{
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = dotnetExePath;
|
||||
process.StartInfo.Arguments = $"exec \"{cscDll}\" /nostdlib /noconfig /shared \"@{rspFile}\"";
|
||||
|
||||
var outMessages = new List<string>();
|
||||
|
||||
var stderr_completed = new ManualResetEvent(false);
|
||||
var stdout_completed = new ManualResetEvent(false);
|
||||
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.ErrorDataReceived += (sender, args) =>
|
||||
{
|
||||
if (args.Data != null)
|
||||
outMessages.Add(args.Data);
|
||||
else
|
||||
stderr_completed.Set();
|
||||
};
|
||||
process.OutputDataReceived += (sender, args) =>
|
||||
{
|
||||
if (args.Data != null)
|
||||
{
|
||||
outMessages.Add(args.Data);
|
||||
return;
|
||||
}
|
||||
|
||||
stdout_completed.Set();
|
||||
};
|
||||
process.StartInfo.StandardOutputEncoding = process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is Win32Exception win32Exception)
|
||||
throw new SystemException(string.Format("Error running {0}: {1}", process.StartInfo.FileName,
|
||||
typeof(Win32Exception)
|
||||
.GetMethod("GetErrorMessage", BindingFlags.Static | BindingFlags.NonPublic)?
|
||||
.Invoke(null, new object[] { win32Exception.NativeErrorCode }) ??
|
||||
$"<Unable to resolve GetErrorMessage function>, NativeErrorCode: {win32Exception.NativeErrorCode}"));
|
||||
throw;
|
||||
}
|
||||
|
||||
int exitCode = -1;
|
||||
try
|
||||
{
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
exitCode = process.ExitCode;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stderr_completed.WaitOne(TimeSpan.FromSeconds(30.0));
|
||||
stdout_completed.WaitOne(TimeSpan.FromSeconds(30.0));
|
||||
process.Close();
|
||||
}
|
||||
|
||||
if (!File.Exists(outLibraryPath))
|
||||
throw new Exception("Compiler failed to produce the assembly. Output: '" +
|
||||
string.Join(Environment.NewLine + Environment.NewLine, outMessages) + "'");
|
||||
|
||||
outputMessages = new List<string>();
|
||||
outputMessages.AddRange(outMessages);
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,58 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FastScriptReload.Runtime;
|
||||
using ImmersiveVRTools.Runtime.Common;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation
|
||||
{
|
||||
public class DynamicAssemblyCompiler
|
||||
{
|
||||
public static CompileResult Compile(List<string> filePathsWithSourceCode, UnityMainThreadDispatcher unityMainThreadDispatcher)
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
#if FastScriptReload_CompileViaMCS
|
||||
var result = McsExeDynamicCompilation.Compile(filePathsWithSourceCode);
|
||||
#else
|
||||
var compileResult = DotnetExeDynamicCompilation.Compile(filePathsWithSourceCode, unityMainThreadDispatcher);
|
||||
#endif
|
||||
|
||||
LoggerScoped.Log($"Files: {string.Join(",", filePathsWithSourceCode.Select(fn => new FileInfo(fn).Name))} changed " +
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
$"<a href=\"{compileResult.SourceCodeCombinedFileLocation}\" line=\"1\">(click here to debug [in bottom details pane])</a>" +
|
||||
#else
|
||||
"(to debug go to Fast Script Reload -> Start Screen -> Debugging -> Auto-open generated source file for debugging)" +
|
||||
#endif
|
||||
$" - compilation (took {sw.ElapsedMilliseconds}ms)");
|
||||
|
||||
return compileResult;
|
||||
}
|
||||
}
|
||||
|
||||
public class CompileResult
|
||||
{
|
||||
public Assembly CompiledAssembly { get; }
|
||||
public string CompiledAssemblyPath { get; }
|
||||
public List<string> MessagesFromCompilerProcess { get; }
|
||||
public bool IsError => string.IsNullOrEmpty(CompiledAssemblyPath);
|
||||
public int NativeCompilerReturnValue { get; }
|
||||
public string SourceCodeCombined { get; }
|
||||
public string SourceCodeCombinedFileLocation { get; }
|
||||
|
||||
public CompileResult(string compiledAssemblyPath, List<string> messagesFromCompilerProcess, int nativeCompilerReturnValue, Assembly compiledAssembly, string sourceCodeCombined, string sourceCodeCombinedFileLocation)
|
||||
{
|
||||
CompiledAssemblyPath = compiledAssemblyPath;
|
||||
MessagesFromCompilerProcess = messagesFromCompilerProcess;
|
||||
NativeCompilerReturnValue = nativeCompilerReturnValue;
|
||||
CompiledAssembly = compiledAssembly;
|
||||
SourceCodeCombined = sourceCodeCombined;
|
||||
SourceCodeCombinedFileLocation = sourceCodeCombinedFileLocation;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,316 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using FastScriptReload.Editor.Compilation.CodeRewriting;
|
||||
using FastScriptReload.Editor.Compilation.ScriptGenerationOverrides;
|
||||
using FastScriptReload.Runtime;
|
||||
using FastScriptReload.Scripts.Runtime;
|
||||
using ImmersiveVRTools.Editor.Common.Cache;
|
||||
using ImmersiveVRTools.Runtime.Common.Utilities;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class DynamicCompilationBase
|
||||
{
|
||||
public static bool DebugWriteRewriteReasonAsComment;
|
||||
public static bool LogHowToFixMessageOnCompilationError;
|
||||
public static bool EnableExperimentalThisCallLimitationFix;
|
||||
public static List<string> ReferencesExcludedFromHotReload = new List<string>();
|
||||
|
||||
public const string DebuggingInformationComment =
|
||||
@"// DEBUGGING READ-ME " +
|
||||
#if !UNITY_2021_1_OR_NEWER
|
||||
"WARN: on Unity versions prior to 2021, opening files in that manner can cause static values to be reinitialized"
|
||||
#else
|
||||
""
|
||||
#endif
|
||||
+
|
||||
@"//
|
||||
// To debug simply add a breakpoint in this file.
|
||||
//
|
||||
// With every code change - new file is generated, currently you'll need to re-set breakpoints after each change.
|
||||
// You can also:
|
||||
// - step into the function that was changed (and that will get you to correct source file)
|
||||
// - add a function breakpoint in your IDE (this way you won't have to re-add it every time)
|
||||
//
|
||||
// Tool can automatically open dynamically-compiled code file every time to make setting breakpoints easier.
|
||||
// You can adjust that behaviour via 'Window -> FastScriptReload -> Start Screen -> Debugging -> Do not auto-open generated cs file'.
|
||||
//
|
||||
// You can always open generated file when needed by clicking link in console, eg.
|
||||
// 'FSR: Files: FunctionLibrary.cs changed (click here to debug [in bottom details pane]) - compilation (took 240ms)'
|
||||
|
||||
|
||||
";
|
||||
|
||||
public static readonly string[] ActiveScriptCompilationDefines;
|
||||
protected static readonly string DynamicallyCreatedAssemblyAttributeSourceCode = $"[assembly: {typeof(DynamicallyCreatedAssemblyAttribute).FullName}()]";
|
||||
private static readonly string AssemblyCsharpFullPath;
|
||||
|
||||
static DynamicCompilationBase()
|
||||
{
|
||||
//needs to be set from main thread
|
||||
ActiveScriptCompilationDefines = EditorUserBuildSettings.activeScriptCompilationDefines;
|
||||
AssemblyCsharpFullPath = SessionStateCache.GetOrCreateString(
|
||||
$"FSR:AssemblyCsharpFullPath",
|
||||
() => AssetDatabase.FindAssets("Microsoft.CSharp")
|
||||
.Select(g => new System.IO.FileInfo(UnityEngine.Application.dataPath + "/../" + AssetDatabase.GUIDToAssetPath(g)))
|
||||
.First(fi => fi.Name.ToLower() == "Microsoft.CSharp.dll".ToLower()).FullName
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
protected static string CreateSourceCodeCombinedContents(List<string> sourceCodeFiles, List<string> definedPreprocessorSymbols)
|
||||
{
|
||||
var combinedUsingStatements = new List<string>();
|
||||
|
||||
var sourceCodeWithAdjustments = sourceCodeFiles.Select(sourceCodeFile =>
|
||||
{
|
||||
var fileCode = File.ReadAllText(sourceCodeFile);
|
||||
var tree = CSharpSyntaxTree.ParseText(fileCode, new CSharpParseOptions(preprocessorSymbols: definedPreprocessorSymbols));
|
||||
var root = tree.GetRoot();
|
||||
|
||||
var typeToNewFieldDeclarations = new Dictionary<string, List<string>>();
|
||||
if (FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport)
|
||||
{
|
||||
//WARN: needs to walk before root class name changes, otherwise it'll resolve wrong name
|
||||
var fieldsWalker = new FieldsWalker();
|
||||
fieldsWalker.Visit(root);
|
||||
|
||||
var typeToFieldDeclarations = fieldsWalker.GetTypeToFieldDeclarations();
|
||||
typeToNewFieldDeclarations = typeToFieldDeclarations.ToDictionary(
|
||||
t => t.Key,
|
||||
t =>
|
||||
{
|
||||
if (!ProjectTypeCache.AllTypesInNonDynamicGeneratedAssemblies.TryGetValue(t.Key, out var existingType))
|
||||
{
|
||||
LoggerScoped.LogDebug($"Unable to find type: {t.Key} in loaded assemblies. If that's the class you've added field to then it may not be properly working. It's possible the class was not yet loaded / used and you can ignore that warning. If it's causing any issues please contact support");
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var existingTypeMembersToReplace = NewFieldsRewriter.GetReplaceableMembers(existingType).Select(m => m.Name).ToList();
|
||||
|
||||
var newFields = t.Value.Where(fD => !existingTypeMembersToReplace.Contains(fD.FieldName)).ToList();
|
||||
|
||||
//TODO: ideally that registration would happen outside of this class
|
||||
//TODO: to work for LSR it needs to be handled in runtime
|
||||
TemporaryNewFieldValues.RegisterNewFields(
|
||||
existingType,
|
||||
newFields.ToDictionary(
|
||||
fD => fD.FieldName,
|
||||
fD => new TemporaryNewFieldValues.GetNewFieldInitialValue((Type forNewlyGeneratedType) =>
|
||||
{
|
||||
//TODO: PERF: could cache those - they run to init every new value (for every instance when accessed)
|
||||
return CreateNewFieldInitMethodRewriter.ResolveNewFieldsToCreateValueFn(forNewlyGeneratedType)[fD.FieldName]();
|
||||
})
|
||||
),
|
||||
newFields.ToDictionary(
|
||||
fD => fD.FieldName,
|
||||
fD => new TemporaryNewFieldValues.GetNewFieldType((Type forNewlyGeneratedType) =>
|
||||
{
|
||||
//TODO: PERF: could cache those - they run to init every new value (for every instance when accessed)
|
||||
return (Type)CreateNewFieldInitMethodRewriter.ResolveNewFieldsToTypeFn(forNewlyGeneratedType)[fD.FieldName]();
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return newFields.Select(fD => fD.FieldName).ToList();
|
||||
}
|
||||
);
|
||||
|
||||
#if LiveScriptReload_Enabled
|
||||
if (typeToNewFieldDeclarations.Any(kv => kv.Value.Any()))
|
||||
{
|
||||
LoggerScoped.LogWarning($"{nameof(FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport)} is enabled. This is not supported in running build. Quite likely it'll crash remote client.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//WARN: application order is important, eg ctors need to happen before class names as otherwise ctors will not be recognised as ctors
|
||||
if (FastScriptReloadManager.Instance.EnableExperimentalThisCallLimitationFix)
|
||||
{
|
||||
root = new ThisCallRewriter(DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
root = new ThisAssignmentRewriter(DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
}
|
||||
|
||||
if (FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport)
|
||||
{
|
||||
root = new NewFieldsRewriter(typeToNewFieldDeclarations, DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
root = new CreateNewFieldInitMethodRewriter(typeToNewFieldDeclarations, DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
}
|
||||
|
||||
root = new ConstructorRewriter(adjustCtorOnlyForNonNestedTypes: true, DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
|
||||
var hotReloadCompliantRewriter = new HotReloadCompliantRewriter(DebugWriteRewriteReasonAsComment);
|
||||
root = hotReloadCompliantRewriter.Visit(root);
|
||||
combinedUsingStatements.AddRange(hotReloadCompliantRewriter.StrippedUsingDirectives);
|
||||
|
||||
//processed as last step to simply rewrite all changes made before
|
||||
if (TryResolveUserDefinedOverridesRoot(sourceCodeFile, definedPreprocessorSymbols, out var userDefinedOverridesRoot))
|
||||
{
|
||||
root = ProcessUserDefinedOverridesReplacements(sourceCodeFile, root, userDefinedOverridesRoot);
|
||||
root = AddUserDefinedOverridenTypes(userDefinedOverridesRoot, root);
|
||||
}
|
||||
|
||||
return root.ToFullString();
|
||||
}).ToList();
|
||||
|
||||
var sourceCodeCombinedSb = new StringBuilder();
|
||||
sourceCodeCombinedSb.Append(DebuggingInformationComment);
|
||||
|
||||
foreach (var usingStatement in combinedUsingStatements.Distinct())
|
||||
{
|
||||
sourceCodeCombinedSb.Append(usingStatement);
|
||||
}
|
||||
|
||||
foreach (var sourceCodeWithAdjustment in sourceCodeWithAdjustments)
|
||||
{
|
||||
sourceCodeCombinedSb.AppendLine(sourceCodeWithAdjustment);
|
||||
}
|
||||
|
||||
LoggerScoped.LogDebug("Source Code Created:\r\n\r\n" + sourceCodeCombinedSb);
|
||||
return sourceCodeCombinedSb.ToString();
|
||||
}
|
||||
|
||||
private static SyntaxNode AddUserDefinedOverridenTypes(SyntaxNode userDefinedOverridesRoot, SyntaxNode root)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userDefinedOverrideTypes = userDefinedOverridesRoot.DescendantNodes().OfType<TypeDeclarationSyntax>()
|
||||
.ToDictionary(n => RoslynUtils.GetMemberFQDN(n, n.Identifier.ToString()));
|
||||
var allDefinedTypesInRecompiledFile = root.DescendantNodes().OfType<TypeDeclarationSyntax>()
|
||||
.ToDictionary(n => RoslynUtils.GetMemberFQDN(n, n.Identifier.ToString())); //what about nested types?
|
||||
|
||||
var userDefinedOverrideTypesWithoutMatchnigInRecompiledFile = userDefinedOverrideTypes.Select(overridenType =>
|
||||
{
|
||||
if (!allDefinedTypesInRecompiledFile.ContainsKey(overridenType.Key))
|
||||
{
|
||||
return overridenType;
|
||||
}
|
||||
|
||||
return default(KeyValuePair<string, TypeDeclarationSyntax>);
|
||||
})
|
||||
.Where(kv => kv.Key != default(string))
|
||||
.ToList();
|
||||
|
||||
//types should be added either to root namespace or root of document
|
||||
var rootNamespace = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
|
||||
foreach (var overridenTypeToAdd in userDefinedOverrideTypesWithoutMatchnigInRecompiledFile)
|
||||
{
|
||||
var newMember = FastScriptReloadCodeRewriterBase.AddRewriteCommentIfNeeded(overridenTypeToAdd.Value,
|
||||
"New type defined in override file",
|
||||
true, //always write reason so it's not easy to miss in generated file
|
||||
true);
|
||||
if (rootNamespace != null)
|
||||
{
|
||||
rootNamespace =
|
||||
root.DescendantNodes().OfType<NamespaceDeclarationSyntax>()
|
||||
.FirstOrDefault(); //need to search again to make sure it didn't change
|
||||
var newRootNamespace = rootNamespace.AddMembers(newMember);
|
||||
root = root.ReplaceNode(rootNamespace, newRootNamespace);
|
||||
}
|
||||
else
|
||||
{
|
||||
root = ((CompilationUnitSyntax)root).AddMembers(newMember);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Unable to add user defined override types. {e}");
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static bool TryResolveUserDefinedOverridesRoot(string sourceCodeFile, List<string> definedPreprocessorSymbols, out SyntaxNode userDefinedOverridesRoot)
|
||||
{
|
||||
if (ScriptGenerationOverridesManager.TryGetScriptOverride(new FileInfo(sourceCodeFile), out var userDefinedOverridesFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
userDefinedOverridesRoot = CSharpSyntaxTree.ParseText(File.ReadAllText(userDefinedOverridesFile.FullName), new CSharpParseOptions(preprocessorSymbols: definedPreprocessorSymbols)).GetRoot();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Unable to resolve user defined overrides for file: '{userDefinedOverridesFile.FullName}' - please make sure it's compilable. Error: '{ex}'");
|
||||
}
|
||||
}
|
||||
|
||||
userDefinedOverridesRoot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static SyntaxNode ProcessUserDefinedOverridesReplacements(string sourceCodeFile, SyntaxNode root, SyntaxNode userDefinedOverridesRoot)
|
||||
{
|
||||
if (ScriptGenerationOverridesManager.TryGetScriptOverride(new FileInfo(sourceCodeFile), out var userDefinedOverridesFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
var userDefinedScriptOverridesRewriter = new ManualUserDefinedScriptOverridesRewriter(userDefinedOverridesRoot,
|
||||
true); //always write rewrite reason so it's not easy to miss
|
||||
root = userDefinedScriptOverridesRewriter.Visit(root);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Unable to resolve user defined overrides for file: '{userDefinedOverridesFile.FullName}' - please make sure it's compilable. Error: '{ex}'");
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected static List<string> ResolveReferencesToAdd(List<string> excludeAssyNames)
|
||||
{
|
||||
var referencesToAdd = new List<string>();
|
||||
foreach (var assembly in AppDomain.CurrentDomain
|
||||
.GetAssemblies() //TODO: PERF: just need to load once and cache? or get assembly based on changed file only?
|
||||
.Where(a => excludeAssyNames.All(assyName => !a.FullName.StartsWith(assyName))
|
||||
&& CustomAttributeExtensions.GetCustomAttribute<DynamicallyCreatedAssemblyAttribute>((Assembly)a) == null))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(assembly.Location))
|
||||
{
|
||||
LoggerScoped.LogDebug($"FastScriptReload: Assembly location is null, usually dynamic assembly, harmless.");
|
||||
continue;
|
||||
}
|
||||
|
||||
referencesToAdd.Add(assembly.Location);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
LoggerScoped.LogDebug($"Unable to add a reference to assembly as unable to get location or null: {assembly.FullName} when hot-reloading, this is likely dynamic assembly and won't cause issues");
|
||||
}
|
||||
}
|
||||
|
||||
referencesToAdd = referencesToAdd.Where(r => !ReferencesExcludedFromHotReload.Any(rTe => r.EndsWith(rTe))).ToList();
|
||||
|
||||
if (EnableExperimentalThisCallLimitationFix || FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport)
|
||||
{
|
||||
IncludeMicrosoftCsharpReferenceToSupportDynamicKeyword(referencesToAdd);
|
||||
}
|
||||
|
||||
return referencesToAdd;
|
||||
}
|
||||
|
||||
private static void IncludeMicrosoftCsharpReferenceToSupportDynamicKeyword(List<string> referencesToAdd)
|
||||
{
|
||||
//TODO: check .net4.5 backend not breaking?
|
||||
//ThisRewriters will cast to dynamic - if using .NET Standard 2.1 - reference is required
|
||||
referencesToAdd.Add(AssemblyCsharpFullPath);
|
||||
// referencesToAdd.Add(@"C:\Program Files\Unity\Hub\Editor\2021.3.12f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll");
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,64 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
#if FastScriptReload_CompileViaMCS
|
||||
public class McsExeDynamicCompilation : DynamicCompilationBase
|
||||
{
|
||||
private const int ReferenceLenghtCountWarningThreshold = 32767 - 2000; //windows can accept up to 32767 chars as args, then it starts thorowing exceptions. MCS.exe is adding references via command /r:<full path>
|
||||
|
||||
private static CompileResult Compile(List<string> filePathsWithSourceCode)
|
||||
{
|
||||
var fileSourceCode = filePathsWithSourceCode.Select(File.ReadAllText);
|
||||
|
||||
var providerOptions = new Dictionary<string, string>();
|
||||
var provider = new Microsoft.CSharp.CSharpCodeProvider(providerOptions);
|
||||
var param = new System.CodeDom.Compiler.CompilerParameters();
|
||||
|
||||
var excludeAssyNames = new List<string>
|
||||
{
|
||||
"mscorlib"
|
||||
};
|
||||
var referencesToAdd = ResolveReferencesToAdd(excludeAssyNames);
|
||||
|
||||
var referencePathCharLenght = referencesToAdd.Sum(r => r.Length);
|
||||
if (referencePathCharLenght > ReferenceLenghtCountWarningThreshold)
|
||||
{
|
||||
LoggerScoped.LogWarning(
|
||||
"Windows can accept up to 32767 chars as args, then it starts throwing exceptions. Dynamic compilation will use MCS.exe and will add references via command /r:<full path>, " +
|
||||
$"currently your assembly have {referencesToAdd.Count} references which full paths amount to: {referencePathCharLenght} chars." +
|
||||
$"\r\nIf you see this warning likely compilation will fail, you can:" +
|
||||
$"\r\n1) Move your project to be more top-level, as references take full paths, eg 'c:\\my-source\\stuff\\unity\\my-project\\' - this then gets repeated for many references, moving it close to top level will help" +
|
||||
$"\r\n2) Remove some of the assemblies if you don't need them" +
|
||||
"\r\n Please let me know via support email if that's causing you issues, there may be a fix if it's affecting many users, sorry about that!");
|
||||
|
||||
//TODO: the process is started from Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch, potentially all it'd be possible to patch that class to maybe copy all
|
||||
//assemblies to some top-level location and change parameters to run from this folder, with a working directory set, this would drastically reduce char count used by full refs
|
||||
//also mcs.exe allows to compile with -pkg:package1[,packageN], which somewhat bundles multiple references, maybe all unity engine refs could go in there, or all refs in general
|
||||
}
|
||||
|
||||
param.ReferencedAssemblies.AddRange(referencesToAdd.ToArray());
|
||||
param.GenerateExecutable = false;
|
||||
param.GenerateInMemory = false;
|
||||
providerOptions.Add(PatchMcsArgsGeneration.PreprocessorDirectivesProviderOptionsKey,
|
||||
string.Join(";", ActiveScriptCompilationDefines));
|
||||
|
||||
var sourceCodeCombined = CreateSourceCodeCombinedContents(fileSourceCode);
|
||||
var result = provider.CompileAssemblyFromSource(param, sourceCodeCombined, DynamicallyCreatedAssemblyAttributeSourceCode);
|
||||
var errors = new List<string>();
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
errors.Add(error.ToString());
|
||||
}
|
||||
return new CompileResult(
|
||||
result.CompiledAssembly.FullName,
|
||||
errors,
|
||||
result.NativeCompilerReturnValue,
|
||||
result.CompiledAssembly,
|
||||
sourceCodeCombined,
|
||||
string.Empty
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -1,110 +0,0 @@
|
||||
#if FastScriptReload_CompileViaMCS
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using FastScriptReload.Runtime;
|
||||
using HarmonyLib;
|
||||
using UnityEditor;
|
||||
|
||||
[InitializeOnLoad]
|
||||
[PreventHotReload]
|
||||
public class PatchMcsArgsGeneration
|
||||
{
|
||||
public const string PreprocessorDirectivesProviderOptionsKey = "PreprocessorDirectives";
|
||||
|
||||
static PatchMcsArgsGeneration()
|
||||
{
|
||||
var harmony = new Harmony(nameof(PatchMcsArgsGeneration));
|
||||
|
||||
var original = AccessTools.Method("Microsoft.CSharp.CSharpCodeGenerator:BuildArgs");
|
||||
var postfix = AccessTools.Method(typeof(PatchMcsArgsGeneration), nameof(BuildArgsPostfix));
|
||||
|
||||
harmony.Patch(original, postfix: new HarmonyMethod(postfix));
|
||||
}
|
||||
|
||||
//Copied from Microsoft.CSharp.CSharpCodeGenerator.BuildArgs
|
||||
private static void BuildArgsPostfix(
|
||||
CompilerParameters options,
|
||||
string[] fileNames,
|
||||
IDictionary<string, string> providerOptions,
|
||||
ref string __result)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (options.GenerateExecutable)
|
||||
stringBuilder.Append("/target:exe ");
|
||||
else
|
||||
stringBuilder.Append("/target:library ");
|
||||
string privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
|
||||
if (privateBinPath != null && privateBinPath.Length > 0)
|
||||
stringBuilder.AppendFormat("/lib:\"{0}\" ", (object) privateBinPath);
|
||||
if (options.Win32Resource != null)
|
||||
stringBuilder.AppendFormat("/win32res:\"{0}\" ", (object) options.Win32Resource);
|
||||
if (options.IncludeDebugInformation)
|
||||
stringBuilder.Append("/debug+ /optimize- ");
|
||||
else
|
||||
stringBuilder.Append("/debug- /optimize+ ");
|
||||
if (options.TreatWarningsAsErrors)
|
||||
stringBuilder.Append("/warnaserror ");
|
||||
if (options.WarningLevel >= 0)
|
||||
stringBuilder.AppendFormat("/warn:{0} ", (object) options.WarningLevel);
|
||||
if (options.OutputAssembly == null || options.OutputAssembly.Length == 0)
|
||||
{
|
||||
string extension = options.GenerateExecutable ? "exe" : "dll"; //TODO:readd
|
||||
// options.OutputAssembly = CSharpCodeGenerator.GetTempFileNameWithExtension(options.TempFiles, extension, !options.GenerateInMemory);
|
||||
}
|
||||
stringBuilder.AppendFormat("/out:\"{0}\" ", (object) options.OutputAssembly);
|
||||
foreach (string referencedAssembly in options.ReferencedAssemblies)
|
||||
{
|
||||
if (referencedAssembly != null && referencedAssembly.Length != 0)
|
||||
stringBuilder.AppendFormat("/r:\"{0}\" ", (object) referencedAssembly);
|
||||
}
|
||||
if (options.CompilerOptions != null)
|
||||
{
|
||||
stringBuilder.Append(options.CompilerOptions);
|
||||
stringBuilder.Append(" ");
|
||||
}
|
||||
foreach (string embeddedResource in options.EmbeddedResources)
|
||||
stringBuilder.AppendFormat("/resource:\"{0}\" ", (object) embeddedResource);
|
||||
foreach (string linkedResource in options.LinkedResources)
|
||||
stringBuilder.AppendFormat("/linkresource:\"{0}\" ", (object) linkedResource);
|
||||
|
||||
//WARN: that's how it's in source, quite odd, doesn't do much if compiler version specified?
|
||||
// if (providerOptions != null && providerOptions.Count > 0)
|
||||
// {
|
||||
// string str;
|
||||
// if (!providerOptions.TryGetValue("CompilerVersion", out str))
|
||||
// str = "3.5";
|
||||
// if (str.Length >= 1 && str[0] == 'v')
|
||||
// str = str.Substring(1);
|
||||
// if (str != "2.0")
|
||||
// {
|
||||
// }
|
||||
// else
|
||||
// stringBuilder.Append("/langversion:ISO-2 ");
|
||||
// }
|
||||
|
||||
stringBuilder.Append("/langversion:experimental ");
|
||||
|
||||
CustomPatchAdditionAddPreprocessorDirectives(providerOptions, stringBuilder);
|
||||
|
||||
stringBuilder.Append("/noconfig ");
|
||||
stringBuilder.Append(" -- ");
|
||||
foreach (string fileName in fileNames)
|
||||
stringBuilder.AppendFormat("\"{0}\" ", (object) fileName);
|
||||
|
||||
__result = stringBuilder.ToString();
|
||||
}
|
||||
|
||||
private static void CustomPatchAdditionAddPreprocessorDirectives(IDictionary<string, string> providerOptions, StringBuilder stringBuilder)
|
||||
{
|
||||
if (providerOptions != null && providerOptions.Count > 0)
|
||||
{
|
||||
if (providerOptions.TryGetValue(PreprocessorDirectivesProviderOptionsKey, out var preprocessorDirectives))
|
||||
{
|
||||
stringBuilder.Append($"/d:\"{preprocessorDirectives}\" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -1,204 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FastScriptReload.Runtime;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.ScriptGenerationOverrides
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public static class ScriptGenerationOverridesManager
|
||||
{
|
||||
private static float LoadOverridesFolderFilesEveryNSeconds = 5;
|
||||
|
||||
private static readonly string TemplateInterfaceDeclaration = @"
|
||||
|
||||
//New interface declaration, this is very useful in cases where code depends on some internal interfaces that re-compiled code can no longer access. Simply define them here and code will compile.
|
||||
//You can add any type in that manner
|
||||
public interface ITestNewInterface {
|
||||
bool Test { get; set; }
|
||||
}";
|
||||
|
||||
private static readonly string TemplateTopComment = @"// You can use this file to specify custom code overrides. Those will be applied to resulting code.
|
||||
// This approach is very useful if your code is failing to compile due to one of the existing limitations.
|
||||
//
|
||||
// While I work on reducing limitations you can simply specify override with proper code to make sure you can continue working.
|
||||
//
|
||||
// 1) Simply define code with same structure as your original class, make sure to include any namespace.
|
||||
// 2) Rename classes and types to have '<ClassPostfix>' postfix.
|
||||
//
|
||||
// eg. 'MyClassName' needs to be changed to MyClassName<ClassPostfix> otherwise it won't be properly connected.
|
||||
//
|
||||
// 3) Add any methods that you want to override, using same method signature. Whole method body will be replaced and no code adjustments will be run on it.
|
||||
// 4) You can add any additional types - this is quite useful when you hit limitation with internal interfaces - where compiler can not access them due to protection modifiers.
|
||||
// You can simply redefine those here, while not ideal it'll allow you to continue using Hot-Reload without modifying your code.
|
||||
//
|
||||
// Tool will now attempt to create a template file for you with first found class and first method as override, please adjust as necessary.
|
||||
// It'll also create an example redefined interface.
|
||||
// If you can't see anything please refer to the above and create overrides file manually.
|
||||
//
|
||||
// You can also refer to documentation section 'User defined script rewrite overrides'
|
||||
";
|
||||
|
||||
public static DirectoryInfo UserDefinedScriptRewriteOverridesFolder { get; }
|
||||
private static double _lastTimeOverridesFolderFilesRead;
|
||||
|
||||
public static List<UserDefinedScriptOverride> UserDefinedScriptOverrides { get; } = new List<UserDefinedScriptOverride>();
|
||||
|
||||
static ScriptGenerationOverridesManager()
|
||||
{
|
||||
//TODO: allow to customize later from code, eg for user that'd like to include in source control
|
||||
UserDefinedScriptRewriteOverridesFolder = new DirectoryInfo(Application.persistentDataPath + @"\FastScriptReload\ScriptOverrides");
|
||||
UpdateUserDefinedScriptOverridesFileCache();
|
||||
EditorApplication.update += Update;
|
||||
}
|
||||
|
||||
private static void Update()
|
||||
{
|
||||
var timeSinceStartup = EditorApplication.timeSinceStartup;
|
||||
if (_lastTimeOverridesFolderFilesRead + LoadOverridesFolderFilesEveryNSeconds < timeSinceStartup)
|
||||
{
|
||||
_lastTimeOverridesFolderFilesRead = timeSinceStartup;
|
||||
|
||||
UpdateUserDefinedScriptOverridesFileCache();
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateUserDefinedScriptOverridesFileCache()
|
||||
{
|
||||
UserDefinedScriptOverrides.Clear();
|
||||
if (UserDefinedScriptRewriteOverridesFolder.Exists)
|
||||
{
|
||||
UserDefinedScriptOverrides.AddRange(UserDefinedScriptRewriteOverridesFolder.GetFiles().Select(f => new UserDefinedScriptOverride(f)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddScriptOverride(MonoScript script)
|
||||
{
|
||||
EnsureOverrideFolderExists();
|
||||
|
||||
var overridenFile = new FileInfo(Path.Combine(UserDefinedScriptRewriteOverridesFolder.FullName, script.name + ".cs"));
|
||||
if (!overridenFile.Exists)
|
||||
{
|
||||
var originalFile = new FileInfo(Path.Combine(Path.Combine(Application.dataPath + "//..", AssetDatabase.GetAssetPath(script))));
|
||||
|
||||
var templateString = string.Empty;
|
||||
try
|
||||
{
|
||||
var fileCode = File.ReadAllText(originalFile.FullName);
|
||||
var tree = CSharpSyntaxTree.ParseText(fileCode, new CSharpParseOptions(preprocessorSymbols: DynamicCompilationBase.ActiveScriptCompilationDefines));
|
||||
var root = tree.GetRoot();
|
||||
|
||||
var firstType = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
||||
if (firstType != null)
|
||||
{
|
||||
var members = new SyntaxList<MemberDeclarationSyntax>();
|
||||
var firstMethod = firstType.DescendantNodes().OfType<MethodDeclarationSyntax>().FirstOrDefault(m => m.Body != null);
|
||||
if (firstMethod != null)
|
||||
{
|
||||
var block = SyntaxFactory.Block();
|
||||
block = block.AddStatements(SyntaxFactory.EmptyStatement().WithLeadingTrivia(
|
||||
SyntaxFactory.Comment(@"/* Any code will be replaced with original method of same signature in same type*/"))
|
||||
);
|
||||
firstMethod = firstMethod
|
||||
.WithBody(block)
|
||||
.WithTriviaFrom(firstMethod);
|
||||
members = members.Add(firstMethod);
|
||||
}
|
||||
|
||||
root = root.ReplaceNode(firstType, firstType
|
||||
.ReplaceToken(
|
||||
firstType.Identifier,
|
||||
SyntaxFactory.Identifier(firstType.Identifier.ValueText + AssemblyChangesLoader.ClassnamePatchedPostfix)
|
||||
)
|
||||
.WithMembers(members)).NormalizeWhitespace();
|
||||
|
||||
var interfaceDeclaration = CSharpSyntaxTree.ParseText(TemplateInterfaceDeclaration);
|
||||
|
||||
root = ((CompilationUnitSyntax)root).AddMembers(
|
||||
interfaceDeclaration.GetRoot().DescendantNodes().OfType<InterfaceDeclarationSyntax>().First()
|
||||
);
|
||||
}
|
||||
|
||||
templateString = root.ToFullString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Unable to generate user defined script override template from your file, please refer to note at the start of the file. {e}");
|
||||
}
|
||||
|
||||
if (!overridenFile.Exists)
|
||||
{
|
||||
File.WriteAllText(overridenFile.FullName,
|
||||
TemplateTopComment.Replace("<ClassPostfix>", AssemblyChangesLoader.ClassnamePatchedPostfix) + templateString
|
||||
);
|
||||
UpdateUserDefinedScriptOverridesFileCache();
|
||||
}
|
||||
}
|
||||
|
||||
InternalEditorUtility.OpenFileAtLineExternal(overridenFile.FullName, 0);
|
||||
}
|
||||
|
||||
public static bool TryRemoveScriptOverride(MonoScript originalScript)
|
||||
{
|
||||
EnsureOverrideFolderExists();
|
||||
|
||||
var overridenFile = new FileInfo(Path.Combine(UserDefinedScriptRewriteOverridesFolder.FullName, originalScript.name + ".cs"));
|
||||
if (overridenFile.Exists)
|
||||
{
|
||||
return TryRemoveScriptOverride(overridenFile);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryRemoveScriptOverride(FileInfo overridenFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
overridenFile.Delete();
|
||||
UpdateUserDefinedScriptOverridesFileCache();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.Log($"Unable to remove: '{overridenFile.Name}' - make sure it's not locked / open in editor");
|
||||
throw;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryRemoveScriptOverride(UserDefinedScriptOverride scriptOverride)
|
||||
{
|
||||
return TryRemoveScriptOverride(scriptOverride.File);
|
||||
}
|
||||
|
||||
public static bool TryGetScriptOverride(FileInfo changedFile, out FileInfo overridesFile)
|
||||
{
|
||||
overridesFile = UserDefinedScriptOverrides.FirstOrDefault(f => f.File.Name == changedFile.Name && f.File.Exists)?.File;
|
||||
return overridesFile?.Exists ?? false;
|
||||
}
|
||||
|
||||
private static void EnsureOverrideFolderExists()
|
||||
{
|
||||
if (!UserDefinedScriptRewriteOverridesFolder.Exists)
|
||||
UserDefinedScriptRewriteOverridesFolder.Create();
|
||||
}
|
||||
}
|
||||
|
||||
public class UserDefinedScriptOverride
|
||||
{
|
||||
public FileInfo File { get; }
|
||||
|
||||
public UserDefinedScriptOverride(FileInfo file)
|
||||
{
|
||||
File = file;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user