// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Tools.DotNETCommon;
using UnrealBuildTool;
namespace UnrealBuildTool
{
///
/// Caches config files and config file hierarchies
///
public static class ConfigCache
{
///
/// Delegate to add a value to an ICollection in a target object
///
/// The object containing the field to be modified
/// The value to add
delegate void AddElementDelegate(object TargetObject, object ValueObject);
///
/// Caches information about a field with a [ConfigFile] attribute in a type
///
class ConfigField
{
///
/// The field with the config attribute
///
public FieldInfo FieldInfo;
///
/// The attribute instance
///
public ConfigFileAttribute Attribute;
///
/// For fields implementing ICollection, specifies the element type
///
public Type ElementType;
///
/// For fields implementing ICollection, a callback to add an element type.
///
public AddElementDelegate AddElement;
}
///
/// Stores information identifying a unique config hierarchy
///
class ConfigHierarchyKey
{
///
/// The hierarchy type
///
public ConfigHierarchyType Type;
///
/// The project directory to read from
///
public DirectoryReference ProjectDir;
///
/// Which platform-specific files to read
///
public UnrealTargetPlatform Platform;
///
/// Constructor
///
/// The hierarchy type
/// The project directory to read from
/// Which platform-specific files to read
public ConfigHierarchyKey(ConfigHierarchyType Type, DirectoryReference ProjectDir, UnrealTargetPlatform Platform)
{
this.Type = Type;
this.ProjectDir = ProjectDir;
this.Platform = Platform;
}
///
/// Test whether this key is equal to another object
///
/// The object to compare against
/// True if the objects match, false otherwise
public override bool Equals(object Other)
{
ConfigHierarchyKey OtherKey = Other as ConfigHierarchyKey;
return (OtherKey != null && OtherKey.Type == Type && OtherKey.ProjectDir == ProjectDir && OtherKey.Platform == Platform);
}
///
/// Returns a stable hash of this object
///
/// Hash value for this object
public override int GetHashCode()
{
return ((ProjectDir != null)? ProjectDir.GetHashCode() : 0) + ((int)Type * 123) + ((int)Platform * 345);
}
}
///
/// Stores the number of config file types
///
static readonly int NumConfigFileTypes = (int)((ConfigHierarchyType[])Enum.GetValues(typeof(ConfigHierarchyType))).Last() + 1;
///
/// Cache of individual config files
///
static Dictionary LocationToConfigFile = new Dictionary();
///
/// Cache of config hierarchies by project
///
static Dictionary HierarchyKeyToHierarchy = new Dictionary();
///
/// Cache of config fields by type
///
static Dictionary> TypeToConfigFields = new Dictionary>();
///
/// Attempts to read a config file (or retrieve it from the cache)
///
/// Location of the file to read
/// On success, receives the parsed config file
/// True if the file exists and was read, false otherwise
internal static bool TryReadFile(FileReference Location, out ConfigFile ConfigFile)
{
lock (LocationToConfigFile)
{
if (!LocationToConfigFile.TryGetValue(Location, out ConfigFile))
{
if (FileReference.Exists(Location))
{
ConfigFile = new ConfigFile(Location);
}
if (ConfigFile != null)
{
LocationToConfigFile.Add(Location, ConfigFile);
}
}
}
return ConfigFile != null;
}
///
/// Reads a config hierarchy (or retrieve it from the cache)
///
/// The type of hierarchy to read
/// The project directory to read the hierarchy for
/// Which platform to read platform-specific config files for
/// The requested config hierarchy
public static ConfigHierarchy ReadHierarchy(ConfigHierarchyType Type, DirectoryReference ProjectDir, UnrealTargetPlatform Platform)
{
// Get the key to use for the cache. It cannot be null, so we use the engine directory if a project directory is not given.
ConfigHierarchyKey Key = new ConfigHierarchyKey(Type, ProjectDir, Platform);
// Try to get the cached hierarchy with this key
ConfigHierarchy Hierarchy;
lock (HierarchyKeyToHierarchy)
{
if (!HierarchyKeyToHierarchy.TryGetValue(Key, out Hierarchy))
{
// Find all the input files
List Files = new List();
foreach (FileReference IniFileName in ConfigHierarchy.EnumerateConfigFileLocations(Type, ProjectDir, Platform))
{
ConfigFile File;
if (TryReadFile(IniFileName, out File))
{
Files.Add(File);
}
}
// Handle command line overrides
string[] CmdLine = Environment.GetCommandLineArgs();
string IniConfigArgPrefix = "-ini:" + Enum.GetName(typeof(ConfigHierarchyType), Type) + ":";
foreach (string CmdLineArg in CmdLine)
{
if (CmdLineArg.StartsWith(IniConfigArgPrefix))
{
ConfigFile OverrideFile = new ConfigFile(CmdLineArg.Substring(IniConfigArgPrefix.Length));
Files.Add(OverrideFile);
}
}
// Create the hierarchy
Hierarchy = new ConfigHierarchy(Files);
HierarchyKeyToHierarchy.Add(Key, Hierarchy);
}
}
return Hierarchy;
}
///
/// Gets a list of ConfigFields for the given type
///
/// Type to get configurable fields for
/// List of config fields for the given type
static List FindConfigFieldsForType(Type TargetObjectType)
{
List Fields;
lock(TypeToConfigFields)
{
if (!TypeToConfigFields.TryGetValue(TargetObjectType, out Fields))
{
Fields = new List();
if(TargetObjectType.BaseType != null)
{
Fields.AddRange(FindConfigFieldsForType(TargetObjectType.BaseType));
}
foreach (FieldInfo FieldInfo in TargetObjectType.GetFields(BindingFlags.Instance | BindingFlags.GetField | BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly))
{
IEnumerable Attributes = FieldInfo.GetCustomAttributes();
foreach (ConfigFileAttribute Attribute in Attributes)
{
// Copy the field
ConfigField Setter = new ConfigField();
Setter.FieldInfo = FieldInfo;
Setter.Attribute = Attribute;
// Check if the field type implements ICollection<>. If so, we can take multiple values.
foreach (Type InterfaceType in FieldInfo.FieldType.GetInterfaces())
{
if (InterfaceType.IsGenericType && InterfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
MethodInfo MethodInfo = InterfaceType.GetRuntimeMethod("Add", new Type[] { InterfaceType.GenericTypeArguments[0] });
Setter.AddElement = (Target, Value) => { MethodInfo.Invoke(Setter.FieldInfo.GetValue(Target), new object[] { Value }); };
Setter.ElementType = InterfaceType.GenericTypeArguments[0];
break;
}
}
// Add it to the output list
Fields.Add(Setter);
}
}
TypeToConfigFields.Add(TargetObjectType, Fields);
}
}
return Fields;
}
///
/// Read config settings for the given object
///
/// Path to the project directory
/// The platform being built
/// Object to receive the settings
public static void ReadSettings(DirectoryReference ProjectDir, UnrealTargetPlatform Platform, object TargetObject)
{
List Fields = FindConfigFieldsForType(TargetObject.GetType());
foreach(ConfigField Field in Fields)
{
// Read the hierarchy listed
ConfigHierarchy Hierarchy = ReadHierarchy(Field.Attribute.ConfigType, ProjectDir, Platform);
// Parse the values from the config files and update the target object
if(Field.AddElement == null)
{
string Text;
if(Hierarchy.TryGetValue(Field.Attribute.SectionName, Field.Attribute.KeyName ?? Field.FieldInfo.Name, out Text))
{
object Value;
if(TryParseValue(Text, Field.FieldInfo.FieldType, out Value))
{
Field.FieldInfo.SetValue(TargetObject, Value);
}
}
}
else
{
IEnumerable Items;
if(Hierarchy.TryGetValues(Field.Attribute.SectionName, Field.Attribute.KeyName ?? Field.FieldInfo.Name, out Items))
{
foreach(string Item in Items)
{
object Value;
if(TryParseValue(Item, Field.ElementType, out Value))
{
Field.AddElement(TargetObject, Value);
}
}
}
}
}
}
///
/// Attempts to parse the given text into an object which matches a specific field type
///
/// The text to parse
/// The type of field to parse
/// If successful, a value of type 'FieldType'
/// True if the value could be parsed, false otherwise
public static bool TryParseValue(string Text, Type FieldType, out object Value)
{
if(FieldType == typeof(string))
{
Value = Text;
return true;
}
else if(FieldType == typeof(bool))
{
bool BoolValue;
if(ConfigHierarchy.TryParse(Text, out BoolValue))
{
Value = BoolValue;
return true;
}
else
{
Value = null;
return false;
}
}
else if(FieldType == typeof(int))
{
int IntValue;
if(ConfigHierarchy.TryParse(Text, out IntValue))
{
Value = IntValue;
return true;
}
else
{
Value = null;
return false;
}
}
else if(FieldType == typeof(float))
{
float FloatValue;
if(ConfigHierarchy.TryParse(Text, out FloatValue))
{
Value = FloatValue;
return true;
}
else
{
Value = null;
return false;
}
}
else if(FieldType == typeof(double))
{
double DoubleValue;
if(ConfigHierarchy.TryParse(Text, out DoubleValue))
{
Value = DoubleValue;
return true;
}
else
{
Value = null;
return false;
}
}
else if(FieldType == typeof(Guid))
{
Guid GuidValue;
if(ConfigHierarchy.TryParse(Text, out GuidValue))
{
Value = GuidValue;
return true;
}
else
{
Value = null;
return false;
}
}
else if(FieldType.IsEnum)
{
try
{
Value = Enum.Parse(FieldType, Text);
return true;
}
catch
{
Value = null;
return false;
}
}
else if(FieldType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
return TryParseValue(Text, FieldType.GetGenericArguments()[0], out Value);
}
else
{
throw new Exception("Unsupported type for [ConfigFile] attribute");
}
}
}
}