Files
UnrealEngine/Engine/Source/Programs/UnrealBuildTool/System/XmlConfigData.cs
2025-05-18 13:04:45 +08:00

281 lines
9.0 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
using EpicGames.Core;
namespace UnrealBuildTool
{
/// <summary>
/// Stores parsed values from XML config files which can be applied to a configurable type. Can be serialized to disk in binary form as a cache.
/// </summary>
class XmlConfigData
{
/// <summary>
/// The current cache serialization version
/// </summary>
const int SerializationVersion = 2;
/// <summary>
/// List of input files. Stored to allow checking cache validity.
/// </summary>
public FileReference[] InputFiles { get; set; }
/// <summary>
/// Abstract description of a target data member.
/// </summary>
public abstract class TargetMember
{
/// <summary>
/// Returns Reflection.MemberInfo describing the target class member.
/// </summary>
public abstract MemberInfo MemberInfo { get; }
/// <summary>
/// Returns Reflection.Type of the target class member.
/// </summary>
public abstract Type Type { get; }
/// <summary>
/// Indicates whether the target class member is static or not.
/// </summary>
public abstract bool IsStatic { get; }
/// <summary>
/// Returns the value setter of the target class member.
/// </summary>
public abstract Action<object?, object?> SetValue { get; }
/// <summary>
/// Returns the value getter of the target class member.
/// </summary>
public abstract Func<object?, object?> GetValue { get; }
}
/// <summary>
/// Description of a field member.
/// </summary>
public class TargetField : TargetMember
{
public override MemberInfo MemberInfo => _fieldInfo;
public override Type Type => _fieldInfo.FieldType;
public override bool IsStatic => _fieldInfo.IsStatic;
public override Action<object?, object?> SetValue => _fieldInfo.SetValue;
public override Func<object?, object?> GetValue => _fieldInfo.GetValue;
private readonly FieldInfo _fieldInfo;
public TargetField(FieldInfo fieldInfo)
{
_fieldInfo = fieldInfo;
}
}
/// <summary>
/// Description of a property member.
/// </summary>
public class TargetProperty : TargetMember
{
public override MemberInfo MemberInfo => _propertyInfo;
public override Type Type => _propertyInfo.PropertyType;
public override bool IsStatic => _propertyInfo.GetGetMethod()!.IsStatic;
public override Action<object?, object?> SetValue => _propertyInfo.SetValue;
public override Func<object?, object?> GetValue => _propertyInfo.GetValue;
private readonly PropertyInfo _propertyInfo;
public TargetProperty(PropertyInfo propertyInfo)
{
_propertyInfo = propertyInfo;
}
}
public class ValueInfo
{
public TargetMember Target { get; init; }
public object Value { get; init; }
public FileReference SourceFile { get; init; }
public XmlConfigFileAttribute XmlConfigAttribute { get; init; }
public ValueInfo(FieldInfo fieldInfo, object value, FileReference sourceFile, XmlConfigFileAttribute xmlConfigAttribute)
: this(new TargetField(fieldInfo), value, sourceFile, xmlConfigAttribute)
{
}
public ValueInfo(PropertyInfo propertyInfo, object value, FileReference sourceFile, XmlConfigFileAttribute xmlConfigAttribute)
: this(new TargetProperty(propertyInfo), value, sourceFile, xmlConfigAttribute)
{
}
public ValueInfo(TargetMember target, object value, FileReference sourceFile, XmlConfigFileAttribute xmlConfigAttribute)
{
Target = target;
Value = value;
SourceFile = sourceFile;
XmlConfigAttribute = xmlConfigAttribute;
}
}
/// <summary>
/// Stores a mapping from type -> member -> value, with all the config values for configurable fields.
/// </summary>
public Dictionary<Type, ValueInfo[]> TypeToValues { get; init; }
/// <summary>
/// Constructor
/// </summary>
/// <param name="inputFiles"></param>
/// <param name="typeToValues"></param>
public XmlConfigData(FileReference[] inputFiles, Dictionary<Type, ValueInfo[]> typeToValues)
{
InputFiles = inputFiles;
TypeToValues = typeToValues;
}
/// <summary>
/// Attempts to read a previous block of config values from disk
/// </summary>
/// <param name="location">The file to read from</param>
/// <param name="types">Array of valid types. Used to resolve serialized type names to concrete types.</param>
/// <param name="data">On success, receives the parsed data</param>
/// <returns>True if the data was read and is valid</returns>
public static bool TryRead(FileReference location, IEnumerable<Type> types, [NotNullWhen(true)] out XmlConfigData? data)
{
// Check the file exists first
if (!FileReference.Exists(location))
{
data = null;
return false;
}
// Read the cache from disk
using (BinaryReader reader = new BinaryReader(File.Open(location.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)))
{
// Check the serialization version matches
if (reader.ReadInt32() != SerializationVersion)
{
data = null;
return false;
}
// Read the input files
FileReference[] inputFiles = reader.ReadArray(() => reader.ReadFileReference())!;
// Read the types
int numTypes = reader.ReadInt32();
Dictionary<Type, ValueInfo[]> typeToValues = new Dictionary<Type, ValueInfo[]>(numTypes);
for (int typeIdx = 0; typeIdx < numTypes; typeIdx++)
{
// Read the type name
string typeName = reader.ReadString();
// Try to find it in the list of configurable types
Type? type = types.FirstOrDefault(x => x.Name == typeName);
if (type == null)
{
data = null;
return false;
}
// Read all the values
ValueInfo[] values = new ValueInfo[reader.ReadInt32()];
for (int valueIdx = 0; valueIdx < values.Length; valueIdx++)
{
string memberName = reader.ReadString();
TargetMember? targetMember = GetTargetMemberWithAttribute<XmlConfigFileAttribute>(type, memberName);
if (targetMember != null)
{
// If TargetMember is not null, we know it has our attribute.
XmlConfigFileAttribute xmlConfigAttribute = targetMember!.MemberInfo.GetCustomAttribute<XmlConfigFileAttribute>()!;
// Try to parse the value and add it to the output array
object value = reader.ReadObject(targetMember.Type)!;
// Read the path of the config file that provided this setting
FileReference sourceFile = reader.ReadFileReference();
values[valueIdx] = new ValueInfo(targetMember, value, sourceFile, xmlConfigAttribute);
}
else
{
data = null;
return false;
}
}
// Add it to the type map
typeToValues.Add(type, values);
}
// Return the parsed data
data = new XmlConfigData(inputFiles.ToArray(), typeToValues);
return true;
}
}
/// <summary>
/// Find a data member (field or property) with the given name and attribute and returns TargetMember wrapper created for it.
/// </summary>
/// <typeparam name="T">Attribute a member has to have to be considered.</typeparam>
/// <param name="type">Type which members are to be searched</param>
/// <param name="memberName">Name of a member (field or property) to find.</param>
/// <returns>TargetMember wrapper or null if no member has been found.</returns>
private static TargetMember? GetTargetMemberWithAttribute<T>(Type type, string memberName)
where T : Attribute
{
FieldInfo? field = type.GetField(memberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
T? xmlConfigAttribute = field?.GetCustomAttribute<T>();
if (field != null && xmlConfigAttribute != null)
{
return new TargetField(field);
}
PropertyInfo? property = type.GetProperty(memberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
xmlConfigAttribute = property?.GetCustomAttribute<T>();
if (property != null && xmlConfigAttribute != null)
{
return new TargetProperty(property);
}
return null;
}
/// <summary>
/// Writes the coalesced config hierarchy to disk
/// </summary>
/// <param name="location">File to write to</param>
public void Write(FileReference location)
{
DirectoryReference.CreateDirectory(location.Directory);
using (BinaryWriter writer = new BinaryWriter(File.Open(location.FullName, FileMode.Create, FileAccess.Write, FileShare.Read)))
{
writer.Write(SerializationVersion);
// Save all the input files. The cache will not be valid if these change.
writer.Write(InputFiles, item => writer.Write(item));
// Write all the categories
writer.Write(TypeToValues.Count);
foreach (KeyValuePair<Type, ValueInfo[]> typePair in TypeToValues)
{
writer.Write(typePair.Key.Name);
writer.Write(typePair.Value.Length);
foreach (ValueInfo memberPair in typePair.Value)
{
writer.Write(memberPair.Target.MemberInfo.Name);
writer.Write(memberPair.Target.Type, memberPair.Value);
writer.Write(memberPair.SourceFile);
}
}
}
}
}
}