1326 lines
46 KiB
C#
1326 lines
46 KiB
C#
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using EpicGames.Core;
|
|
using EpicGames.UHT.Tables;
|
|
using EpicGames.UHT.Tokenizer;
|
|
using EpicGames.UHT.Types;
|
|
using EpicGames.UHT.Utils;
|
|
using Microsoft.Extensions.Logging;
|
|
using UnrealBuildBase;
|
|
|
|
namespace UnrealBuildTool.Modes
|
|
{
|
|
|
|
/// <summary>
|
|
/// Implement the UHT configuration interface. Due to the configuration system being fairly embedded into
|
|
/// UBT, the implementation must be part of UBT.
|
|
/// </summary>
|
|
public class UhtConfigImpl : IUhtConfig
|
|
{
|
|
private readonly ConfigHierarchy _ini;
|
|
|
|
/// <summary>
|
|
/// Types that have been renamed, treat the old deprecated name as the new name for code generation
|
|
/// </summary>
|
|
private readonly Dictionary<StringView, StringView> _typeRedirectMap;
|
|
|
|
/// <summary>
|
|
/// Meta data that have been renamed, treat the old deprecated name as the new name for code generation
|
|
/// </summary>
|
|
private readonly Dictionary<string, string> _metaDataRedirectMap;
|
|
|
|
/// <summary>
|
|
/// Supported units in the game
|
|
/// </summary>
|
|
private readonly HashSet<StringView> _units;
|
|
|
|
/// <summary>
|
|
/// Special parsed struct names that do not require a prefix
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "<Pending>")]
|
|
private readonly HashSet<StringView> _structsWithNoPrefix;
|
|
|
|
/// <summary>
|
|
/// Special parsed struct names that have a 'T' prefix
|
|
/// </summary>
|
|
private readonly HashSet<StringView> _structsWithTPrefix;
|
|
|
|
/// <summary>
|
|
/// Mapping from 'human-readable' macro substring to # of parameters for delegate declarations
|
|
/// Index 0 is 1 parameter, Index 1 is 2, etc...
|
|
/// </summary>
|
|
private readonly List<StringView> _delegateParameterCountStrings;
|
|
|
|
/// <summary>
|
|
/// Default version of generated code. Defaults to oldest possible, unless specified otherwise in config.
|
|
/// </summary>
|
|
private readonly EGeneratedCodeVersion _defaultGeneratedCodeVersion = EGeneratedCodeVersion.V1;
|
|
|
|
/// <summary>
|
|
/// Internal version of pointer warning for native pointers in the engine
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _engineNativePointerMemberBehavior = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Internal version of pointer warning for object pointers in the engine
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _engineObjectPtrMemberBehavior = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Internal version of pointer warning for native pointers in engine plugins
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _enginePluginNativePointerMemberBehavior = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Internal version of pointer warning for object pointers in engine plugins
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _enginePluginObjectPtrMemberBehavior = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Internal version of pointer warning for native pointers outside the engine
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _nonEngineNativePointerMemberBehavior = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Internal version of pointer warning for object pointers outside the engine
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _nonEngineObjectPtrMemberBehavior = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// If true, deprecation warnings should be shown
|
|
/// </summary>
|
|
private readonly bool _showDeprecations = true;
|
|
|
|
/// <summary>
|
|
/// If true, UObject properties are enabled in RigVM
|
|
/// </summary>
|
|
private readonly bool _areRigVMUObjectPropertiesEnabled = false;
|
|
|
|
/// <summary>
|
|
/// If true, UInterface properties are enabled in RigVM
|
|
/// </summary>
|
|
private readonly bool _areRigVMUInterfaceProeprtiesEnabled = false;
|
|
|
|
/// <summary>
|
|
/// Internal version of behavior when there is a missing generated header include in the engine code.
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _engineMissingGeneratedHeaderIncludeBehavior = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Internal version of behavior when there is a missing generated header include in non engine code.
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _nonEngineMissingGeneratedHeaderIncludeBehavior = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Internal version of the behavior set when the underlying type of a regular and namespaced enum isn't set in the engine code.
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _engineEnumUnderlyingTypeNotSet = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Internal version of the behavior set when the underlying type of a regular and namespaced enum isn't set in the non engine code.
|
|
/// </summary>
|
|
private readonly UhtIssueBehavior _nonEngineEnumUnderlyingTypeNotSet = UhtIssueBehavior.AllowSilently;
|
|
|
|
/// <summary>
|
|
/// Collection of known documentation policies
|
|
/// </summary>
|
|
public Dictionary<string, UhtDocumentationPolicy> _documentationPolicies = new(StringComparer.OrdinalIgnoreCase);
|
|
|
|
/// <summary>
|
|
/// Default documentation policy (usually empty)
|
|
/// </summary>
|
|
private readonly string _defaultDocumentationPolicy = "";
|
|
|
|
/// <summary>
|
|
/// Settings to use for the development status
|
|
/// </summary>
|
|
public string _valkyrieDevelopmentStatusKey = "Valkyrie_DevelopmentStatus";
|
|
|
|
/// <summary>
|
|
/// Settings to use for the development status
|
|
/// </summary>
|
|
public string _valkyrieDevelopmentStatusValueExperimental = "Experimental";
|
|
|
|
/// <summary>
|
|
/// Settings to use for the deprecation status
|
|
/// </summary>
|
|
public string _valkyrieDeprecationStatusKey = "Valkyrie_DeprecationStatus";
|
|
|
|
/// <summary>
|
|
/// Settings to use for the deprecation status
|
|
/// </summary>
|
|
public string _valkyrieDeprecationStatusValueDeprecated = "Deprecated";
|
|
|
|
#region IUhtConfig Implementation
|
|
/// <inheritdoc/>
|
|
public EGeneratedCodeVersion DefaultGeneratedCodeVersion => _defaultGeneratedCodeVersion;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior EngineNativePointerMemberBehavior => _engineNativePointerMemberBehavior;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior EngineObjectPtrMemberBehavior => _engineObjectPtrMemberBehavior;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior EnginePluginNativePointerMemberBehavior => _enginePluginNativePointerMemberBehavior;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior EnginePluginObjectPtrMemberBehavior => _enginePluginObjectPtrMemberBehavior;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior NonEngineNativePointerMemberBehavior => _nonEngineNativePointerMemberBehavior;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior NonEngineObjectPtrMemberBehavior => _nonEngineObjectPtrMemberBehavior;
|
|
|
|
/// <summary>
|
|
/// If true, UObject properties are enabled in RigVM
|
|
/// </summary>
|
|
public bool AreRigVMUObjectPropertiesEnabled => _areRigVMUObjectPropertiesEnabled;
|
|
|
|
/// <summary>
|
|
/// If true, UInterface properties are enabled in RigVM
|
|
/// </summary>
|
|
public bool AreRigVMUInterfaceProeprtiesEnabled => _areRigVMUInterfaceProeprtiesEnabled;
|
|
|
|
/// <summary>
|
|
/// If true, deprecation warnings should be shown
|
|
/// </summary>
|
|
public bool ShowDeprecations => _showDeprecations;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior EngineMissingGeneratedHeaderIncludeBehavior => _engineMissingGeneratedHeaderIncludeBehavior;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior NonEngineMissingGeneratedHeaderIncludeBehavior => _nonEngineMissingGeneratedHeaderIncludeBehavior;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior EngineEnumUnderlyingTypeNotSet => _engineEnumUnderlyingTypeNotSet;
|
|
|
|
/// <inheritdoc/>
|
|
public UhtIssueBehavior NonEngineEnumUnderlyingTypeNotSet => _nonEngineEnumUnderlyingTypeNotSet;
|
|
|
|
/// <inheritdoc/>
|
|
public IReadOnlyDictionary<string, UhtDocumentationPolicy> DocumentationPolicies => _documentationPolicies;
|
|
|
|
/// <inheritdoc/>
|
|
public string DefaultDocumentationPolicy => _defaultDocumentationPolicy;
|
|
|
|
/// <inheritdoc/>
|
|
public string ValkyrieDevelopmentStatusKey => _valkyrieDevelopmentStatusKey;
|
|
|
|
/// <inheritdoc/>
|
|
public string ValkyrieDevelopmentStatusValueExperimental => _valkyrieDevelopmentStatusValueExperimental;
|
|
|
|
/// <inheritdoc/>
|
|
public string ValkyrieDeprecationStatusKey => _valkyrieDeprecationStatusKey;
|
|
|
|
/// <inheritdoc/>
|
|
public string ValkyrieDeprecationStatusValueDeprecated => _valkyrieDeprecationStatusValueDeprecated;
|
|
|
|
/// <inheritdoc/>
|
|
public void RedirectTypeIdentifier(ref UhtToken Token)
|
|
{
|
|
if (!Token.IsIdentifier())
|
|
{
|
|
throw new Exception("Attempt to redirect type identifier when the token isn't an identifier.");
|
|
}
|
|
|
|
if (_typeRedirectMap.TryGetValue(Token.Value, out StringView Redirect))
|
|
{
|
|
Token.Value = Redirect;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool RedirectMetaDataKey(string Key, out string NewKey)
|
|
{
|
|
if (_metaDataRedirectMap.TryGetValue(Key, out string? Redirect))
|
|
{
|
|
NewKey = Redirect;
|
|
return Key != NewKey;
|
|
}
|
|
else
|
|
{
|
|
NewKey = Key;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool IsValidUnits(StringView Units)
|
|
{
|
|
return _units.Contains(Units);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool IsStructWithTPrefix(StringView Name)
|
|
{
|
|
return _structsWithTPrefix.Contains(Name);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public int FindDelegateParameterCount(StringView DelegateMacro)
|
|
{
|
|
for (int Index = 0, Count = _delegateParameterCountStrings.Count; Index < Count; ++Index)
|
|
{
|
|
if (DelegateMacro.Span.Contains(_delegateParameterCountStrings[Index].Span, StringComparison.Ordinal))
|
|
{
|
|
return Index;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public StringView GetDelegateParameterCountString(int Index)
|
|
{
|
|
return Index >= 0 ? _delegateParameterCountStrings[Index] : "";
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool IsExporterEnabled(string Name)
|
|
{
|
|
_ini.GetBool("UnrealHeaderTool", Name, out bool Value);
|
|
return Value;
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Read the UHT configuration
|
|
/// </summary>
|
|
/// <param name="Args">Extra command line arguments</param>
|
|
public UhtConfigImpl(CommandLineArguments Args)
|
|
{
|
|
DirectoryReference ConfigDirectory = DirectoryReference.Combine(Unreal.EngineDirectory, "Programs", "UnrealHeaderTool");
|
|
_ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, ConfigDirectory, BuildHostPlatform.Current.Platform, "", Args.GetRawArray());
|
|
|
|
_typeRedirectMap = GetRedirectsStringView("UnrealHeaderTool", "TypeRedirects", "OldType", "NewType");
|
|
_metaDataRedirectMap = GetRedirectsString("CoreUObject.Metadata", "MetadataRedirects", "OldKey", "NewKey");
|
|
_structsWithNoPrefix = GetHashSet("UnrealHeaderTool", "StructsWithNoPrefix", StringViewComparer.Ordinal);
|
|
_structsWithTPrefix = GetHashSet("UnrealHeaderTool", "StructsWithTPrefix", StringViewComparer.Ordinal);
|
|
_units = GetHashSet("UnrealHeaderTool", "Units", StringViewComparer.OrdinalIgnoreCase);
|
|
_delegateParameterCountStrings = GetList("UnrealHeaderTool", "DelegateParameterCountStrings");
|
|
_defaultGeneratedCodeVersion = GetGeneratedCodeVersion("UnrealHeaderTool", "DefaultGeneratedCodeVersion", EGeneratedCodeVersion.V1);
|
|
_engineNativePointerMemberBehavior = GetIssueBehavior("UnrealHeaderTool", "EngineNativePointerMemberBehavior", UhtIssueBehavior.AllowSilently);
|
|
_engineObjectPtrMemberBehavior = GetIssueBehavior("UnrealHeaderTool", "EngineObjectPtrMemberBehavior", UhtIssueBehavior.AllowSilently);
|
|
_enginePluginNativePointerMemberBehavior = GetIssueBehavior("UnrealHeaderTool", "EnginePluginNativePointerMemberBehavior", UhtIssueBehavior.AllowSilently);
|
|
_enginePluginObjectPtrMemberBehavior = GetIssueBehavior("UnrealHeaderTool", "EnginePluginObjectPtrMemberBehavior", UhtIssueBehavior.AllowSilently);
|
|
_nonEngineNativePointerMemberBehavior = GetIssueBehavior("UnrealHeaderTool", "NonEngineNativePointerMemberBehavior", UhtIssueBehavior.AllowSilently);
|
|
_nonEngineObjectPtrMemberBehavior = GetIssueBehavior("UnrealHeaderTool", "NonEngineObjectPtrMemberBehavior", UhtIssueBehavior.AllowSilently);
|
|
_areRigVMUObjectPropertiesEnabled = GetBoolean("UnrealHeaderTool", "AreRigVMUObjectPropertiesEnabled", false);
|
|
_areRigVMUInterfaceProeprtiesEnabled = GetBoolean("UnrealHeaderTool", "AreRigVMUInterfaceProeprtiesEnabled", false);
|
|
_showDeprecations = GetBoolean("UnrealHeaderTool", "ShowDeprecations", true);
|
|
_engineMissingGeneratedHeaderIncludeBehavior = GetIssueBehavior("UnrealHeaderTool", "EngineMissingGeneratedHeaderIncludeBehavior", UhtIssueBehavior.AllowSilently);
|
|
_nonEngineMissingGeneratedHeaderIncludeBehavior = GetIssueBehavior("UnrealHeaderTool", "NonEngineMissingGeneratedHeaderIncludeBehavior", UhtIssueBehavior.AllowSilently);
|
|
_engineEnumUnderlyingTypeNotSet = GetIssueBehavior("UnrealHeaderTool", "EngineEnumUnderlyingTypeNotSet", UhtIssueBehavior.AllowSilently);
|
|
_nonEngineEnumUnderlyingTypeNotSet = GetIssueBehavior("UnrealHeaderTool", "NonEngineEnumUnderlyingTypeNotSet", UhtIssueBehavior.AllowSilently);
|
|
|
|
GetDocumentationPolicies("UnrealHeaderTool", "DocumentationPolicies");
|
|
_defaultDocumentationPolicy = GetString("UnrealHeaderTool", "DefaultDocumentationPolicy", "");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read any game configuration for the editor...
|
|
/// </summary>
|
|
/// <param name="ProjectPath"></param>
|
|
public void ProjectSpecificConfigs(string? ProjectPath)
|
|
{
|
|
if (string.IsNullOrEmpty(ProjectPath))
|
|
{
|
|
return;
|
|
}
|
|
DirectoryReference? ProjectDirectory = DirectoryReference.FromString(ProjectPath);
|
|
if (ProjectDirectory == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ConfigHierarchy editorConfig = ConfigCache.ReadHierarchy(ConfigHierarchyType.Editor, ProjectDirectory, BuildHostPlatform.Current.Platform, "");
|
|
|
|
_valkyrieDevelopmentStatusKey = GetString(editorConfig, "/Script/Engine.ValkyrieMetaData", "DevelopmentStatusKey", _valkyrieDevelopmentStatusKey);
|
|
_valkyrieDevelopmentStatusValueExperimental = GetString(editorConfig, "/Script/Engine.ValkyrieMetaData", "DevelopmentStatusValue_Experimental", _valkyrieDevelopmentStatusValueExperimental);
|
|
_valkyrieDeprecationStatusKey = GetString(editorConfig, "/Script/Engine.ValkyrieMetaData", "DeprecationStatusKey", _valkyrieDeprecationStatusKey);
|
|
_valkyrieDeprecationStatusValueDeprecated = GetString(editorConfig, "/Script/Engine.ValkyrieMetaData", "DeprecationStatusValue_Deprecated", _valkyrieDeprecationStatusValueDeprecated);
|
|
}
|
|
|
|
private bool GetBoolean(string SectionName, string KeyName, bool bDefault)
|
|
{
|
|
if (_ini.TryGetValue(SectionName, KeyName, out bool value))
|
|
{
|
|
return value;
|
|
}
|
|
return bDefault;
|
|
}
|
|
|
|
private string GetString(string SectionName, string KeyName, string Default)
|
|
{
|
|
return GetString(_ini, SectionName, KeyName, Default);
|
|
}
|
|
|
|
private static string GetString(ConfigHierarchy config, string SectionName, string KeyName, string Default)
|
|
{
|
|
if (config.TryGetValue(SectionName, KeyName, out string? value))
|
|
{
|
|
return value;
|
|
}
|
|
return Default;
|
|
}
|
|
|
|
private UhtIssueBehavior GetIssueBehavior(string SectionName, string KeyName, UhtIssueBehavior Default)
|
|
{
|
|
if (_ini.TryGetValue(SectionName, KeyName, out string? BehaviorStr))
|
|
{
|
|
if (!Enum.TryParse(BehaviorStr, out UhtIssueBehavior Value))
|
|
{
|
|
throw new Exception(String.Format("Unrecognized issue behavior '{0}'", BehaviorStr));
|
|
}
|
|
return Value;
|
|
}
|
|
return Default;
|
|
}
|
|
|
|
private EGeneratedCodeVersion GetGeneratedCodeVersion(string SectionName, string KeyName, EGeneratedCodeVersion Default)
|
|
{
|
|
if (_ini.TryGetValue(SectionName, KeyName, out string? BehaviorStr))
|
|
{
|
|
if (!Enum.TryParse(BehaviorStr, out EGeneratedCodeVersion Value))
|
|
{
|
|
throw new Exception(String.Format("Unrecognized generated code version '{0}'", BehaviorStr));
|
|
}
|
|
return Value;
|
|
}
|
|
return Default;
|
|
}
|
|
|
|
private Dictionary<StringView, StringView> GetRedirectsStringView(string Section, string Key, string OldKeyName, string NewKeyName)
|
|
{
|
|
Dictionary<StringView, StringView> Redirects = [];
|
|
|
|
if (_ini.TryGetValues(Section, Key, out IReadOnlyList<string>? StringList))
|
|
{
|
|
foreach (string Line in StringList)
|
|
{
|
|
if (ConfigHierarchy.TryParse(Line, out Dictionary<string, string>? Properties))
|
|
{
|
|
if (!Properties.TryGetValue(OldKeyName, out string? OldKey))
|
|
{
|
|
throw new Exception(String.Format("Unable to get the {0} from the {1} value", OldKeyName, Key));
|
|
}
|
|
if (!Properties.TryGetValue(NewKeyName, out string? NewKey))
|
|
{
|
|
throw new Exception(String.Format("Unable to get the {0} from the {1} value", NewKeyName, Key));
|
|
}
|
|
Redirects.Add(OldKey, NewKey);
|
|
}
|
|
}
|
|
}
|
|
return Redirects;
|
|
}
|
|
|
|
private Dictionary<string, string> GetRedirectsString(string Section, string Key, string OldKeyName, string NewKeyName)
|
|
{
|
|
Dictionary<string, string> Redirects = [];
|
|
|
|
if (_ini.TryGetValues(Section, Key, out IReadOnlyList<string>? StringList))
|
|
{
|
|
foreach (string Line in StringList)
|
|
{
|
|
if (ConfigHierarchy.TryParse(Line, out Dictionary<string, string>? Properties))
|
|
{
|
|
if (!Properties.TryGetValue(OldKeyName, out string? OldKey))
|
|
{
|
|
throw new Exception(String.Format("Unable to get the {0} from the {1} value", OldKeyName, Key));
|
|
}
|
|
if (!Properties.TryGetValue(NewKeyName, out string? NewKey))
|
|
{
|
|
throw new Exception(String.Format("Unable to get the {0} from the {1} value", NewKeyName, Key));
|
|
}
|
|
Redirects.Add(OldKey, NewKey);
|
|
}
|
|
}
|
|
}
|
|
return Redirects;
|
|
}
|
|
|
|
private List<StringView> GetList(string Section, string Key)
|
|
{
|
|
List<StringView> List = [];
|
|
|
|
if (_ini.TryGetValues(Section, Key, out IReadOnlyList<string>? StringList))
|
|
{
|
|
foreach (string Value in StringList)
|
|
{
|
|
List.Add(new StringView(Value));
|
|
}
|
|
}
|
|
return List;
|
|
}
|
|
|
|
private HashSet<StringView> GetHashSet(string Section, string Key, StringViewComparer Comparer)
|
|
{
|
|
HashSet<StringView> Set = new(Comparer);
|
|
|
|
if (_ini.TryGetValues(Section, Key, out IReadOnlyList<string>? StringList))
|
|
{
|
|
foreach (string Value in StringList)
|
|
{
|
|
Set.Add(new StringView(Value));
|
|
}
|
|
}
|
|
return Set;
|
|
}
|
|
|
|
private void GetDocumentationPolicies(string Section, string Key)
|
|
{
|
|
_documentationPolicies["Strict"] = new()
|
|
{
|
|
ClassOrStructCommentRequired = true,
|
|
FunctionToolTipsRequired = true,
|
|
MemberToolTipsRequired = true,
|
|
ParameterToolTipsRequired = true,
|
|
FloatRangesRequired = true,
|
|
};
|
|
|
|
if (_ini.TryGetValues(Section, Key, out IReadOnlyList<string>? StringList))
|
|
{
|
|
foreach (string Value in StringList)
|
|
{
|
|
if (ConfigHierarchy.TryParse(Value, out Dictionary<string, string>? Properties))
|
|
{
|
|
if (Properties.TryGetValue("Name", out string? PolicyName))
|
|
{
|
|
if (!_documentationPolicies.TryGetValue(PolicyName, out UhtDocumentationPolicy? Policy))
|
|
{
|
|
Policy = new UhtDocumentationPolicy();
|
|
}
|
|
Policy.ClassOrStructCommentRequired = GetPropertyBool(Properties, "ClassOrStructCommentRequired", Policy.ClassOrStructCommentRequired);
|
|
Policy.FunctionToolTipsRequired = GetPropertyBool(Properties, "FunctionToolTipsRequired", Policy.FunctionToolTipsRequired);
|
|
Policy.MemberToolTipsRequired = GetPropertyBool(Properties, "MemberToolTipsRequired", Policy.MemberToolTipsRequired);
|
|
Policy.ParameterToolTipsRequired = GetPropertyBool(Properties, "ParameterToolTipsRequired", Policy.ParameterToolTipsRequired);
|
|
Policy.FloatRangesRequired = GetPropertyBool(Properties, "FloatRangesRequired", Policy.FloatRangesRequired);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool GetPropertyBool(Dictionary<string, string> Properties, string Key, bool DefaultValue)
|
|
{
|
|
if (Properties.TryGetValue(Key, out string? PropValueString) && ConfigHierarchy.TryParse(PropValueString, out bool PropValue))
|
|
{
|
|
return PropValue;
|
|
}
|
|
return DefaultValue;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Global options for UBT (any modes)
|
|
/// </summary>
|
|
class UhtGlobalOptions
|
|
{
|
|
/// <summary>
|
|
/// User asked for help
|
|
/// </summary>
|
|
[CommandLine(Prefix = "-Help", Description = "Display this help.")]
|
|
[CommandLine(Prefix = "-h")]
|
|
[CommandLine(Prefix = "--help")]
|
|
public bool bGetHelp = false;
|
|
|
|
/// <summary>
|
|
/// The amount of detail to write to the log
|
|
/// </summary>
|
|
[CommandLine(Prefix = "-Verbose", Value = "Verbose", Description = "Increase output verbosity")]
|
|
[CommandLine(Prefix = "-VeryVerbose", Value = "VeryVerbose", Description = "Increase output verbosity more")]
|
|
public LogEventType LogOutputLevel = LogEventType.Log;
|
|
|
|
/// <summary>
|
|
/// Specifies the path to a log file to write. Note that the default mode (eg. building, generating project files) will create a log file by default if this not specified.
|
|
/// </summary>
|
|
[CommandLine(Prefix = "-Log", Description = "Specify a log file location instead of the default Engine/Programs/UnrealHeaderTool/Saved/Logs/UnrealHeaderTool.log")]
|
|
public FileReference? LogFileName = null;
|
|
|
|
/// <summary>
|
|
/// Whether to include timestamps in the log
|
|
/// </summary>
|
|
[CommandLine(Prefix = "-Timestamps", Description = "Include timestamps in the log")]
|
|
public bool bLogTimestamps = false;
|
|
|
|
/// <summary>
|
|
/// Whether to format messages in MsBuild format
|
|
/// </summary>
|
|
[CommandLine(Prefix = "-FromMsBuild", Description = "Format messages for msbuild")]
|
|
public bool bLogFromMsBuild = false;
|
|
|
|
/// <summary>
|
|
/// Disables all logging including the default log location
|
|
/// </summary>
|
|
[CommandLine(Prefix = "-NoLog", Description = "Disable log file creation including the default log file")]
|
|
public bool bNoLog = false;
|
|
|
|
[CommandLine(Prefix = "-Test", Description = "Run testing scripts")]
|
|
public bool bTest = false;
|
|
|
|
[CommandLine("-WarningsAsErrors", Description = "Treat warnings as errors")]
|
|
public bool bWarningsAsErrors = false;
|
|
|
|
[CommandLine("-NoGoWide", Description = "Disable concurrent parsing and code generation")]
|
|
public bool bNoGoWide = false;
|
|
|
|
[CommandLine("-WriteRef", Description = "Write all the output to a reference directory")]
|
|
public bool bWriteRef = false;
|
|
|
|
[CommandLine("-VerifyRef", Description = "Write all the output to a verification directory and compare to the reference output")]
|
|
public bool bVerifyRef = false;
|
|
|
|
[CommandLine("-FailIfGeneratedCodeChanges", Description = "Consider any changes to output files as being an error")]
|
|
public bool bFailIfGeneratedCodeChanges = false;
|
|
|
|
[CommandLine("-NoOutput", Description = "Do not save any output files other than reference output")]
|
|
public bool bNoOutput = false;
|
|
|
|
[CommandLine("-IncludeDebugOutput", Description = "Include extra content in generated output to assist with debugging")]
|
|
public bool bIncludeDebugOutput = false;
|
|
|
|
[CommandLine("-NoDefaultExporters", Description = "Disable all default exporters. Useful for when a specific exporter is to be run")]
|
|
public bool bNoDefaultExporters = false;
|
|
|
|
/// <summary>
|
|
/// Initialize the options with the given command line arguments
|
|
/// </summary>
|
|
/// <param name="Arguments"></param>
|
|
public UhtGlobalOptions(CommandLineArguments Arguments)
|
|
{
|
|
Arguments.ApplyTo(this);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// File manager for the test harness
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Construct a new instance of the test file manager
|
|
/// </remarks>
|
|
/// <param name="RootDirectory">Root directory of the UE</param>
|
|
public class UhtTestFileManager(string RootDirectory) : IUhtFileManager
|
|
{
|
|
/// <summary>
|
|
/// Collection of test fragments that can be read
|
|
/// </summary>
|
|
public Dictionary<string, UhtSourceFragment> SourceFragments = [];
|
|
|
|
/// <summary>
|
|
/// All output segments generated by code gen
|
|
/// </summary>
|
|
public SortedDictionary<string, string> Outputs = [];
|
|
|
|
private readonly UhtStdFileManager InnerManager = new();
|
|
private readonly string? RootDirectory = RootDirectory;
|
|
|
|
/// <inheritdoc/>
|
|
public string GetFullFilePath(string FilePath)
|
|
{
|
|
if (RootDirectory == null)
|
|
{
|
|
return FilePath;
|
|
}
|
|
else
|
|
{
|
|
return Path.Combine(RootDirectory, FilePath);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool ReadSource(string FilePath, out UhtSourceFragment Fragment)
|
|
{
|
|
if (SourceFragments.TryGetValue(FilePath, out Fragment))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return InnerManager.ReadSource(GetFullFilePath(FilePath), out Fragment);
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
[Obsolete("Use the new ReadOutput with UhtPoolBuffer")]
|
|
public UhtBuffer? ReadOutput(string FilePath)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool ReadOutput(string FilePath, out UhtPoolBuffer<char> Output)
|
|
{
|
|
Output = default;
|
|
return false;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool WriteOutput(string FilePath, ReadOnlySpan<char> Contents)
|
|
{
|
|
lock (Outputs)
|
|
{
|
|
Outputs.Add(FilePath, Contents.ToString());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
public bool RenameOutput(string OldFilePath, string NewFilePath)
|
|
{
|
|
lock (Outputs)
|
|
{
|
|
if (Outputs.TryGetValue(OldFilePath, out string? Content))
|
|
{
|
|
Outputs.Remove(OldFilePath);
|
|
Outputs.Add(NewFilePath, Content);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add a source file fragment to the session. When requests are made to read sources, the
|
|
/// fragment list will be searched first.
|
|
/// </summary>
|
|
/// <param name="SourceFile">Source file</param>
|
|
/// <param name="FilePath">The relative path to add</param>
|
|
/// <param name="LineNumber">Starting line number</param>
|
|
/// <param name="Data">The data associated with the path</param>
|
|
public void AddSourceFragment(UhtSourceFile SourceFile, string FilePath, int LineNumber, StringView Data)
|
|
{
|
|
SourceFragments.Add(FilePath, new UhtSourceFragment { SourceFile = SourceFile, FilePath = FilePath, LineNumber = LineNumber, Data = Data });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Testing harness to run the test scripts
|
|
/// </summary>
|
|
class UhtTestHarness
|
|
{
|
|
private enum ScriptFragmentType
|
|
{
|
|
Unknown,
|
|
Manifest,
|
|
Header,
|
|
Console,
|
|
Output,
|
|
}
|
|
|
|
private struct ScriptFragment
|
|
{
|
|
public ScriptFragmentType Type;
|
|
public string Name;
|
|
public int LineNumber;
|
|
public StringView Header;
|
|
public StringView Body;
|
|
public bool External;
|
|
}
|
|
|
|
private static bool RunScriptTest(UhtTables Tables, IUhtConfig Config, UhtGlobalOptions Options, string TestDirectory, string TestOutputDirectory, string Script, ILogger Logger)
|
|
{
|
|
string InPath = Path.Combine(TestDirectory, Script);
|
|
string OutPath = Path.Combine(TestOutputDirectory, Script);
|
|
|
|
UhtTestFileManager TestFileManager = new(TestDirectory);
|
|
UhtSession Session = new(Logger)
|
|
{
|
|
Tables = Tables,
|
|
Config = Config,
|
|
FileManager = TestFileManager,
|
|
RootDirectory = TestDirectory,
|
|
WarningsAsErrors = Options.bWarningsAsErrors,
|
|
RelativePathInLog = true,
|
|
GoWide = !Options.bNoGoWide,
|
|
NoOutput = false,
|
|
CullOutput = false,
|
|
CacheMessages = true,
|
|
IncludeDebugOutput = true,
|
|
};
|
|
|
|
// Read the testing script
|
|
List<ScriptFragment> ScriptFragments = [];
|
|
int ManifestIndex = -1;
|
|
int ConsoleIndex = -1;
|
|
UhtSourceFile ScriptSourceFile = new(Session, Script);
|
|
Dictionary<string, int> OutputFragments = [];
|
|
Session.Try(ScriptSourceFile, () =>
|
|
{
|
|
ScriptSourceFile.Read();
|
|
UhtTokenBufferReader Reader = new(ScriptSourceFile, ScriptSourceFile.Data.Memory);
|
|
|
|
bool done = false;
|
|
while (!done)
|
|
{
|
|
|
|
// Scan for the fragment header
|
|
ScriptFragmentType Type = ScriptFragmentType.Unknown;
|
|
string Name = "";
|
|
int HeaderStartPos = Reader.InputPos;
|
|
int HeaderEndPos = HeaderStartPos;
|
|
int LineNumber = 1;
|
|
while (true)
|
|
{
|
|
using UhtTokenSaveState SaveState = new(Reader);
|
|
UhtToken Token = Reader.GetLine();
|
|
if (Token.TokenType == UhtTokenType.EndOfFile)
|
|
{
|
|
break;
|
|
}
|
|
if (Token.Value.Span.Length == 0 || (Token.Value.Span.Length > 0 && Token.Value.Span[0] != '!'))
|
|
{
|
|
break;
|
|
}
|
|
HeaderEndPos = Reader.InputPos;
|
|
|
|
int EndCommandPos = Token.Value.Span.IndexOf(' ');
|
|
if (EndCommandPos == -1)
|
|
{
|
|
EndCommandPos = Token.Value.Span.Length;
|
|
}
|
|
string ScriptFragmentTypeString = Token.Value.Span[1..EndCommandPos].Trim().ToString();
|
|
|
|
if (!System.Enum.TryParse<ScriptFragmentType>(ScriptFragmentTypeString, true, out Type))
|
|
{
|
|
continue;
|
|
}
|
|
if (Type == ScriptFragmentType.Unknown)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Name = Token.Value.Span[EndCommandPos..].Trim().ToString();
|
|
LineNumber = Token.InputLine;
|
|
SaveState.AbandonState();
|
|
break;
|
|
}
|
|
|
|
// Scan for the fragment body
|
|
int BodyStartPos = Reader.InputPos;
|
|
int BodyEndPos = BodyStartPos;
|
|
while (true)
|
|
{
|
|
using UhtTokenSaveState SaveState = new(Reader);
|
|
UhtToken Token = Reader.GetLine();
|
|
if (Token.TokenType == UhtTokenType.EndOfFile)
|
|
{
|
|
done = true;
|
|
break;
|
|
}
|
|
if (Token.Value.Span.Length > 0 && Token.Value.Span[0] == '!')
|
|
{
|
|
break;
|
|
}
|
|
BodyEndPos = Reader.InputPos;
|
|
SaveState.AbandonState();
|
|
}
|
|
|
|
ScriptFragments.Add(new ScriptFragment
|
|
{
|
|
Type = Type,
|
|
Name = Name.Replace("\\\\", "\\"), // Be kind to people cut/copy/paste escaped strings around
|
|
LineNumber = LineNumber,
|
|
Header = new StringView(ScriptSourceFile.Data.Memory[HeaderStartPos..HeaderEndPos]),
|
|
Body = new StringView(ScriptSourceFile.Data.Memory[BodyStartPos..BodyEndPos]),
|
|
External = false,
|
|
});
|
|
}
|
|
|
|
// Search for the manifest and any output. Add fragments to the session
|
|
for (int i = 0; i < ScriptFragments.Count; ++i)
|
|
{
|
|
switch (ScriptFragments[i].Type)
|
|
{
|
|
case ScriptFragmentType.Manifest:
|
|
if (ManifestIndex != -1)
|
|
{
|
|
ScriptSourceFile.LogError(ScriptFragments[i].LineNumber, "There can be only one manifest section in a test script");
|
|
break;
|
|
}
|
|
ManifestIndex = i;
|
|
if (ScriptFragments[i].Name.Length == 0)
|
|
{
|
|
ScriptSourceFile.LogError(ScriptFragments[i].LineNumber, "Manifest name can not be blank");
|
|
break;
|
|
}
|
|
TestFileManager.AddSourceFragment(ScriptSourceFile, ScriptFragments[i].Name, ScriptFragments[i].LineNumber, ScriptFragments[i].Body);
|
|
break;
|
|
|
|
case ScriptFragmentType.Console:
|
|
if (ConsoleIndex != -1)
|
|
{
|
|
ScriptSourceFile.LogError(ScriptFragments[i].LineNumber, "There can be only one console section in a test script");
|
|
break;
|
|
}
|
|
ConsoleIndex = i;
|
|
break;
|
|
|
|
case ScriptFragmentType.Header:
|
|
if (ScriptFragments[i].Name.Length == 0)
|
|
{
|
|
ScriptSourceFile.LogError(ScriptFragments[i].LineNumber, "Header name can not be blank");
|
|
break;
|
|
}
|
|
if (ScriptFragments[i].Body.Length == 0)
|
|
{
|
|
// Read the NoExportTypes.h file from the engine source so we don't have to keep a copy
|
|
if (Path.GetFileName(ScriptFragments[i].Name).Equals("NoExportTypes.h", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
string ExternalPath = Path.Combine(Unreal.EngineDirectory.FullName, ScriptFragments[i].Name);
|
|
if (File.Exists(ExternalPath))
|
|
{
|
|
ScriptFragment Copy = ScriptFragments[i];
|
|
Copy.Body = new StringView(File.ReadAllText(ExternalPath));
|
|
Copy.External = true;
|
|
ScriptFragments[i] = Copy;
|
|
}
|
|
}
|
|
}
|
|
TestFileManager.AddSourceFragment(ScriptSourceFile, ScriptFragments[i].Name, ScriptFragments[i].LineNumber, ScriptFragments[i].Body);
|
|
break;
|
|
|
|
case ScriptFragmentType.Output:
|
|
OutputFragments.Add(ScriptFragments[i].Name, i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ManifestIndex == -1)
|
|
{
|
|
ScriptSourceFile.LogError("There must be a manifest section in a test script");
|
|
}
|
|
|
|
if (ConsoleIndex == -1)
|
|
{
|
|
ScriptSourceFile.LogError("There must be a console section in a test script");
|
|
}
|
|
});
|
|
|
|
// Run UHT
|
|
if (!Session.HasErrors)
|
|
{
|
|
Session.Run(ScriptFragments[ManifestIndex].Name);
|
|
}
|
|
|
|
// If we have no console index, then there is nothing we can do. This is a fatal error than can not be tested
|
|
bool bSuccess = true;
|
|
if (ConsoleIndex == -1)
|
|
{
|
|
ScriptSourceFile.LogError("Unable to do any verification without a console section");
|
|
Session.LogMessages();
|
|
File.Copy(InPath, OutPath, true);
|
|
bSuccess = false;
|
|
}
|
|
else
|
|
{
|
|
|
|
// Generate the console block
|
|
List<string> ConsoleLines = Session.CollectMessages();
|
|
StringBuilder SBConsole = new();
|
|
foreach (string Line in ConsoleLines)
|
|
{
|
|
SBConsole.AppendLine(Line);
|
|
}
|
|
|
|
// Verify the console block
|
|
// We trim the ends because it is too easy to leave off the ending CRLF in the script file.
|
|
if (ScriptFragments[ConsoleIndex].Body.ToString().TrimEnd() != SBConsole.ToString().TrimEnd())
|
|
{
|
|
Logger.LogError("Console output failed to match");
|
|
bSuccess = false;
|
|
}
|
|
|
|
// Check the output
|
|
foreach (KeyValuePair<string, string> KVP in TestFileManager.Outputs)
|
|
{
|
|
if (OutputFragments.TryGetValue(KVP.Key, out int Index))
|
|
{
|
|
if (ScriptFragments[Index].Body.ToString().TrimEnd() != KVP.Value.TrimEnd())
|
|
{
|
|
Logger.LogError("Output \"{Key}\" failed to match", KVP.Key);
|
|
bSuccess = false;
|
|
}
|
|
OutputFragments.Remove(KVP.Key);
|
|
}
|
|
else
|
|
{
|
|
Logger.LogError("Output \"{Key}\" not found in test script", KVP.Key);
|
|
}
|
|
}
|
|
foreach (KeyValuePair<string, int> KVP in OutputFragments)
|
|
{
|
|
Logger.LogError("Output \"{Key}\" in test script but not generated", KVP.Key);
|
|
}
|
|
|
|
// Create the complete output. Output includes all of the source fragments and console fragments
|
|
// and followed the output data sorted by file name.
|
|
StringBuilder SBTest = new();
|
|
for (int i = 0; i < ScriptFragments.Count; ++i)
|
|
{
|
|
if (ScriptFragments[i].Type != ScriptFragmentType.Output)
|
|
{
|
|
SBTest.Append(ScriptFragments[i].Header);
|
|
if (i == ConsoleIndex)
|
|
{
|
|
SBTest.Append(SBConsole);
|
|
}
|
|
else if (!ScriptFragments[i].External)
|
|
{
|
|
SBTest.Append(ScriptFragments[i].Body);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add the output
|
|
foreach (KeyValuePair<string, string> KVP in TestFileManager.Outputs)
|
|
{
|
|
SBTest.Append($"!output {KVP.Key}\r\n");
|
|
SBTest.Append(KVP.Value);
|
|
}
|
|
|
|
// Write the final content
|
|
try
|
|
{
|
|
File.WriteAllText(OutPath, SBTest.ToString());
|
|
}
|
|
catch (Exception E)
|
|
{
|
|
Logger.LogError(E, "Unable to write test result to \"{Ex}\"", E.Message);
|
|
}
|
|
}
|
|
|
|
if (bSuccess)
|
|
{
|
|
Logger.LogInformation("Test {InPath} succeeded", InPath);
|
|
}
|
|
else
|
|
{
|
|
Logger.LogError("Test {InPath} failed", InPath);
|
|
}
|
|
return bSuccess;
|
|
}
|
|
|
|
private static bool RunScriptTests(UhtTables Tables, IUhtConfig Config, UhtGlobalOptions Options, string TestDirectory, string TestOutputDirectory, List<string> Scripts, ILogger Logger)
|
|
{
|
|
bool bResult = true;
|
|
foreach (string Script in Scripts)
|
|
{
|
|
bResult &= RunScriptTest(Tables, Config, Options, TestDirectory, TestOutputDirectory, Script, Logger);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
private static bool RunDirectoryTests(UhtTables Tables, IUhtConfig Config, UhtGlobalOptions Options, string TestDirectory, string TestOutputDirectory, List<string> Directories, ILogger Logger)
|
|
{
|
|
bool bResult = true;
|
|
foreach (string Directory in Directories)
|
|
{
|
|
bResult &= RunTests(Tables, Config, Options, Path.Combine(TestDirectory, Directory), Path.Combine(TestOutputDirectory, Directory), Logger);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
private static bool RunTests(UhtTables Tables, IUhtConfig Config, UhtGlobalOptions Options, string TestDirectory, string TestOutputDirectory, ILogger Logger)
|
|
{
|
|
// Create output directory
|
|
Directory.CreateDirectory(TestOutputDirectory);
|
|
|
|
List<string> Scripts = [];
|
|
foreach (string Script in Directory.EnumerateFiles(TestDirectory, "*.uhttest"))
|
|
{
|
|
Scripts.Add(Path.GetFileName(Script));
|
|
}
|
|
Scripts.Sort(StringComparer.OrdinalIgnoreCase);
|
|
|
|
List<string> Directories = [];
|
|
foreach (string Directory in Directory.EnumerateDirectories(TestDirectory))
|
|
{
|
|
Directories.Add(Path.GetFileName(Directory));
|
|
}
|
|
Directories.Sort(StringComparer.OrdinalIgnoreCase);
|
|
|
|
List<string> Manifests = [];
|
|
foreach (string Manifest in Directory.EnumerateFiles(TestDirectory, "*.uhtmanifest"))
|
|
{
|
|
Manifests.Add(Path.GetFileName(Manifest));
|
|
}
|
|
Manifests.Sort(StringComparer.OrdinalIgnoreCase);
|
|
|
|
return
|
|
RunScriptTests(Tables, Config, Options, TestDirectory, TestOutputDirectory, Scripts, Logger) &&
|
|
RunDirectoryTests(Tables, Config, Options, TestDirectory, TestOutputDirectory, Directories, Logger);
|
|
}
|
|
|
|
public static bool RunTests(UhtTables Tables, IUhtConfig Config, UhtGlobalOptions Options, ILogger Logger)
|
|
{
|
|
DirectoryReference EngineSourceProgramDirectory = DirectoryReference.Combine(Unreal.EngineDirectory, "Source", "Programs");
|
|
string TestDirectory = FileReference.Combine(EngineSourceProgramDirectory, "UnrealBuildTool.Tests", "UHT").FullName;
|
|
string TestOutputDirectory = FileReference.Combine(EngineSourceProgramDirectory, "UnrealBuildTool.Tests", "UHT.Out").FullName;
|
|
|
|
// Clear the output directory
|
|
try
|
|
{
|
|
Directory.Delete(TestOutputDirectory, true);
|
|
}
|
|
catch (Exception)
|
|
{ }
|
|
|
|
// Collect a list of all the test scripts
|
|
Logger.LogInformation("Running tests in {TestDirectory}", TestDirectory);
|
|
Logger.LogInformation("Output can be compared in {TestOutputDirectory}", TestOutputDirectory);
|
|
|
|
// Run the tests on the directory
|
|
return RunTests(Tables, Config, Options, TestDirectory, TestOutputDirectory, Logger);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Invoke UHT
|
|
/// </summary>
|
|
[ToolMode("UnrealHeaderTool", ToolModeOptions.XmlConfig | ToolModeOptions.BuildPlatforms | ToolModeOptions.ShowExecutionTime)]
|
|
class UnrealHeaderToolMode : ToolMode
|
|
{
|
|
/// <summary>
|
|
/// Directory for saved application settings (typically Engine/Programs)
|
|
/// </summary>
|
|
static DirectoryReference? CachedEngineProgramSavedDirectory;
|
|
|
|
/// <summary>
|
|
/// The engine programs directory
|
|
/// </summary>
|
|
public static DirectoryReference EngineProgramSavedDirectory
|
|
{
|
|
get
|
|
{
|
|
if (CachedEngineProgramSavedDirectory == null)
|
|
{
|
|
if (Unreal.IsEngineInstalled())
|
|
{
|
|
CachedEngineProgramSavedDirectory = Unreal.UserSettingDirectory ?? DirectoryReference.Combine(Unreal.EngineDirectory, "Programs");
|
|
}
|
|
else
|
|
{
|
|
CachedEngineProgramSavedDirectory = DirectoryReference.Combine(Unreal.EngineDirectory, "Programs");
|
|
}
|
|
}
|
|
return CachedEngineProgramSavedDirectory;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Print (incomplete) usage information
|
|
/// </summary>
|
|
/// <param name="ExporterTable">Defined exporters</param>
|
|
/// <param name="Config">Configuration</param>
|
|
private static void PrintUsage(UhtExporterTable ExporterTable, UhtConfigImpl Config)
|
|
{
|
|
Console.WriteLine("UnrealBuildTool -Mode=UnrealHeaderTool [ProjectFile ManifestFile] -OR [\"-Target...\"] [Options]");
|
|
Console.WriteLine("");
|
|
Console.WriteLine("Options:");
|
|
int LongestPrefix = 0;
|
|
foreach (FieldInfo Info in typeof(UhtGlobalOptions).GetFields())
|
|
{
|
|
foreach (CommandLineAttribute Att in Info.GetCustomAttributes<CommandLineAttribute>())
|
|
{
|
|
if (Att.Prefix != null && Att.Description != null)
|
|
{
|
|
LongestPrefix = Att.Prefix.Length > LongestPrefix ? Att.Prefix.Length : LongestPrefix;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (UhtExporter Generator in ExporterTable)
|
|
{
|
|
LongestPrefix = Generator.Name.Length + 2 > LongestPrefix ? Generator.Name.Length + 2 : LongestPrefix;
|
|
}
|
|
|
|
foreach (FieldInfo Info in typeof(UhtGlobalOptions).GetFields())
|
|
{
|
|
foreach (CommandLineAttribute Att in Info.GetCustomAttributes<CommandLineAttribute>())
|
|
{
|
|
if (Att.Prefix != null && Att.Description != null)
|
|
{
|
|
Console.WriteLine($" {Att.Prefix.PadRight(LongestPrefix)} : {Att.Description}");
|
|
}
|
|
}
|
|
}
|
|
|
|
Console.WriteLine("");
|
|
Console.WriteLine("Generators: Prefix with 'no' to disable a generator");
|
|
foreach (UhtExporter Generator in ExporterTable)
|
|
{
|
|
string IsDefault = Config.IsExporterEnabled(Generator.Name) || Generator.Options.HasAnyFlags(UhtExporterOptions.Default) ? " (Default)" : "";
|
|
Console.WriteLine($" -{Generator.Name.PadRight(LongestPrefix)} : {Generator.Description}{IsDefault}");
|
|
}
|
|
Console.WriteLine("");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute the command
|
|
/// </summary>
|
|
/// <param name="Arguments">Command line arguments</param>
|
|
/// <returns>Exit code</returns>
|
|
/// <param name="Logger"></param>
|
|
public override async Task<int> ExecuteAsync(CommandLineArguments Arguments, ILogger Logger)
|
|
{
|
|
try
|
|
{
|
|
|
|
// Initialize the attributes
|
|
UhtTables Tables = new();
|
|
|
|
// Initialize the config
|
|
UhtConfigImpl Config = new(Arguments);
|
|
|
|
// Parse the global options
|
|
UhtGlobalOptions Options = new(Arguments);
|
|
int TargetArgumentIndex = -1;
|
|
if (Arguments.GetPositionalArgumentCount() == 0)
|
|
{
|
|
for (int Index = 0; Index < Arguments.Count; ++Index)
|
|
{
|
|
if (Arguments[Index].StartsWith("-Target", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
TargetArgumentIndex = Index;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
int RequiredArgCount = TargetArgumentIndex >= 0 || Options.bTest ? 0 : 2;
|
|
if (Arguments.GetPositionalArgumentCount() != RequiredArgCount || Options.bGetHelp)
|
|
{
|
|
PrintUsage(Tables.ExporterTable, Config);
|
|
return Options.bGetHelp ? (int)CompilationResult.Succeeded : (int)CompilationResult.OtherCompilationError;
|
|
}
|
|
|
|
// Configure the log system
|
|
Log.OutputLevel = Options.LogOutputLevel;
|
|
Log.IncludeTimestamps = Options.bLogTimestamps;
|
|
Log.IncludeProgramNameWithSeverityPrefix = Options.bLogFromMsBuild;
|
|
|
|
// Add the log writer if requested. When building a target, we'll create the writer for the default log file later.
|
|
if (!Options.bNoLog)
|
|
{
|
|
if (Options.LogFileName != null)
|
|
{
|
|
Log.AddFileWriter("LogTraceListener", Options.LogFileName);
|
|
}
|
|
|
|
if (!Log.HasFileWriter())
|
|
{
|
|
string BaseLogFileName = FileReference.Combine(EngineProgramSavedDirectory, "UnrealHeaderTool", "Saved", "Logs", "UnrealHeaderTool.log").FullName;
|
|
|
|
FileReference LogFile = new(BaseLogFileName);
|
|
foreach (string LogSuffix in Arguments.GetValues("-LogSuffix="))
|
|
{
|
|
LogFile = LogFile.ChangeExtension(null) + "_" + LogSuffix + LogFile.GetExtension();
|
|
}
|
|
|
|
Log.AddFileWriter("DefaultLogTraceListener", LogFile);
|
|
}
|
|
}
|
|
|
|
// If we are running test scripts
|
|
if (Options.bTest)
|
|
{
|
|
return UhtTestHarness.RunTests(Tables, Config, Options, Logger) ? (int)CompilationResult.Succeeded : (int)CompilationResult.OtherCompilationError;
|
|
}
|
|
|
|
string? ProjectFile = null;
|
|
string? ManifestPath = null;
|
|
|
|
if (TargetArgumentIndex >= 0)
|
|
{
|
|
// Create the build configuration object, and read the settings
|
|
BuildConfiguration BuildConfiguration = new();
|
|
XmlConfig.ApplyTo(BuildConfiguration);
|
|
Arguments.ApplyTo(BuildConfiguration);
|
|
|
|
CommandLineArguments LocalArguments = new([Arguments[TargetArgumentIndex]]);
|
|
List<TargetDescriptor> TargetDescriptors = TargetDescriptor.ParseCommandLine(LocalArguments, BuildConfiguration, Logger);
|
|
if (TargetDescriptors.Count == 0)
|
|
{
|
|
Logger.LogError("No target descriptors found.");
|
|
return (int)CompilationResult.OtherCompilationError;
|
|
}
|
|
|
|
TargetDescriptor TargetDesc = TargetDescriptors[0];
|
|
|
|
// Create the target
|
|
UEBuildTarget Target = UEBuildTarget.Create(TargetDesc, BuildConfiguration, Logger);
|
|
|
|
// Create the makefile for the target and export the module information
|
|
using ISourceFileWorkingSet WorkingSet = new EmptySourceFileWorkingSet();
|
|
|
|
// Create the makefile
|
|
TargetMakefile Makefile = await Target.BuildAsync(BuildConfiguration, WorkingSet, TargetDesc, Logger, true);
|
|
|
|
FileReference ModuleInfoFileName = ExternalExecution.GetUHTModuleInfoFileName(Makefile, Target.TargetName);
|
|
FileReference DepsFileName = ExternalExecution.GetUHTDepsFileName(ModuleInfoFileName);
|
|
ManifestPath = ModuleInfoFileName.FullName;
|
|
ExternalExecution.WriteUHTManifest(Makefile, Target.TargetName, ModuleInfoFileName, DepsFileName);
|
|
|
|
if (Target.ProjectFile != null)
|
|
{
|
|
ProjectFile = Target.ProjectFile.FullName;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ProjectFile = Arguments.GetPositionalArguments()[0];
|
|
ManifestPath = Arguments.GetPositionalArguments()[1];
|
|
}
|
|
|
|
string? ProjectPath = ProjectFile != null ? Path.GetDirectoryName(ProjectFile) : null;
|
|
Config.ProjectSpecificConfigs(ProjectPath);
|
|
|
|
UhtSession Session = new(Logger)
|
|
{
|
|
Tables = Tables,
|
|
Config = Config,
|
|
FileManager = new UhtStdFileManager(),
|
|
EngineDirectory = Unreal.EngineDirectory.FullName,
|
|
ProjectFile = ProjectFile,
|
|
ProjectDirectory = String.IsNullOrEmpty(ProjectPath) ? null : ProjectPath,
|
|
ReferenceDirectory = FileReference.Combine(EngineProgramSavedDirectory, "UnrealHeaderTool", "Saved", "ReferenceExports").FullName,
|
|
VerifyDirectory = FileReference.Combine(EngineProgramSavedDirectory, "UnrealHeaderTool", "Saved", "VerifyExports").FullName,
|
|
WarningsAsErrors = Options.bWarningsAsErrors,
|
|
GoWide = !Options.bNoGoWide,
|
|
FailIfGeneratedCodeChanges = Options.bFailIfGeneratedCodeChanges,
|
|
NoOutput = Options.bNoOutput,
|
|
IncludeDebugOutput = Options.bIncludeDebugOutput,
|
|
NoDefaultExporters = Options.bNoDefaultExporters,
|
|
};
|
|
|
|
if (Options.bWriteRef)
|
|
{
|
|
Session.ReferenceMode = UhtReferenceMode.Reference;
|
|
}
|
|
else if (Options.bVerifyRef)
|
|
{
|
|
Session.ReferenceMode = UhtReferenceMode.Verify;
|
|
}
|
|
|
|
// Read and parse
|
|
Session.Run(ManifestPath!, Arguments);
|
|
Session.LogMessages();
|
|
return (int)(Session.HasErrors ? CompilationResult.OtherCompilationError : CompilationResult.Succeeded);
|
|
}
|
|
catch (Exception Ex)
|
|
{
|
|
// Unhandled exception.
|
|
Logger.LogError(Ex, "Unhandled exception: {Ex}", ExceptionUtils.FormatException(Ex));
|
|
Logger.LogDebug(Ex, "Unhandled exception: {Ex}", ExceptionUtils.FormatExceptionDetails(Ex));
|
|
return (int)CompilationResult.OtherCompilationError;
|
|
}
|
|
}
|
|
}
|
|
}
|