Skip to content

Commit

Permalink
(GH-2487) Warn&exclude duplicate alias in codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
devlead committed Feb 26, 2019
1 parent 5e8e454 commit c60e510
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 49 deletions.
20 changes: 19 additions & 1 deletion src/Cake.Core/Scripting/CodeGen/MethodAliasGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,26 @@ namespace Cake.Core.Scripting.CodeGen
/// </summary>
public static class MethodAliasGenerator
{
private static readonly System.Security.Cryptography.SHA256 SHA256 = System.Security.Cryptography.SHA256.Create();

/// <summary>
/// Generates a script method alias from the specified method.
/// The provided method must be an extension method for <see cref="ICakeContext"/>
/// and it must be decorated with the <see cref="CakeMethodAliasAttribute"/>.
/// </summary>
/// <param name="method">The method to generate the code for.</param>
/// <returns>The generated code.</returns>
public static string Generate(MethodInfo method) => Generate(method, out _);

/// <summary>
/// Generates a script method alias from the specified method.
/// The provided method must be an extension method for <see cref="ICakeContext"/>
/// and it must be decorated with the <see cref="CakeMethodAliasAttribute"/>.
/// </summary>
/// <param name="method">The method to generate the code for.</param>
/// <param name="hash">The hash of method signature.</param>
/// <returns>The generated code.</returns>
public static string Generate(MethodInfo method)
public static string Generate(MethodInfo method, out string hash)
{
if (method == null)
{
Expand Down Expand Up @@ -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();
Expand Down
56 changes: 40 additions & 16 deletions src/Cake.Core/Scripting/CodeGen/PropertyAliasGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -17,14 +18,26 @@ namespace Cake.Core.Scripting.CodeGen
/// </summary>
public static class PropertyAliasGenerator
{
private static readonly System.Security.Cryptography.SHA256 SHA256 = System.Security.Cryptography.SHA256.Create();

/// <summary>
/// Generates a script property alias from the specified method.
/// The provided method must be an extension method for <see cref="ICakeContext"/>
/// and it must be decorated with the <see cref="CakePropertyAliasAttribute"/>.
/// </summary>
/// <param name="method">The method to generate the code for.</param>
/// <returns>The generated code.</returns>
public static string Generate(MethodInfo method)
public static string Generate(MethodInfo method) => Generate(method, out _);

/// <summary>
/// Generates a script property alias from the specified method.
/// The provided method must be an extension method for <see cref="ICakeContext"/>
/// and it must be decorated with the <see cref="CakePropertyAliasAttribute"/>.
/// </summary>
/// <param name="method">The method to generate the code for.</param>
/// <param name="hash">The hash of property signature.</param>
/// <returns>The generated code.</returns>
public static string Generate(MethodInfo method, out string hash)
{
if (method == null)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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();

Expand All @@ -160,7 +188,7 @@ private static string GenerateCachedCode(MethodInfo method)
{
if (obsolete.IsError)
{
return GenerateCode(method);
return GenerateCode(method, out hash);
}
}

Expand All @@ -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");
Expand Down
37 changes: 18 additions & 19 deletions src/Cake.Core/Scripting/Script.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,50 +12,48 @@ namespace Cake.Core.Scripting
/// </summary>
public sealed class Script
{
private readonly List<string> _namespaces;
private readonly List<string> _lines;
private readonly List<ScriptAlias> _aliases;
private readonly List<string> _usingAliasDirectives;
private readonly List<string> _usingStaticDirectives;
private readonly List<string> _defines;

/// <summary>
/// Gets the namespaces imported via the <c>using</c> statement.
/// </summary>
/// <value>The namespaces.</value>
public IReadOnlyList<string> Namespaces => _namespaces;
public IReadOnlyList<string> Namespaces { get; }

/// <summary>
/// Gets the namespaces flagged to be excluded by code generation and affected aliases.
/// </summary>
public IDictionary<string, IList<string>> ExcludedNamespaces { get; }

/// <summary>
/// Gets the script lines.
/// </summary>
/// <value>
/// The lines.
/// </value>
public IReadOnlyList<string> Lines => _lines;
public IReadOnlyList<string> Lines { get; }

/// <summary>
/// Gets the aliases.
/// </summary>
/// <value>The aliases.</value>
public IReadOnlyList<ScriptAlias> Aliases => _aliases;
public IReadOnlyList<ScriptAlias> Aliases { get; }

/// <summary>
/// Gets the using alias directives.
/// </summary>
/// <value>The using alias directives.</value>
public IReadOnlyList<string> UsingAliasDirectives => _usingAliasDirectives;
public IReadOnlyList<string> UsingAliasDirectives { get; }

/// <summary>
/// Gets the using static directives.
/// </summary>
/// <value>The using static directives.</value>
public IReadOnlyList<string> UsingStaticDirectives => _usingStaticDirectives;
public IReadOnlyList<string> UsingStaticDirectives { get; }

/// <summary>
/// Gets the defines.
/// </summary>
/// <value>The defines.</value>
public IReadOnlyList<string> Defines => _defines;
public IReadOnlyList<string> Defines { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Script" /> class.
Expand All @@ -74,12 +72,13 @@ public Script(
IEnumerable<string> usingStaticDirectives,
IEnumerable<string> defines)
{
_namespaces = new List<string>(namespaces ?? Enumerable.Empty<string>());
_lines = new List<string>(lines ?? Enumerable.Empty<string>());
_aliases = new List<ScriptAlias>(aliases ?? Enumerable.Empty<ScriptAlias>());
_usingAliasDirectives = new List<string>(usingAliasDirectives ?? Enumerable.Empty<string>());
_usingStaticDirectives = new List<string>(usingStaticDirectives ?? Enumerable.Empty<string>());
_defines = new List<string>(defines ?? Enumerable.Empty<string>());
Namespaces = new List<string>(namespaces ?? Enumerable.Empty<string>());
Lines = new List<string>(lines ?? Enumerable.Empty<string>());
Aliases = new List<ScriptAlias>(aliases ?? Enumerable.Empty<ScriptAlias>());
UsingAliasDirectives = new List<string>(usingAliasDirectives ?? Enumerable.Empty<string>());
UsingStaticDirectives = new List<string>(usingStaticDirectives ?? Enumerable.Empty<string>());
Defines = new List<string>(defines ?? Enumerable.Empty<string>());
ExcludedNamespaces = new Dictionary<string, IList<string>>(System.StringComparer.Ordinal);
}
}
}
34 changes: 29 additions & 5 deletions src/Cake/Scripting/Roslyn/RoslynCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,38 @@ public string Generate(Script script)

private static string GetAliasCode(Script context)
{
var result = new List<string>();
var result = new Dictionary<string, string>();
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<string>() { 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);
}
}
}
10 changes: 9 additions & 1 deletion src/Cake/Scripting/Roslyn/RoslynScriptSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
25 changes: 18 additions & 7 deletions tests/integration/Cake.Core/Scripting/AddinDirective.cake
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#addin nuget:?package=Cake.Incubator&version=4.0.0

Task("Cake.Core.Scripting.AddinDirective.LoadNetStandardAddin")
.Does(() =>
{
Expand All @@ -12,15 +14,24 @@ Task("Cake.Core.Scripting.AddinDirective.LoadNetStandardAddin")
";
CakeExecuteExpression(script,
new CakeSettings {
EnvironmentVariables = new Dictionary<string, string>{
{"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<string, string>{
{"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");
.IsDependentOn("Cake.Core.Scripting.AddinDirective.LoadNetStandardAddin")
.IsDependentOn("Cake.Core.Scripting.AddinDirective.CallDuplicatedMethod");

0 comments on commit c60e510

Please sign in to comment.