Browse Source

Code generation for actions

Equbuxu 3 years ago
parent
commit
28075d9aed

+ 49 - 0
src/PixiEditor.ChangeableDocument.Gen/ChangeActionGenerator.cs

@@ -0,0 +1,49 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace PixiEditor.ChangeableDocument.Gen
+{
+    [Generator]
+    public class ChangeActionGenerator : IIncrementalGenerator
+    {
+        public void Initialize(IncrementalGeneratorInitializationContext context)
+        {
+            // find contrustors with the attribute
+            var contructorSymbolProvider = context.SyntaxProvider.CreateSyntaxProvider(
+                predicate: Helpers.IsConstructorWithAttribute,
+                transform: static (context, cancelToken) =>
+                {
+                    var constructor = (ConstructorDeclarationSyntax)context.Node;
+                    if (!Helpers.MethodHasAttribute(
+                        context,
+                        cancelToken,
+                        constructor,
+                        new NamespacedType("GenerateMakeChangeActionAttribute", "PixiEditor.ChangeableDocument.Actions")
+                        ))
+                        return null;
+
+                    var contructorSymbol = context.SemanticModel.GetDeclaredSymbol(constructor, cancelToken);
+                    if (contructorSymbol is not IMethodSymbol methodConstructorSymbol ||
+                        methodConstructorSymbol.Kind != SymbolKind.Method)
+                        return null;
+                    return methodConstructorSymbol;
+                }
+            ).Where(a => a is not null);
+
+            // generate action source code
+            var actionSourceCodeProvider = contructorSymbolProvider.Select(
+                static (constructor, cancelToken) =>
+                {
+                    var info = Helpers.ExtractMethodInfo(constructor!);
+                    return new NamedSourceCode(info.ContainingClass.NameWithNamespace + "MakeChangeAction", Helpers.CreateMakeChangeAction(info));
+                }
+            );
+
+            // add the source code into compiler input
+            context.RegisterSourceOutput(actionSourceCodeProvider, static (context, namedCode) =>
+            {
+                context.AddSource(namedCode.Name, namedCode.Code);
+            });
+        }
+    }
+}

+ 236 - 0
src/PixiEditor.ChangeableDocument.Gen/Helpers.cs

