From c60e5103e02918cab13513584bc4155efa94a6a5 Mon Sep 17 00:00:00 2001 From: Mattias Karlsson Date: Sun, 24 Feb 2019 18:35:41 +0100 Subject: [PATCH] (GH-2487) Warn&exclude duplicate alias in codegen --- .../Scripting/CodeGen/MethodAliasGenerator.cs | 20 ++++++- .../CodeGen/PropertyAliasGenerator.cs | 56 +++++++++++++------ src/Cake.Core/Scripting/Script.cs | 37 ++++++------ .../Scripting/Roslyn/RoslynCodeGenerator.cs | 34 +++++++++-- .../Scripting/Roslyn/RoslynScriptSession.cs | 10 +++- .../Cake.Core/Scripting/AddinDirective.cake | 25 ++++++--- 6 files changed, 133 insertions(+), 49 deletions(-) diff --git a/src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs b/src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs index 2a420d8fac..0cc0bd8c8e 100644 --- a/src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs +++ b/src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs @@ -17,14 +17,26 @@ namespace Cake.Core.Scripting.CodeGen /// public static class MethodAliasGenerator { + private static readonly System.Security.Cryptography.SHA256 SHA256 = System.Security.Cryptography.SHA256.Create(); + + /// + /// Generates a script method alias from the specified method. + /// The provided method must be an extension method for + /// and it must be decorated with the . + /// + /// The method to generate the code for. + /// The generated code. + public static string Generate(MethodInfo method) => Generate(method, out _); + /// /// Generates a script method alias from the specified method. /// The provided method must be an extension method for /// and it must be decorated with the . /// /// The method to generate the code for. + /// The hash of method signature. /// The generated code. - public static string Generate(MethodInfo method) + public static string Generate(MethodInfo method, out string hash) { if (method == null) { @@ -57,6 +69,12 @@ public static string Generate(MethodInfo method) GenericParameterConstraintEmitter.BuildGenericConstraints(method, builder); } + hash = SHA256 + .ComputeHash(Encoding.UTF8.GetBytes(builder.ToString())) + .Aggregate(new StringBuilder(), + (sb, b) => sb.AppendFormat("{0:x2}", b), + sb => sb.ToString()); + builder.AppendLine(); builder.Append("{"); builder.AppendLine(); diff --git a/src/Cake.Core/Scripting/CodeGen/PropertyAliasGenerator.cs b/src/Cake.Core/Scripting/CodeGen/PropertyAliasGenerator.cs index b83f428108..f9922cd949 100644 --- a/src/Cake.Core/Scripting/CodeGen/PropertyAliasGenerator.cs +++ b/src/Cake.Core/Scripting/CodeGen/PropertyAliasGenerator.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Text; @@ -17,6 +18,8 @@ namespace Cake.Core.Scripting.CodeGen /// public static class PropertyAliasGenerator { + private static readonly System.Security.Cryptography.SHA256 SHA256 = System.Security.Cryptography.SHA256.Create(); + /// /// Generates a script property alias from the specified method. /// The provided method must be an extension method for @@ -24,7 +27,17 @@ public static class PropertyAliasGenerator /// /// The method to generate the code for. /// The generated code. - public static string Generate(MethodInfo method) + public static string Generate(MethodInfo method) => Generate(method, out _); + + /// + /// Generates a script property alias from the specified method. + /// The provided method must be an extension method for + /// and it must be decorated with the . + /// + /// The method to generate the code for. + /// The hash of property signature. + /// The generated code. + public static string Generate(MethodInfo method, out string hash) { if (method == null) { @@ -40,8 +53,8 @@ public static string Generate(MethodInfo method) // Generate code. return attribute.Cache - ? GenerateCachedCode(method) - : GenerateCode(method); + ? GenerateCachedCode(method, out hash) + : GenerateCode(method, out hash); } private static void ValidateMethod(MethodInfo method) @@ -98,15 +111,12 @@ private static void ValidateMethodParameters(MethodInfo method) } } - private static string GenerateCode(MethodInfo method) + private static string GenerateCode(MethodInfo method, out string hash) { var builder = new StringBuilder(); - builder.Append("public "); - builder.Append(GetReturnType(method)); - builder.Append(" "); - builder.Append(method.Name); - builder.AppendLine(); + hash = GenerateCommonInitalCode(method, builder); + builder.Append("{"); builder.AppendLine(); builder.Append(" get"); @@ -150,7 +160,25 @@ private static string GenerateCode(MethodInfo method) return builder.ToString(); } - private static string GenerateCachedCode(MethodInfo method) + private static string GenerateCommonInitalCode(MethodInfo method, StringBuilder builder) + { + string hash; + var curPos = builder.Length; + builder.Append("public "); + builder.Append(GetReturnType(method)); + builder.Append(" "); + builder.Append(method.Name); + builder.AppendLine(); + + hash = SHA256 + .ComputeHash(Encoding.UTF8.GetBytes(builder.ToString(curPos, builder.Length - curPos))) + .Aggregate(new StringBuilder(), + (sb, b) => sb.AppendFormat("{0:x2}", b), + sb => sb.ToString()); + return hash; + } + + private static string GenerateCachedCode(MethodInfo method, out string hash) { var builder = new StringBuilder(); @@ -160,7 +188,7 @@ private static string GenerateCachedCode(MethodInfo method) { if (obsolete.IsError) { - return GenerateCode(method); + return GenerateCode(method, out hash); } } @@ -177,11 +205,7 @@ private static string GenerateCachedCode(MethodInfo method) builder.AppendLine(); // Property - builder.Append("public "); - builder.Append(GetReturnType(method)); - builder.Append(" "); - builder.Append(method.Name); - builder.AppendLine(); + hash = GenerateCommonInitalCode(method, builder); builder.Append("{"); builder.AppendLine(); builder.AppendLine(" get"); diff --git a/src/Cake.Core/Scripting/Script.cs b/src/Cake.Core/Scripting/Script.cs index 373286aca2..1d8ccc4a57 100644 --- a/src/Cake.Core/Scripting/Script.cs +++ b/src/Cake.Core/Scripting/Script.cs @@ -12,18 +12,16 @@ namespace Cake.Core.Scripting /// public sealed class Script { - private readonly List _namespaces; - private readonly List _lines; - private readonly List _aliases; - private readonly List _usingAliasDirectives; - private readonly List _usingStaticDirectives; - private readonly List _defines; - /// /// Gets the namespaces imported via the using statement. /// /// The namespaces. - public IReadOnlyList Namespaces => _namespaces; + public IReadOnlyList Namespaces { get; } + + /// + /// Gets the namespaces flagged to be excluded by code generation and affected aliases. + /// + public IDictionary> ExcludedNamespaces { get; } /// /// Gets the script lines. @@ -31,31 +29,31 @@ public sealed class Script /// /// The lines. /// - public IReadOnlyList Lines => _lines; + public IReadOnlyList Lines { get; } /// /// Gets the aliases. /// /// The aliases. - public IReadOnlyList Aliases => _aliases; + public IReadOnlyList Aliases { get; } /// /// Gets the using alias directives. /// /// The using alias directives. - public IReadOnlyList UsingAliasDirectives => _usingAliasDirectives; + public IReadOnlyList UsingAliasDirectives { get; } /// /// Gets the using static directives. /// /// The using static directives. - public IReadOnlyList UsingStaticDirectives => _usingStaticDirectives; + public IReadOnlyList UsingStaticDirectives { get; } /// /// Gets the defines. /// /// The defines. - public IReadOnlyList Defines => _defines; + public IReadOnlyList Defines { get; } /// /// Initializes a new instance of the class. @@ -74,12 +72,13 @@ public Script( IEnumerable usingStaticDirectives, IEnumerable defines) { - _namespaces = new List(namespaces ?? Enumerable.Empty()); - _lines = new List(lines ?? Enumerable.Empty()); - _aliases = new List(aliases ?? Enumerable.Empty()); - _usingAliasDirectives = new List(usingAliasDirectives ?? Enumerable.Empty()); - _usingStaticDirectives = new List(usingStaticDirectives ?? Enumerable.Empty()); - _defines = new List(defines ?? Enumerable.Empty()); + Namespaces = new List(namespaces ?? Enumerable.Empty()); + Lines = new List(lines ?? Enumerable.Empty()); + Aliases = new List(aliases ?? Enumerable.Empty()); + UsingAliasDirectives = new List(usingAliasDirectives ?? Enumerable.Empty()); + UsingStaticDirectives = new List(usingStaticDirectives ?? Enumerable.Empty()); + Defines = new List(defines ?? Enumerable.Empty()); + ExcludedNamespaces = new Dictionary>(System.StringComparer.Ordinal); } } } \ No newline at end of file diff --git a/src/Cake/Scripting/Roslyn/RoslynCodeGenerator.cs b/src/Cake/Scripting/Roslyn/RoslynCodeGenerator.cs index 9340c66056..e7ef52f366 100644 --- a/src/Cake/Scripting/Roslyn/RoslynCodeGenerator.cs +++ b/src/Cake/Scripting/Roslyn/RoslynCodeGenerator.cs @@ -22,14 +22,38 @@ public string Generate(Script script) private static string GetAliasCode(Script context) { - var result = new List(); + var result = new Dictionary(); foreach (var alias in context.Aliases) { - result.Add(alias.Type == ScriptAliasType.Method - ? MethodAliasGenerator.Generate(alias.Method) - : PropertyAliasGenerator.Generate(alias.Method)); + string hash, + code = alias.Type == ScriptAliasType.Method + ? MethodAliasGenerator.Generate(alias.Method, out hash) + : PropertyAliasGenerator.Generate(alias.Method, out hash); + + string @namespace = alias.Method.DeclaringType.Namespace ?? "@null"; + if (result.ContainsKey(hash)) + { + var message = $"{alias.Type} \"{alias.Name}\" excluded from code generation and will need to be fully qualified on ICakeContext."; + if (context.ExcludedNamespaces.ContainsKey(@namespace)) + { + context.ExcludedNamespaces[@namespace].Add(message); + } + else + { + context.ExcludedNamespaces.Add(@namespace, + new List() { message }); + } + continue; + } + else if (context.ExcludedNamespaces.ContainsKey(@namespace)) + { + var message = $"{alias.Type} \"{alias.Name}\" was included in code generation, but will need to be fully qualified on ICakeContext."; + context.ExcludedNamespaces[@namespace].Add(message); + } + + result.Add(hash, code); } - return string.Join("\r\n", result); + return string.Join("\r\n", result.Values); } } } \ No newline at end of file diff --git a/src/Cake/Scripting/Roslyn/RoslynScriptSession.cs b/src/Cake/Scripting/Roslyn/RoslynScriptSession.cs index 6c16fd6653..689d07914d 100644 --- a/src/Cake/Scripting/Roslyn/RoslynScriptSession.cs +++ b/src/Cake/Scripting/Roslyn/RoslynScriptSession.cs @@ -82,9 +82,17 @@ public void Execute(Script script) var generator = new RoslynCodeGenerator(); var code = generator.Generate(script); + // Warn about any code generation excluded namespaces + foreach (var @namespace in script.ExcludedNamespaces) + { + _log.Warning("Namespace {0} excluded by code generation, affected methods:\r\n\t{1}", + @namespace.Key, + string.Join("\r\n\t", @namespace.Value)); + } + // Create the script options dynamically. var options = Microsoft.CodeAnalysis.Scripting.ScriptOptions.Default - .AddImports(Namespaces) + .AddImports(Namespaces.Except(script.ExcludedNamespaces.Keys)) .AddReferences(References) .AddReferences(ReferencePaths.Select(r => r.FullPath)) .WithEmitDebugInformation(_options.PerformDebug) diff --git a/tests/integration/Cake.Core/Scripting/AddinDirective.cake b/tests/integration/Cake.Core/Scripting/AddinDirective.cake index 8b99216b0e..9aedc802e2 100644 --- a/tests/integration/Cake.Core/Scripting/AddinDirective.cake +++ b/tests/integration/Cake.Core/Scripting/AddinDirective.cake @@ -1,3 +1,5 @@ +#addin nuget:?package=Cake.Incubator&version=4.0.0 + Task("Cake.Core.Scripting.AddinDirective.LoadNetStandardAddin") .Does(() => { @@ -12,15 +14,24 @@ Task("Cake.Core.Scripting.AddinDirective.LoadNetStandardAddin") "; CakeExecuteExpression(script, - new CakeSettings { - EnvironmentVariables = new Dictionary{ - {"CAKE_PATHS_ADDINS", $"{Paths.Temp}/tools/Addins"}, - {"CAKE_PATHS_TOOLS", $"{Paths.Temp}/tools"}, - {"CAKE_PATHS_MODULES", $"{Paths.Temp}/tools/Modules"} - }}); + new CakeSettings { + EnvironmentVariables = new Dictionary{ + {"CAKE_PATHS_ADDINS", $"{Paths.Temp}/tools/Addins"}, + {"CAKE_PATHS_TOOLS", $"{Paths.Temp}/tools"}, + {"CAKE_PATHS_MODULES", $"{Paths.Temp}/tools/Modules"}, + }, + Verbosity = Context.Log.Verbosity + }); +}); + +Task("Cake.Core.Scripting.AddinDirective.CallDuplicatedMethod") + .Does(context => +{ + var result = context.EnvironmentVariable("CAKE_DOES_ROCK", true); }); ////////////////////////////////////////////////////////////////////////////// Task("Cake.Core.Scripting.AddinDirective") - .IsDependentOn("Cake.Core.Scripting.AddinDirective.LoadNetStandardAddin"); \ No newline at end of file + .IsDependentOn("Cake.Core.Scripting.AddinDirective.LoadNetStandardAddin") + .IsDependentOn("Cake.Core.Scripting.AddinDirective.CallDuplicatedMethod"); \ No newline at end of file