// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
namespace UnrealBuildTool.Configuration.CompileWarnings
{
///
/// Flag used to communicate which context the should be applied.
///
internal enum InitializationContext
{
///
/// Apply the in all contexts.
///
Any,
///
/// Apply the exclusively in the context.
///
Target,
///
/// Apply the exclusively in the context.
///
Constructor
}
internal static class WarningLevelDefaultHelpers
{
public static IList? CastWarningLevelDefaultAttirbuteToTarget(IList uncastedAttributes, ILogger? logger)
{
List? castedAttributes = null;
try
{
castedAttributes = uncastedAttributes.Cast().ToList();
}
catch (Exception ex)
{
logger?.LogError("Exception occurred {ExMessage}. Received a set of attributes that weren't of the correct warning attribute type ({Type})", ex.Message, nameof(T));
}
return castedAttributes;
}
}
///
/// Abstract attribute used to specify the default to assign a member, and the interface,
///
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
internal abstract class WarningLevelDefaultAttribute : Attribute
{
protected static readonly ILogger? Logger = Log.Logger;
///
/// Constructor a WarningLevelDefaultAttribute.
///
/// The context in which to apply this default.
public WarningLevelDefaultAttribute(InitializationContext context = InitializationContext.Any)
{
Context = context;
}
///
/// Gets the default to apply to the member.
///
/// The build system context used to consider defaults and applicability.
///
public abstract WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext);
///
/// The context in which this attribute should be applied.
///
public InitializationContext Context { get; }
}
///
/// Basic attribute used to set the default values of a under the context of context and context.
///
/// When combined with any other , it will act as the fallback in resolution if no other attribute sets a non-.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
#pragma warning disable CA1813 // Avoid unsealed attributes
internal class BasicWarningLevelDefaultAttribute : WarningLevelDefaultAttribute
#pragma warning restore CA1813 // Avoid unsealed attributes
{
///
/// Constructor a BasicWarningLevelDefaultAttribute.
///
/// The default warning level to apply for this bound.
/// The context in which to apply this default.
#pragma warning disable CA1019 // Define accessors for attribute arguments
public BasicWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(context)
#pragma warning restore CA1019 // Define accessors for attribute arguments
{
DefaultLevel = defaultLevel;
}
///
/// The default warning level.
///
/// The build system context used to conditionally augment the by.
public override WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext = null)
{
return DefaultLevel;
}
///
/// Resolver for .
///
/// The set of attributes of which to consider for resolution.
/// The build system context used to consider defaults and applicability.
/// The corresponding of which to apply to the property with the default value attributes.
[WarningLevelResolverDelegate(nameof(BasicWarningLevelDefaultAttribute))]
internal static WarningLevel ResolveWarningLevelDefault(IList unsortedDefaultValueAttributes, BuildSystemContext? buildSystemContext)
{
if (unsortedDefaultValueAttributes.Count != 1)
{
Logger?.LogError("Invalid attribute definition for {Name}.", nameof(WarningLevelDefaultAttribute));
return WarningLevel.Default;
}
return unsortedDefaultValueAttributes[0].GetDefaultLevel(buildSystemContext);
}
protected readonly WarningLevel DefaultLevel;
}
#region -- Compiler Constrained Warning Level Defaults --
///
/// Compiler constrained Attribute used to set the default values of a under the context of context and context.
///
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
internal abstract class CompilerWarningLevelDefaultAttribute : BasicWarningLevelDefaultAttribute
{
public WindowsCompiler Compiler { get; }
///
/// Constructs a CompilerWarningLevelDefaultAttribute.
///
/// The compiler which this attribute should be applied.
/// The default warning level to apply for this bound.
/// The context in which to apply this default.
public CompilerWarningLevelDefaultAttribute(WindowsCompiler compiler, WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(defaultLevel, context)
{
Compiler = compiler;
}
///
/// Resolves the set of to a provided the current .
///
/// The set of attributes of which to consider for resolution.
/// The build system context used to consider defaults and applicability.
/// The corresponding of which to apply to the property with the default value attributes.
[WarningLevelResolverDelegate(nameof(CompilerWarningLevelDefaultAttribute))]
#pragma warning disable IDE0060 // Remove unused parameter
internal static WarningLevel ResolveVersionWarningLevelDefault(IList unsortedDefaultValueAttributes, BuildSystemContext? buildSystemContext)
#pragma warning restore IDE0060 // Remove unused parameter
{
IList? castedAttributes = WarningLevelDefaultHelpers.CastWarningLevelDefaultAttirbuteToTarget(unsortedDefaultValueAttributes, Logger);
if (castedAttributes == null)
{
Logger?.LogWarning("Unable to discern default warning level through resolution. Defaulting to WarningLevel.Default.");
return WarningLevel.Default;
}
int nonDefaultCount = castedAttributes.Select(x => x.GetDefaultLevel(buildSystemContext) != WarningLevel.Default).Count();
if (nonDefaultCount > 1)
{
Logger?.LogWarning("Unable to disambiguate the warning level through resolution. Too many CompilerWarningLevelDefaultAttribute returned a resolved value. Defaulting to WarningLevel.Default.");
return WarningLevel.Default;
}
if (nonDefaultCount == 1)
{
return castedAttributes.Where(x => x.GetDefaultLevel(buildSystemContext) != WarningLevel.Default).Select(x => x.GetDefaultLevel(buildSystemContext)).First();
}
return WarningLevel.Default;
}
}
///
/// Version constrained attribute used to set the default values of a under the context of context and context.
///
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
internal sealed class IntelCompilerWarningLevelDefaultAttribute : CompilerWarningLevelDefaultAttribute
{
///
/// Constructs a IntelCompilerWarningLevelDefaultAttribute.
///
/// The default warning level to apply for this bound.
/// The context in which to apply this default.
public IntelCompilerWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(WindowsCompiler.Intel, defaultLevel, context) { }
public override WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext = null)
{
if (buildSystemContext == null)
{
return WarningLevel.Default;
}
ReadOnlyTargetRules? readOnlyTargetRules = buildSystemContext.GetReadOnlyTargetRules();
if (readOnlyTargetRules?.WindowsPlatform?.Compiler.IsIntel() == true)
{
return DefaultLevel;
}
return WarningLevel.Default;
}
}
///
/// Version constrained attribute used to set the default values of a under the context of context and context.
///
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
internal sealed class MSVCCompilerWarningLevelDefaultAttribute : CompilerWarningLevelDefaultAttribute
{
///
/// Constructs a MSVCCompilerWarningLevelDefaultAttribute.
///
/// The default warning level to apply for this bound.
/// The context in which to apply this default.
public MSVCCompilerWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(WindowsCompiler.VisualStudio2022, defaultLevel, context) { }
public override WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext = null)
{
if (buildSystemContext == null)
{
return WarningLevel.Default;
}
ReadOnlyTargetRules? readOnlyTargetRules = buildSystemContext.GetReadOnlyTargetRules();
if (readOnlyTargetRules?.WindowsPlatform?.Compiler.IsMSVC() == true)
{
return DefaultLevel;
}
return WarningLevel.Default;
}
}
///
/// Version constrained attribute used to set the default values of a under the context of context and context.
///
/// Although is considered clang, this specificaly removes that.
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
internal sealed class ClangNonIntelSpecializedCompilerWarningLevelDefaultAttribute : CompilerWarningLevelDefaultAttribute
{
///
/// Constructs a ClangNonIntelSpecializedCompilerWarningLevelDefaultAttribute.
///
/// The default warning level to apply for this bound.
/// The context in which to apply this default.
public ClangNonIntelSpecializedCompilerWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, InitializationContext context = InitializationContext.Any) : base(WindowsCompiler.Clang, defaultLevel, context) { }
public override WarningLevel GetDefaultLevel(BuildSystemContext? buildSystemContext = null)
{
if (buildSystemContext == null)
{
return WarningLevel.Default;
}
ReadOnlyTargetRules? readOnlyTargetRules = buildSystemContext.GetReadOnlyTargetRules();
if (readOnlyTargetRules?.WindowsPlatform?.Compiler.IsClang() == true && !readOnlyTargetRules.WindowsPlatform.Compiler.IsIntel())
{
return DefaultLevel;
}
return WarningLevel.Default;
}
}
#endregion -- Compiler Constrained Warning Level Defaults --
///
/// Version constrained attribute used to set the default values of a under the context of context and context.
///
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)]
internal sealed class VersionWarningLevelDefaultAttribute : BasicWarningLevelDefaultAttribute
{
///
/// The lower bound (inclusive) of which to apply this to.
///
public BuildSettingsVersion MinVersion { get; }
///
/// The upper bound (inclusive) of which to apply this to.
///
public BuildSettingsVersion MaxVersion { get; }
///
/// Constructs a VersionWarningLevelDefaultAttribute.
///
/// Minimum version of the bound to apply this setting to. Inclusive.
/// Maximum version of the bound to apply this setting to. Inclusive.
/// The default warning level to apply for this bound.
/// The context in which to apply this default.
#pragma warning disable CA1019
public VersionWarningLevelDefaultAttribute(WarningLevel defaultLevel = WarningLevel.Default, BuildSettingsVersion minVersion = BuildSettingsVersion.V1, BuildSettingsVersion maxVersion = BuildSettingsVersion.Latest, InitializationContext context = InitializationContext.Any) : base(defaultLevel, context)
#pragma warning restore CA1019
{
MinVersion = minVersion;
MaxVersion = maxVersion;
}
///
/// Resolves the set of to a provided the current .
///
/// The set of attributes of which to consider for resolution.
/// The build system context used to consider defaults and applicability.
/// The corresponding of which to apply to the property with the default value attributes.
/// Will invoke on the input .
[WarningLevelResolverDelegate(nameof(VersionWarningLevelDefaultAttribute))]
internal static WarningLevel ResolveVersionWarningLevelDefault(IList unsortedDefaultValueAttributes, BuildSystemContext? buildSystemContext)
{
IList? castedAttributes = WarningLevelDefaultHelpers.CastWarningLevelDefaultAttirbuteToTarget(unsortedDefaultValueAttributes, Logger);
if (castedAttributes == null)
{
Logger?.LogWarning("Unable to discern default warning level through resolution. Defaulting to WarningLevel.Default.");
return WarningLevel.Default;
}
BuildSettingsVersion activeVersion = buildSystemContext != null ? buildSystemContext._buildContext.GetBuildSettings() : BuildSettingsVersion.V1;
List sortedAndMerged = EnsureWarningLevelDefaultBounds(castedAttributes);
WarningLevel returnWarningLevel = WarningLevel.Default;
foreach (VersionWarningLevelDefaultAttribute attr in sortedAndMerged)
{
// If we find our appropriate range, we early out.
if (attr.MinVersion <= activeVersion && activeVersion <= attr.MaxVersion)
{
returnWarningLevel = attr.GetDefaultLevel(buildSystemContext);
break;
}
}
return returnWarningLevel;
}
///
/// Ensures the attributes collection represents a contiguous, non-overlapping range.
///
/// The list of attributes to verify.
/// A list of attributes that has no overlaps, and is contiguous.
internal static List EnsureWarningLevelDefaultBounds(IList attributes)
{
List sorted = attributes.OrderBy(a => a.MinVersion).ToList();
List merged = new List(sorted.Count);
for (int i = 0; i < sorted.Count - 1; i++)
{
VersionWarningLevelDefaultAttribute current = sorted[i];
VersionWarningLevelDefaultAttribute next = sorted[i + 1];
if (current.MaxVersion < next.MinVersion - 1)
{
Logger?.LogWarning("Malformed VersionWarningLevelDefaultAttribute collection; taking corrective action to address gap in range (CurrentMax: {CurrentMax} NextMin: {NextMin}-1).", next.MinVersion, current.MaxVersion);
// Extend current range to cover the gap up to the next MinVersion,using the old build settings version standard.
current = new VersionWarningLevelDefaultAttribute(
current.DefaultLevel,
current.MinVersion,
next.MinVersion - 1
);
}
else if (next.MinVersion <= current.MaxVersion)
{
Logger?.LogWarning("Malformed VersionWarningLevelDefaultAttribute collection; taking corrective action to address overlap in range (NextMin: {NextMin} <= CurrentMax: {CurrentMax}).", next.MinVersion, current.MaxVersion);
// Reduce next range to be more constrained, leaving the larger current range at the old build settings version standard.
next = new VersionWarningLevelDefaultAttribute(
next.DefaultLevel,
current.MaxVersion + 1,
next.MaxVersion
);
// Flatten the old value with the updated one.
sorted[i + 1] = next;
}
merged.Add(current);
}
merged.Add(sorted.Last());
return merged;
}
}
}