@@ -0,0 +1,236 @@
+using System.Text;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace PixiEditor.ChangeableDocument.Gen;
+
+internal static class Helpers
+{
+    public static string CreateMakeChangeAction(MethodInfo changeConstructorInfo)
+    {
+        string actionName = changeConstructorInfo.ContainingClass.Name.Split('_')[0] + "_Action";
+        List<TypeWithName> constructorArgs = changeConstructorInfo.Arguments;
+        List<TypeWithName> properties = constructorArgs.Select(static typeWithName =>
+            {
+                return new TypeWithName(typeWithName.Type, typeWithName.FullNamespace, VariableNameIntoPropertyName(typeWithName.Name));
+            }).ToList();
+
+        var propToVar = MatchMembers(properties, constructorArgs);
+
+        StringBuilder sb = new();
+
+        sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;");
+        sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IMakeChangeAction");
+        sb.AppendLine("{");
+        sb.Append($"public {actionName}");
+        AppendArgumentList(sb, constructorArgs);
+        AppendConstructorBody(sb, propToVar);
+        AppendProperties(sb, properties);
+        AppendCreateCorrespondingChange(sb, changeConstructorInfo.ContainingClass, properties);
+        sb.AppendLine("}");
+
+        return sb.ToString();
+    }
+
+    public static Result<string> CreateStartUpdateChangeAction(MethodInfo changeConstructorInfo, MethodInfo updateMethodInfo)
+    {
+        string actionName = changeConstructorInfo.ContainingClass.Name.Split('_')[0] + "_Action";
+        List<TypeWithName> constructorArgs = changeConstructorInfo.Arguments;
+        List<TypeWithName> properties = constructorArgs.Select(static typeWithName =>
+        {
+            return new TypeWithName(typeWithName.Type, typeWithName.FullNamespace, VariableNameIntoPropertyName(typeWithName.Name));
+        }).ToList();
+
+        var constructorAssignments = MatchMembers(properties, constructorArgs);
+        var updatePropsToPass = MatchMembers(updateMethodInfo.Arguments, properties).Select(pair => pair.Item2).ToList();
+        if (updatePropsToPass.Count != updateMethodInfo.Arguments.Count)
+            return Result<string>.Error("Couldn't match update method arguments with constructor arguments");
+
+        StringBuilder sb = new();
+
+        sb.AppendLine("namespace PixiEditor.ChangeableDocument.Actions.Generated;");
+        sb.AppendLine($"public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction");
+        sb.AppendLine("{");
+        sb.Append($"public {actionName}");
+        AppendArgumentList(sb, constructorArgs);
+        AppendConstructorBody(sb, constructorAssignments);
+        AppendProperties(sb, properties);
+        AppendCreateUpdateableCorrespondingChange(sb, changeConstructorInfo.ContainingClass, properties);
+        AppendUpdateCorrespondingChange(sb, updateMethodInfo.Name, changeConstructorInfo.ContainingClass, updatePropsToPass);
+        sb.AppendLine("}");
+
+        return sb.ToString();
+    }
+
+    public static string CreateEndChangeAction(MethodInfo changeConstructorInfo)
+    {
+        string actionName = "End" + changeConstructorInfo.ContainingClass.Name.Split('_')[0] + "_Action";
+        return $@"
+namespace PixiEditor.ChangeableDocument.Actions.Generated;
+
+public record class {actionName} : PixiEditor.ChangeableDocument.Actions.IEndChangeAction
+{{
+    bool PixiEditor.ChangeableDocument.Actions.IEndChangeAction.IsChangeTypeMatching(PixiEditor.ChangeableDocument.Changes.Change change)
+    {{
+        return change is {changeConstructorInfo.ContainingClass.NameWithNamespace};
+    }}
+}}
+";
+    }
+
+    public static MethodInfo ExtractMethodInfo(IMethodSymbol method)
+    {
+        List<TypeWithName> variables = method.Parameters.Select(static parameter =>
+        {
+            return new TypeWithName(
+                parameter.Type.Name,
+                parameter.Type.ContainingNamespace.ToDisplayString(),
+                parameter.Name
+                );
+        }).ToList();
+        string changeName = method.ContainingType.Name;
+        string changeFullNamespace = method.ContainingNamespace.ToDisplayString();
+        return new MethodInfo(method.Name, variables, new NamespacedType(changeName, changeFullNamespace));
+    }
+
+    private static void AppendConstructorBody(StringBuilder sb, List<(TypeWithName, TypeWithName)> assignments)
+    {
+        sb.AppendLine("{");
+        foreach (var (property, variable) in assignments)
+        {
+            sb.Append("this.").Append(property.Name).Append(" = ").Append(variable.Name).AppendLine(";");
+        }
+        sb.AppendLine("}");
+    }
+
+    private static List<(TypeWithName, TypeWithName)> MatchMembers(List<TypeWithName> list1, List<TypeWithName> list2)
+    {
+        List<(TypeWithName, TypeWithName)> paired = new();
+        for (int i = list1.Count - 1; i >= 0; i--)
+        {
+            for (int j = list2.Count - 1; j >= 0; j--)
+            {
+                if (list1[i].TypeWithNamespace == list2[j].TypeWithNamespace &&
+                    list1[i].Name.ToLower() == list2[j].Name.ToLower())
+                {
+                    paired.Add((list1[i], list2[j]));
+                }
+            }
+        }
+        return paired;
+    }
+
+    private static void AppendArgumentList(StringBuilder sb, List<TypeWithName> variables)
+    {
+        sb.Append("(");
+        for (int i = 0; i < variables.Count; i++)
+        {
+            sb.Append(variables[i].TypeWithNamespace).Append(" ").Append(variables[i].Name);
+            if (i != variables.Count - 1)
+                sb.Append(", ");
+        }
+        sb.AppendLine(")");
+    }
+
+    private static void AppendUpdateCorrespondingChange
+        (StringBuilder sb, string updateMethodName, NamespacedType corrChangeType, List<TypeWithName> propertiesToPass)
+    {
+        sb.AppendLine("void PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction.UpdateCorrespodingChange(PixiEditor.ChangeableDocument.Changes.UpdateableChange change)");
+        sb.AppendLine("{");
+        sb.Append($"(({corrChangeType.NameWithNamespace})change).{updateMethodName}(");
+        for (int i = 0; i < propertiesToPass.Count; i++)
+        {
+            sb.Append(propertiesToPass[i].Name);
+            if (i != propertiesToPass.Count - 1)
+                sb.Append(", ");
+        }
+        sb.AppendLine(");");
+        sb.AppendLine("}");
+    }
+
+    private static void AppendCreateUpdateableCorrespondingChange
+        (StringBuilder sb, NamespacedType corrChangeType, List<TypeWithName> propertiesToPass)
+    {
+        sb.AppendLine("PixiEditor.ChangeableDocument.Changes.UpdateableChange PixiEditor.ChangeableDocument.Actions.IStartOrUpdateChangeAction.CreateCorrespondingChange()");
+        sb.AppendLine("{");
+        sb.Append($"return new {corrChangeType.NameWithNamespace}(");
+        for (int i = 0; i < propertiesToPass.Count; i++)
+        {
+            sb.Append(propertiesToPass[i].Name);
+            if (i != propertiesToPass.Count - 1)
+                sb.Append(", ");
+        }
+        sb.AppendLine(");");
+        sb.AppendLine("}");
+    }
+
+    private static void AppendCreateCorrespondingChange
+        (StringBuilder sb, NamespacedType corrChangeType, List<TypeWithName> propertiesToPass)
+    {
+        sb.AppendLine("PixiEditor.ChangeableDocument.Changes.Change PixiEditor.ChangeableDocument.Actions.IMakeChangeAction.CreateCorrespondingChange()");
+        sb.AppendLine("{");
+        sb.Append($"return new {corrChangeType.NameWithNamespace}(");
+        for (int i = 0; i < propertiesToPass.Count; i++)
+        {
+            sb.Append(propertiesToPass[i].Name);
+            if (i != propertiesToPass.Count - 1)
+                sb.Append(", ");
+        }
+        sb.AppendLine(");");
+        sb.AppendLine("}");
+    }
+
+    private static void AppendProperties(StringBuilder sb, List<TypeWithName> properties)
+    {
+        foreach (var typeWithName in properties)
+        {
+            sb.AppendLine($"public {typeWithName.TypeWithNamespace} {typeWithName.Name} {{ get; init; }}");
+        }
+    }
+
+    private static string VariableNameIntoPropertyName(string varName)
+    {
+        string lowerCaseName = varName.Substring(0, 1).ToUpperInvariant() + varName.Substring(1);
+        return lowerCaseName;
+    }
+
+    public static bool IsConstructorWithAttribute(SyntaxNode node, CancellationToken token)
+    {
+        return node is ConstructorDeclarationSyntax constructor && constructor.AttributeLists.Count > 0;
+    }
+
+    public static bool IsMethodWithAttribute(SyntaxNode node, CancellationToken token)
+    {
+        return node is MethodDeclarationSyntax method && method.AttributeLists.Count > 0;
+    }
+
+    public static bool IsInheritedFrom(INamedTypeSymbol classSymbol, NamespacedType type)
+    {
+        while (classSymbol.BaseType is not null)
+        {
+            if (classSymbol.BaseType.ToDisplayString() == type.NameWithNamespace)
+                return true;
+            classSymbol = classSymbol.BaseType;
+        }
+        return false;
+    }
+
+    public static bool MethodHasAttribute
+        (GeneratorSyntaxContext context, CancellationToken cancelToken, BaseMethodDeclarationSyntax method, NamespacedType attributeType)
+    {
+        foreach (var attrList in method.AttributeLists)
+        {
+            foreach (var attribute in attrList.Attributes)
+            {
+                cancelToken.ThrowIfCancellationRequested();
+                var symbol = context.SemanticModel.GetSymbolInfo(attribute, cancelToken);
+                if (symbol.Symbol is not IMethodSymbol methodSymbol)
+                    continue;
+                if (methodSymbol.ContainingType.ToDisplayString() != attributeType.NameWithNamespace)
+                    continue;
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 4 - 0
src/PixiEditor.ChangeableDocument.Gen/MethodInfo.cs

@@ -0,0 +1,4 @@
+namespace PixiEditor.ChangeableDocument.Gen
+{
+    internal record struct MethodInfo(string Name, List<TypeWithName> Arguments, NamespacedType ContainingClass);
+}

+ 12 - 0
src/PixiEditor.ChangeableDocument.Gen/NamedSourceCode.cs

@@ -0,0 +1,12 @@
+namespace PixiEditor.ChangeableDocument.Gen;
+internal struct NamedSourceCode
+{
+    public NamedSourceCode(string name, string code)
+    {
+        Name = name;
+        Code = code;
+    }
+
+    public string Name { get; }
+    public string Code { get; }
+}

+ 13 - 0
src/PixiEditor.ChangeableDocument.Gen/NamespacedType.cs

@@ -0,0 +1,13 @@
+namespace PixiEditor.ChangeableDocument.Gen;
+internal struct NamespacedType
+{
+    public NamespacedType(string name, string fullNamespace)
+    {
+        Name = name;
+        FullNamespace = fullNamespace;
+    }
+
+    public string Name { get; }
+    public string FullNamespace { get; }
+    public string NameWithNamespace => FullNamespace + "." + Name;
+}

+ 18 - 0
src/PixiEditor.ChangeableDocument.Gen/PixiEditor.ChangeableDocument.Gen.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <IncludeBuildOutput>false</IncludeBuildOutput>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>true</ImplicitUsings>
+    <LangVersion>Latest</LangVersion>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true"
+        PackagePath="analyzers/dotnet/cs" Visible="false" />
+  </ItemGroup>
+</Project>

+ 21 - 0
src/PixiEditor.ChangeableDocument.Gen/Result.cs

@@ -0,0 +1,21 @@
+namespace PixiEditor.ChangeableDocument.Gen;
+internal struct Result<T>
+{
+    public string? ErrorText { get; }
+    public T? Value { get; }
+
+    private Result(string? error, T? value)
+    {
+        ErrorText = error;
+        Value = value;
+    }
+    public static Result<T> Error(string text)
+    {
+        return new Result<T>(text, default(T));
+    }
+
+    public static implicit operator Result<T>(T value)
+    {
+        return new Result<T>(null, value);
+    }
+}

+ 15 - 0
src/PixiEditor.ChangeableDocument.Gen/TypeWithName.cs

@@ -0,0 +1,15 @@
+namespace PixiEditor.ChangeableDocument.Gen;
+internal struct TypeWithName
+{
+    public TypeWithName(string type, string fullNamespace, string name)
+    {
+        Type = type;
+        FullNamespace = fullNamespace;
+        Name = name;
+    }
+
+    public string Type { get; }
+    public string FullNamespace { get; }
+    public string TypeWithNamespace => FullNamespace + "." + Type;
+    public string Name { get; }
+}

+ 118 - 0
src/PixiEditor.ChangeableDocument.Gen/UpdateableChangeActionGenerator.cs

@@ -0,0 +1,118 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace PixiEditor.ChangeableDocument.Gen;
+[Generator]
+public class UpdateableChangeActionGenerator : IIncrementalGenerator
+{
+    private const string ActionsNamespace = "PixiEditor.ChangeableDocument.Actions";
+    private const string ConstructorAttribute = "GenerateUpdateableChangeActionsAttribute";
+    private const string UpdateMethodAttribute = "UpdateChangeMethodAttribute";
+    private static NamespacedType ConstructorAttributeType = new NamespacedType(ConstructorAttribute, ActionsNamespace);
+    private static NamespacedType UpdateMethodAttributeType = new NamespacedType(UpdateMethodAttribute, ActionsNamespace);
+
+    private static Result<(IMethodSymbol, IMethodSymbol)>? TransformSyntax(GeneratorSyntaxContext context, CancellationToken cancelToken)
+    {
+        ClassDeclarationSyntax containingClass;
+        ConstructorDeclarationSyntax constructorSyntax;
+        // make sure we are actually working with a constructor
+        if (context.Node is ConstructorDeclarationSyntax constructor)
+        {
+            if (!Helpers.MethodHasAttribute(context, cancelToken, constructor, ConstructorAttributeType))
+                return null;
+            containingClass = (ClassDeclarationSyntax)constructor.Parent!;
+            constructorSyntax = constructor;
+        }
+        else
+        {
+            return null;
+        }
+
+        var classSymbol = (INamedTypeSymbol)context.SemanticModel.GetDeclaredSymbol(containingClass)!;
+        if (!Helpers.IsInheritedFrom(classSymbol, new("UpdateableChange", "PixiEditor.ChangeableDocument.Changes")))
+        {
+            return Result<(IMethodSymbol, IMethodSymbol)>.Error
+                ("The GenerateUpdateableChangeActions and UpdateChangeMethodAttribute can only be used inside UpdateableChanges");
+        }
+
+        // here we are sure we are inside an updateable change, time to find the update method
+        MethodDeclarationSyntax? methodSyntax = null;
+        var members = containingClass.Members.Where(node => node is MethodDeclarationSyntax);
+        const string errorMessage = $"Update method isn't marked with {UpdateMethodAttribute}";
+        if (!members.Any())
+            return Result<(IMethodSymbol, IMethodSymbol)>.Error(errorMessage);
+        foreach (var member in members)
+        {
+            cancelToken.ThrowIfCancellationRequested();
+            var method = (MethodDeclarationSyntax)member;
+            bool hasAttr = Helpers.MethodHasAttribute(context, cancelToken, method, UpdateMethodAttributeType);
+            if (hasAttr)
+            {
+                methodSyntax = method;
+                break;
+            }
+        }
+        if (methodSyntax is null)
+        {
+            return Result<(IMethodSymbol, IMethodSymbol)>.Error(errorMessage);
+        }
+
+        // finally, get symbols
+        var methodSymbol = context.SemanticModel.GetDeclaredSymbol(methodSyntax, cancelToken);
+        var contructorSymbol = context.SemanticModel.GetDeclaredSymbol(constructorSyntax, cancelToken);
+        if (contructorSymbol is not IMethodSymbol || methodSymbol is not IMethodSymbol)
+            return null;
+        return ((IMethodSymbol)contructorSymbol, (IMethodSymbol)methodSymbol);
+    }
+
+    private static Result<(NamedSourceCode, NamedSourceCode)> GenerateActions
+        (Result<(IMethodSymbol, IMethodSymbol)>? prevResult, CancellationToken cancelToken)
+    {
+        if (prevResult!.Value.ErrorText is not null)
+            return Result<(NamedSourceCode, NamedSourceCode)>.Error(prevResult.Value.ErrorText);
+        var (constructor, update) = prevResult.Value.Value;
+
+        var constructorInfo = Helpers.ExtractMethodInfo(constructor!);
+        var updateInfo = Helpers.ExtractMethodInfo(update!);
+
+        var maybeStartUpdateAction = Helpers.CreateStartUpdateChangeAction(constructorInfo, updateInfo);
+        if (maybeStartUpdateAction.ErrorText is not null)
+            return Result<(NamedSourceCode, NamedSourceCode)>.Error(maybeStartUpdateAction.ErrorText);
+
+        var endAction = Helpers.CreateEndChangeAction(constructorInfo);
+
+        return (
+            new NamedSourceCode(constructorInfo.ContainingClass.Name + "StartUpdate", maybeStartUpdateAction.Value!),
+            new NamedSourceCode(constructorInfo.ContainingClass.Name + "End", endAction)
+            );
+    }
+
+    public void Initialize(IncrementalGeneratorInitializationContext context)
+    {
+        // find the contrustor and the update method using the attributes
+        var constructorSymbolProvider = context.SyntaxProvider.CreateSyntaxProvider(
+            predicate: static (node, token) => Helpers.IsMethodWithAttribute(node, token) || Helpers.IsConstructorWithAttribute(node, token),
+            transform: TransformSyntax
+        ).Where(a => a is not null);
+
+        // generate the source code of actions
+        var actionSourceCodeProvider = constructorSymbolProvider.Select(GenerateActions);
+
+        // add the source code into compiler input
+        context.RegisterSourceOutput(actionSourceCodeProvider, static (context, namedActions) =>
+        {
+            if (namedActions.ErrorText is not null)
+            {
+                context.ReportDiagnostic(
+                    Diagnostic.Create(
+                        new DiagnosticDescriptor("AGErr", "", namedActions.ErrorText, "UpdateableActionGenerator", DiagnosticSeverity.Error, true),
+                        null));
+                return;
+            }
+
+            var (act1, act2) = namedActions.Value;
+            context.AddSource(act1.Name, act1.Code);
+            context.AddSource(act2.Name, act2.Code);
+        });
+    }
+}

+ 3 - 0
src/PixiEditor.ChangeableDocument/Actions/GenerateMakeChangeActionAttribute.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.Actions;
+[System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
+internal sealed class GenerateMakeChangeActionAttribute : Attribute { }

+ 3 - 0
src/PixiEditor.ChangeableDocument/Actions/GenerateUpdateableChangeActionsAttribute.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.Actions;
+[System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
+internal sealed class GenerateUpdateableChangeActionsAttribute : Attribute { }

+ 3 - 0
src/PixiEditor.ChangeableDocument/Actions/UpdateChangeMethodAttribute.cs

@@ -0,0 +1,3 @@
+namespace PixiEditor.ChangeableDocument.Actions;
+[System.AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
+internal sealed class UpdateChangeMethodAttribute : Attribute { }

+ 4 - 0
src/PixiEditor.ChangeableDocument/PixiEditor.ChangeableDocument.csproj

@@ -9,6 +9,10 @@
 
 
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\ChunkyImageLib\ChunkyImageLib.csproj" />
     <ProjectReference Include="..\ChunkyImageLib\ChunkyImageLib.csproj" />
+    <ProjectReference 
+      Include="..\PixiEditor.ChangeableDocument.Gen\PixiEditor.ChangeableDocument.Gen.csproj" 
+      OutputItemType="Analyzer" 
+      ReferenceOutputAssembly="false"/>
   </ItemGroup>
   </ItemGroup>
 
 
 </Project>
 </Project>

+ 6 - 0
src/PixiEditorPrototype.sln

@@ -22,6 +22,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 		.editorconfig = .editorconfig
 		.editorconfig = .editorconfig
 	EndProjectSection
 	EndProjectSection
 EndProject
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PixiEditor.ChangeableDocument.Gen", "PixiEditor.ChangeableDocument.Gen\PixiEditor.ChangeableDocument.Gen.csproj", "{07EE9CD1-5B4B-454D-A362-F7B42C49412F}"
+EndProject
 Global
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Debug|Any CPU = Debug|Any CPU
@@ -56,6 +58,10 @@ Global
 		{3998B3EB-F11F-4637-A135-FBC3CCA24299}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3998B3EB-F11F-4637-A135-FBC3CCA24299}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{3998B3EB-F11F-4637-A135-FBC3CCA24299}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{3998B3EB-F11F-4637-A135-FBC3CCA24299}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{3998B3EB-F11F-4637-A135-FBC3CCA24299}.Release|Any CPU.Build.0 = Release|Any CPU
 		{3998B3EB-F11F-4637-A135-FBC3CCA24299}.Release|Any CPU.Build.0 = Release|Any CPU
+		{07EE9CD1-5B4B-454D-A362-F7B42C49412F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{07EE9CD1-5B4B-454D-A362-F7B42C49412F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{07EE9CD1-5B4B-454D-A362-F7B42C49412F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{07EE9CD1-5B4B-454D-A362-F7B42C49412F}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 		HideSolutionNode = FALSE