Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/Modes/WriteMetadataMode.cs
ben marsh e572f6d330 UBT: Revert CL 18260416. We need to update the timestamp on the engine version file to prevent the update action running multiple times.
The action should not run if no engine files have been modified. If new engine files are being added to the manifest, the version file can be written without having any changes. Installed builds will not run the action due to the path check.

#jira UE-133086
#preflight 61df35b7484d866ec01c8597

#ROBOMERGE-AUTHOR: ben.marsh
#ROBOMERGE-SOURCE: CL 18588751 in //UE5/Release-5.0/... via CL 18588762 via CL 18588785
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Test -> Main) (v899-18417669)

[CL 18588797 by ben marsh in ue5-main branch]
2022-01-12 15:27:43 -05:00

278 lines
9.4 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.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading.Tasks;
using EpicGames.Core;
using UnrealBuildBase;
namespace UnrealBuildTool
{
/// <summary>
/// Parameters for the WriteMetadata mode
/// </summary>
[Serializable]
class WriteMetadataTargetInfo
{
/// <summary>
/// The project file
/// </summary>
public FileReference? ProjectFile;
/// <summary>
/// Output location for the version file
/// </summary>
public FileReference? VersionFile;
/// <summary>
/// The new version to write. This should only be set on the engine step.
/// </summary>
public BuildVersion? Version;
/// <summary>
/// Output location for the target file
/// </summary>
public FileReference? ReceiptFile;
/// <summary>
/// The new receipt to write. This should only be set on the target step.
/// </summary>
public TargetReceipt? Receipt;
/// <summary>
/// Map of module manifest filenames to their location on disk.
/// </summary>
public Dictionary<FileReference, ModuleManifest> FileToManifest;
/// <summary>
/// Constructor
/// </summary>
/// <param name="ProjectFile"></param>
/// <param name="VersionFile"></param>
/// <param name="Version"></param>
/// <param name="ReceiptFile"></param>
/// <param name="Receipt"></param>
/// <param name="FileToManifest"></param>
public WriteMetadataTargetInfo(FileReference? ProjectFile, FileReference? VersionFile, BuildVersion? Version, FileReference? ReceiptFile, TargetReceipt? Receipt, Dictionary<FileReference, ModuleManifest> FileToManifest)
{
this.ProjectFile = ProjectFile;
this.VersionFile = VersionFile;
this.Version = Version;
this.ReceiptFile = ReceiptFile;
this.Receipt = Receipt;
this.FileToManifest = FileToManifest;
}
}
/// <summary>
/// Writes all metadata files at the end of a build (receipts, version files, etc...). This is implemented as a separate mode to allow it to be done as part of the action graph.
/// </summary>
[ToolMode("WriteMetadata", ToolModeOptions.None)]
class WriteMetadataMode : ToolMode
{
/// <summary>
/// Version number for output files. This is not used directly, but can be appended to command-line invocations of the tool to ensure that actions to generate metadata are updated if the output format changes.
/// The action graph is regenerated whenever UBT is rebuilt, so this should always match.
/// </summary>
public const int CurrentVersionNumber = 2;
/// <summary>
/// Execute the command
/// </summary>
/// <param name="Arguments">Command line arguments</param>
/// <returns>Exit code</returns>
public override int Execute(CommandLineArguments Arguments)
{
// Acquire a different mutex to the regular UBT instance, since this mode will be called as part of a build. We need the mutex to ensure that building two modular configurations
// in parallel don't clash over writing shared *.modules files (eg. DebugGame and Development editors).
string MutexName = SingleInstanceMutex.GetUniqueMutexForPath("UnrealBuildTool_WriteMetadata", Unreal.RootDirectory.FullName);
using(new SingleInstanceMutex(MutexName, true))
{
return ExecuteInternal(Arguments);
}
}
/// <summary>
/// Execute the command, having obtained the appropriate mutex
/// </summary>
/// <param name="Arguments">Command line arguments</param>
/// <returns>Exit code</returns>
private int ExecuteInternal(CommandLineArguments Arguments)
{
// Read the target info
WriteMetadataTargetInfo TargetInfo = BinaryFormatterUtils.Load<WriteMetadataTargetInfo>(Arguments.GetFileReference("-Input="));
bool bNoManifestChanges = Arguments.HasOption("-NoManifestChanges");
int VersionNumber = Arguments.GetInteger("-Version=");
Arguments.CheckAllArgumentsUsed();
// Make sure the version number is correct
if(VersionNumber != CurrentVersionNumber)
{
throw new BuildException("Version number to WriteMetadataMode is incorrect (expected {0}, got {1})", CurrentVersionNumber, VersionNumber);
}
// Get the build id to use
string? BuildId;
if (TargetInfo.Version != null && !String.IsNullOrEmpty(TargetInfo.Version.BuildId))
{
BuildId = TargetInfo.Version.BuildId;
}
else if (TargetInfo.Receipt != null && !String.IsNullOrEmpty(TargetInfo.Receipt.Version.BuildId))
{
BuildId = TargetInfo.Receipt.Version.BuildId;
}
else if (TargetInfo.VersionFile != null && BuildVersion.TryRead(TargetInfo.VersionFile, out BuildVersion? PrevVersion) && CanRecycleBuildId(PrevVersion.BuildId, TargetInfo.FileToManifest))
{
BuildId = PrevVersion.BuildId;
}
else
{
BuildId = Guid.NewGuid().ToString();
}
// Read all the existing manifests and merge them into the new ones if they have the same build id
foreach(KeyValuePair<FileReference, ModuleManifest> Pair in TargetInfo.FileToManifest)
{
ModuleManifest? SourceManifest;
if(TryReadManifest(Pair.Key, out SourceManifest) && SourceManifest.BuildId == BuildId)
{
MergeManifests(SourceManifest, Pair.Value);
}
}
// Update the build id in all the manifests, and write them out
foreach (KeyValuePair<FileReference, ModuleManifest> Pair in TargetInfo.FileToManifest)
{
FileReference ManifestFile = Pair.Key;
if(!UnrealBuildTool.IsFileInstalled(ManifestFile))
{
ModuleManifest Manifest = Pair.Value;
Manifest.BuildId = BuildId ?? String.Empty;
if(!FileReference.Exists(ManifestFile))
{
// If the file doesn't already exist, just write it out
DirectoryReference.CreateDirectory(ManifestFile.Directory);
Manifest.Write(ManifestFile);
}
else
{
// Otherwise write it to a buffer first
string OutputText;
using (StringWriter Writer = new StringWriter())
{
Manifest.Write(Writer);
OutputText = Writer.ToString();
}
// Check if the manifest has changed. Note that if a manifest is out of date, we should have generated a new build id causing the contents to differ.
if (bNoManifestChanges)
{
string CurrentText = FileReference.ReadAllText(ManifestFile);
if (CurrentText != OutputText)
{
Log.TraceError("Build modifies {0}. This is not permitted. Before:\n {1}\nAfter:\n {2}", ManifestFile, CurrentText.Replace("\n", "\n "), OutputText.Replace("\n", "\n "));
}
}
// Write it to disk
FileReference.WriteAllText(ManifestFile, OutputText);
}
}
}
// Write out the version file
if (TargetInfo.Version != null && TargetInfo.VersionFile != null)
{
DirectoryReference.CreateDirectory(TargetInfo.VersionFile.Directory);
TargetInfo.Version.BuildId = BuildId;
TargetInfo.Version.Write(TargetInfo.VersionFile);
}
// Write out the receipt
if (TargetInfo.Receipt != null && TargetInfo.ReceiptFile != null)
{
DirectoryReference.CreateDirectory(TargetInfo.ReceiptFile.Directory);
TargetInfo.Receipt.Version.BuildId = BuildId;
TargetInfo.Receipt.Write(TargetInfo.ReceiptFile);
}
return 0;
}
/// <summary>
/// Checks if this
/// </summary>
/// <param name="BuildId"></param>
/// <param name="FileToManifest"></param>
/// <returns></returns>
bool CanRecycleBuildId(string? BuildId, Dictionary<FileReference, ModuleManifest> FileToManifest)
{
foreach (FileReference ManifestFileName in FileToManifest.Keys)
{
ModuleManifest? Manifest;
if (ManifestFileName.IsUnderDirectory(Unreal.EngineDirectory) && TryReadManifest(ManifestFileName, out Manifest) && Manifest.BuildId == BuildId)
{
DateTime ManifestTime = FileReference.GetLastWriteTimeUtc(ManifestFileName);
foreach (string FileName in Manifest.ModuleNameToFileName.Values)
{
FileInfo ModuleInfo = new FileInfo(FileReference.Combine(ManifestFileName.Directory, FileName).FullName);
if (!ModuleInfo.Exists || ModuleInfo.LastWriteTimeUtc > ManifestTime)
{
return false;
}
}
}
}
return true;
}
/// <summary>
/// Attempts to read a manifest from the given location
/// </summary>
/// <param name="ManifestFileName">Path to the manifest</param>
/// <param name="Manifest">If successful, receives the manifest that was read</param>
/// <returns>True if the manifest was read correctly, false otherwise</returns>
public static bool TryReadManifest(FileReference ManifestFileName, [NotNullWhen(true)] out ModuleManifest? Manifest)
{
if(FileReference.Exists(ManifestFileName))
{
try
{
Manifest = ModuleManifest.Read(ManifestFileName);
return true;
}
catch(Exception Ex)
{
Log.TraceWarning("Unable to read '{0}'; ignoring.", ManifestFileName);
Log.TraceLog(ExceptionUtils.FormatExceptionDetails(Ex));
}
}
Manifest = null;
return false;
}
/// <summary>
/// Merge a manifest into another manifest
/// </summary>
/// <param name="SourceManifest">The source manifest</param>
/// <param name="TargetManifest">The target manifest to merge into</param>
static void MergeManifests(ModuleManifest SourceManifest, ModuleManifest TargetManifest)
{
foreach(KeyValuePair<string, string> ModulePair in SourceManifest.ModuleNameToFileName)
{
if(!TargetManifest.ModuleNameToFileName.ContainsKey(ModulePair.Key))
{
TargetManifest.ModuleNameToFileName.Add(ModulePair.Key, ModulePair.Value);
}
}
}
}
}