From 51ad9e38c21046c1c6c3a423aea160648fb19ce3 Mon Sep 17 00:00:00 2001 From: AdmiringWorm Date: Sun, 19 Jul 2020 16:10:12 +0200 Subject: [PATCH] (GH-17) Add support for loading generic reporter dynamically --- Cake.Issues.Recipe/Content/addins.cake | 5 +- Cake.Issues.Recipe/Content/build.cake | 29 ++-- .../Content/loader/AddinData.cake | 126 +++++++++++------- .../reporters/GenericReporterData.cake | 38 ++++++ .../Content/reporters/reporters.cake | 1 + .../buildservers/AzureDevOpsBuildServer.cake | 23 ++-- 6 files changed, 151 insertions(+), 71 deletions(-) create mode 100644 Cake.Issues.Recipe/Content/reporters/GenericReporterData.cake create mode 100644 Cake.Issues.Recipe/Content/reporters/reporters.cake diff --git a/Cake.Issues.Recipe/Content/addins.cake b/Cake.Issues.Recipe/Content/addins.cake index 7c65af09..47461542 100644 --- a/Cake.Issues.Recipe/Content/addins.cake +++ b/Cake.Issues.Recipe/Content/addins.cake @@ -8,8 +8,9 @@ #addin nuget:?package=Cake.Issues.InspectCode&version=0.8.0 #addin nuget:?package=Cake.Issues.Markdownlint&version=0.8.2 #addin nuget:?package=Cake.Issues.Reporting&version=0.8.0 -#addin nuget:?package=Cake.Issues.Reporting.Generic&version=0.8.2 #addin nuget:?package=Cake.Issues.PullRequests&version=0.8.1 #addin nuget:?package=Cake.Issues.PullRequests.AppVeyor&version=0.8.0 #addin nuget:?package=Cake.Issues.PullRequests.AzureDevOps&version=0.8.0 -#addin nuget:?package=Cake.AzureDevOps&version=0.4.4 \ No newline at end of file +#addin nuget:?package=Cake.AzureDevOps&version=0.4.4 + +public const string CakeIssuesReportingGenericVersion = "0.8.2"; \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/build.cake b/Cake.Issues.Recipe/Content/build.cake index 71def00e..4ec39ce1 100644 --- a/Cake.Issues.Recipe/Content/build.cake +++ b/Cake.Issues.Recipe/Content/build.cake @@ -2,7 +2,9 @@ #load IssuesBuildTasksDefinitions.cake #load version.cake #load data/data.cake +#load loader/loader.cake #load parameters/parameters.cake +#load reporters/reporters.cake /////////////////////////////////////////////////////////////////////////////// // GLOBAL VARIABLES @@ -113,16 +115,23 @@ IssuesBuildTasks.CreateFullIssuesReportTask = Task("Create-FullIssuesReport") IssuesParameters.OutputDirectory.CombineWithFilePath(reportFileName); EnsureDirectoryExists(IssuesParameters.OutputDirectory); - // Create HTML report using DevExpress template. - var settings = - GenericIssueReportFormatSettings - .FromEmbeddedTemplate(GenericIssueReportTemplate.HtmlDxDataGrid) - .WithOption(HtmlDxDataGridOption.Theme, DevExtremeTheme.MaterialBlueLight); - CreateIssueReport( - data.Issues, - GenericIssueReportFormat(settings), - data.RepositoryRootDirectory, - data.FullIssuesReport); + var issueFormats = new List(); + + if (!Context.Environment.Runtime.IsCoreClr) + { + Information("Creating report format using Generic Reporter"); + issueFormats.Add(GenericReporterData.CreateIssueFormatFromEmbeddedTemplate(Context, IssuesParameters.Reporting)); + } + + foreach (var issueFormat in issueFormats) + { + CreateIssueReport( + data.Issues, + issueFormat, + data.RepositoryRootDirectory, + data.FullIssuesReport + ); + } }); IssuesBuildTasks.PublishIssuesArtifactsTask = Task("Publish-IssuesArtifacts") diff --git a/Cake.Issues.Recipe/Content/loader/AddinData.cake b/Cake.Issues.Recipe/Content/loader/AddinData.cake index 8e566422..3122b86d 100644 --- a/Cake.Issues.Recipe/Content/loader/AddinData.cake +++ b/Cake.Issues.Recipe/Content/loader/AddinData.cake @@ -37,11 +37,7 @@ public class AddinData foreach (var ctx in constructors) { var ctxParams = ctx.GetParameters(); - bool useCtx = true; - for (int i = 0; i < ctxParams.Length && useCtx; i++) - { - useCtx = ctxParams[i].ParameterType == parameters[i].GetType(); - } + bool useCtx = ParametersMatch(ctxParams, parameters); if (useCtx) { @@ -60,76 +56,104 @@ public class AddinData public object CallStaticMethod(string methodName, params object[] parameters) { - parameters = parameters ?? new object[0]; + parameters = TransformParameters(parameters); - for (int i = 0; i < parameters.Length; i++) + var methods = this._definedMethods.Where(m => m.IsPublic && m.IsStatic && string.Compare(m.Name, methodName, StringComparison.OrdinalIgnoreCase) == 0); + MethodInfo method = null; + + foreach (var m in methods.Where(m => m.GetParameters().Length == parameters.Length)) { - var parameterType = parameters[i].GetType(); - int index = parameterType == typeof(string) ? parameters[i].ToString().IndexOf('.') : -1; - if (index >= 0) + var methodParams = m.GetParameters(); + bool useMethod = ParametersMatch(methodParams, parameters); + + if (useMethod) { - var enumOrClass = parameters[i].ToString().Substring(0, index); - var enumType = _declaredEnums.FirstOrDefault(e => string.Compare(e.Name, enumOrClass, StringComparison.OrdinalIgnoreCase) == 0); - if (enumType is object) - { - var value = parameters[i].ToString().Substring(index+1); - parameters[i] = Enum.Parse(enumType, value); - } + method = m; + break; } } - var methods = this._definedMethods.Where(m => m.IsPublic && m.IsStatic && string.Compare(m.Name, methodName, StringComparison.OrdinalIgnoreCase) == 0); - MethodInfo method = null; + if (method is null) + { + throw new NullReferenceException($"No method with the name '{methodName}' was found!"); + } - foreach (var m in methods.Where(m => m.GetParameters().Length == parameters.Length)) + return method.Invoke(null, parameters); + } + + public object[] TransformParameters(params object[] parameters) + { + var newParameters = new List(); + if (parameters is null) { - var methodParams = m.GetParameters(); - bool useMethod = true; + return newParameters.ToArray(); + } - for (int i = 0; i < methodParams.Length && useMethod; i++) + foreach (var parameter in parameters) + { + object value = parameter; + if (parameter is string sParam) { - var methodParamType = methodParams[i].ParameterType; - var optionParamType = parameters[i].GetType(); - if (methodParamType.IsEnum && optionParamType == typeof(string)) + int index = sParam.IndexOf('.'); + if (index >= 0) { - try + var enumOrClass = sParam.Substring(0, index); + var subValue = sParam.Substring(index+1); + var enumType = _declaredEnums.FirstOrDefault(e => string.Compare(e.Name, enumOrClass, StringComparison.OrdinalIgnoreCase) == 0); + var classType = _definedClasses.FirstOrDefault(c => string.Compare(c.Name, enumOrClass, StringComparison.OrdinalIgnoreCase) == 0); + if (enumType is object) { - var parsedValue = Enum.Parse(methodParamType, parameters[i].ToString()); - if (parsedValue is object) - { - parameters[i] = parsedValue; - } - else - useMethod = false; + value = Enum.Parse(enumType, subValue); } - catch + else if (classType is object) { - useMethod = false; + var property = classType.GetProperty(subValue, BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Static); + value = property.GetValue(null); } } - else if (methodParamType == typeof(Enum) && optionParamType.IsEnum) + } + + newParameters.Add(value); + } + + return newParameters.ToArray(); + } + + public static bool ParametersMatch(ParameterInfo[] methodParameters, object[] parameters) + { + bool useMethod = true; + for (int i = 0; i < methodParameters.Length && useMethod; i++) + { + var methodParamType = methodParameters[i].ParameterType; + var optionParamType = parameters[i].GetType(); + if (methodParamType.IsEnum && optionParamType == typeof(string)) + { + try { - useMethod = true; - } - else + var parsedValue = Enum.Parse(methodParamType, parameters[i].ToString()); + if (parsedValue is object) + { + parameters[i] = parsedValue; + } + else + useMethod = false; + } + catch { - useMethod = methodParamType == optionParamType || methodParamType.IsAssignableFrom(optionParamType); + useMethod = false; } } - - if (useMethod) + else if (methodParamType == typeof(Enum) && optionParamType.IsEnum) { - method = m; - break; + useMethod = true; + } + else + { + useMethod = methodParamType == optionParamType || methodParamType.IsAssignableFrom(optionParamType); } } - if (method is null) - { - throw new NullReferenceException("No method with the specified name was found!"); - } - - return method.Invoke(null, parameters); + return useMethod; } protected void Initialize(ICakeContext context, string packageName, string packageVersion, string assemblyName = null) diff --git a/Cake.Issues.Recipe/Content/reporters/GenericReporterData.cake b/Cake.Issues.Recipe/Content/reporters/GenericReporterData.cake new file mode 100644 index 00000000..01ffa09a --- /dev/null +++ b/Cake.Issues.Recipe/Content/reporters/GenericReporterData.cake @@ -0,0 +1,38 @@ +public class GenericReporterData : AddinData +{ + private static GenericReporterData _reporter = null; + + private GenericReporterData(ICakeContext context) + : base(context, "Cake.Issues.Reporting.Generic", CakeIssuesReportingGenericVersion) + { + _reporter = this; + } + + public static GenericReporterData GetReporter(ICakeContext context) + { + return _reporter ?? new GenericReporterData(context); + } + + public static IIssueReportFormat CreateIssueFormatFromEmbeddedTemplate(ICakeContext context, IssuesParametersReporting parameters) + { + var reporter = GetReporter(context); + + var settings = reporter.CallStaticMethod("FromEmbeddedTemplate", "HtmlDxDataGrid"); + + var theme = "DevExtremeTheme.MaterialBlueLight"; // Should be changed to be set on IssuesParametersReporting settings class. + reporter.CallStaticMethod("WithOption", settings, "HtmlDxDataGridOption.Theme", theme); + + var issueFormat = (IIssueReportFormat)reporter.CallStaticMethod("GenericIssueReportFormat", context, settings); + + return issueFormat; + } + + public static IIssueReportFormat CreateIssueFormatFromFilePath(ICakeContext context, IssuesParametersReporting parameters, FilePath reportPath) + { + var reporter = GetReporter(context); + + var issueFormat = (IIssueReportFormat)reporter.CallStaticMethod("GenericIssueReportFormatFromFilePath", context, reportPath); + + return issueFormat; + } +} \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/reporters/reporters.cake b/Cake.Issues.Recipe/Content/reporters/reporters.cake new file mode 100644 index 00000000..49a32e77 --- /dev/null +++ b/Cake.Issues.Recipe/Content/reporters/reporters.cake @@ -0,0 +1 @@ +#load ./GenericReporterData.cake \ No newline at end of file diff --git a/Cake.Issues.Recipe/Content/tasks/buildservers/AzureDevOpsBuildServer.cake b/Cake.Issues.Recipe/Content/tasks/buildservers/AzureDevOpsBuildServer.cake index b1d6f539..0fe1ff9e 100644 --- a/Cake.Issues.Recipe/Content/tasks/buildservers/AzureDevOpsBuildServer.cake +++ b/Cake.Issues.Recipe/Content/tasks/buildservers/AzureDevOpsBuildServer.cake @@ -97,15 +97,22 @@ public class AzureDevOpsBuildServer : BaseBuildServer summaryFileName += ".md"; var summaryFilePath = IssuesParameters.OutputDirectory.CombineWithFilePath(summaryFileName); - // Create summary for Azure Pipelines using custom template. - context.CreateIssueReport( - data.Issues, - context.GenericIssueReportFormatFromFilePath( - new FilePath(sourceFilePath).GetDirectory().Combine("tasks").Combine("buildservers").CombineWithFilePath("AzurePipelineSummary.cshtml")), - data.RepositoryRootDirectory, - summaryFilePath); + var issueFormats = new List(); + if (!context.Environment.Runtime.IsCoreClr) + { + var reportPath = new FilePath(sourceFilePath).GetDirectory().Combine("tasks").Combine("buildservers").CombineWithFilePath("AzurePipelinesSummary.cshtml"); + issueFormats.Add(GenericReporterData.CreateIssueFormatFromFilePath(context, IssuesParameters.Reporting, reportPath)); + } - context.TFBuild().Commands.UploadTaskSummary(summaryFilePath); + foreach (var issueFormat in issueFormats) + { + context.CreateIssueReport( + data.Issues, + issueFormat, + data.RepositoryRootDirectory, + summaryFilePath + ); + } } ///