// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using EpicGames.Core;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Logging;
using UnrealBuildTool.Configuration.CompileWarnings;
namespace UnrealBuildTool
{
///
/// Interface that participates in resolving a set of against a member.
///
internal interface IWarningLevelResolver
{
///
/// Resolves a given a set of .
///
/// The set of attributes to consider in resolution.
/// The build system context to evaluate these under.
/// The resolved warning level.
WarningLevel ResolveWarningLevelDefault(IList unsortedDefaultValueAttributes, BuildSystemContext? buildSystemContext);
}
///
/// WarningLevelResolverRegistry is the container for all of the resolvers applicable within the and contexts.
///
/// The primary entry point for extensibility is .
internal class WarningLevelResolverRegistry
{
private readonly ILogger? _logger = Log.Logger;
private static Lazy s_resolverRegistry = new(() => new());
private readonly Dictionary _attributeNameToBehaviour = [];
///
/// The resolver name to resolver representation of the registry.
///
internal static IReadOnlyDictionary Resolvers => s_resolverRegistry.Value._attributeNameToBehaviour;
///
/// Requests a resolver by name.
///
/// The name of the resolver to obtain.
/// The IWarningLevelResolver if it has been registered, null if it was not found.
internal static IWarningLevelResolver? RequestResolver(string resolverName)
{
IWarningLevelResolver? returnResolver;
Resolvers.TryGetValue(resolverName, out returnResolver);
return returnResolver;
}
private WarningLevelResolverRegistry()
{
// Process all of the static methods in the assembly that are annotated wtih WarningLevelResolverDelegateAttribute, and evaluate given they match
// the execution delegate, which is effectively IWarningLevelResolver.ResolveWarningLevelDefault
MethodInfo? reflectiveInterfaceMethodInfo = typeof(IWarningLevelResolver).GetMethod(nameof(IWarningLevelResolver.ResolveWarningLevelDefault));
if (reflectiveInterfaceMethodInfo != null)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type[] allTypes = assembly.GetTypes();
List delegatesForConsideration = new List();
foreach (Type type in allTypes)
{
IEnumerable methodsWithAttribute = type
.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
.Where(m => m.GetCustomAttributes(typeof(WarningLevelResolverDelegateAttribute), false).Length > 0);
delegatesForConsideration.AddRange(methodsWithAttribute);
}
foreach (MethodInfo mi in delegatesForConsideration)
{
if (mi.ReturnType != reflectiveInterfaceMethodInfo.ReturnType)
{
_logger?.LogWarning("Skipping MethodInfo (name:{MethodName}) annotated with WarningLevelResolverDelegateAttribute as it doesn't return {ReturnType}.", mi.Name, reflectiveInterfaceMethodInfo.ReturnType.Name);
continue;
}
if (mi.GetParameters().Length != reflectiveInterfaceMethodInfo.GetParameters().Length)
{
_logger?.LogWarning("Skipping MethodInfo (name:{MethodName}) annotated with WarningLevelResolverDelegateAttribute as it doesn't contain the same number of parameters as the base method interface ({Count})", mi.Name, reflectiveInterfaceMethodInfo.GetParameters().Length);
continue;
}
if (!mi.GetParameters().SequenceEqual(reflectiveInterfaceMethodInfo.GetParameters(), EqualityComparer.Create((a, b) => a?.ParameterType == b?.ParameterType)))
{
_logger?.LogWarning("Skipping MethodInfo (name:{MethodName}) annotated with WarningLevelResolverDelegateAttribute it's parameters don't match the base method interface.", mi.Name);
continue;
}
WarningLevelResolverDelegateAttribute? attributeInstance = mi.GetCustomAttribute();
if (attributeInstance == null)
{
_logger?.LogWarning("Skipping MethodInfo (name:{MethodName}) annotated with WarningLevelResolverDelegateAttribute as reflection has failed to obtain the attribute instance.", mi.Name);
continue;
}
if (!_attributeNameToBehaviour.TryAdd(attributeInstance.ResolverName, new WarningLevelResolverWrapper(attributeInstance, mi)))
{
_logger?.LogWarning("Duplicate resolver names ({DuplicateName}) encountered when building the WarningLevelResolverRegistry.", attributeInstance.ResolverName);
}
}
}
else
{
_logger?.LogWarning("Base method interface was null. Ignoring all method reflective methods used for resolvers.");
}
}
}
}