Merging //UE4/Dev-Main to Dev-Editor (//UE4/Dev-Editor) at CL 7473521

#rb none
#fyi Max.Chen, Tim.Gautier

[CL 7614721 by Chris Gagnon in Dev-Editor branch]
This commit is contained in:
Chris Gagnon
2019-07-24 15:05:52 -04:00
4757 changed files with 389065 additions and 809260 deletions

View File

@@ -29,17 +29,28 @@ namespace Tools.DotNETCommon
/// </summary>
public Dictionary<FileReference, bool> ProjectReferences = new Dictionary<FileReference, bool>();
/// <summary>
/// List of compile references in the project.
/// </summary>
public List<FileReference> CompileReferences = new List<FileReference>();
/// <summary>
/// Mapping of content IF they are flagged Always or Newer
/// </summary>
public Dictionary<FileReference, bool> ContentReferences = new Dictionary<FileReference, bool>();
/// <summary>
/// Path to the CSProject file
/// </summary>
public FileReference ProjectPath;
/// <summary>
/// Constructor
/// </summary>
/// <param name="InProperties">Initial mapping of property names to values</param>
CsProjectInfo(Dictionary<string, string> InProperties)
CsProjectInfo(Dictionary<string, string> InProperties, FileReference InProjectPath)
{
ProjectPath = InProjectPath;
Properties = new Dictionary<string, string>(InProperties);
}
@@ -61,6 +72,17 @@ namespace Tools.DotNETCommon
}
}
/// <summary>
/// Returns the assembly name used by this project
/// </summary>
/// <returns></returns>
public string GetAssemblyName()
{
string Output = "";
Properties.TryGetValue("AssemblyName", out Output);
return Output;
}
/// <summary>
/// Finds all build products from this project. This includes content and other assemblies marked to be copied local.
/// </summary>
@@ -234,7 +256,7 @@ namespace Tools.DotNETCommon
}
// Parse the basic structure of the document, updating properties and recursing into other referenced projects as we go
CsProjectInfo ProjectInfo = new CsProjectInfo(Properties);
CsProjectInfo ProjectInfo = new CsProjectInfo(Properties, File);
foreach (XmlElement Element in Document.DocumentElement.ChildNodes.OfType<XmlElement>())
{
switch (Element.Name)
@@ -303,6 +325,13 @@ namespace Tools.DotNETCommon
ParseProjectReference(BaseDirectory, ItemElement, ProjectInfo.ProjectReferences);
}
break;
case "Compile":
// Reference to another project
if (EvaluateCondition(ItemElement, ProjectInfo.Properties))
{
ParseCompileReference(BaseDirectory, ItemElement, ProjectInfo.CompileReferences);
}
break;
case "Content":
case "None":
// Reference to another project
@@ -354,6 +383,22 @@ namespace Tools.DotNETCommon
}
}
/// <summary>
/// Parses a project reference from a given 'ProjectReference' element
/// </summary>
/// <param name="BaseDirectory">Directory to resolve relative paths against</param>
/// <param name="ParentElement">The parent 'ProjectReference' element</param>
/// <param name="CompileReferences">List of source files.</param>
static void ParseCompileReference(DirectoryReference BaseDirectory, XmlElement ParentElement, List<FileReference> CompileReferences)
{
string IncludePath = UnescapeString(ParentElement.GetAttribute("Include"));
if (!String.IsNullOrEmpty(IncludePath))
{
FileReference SourceFile = FileReference.Combine(BaseDirectory, IncludePath);
CompileReferences.Add(SourceFile);
}
}
/// <summary>
/// Parses an assembly reference from a given 'Content' element
/// </summary>
@@ -694,4 +739,37 @@ namespace Tools.DotNETCommon
return NewText;
}
}
/// <summary>
/// Extension methods for CsProject support
/// </summary>
public static class CsProjectInfoExtensionMethods
{
/// <summary>
/// Adds aall input/output properties of a CSProject to a hash collection
/// </summary>
/// <param name="Hasher"></param>
/// <param name="Project"></param>
/// <returns></returns>
public static bool AddCsProjectInfo(this HashCollection Hasher, CsProjectInfo Project, HashCollection.HashType HashType)
{
// Get the output assembly and pdb file
DirectoryReference ProjectDirectory = Project.ProjectPath.Directory;
DirectoryReference OutputDir = Project.GetOutputDir(ProjectDirectory);
FileReference OutputFile = FileReference.Combine(OutputDir, Project.GetAssemblyName() + ".dll");
FileReference DebugFile = OutputFile.ChangeExtension("pdb");
// build a list of all input and output files from this module
List<FileReference> DependentFiles = new List<FileReference> { Project.ProjectPath, OutputFile, DebugFile };
DependentFiles.AddRange(Project.CompileReferences);
if (!Hasher.AddFiles(DependentFiles, HashType))
{
return false;
}
return true;
}
}
}

View File

@@ -161,6 +161,7 @@
<Compile Include="ResXResource\UEResXWriter.cs" />
<Compile Include="StringUtils.cs" />
<Compile Include="ThreadPoolWorkQueue.cs" />
<Compile Include="HashCollection.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

@@ -0,0 +1,250 @@
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Linq;
using Tools.DotNETCommon;
namespace Tools.DotNETCommon
{
/// <summary>
/// Class that holds a single hash entry. Only the hash is used for comparisons but the
/// name and meta fields can hold related data for debugging etc.
/// </summary>
public class HashEntry : IEquatable<HashEntry>
{
public string Name { get; set; }
public string MetaData { get; set; }
[XmlIgnore]
public ContentHash Hash { get; set; }
[XmlElement(ElementName = "Hash", DataType = "hexBinary")]
public byte[] HashBytes
{
get { return Hash.Bytes; }
set { Hash = new ContentHash(value); }
}
public HashEntry(string InName, ContentHash InHash, string InMeta)
{
Name = InName;
Hash = InHash;
MetaData = InMeta;
}
// parameterless constructor for serialization
public HashEntry()
{
}
// Equality check using the underlying content hash
public bool Equals(HashEntry RHS)
{
return Hash == RHS.Hash;
}
public override int GetHashCode()
{
return Hash.GetHashCode();
}
public override string ToString()
{
return Hash.ToString();
}
}
/// <summary>
/// Class that holds a collection of hashes with helpers for adding files, comparisions, and
/// serialization to and from files
/// </summary>
public class HashCollection
{
public enum HashType
{
MetaData,
Content
}
// underlying hashset that holds our data
public HashSet<HashEntry> Hashes { get; protected set; }
public HashCollection()
{
Hashes = new HashSet<HashEntry>();
}
/// <summary>
/// Adds a file to our hash collection. If the specified path does not exist an exception
/// will be thrown
/// </summary>
/// <param name="InPath"></param>
/// <param name="HashType"></param>
public bool AddFile(FileReference InPath, HashCollection.HashType HashType)
{
var Fi = new FileInfo(InPath.FullName);
if (!Fi.Exists)
{
return false;
}
string MetaData = string.Format("File={0} Size={1} LastWrite={2}", InPath, Fi.Length, Fi.LastWriteTimeUtc.ToString());
// Hash metadata or content
ContentHash Hash = HashType == HashCollection.HashType.MetaData ? ContentHash.SHA1(MetaData) : ContentHash.SHA1(InPath);
HashEntry Entry = new HashEntry(InPath.FullName, Hash, MetaData);
Hashes.Add(Entry);
return true;
}
/// <summary>
/// Convenience function that adds a range of files
/// </summary>
/// <param name="Files"></param>
/// <param name="HashType"></param>
public bool AddFiles(IEnumerable<FileReference> Files, HashCollection.HashType HashType)
{
foreach (var File in Files)
{
if (!AddFile(File, HashType))
{
return false;
}
}
return true;
}
/// <summary>
/// Compares two collections by comparing the underlying hashsets
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
HashCollection RHS = obj as HashCollection;
if (RHS == null)
{
return false;
}
return Hashes.SetEquals(RHS.Hashes);
}
/// <summary>
/// Debugging helper that returns true if there's a hash with the specified name
/// </summary>
/// <param name="Name"></param>
/// <returns></returns>
public bool HasEntryForName(string Name)
{
return Hashes.Where(E => E.Name == Name).Any();
}
/// <summary>
/// Debugging helper that returns the hash entry for the specified name
/// </summary>
/// <param name="Input"></param>
/// <returns></returns>
public HashEntry GetEntryForName(string Input)
{
return Hashes.Where(E => E.Name == Input).FirstOrDefault();
}
/// <summary>
/// Logs differences between two collections for debugging
/// </summary>
/// <param name="RHS"></param>
public void LogDifferences(HashCollection RHS)
{
foreach (var Entry in Hashes)
{
if (!RHS.HasEntryForName(Entry.Name))
{
Log.TraceInformation("RHS does not have an entry for {0}", Entry.Name);
}
else if (!RHS.Hashes.Contains(Entry))
{
HashEntry RHSEntry = RHS.GetEntryForName(Entry.Name);
Log.TraceInformation("RHS hash mismatch for {0}", Entry.Name);
Log.TraceInformation("Current:\n\t{0}\n\t{1}\n\t{2}", Entry.Hash, Entry.Name, Entry.MetaData);
Log.TraceInformation("RHS:\n\t{0}\n\t{1}\n\t{2}", RHSEntry.Hash, RHSEntry.Name, RHSEntry.MetaData);
}
}
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return EqualityComparer<HashSet<HashEntry>>.Default.GetHashCode(Hashes);
}
/// <summary>
/// Creates a hash collection from a previously serialized file
/// </summary>
/// <param name="InPath"></param>
/// <returns></returns>
public static HashCollection CreateFromFile(string InPath)
{
try
{
using (var stream = System.IO.File.OpenRead(InPath))
{
var Serializer = new XmlSerializer(typeof(HashCollection));
return Serializer.Deserialize(stream) as HashCollection;
}
}
catch (Exception Ex)
{
Log.TraceWarning("Failed to load HashCollection from {0}. {1}", InPath, Ex.Message);
}
return null;
}
/// <summary>
/// Saves a hash collection to the specified file path as XML.
/// </summary>
/// <param name="OutPath"></param>
public void SaveToFile(string OutPath)
{
XmlSerializer XS = new XmlSerializer(this.GetType());
//first serialize the object to memory stream,
//in case of exception, the original file is not corrupted
using (MemoryStream MS = new MemoryStream())
{
XmlWriterSettings Settings = new XmlWriterSettings();
Settings.Indent = true;
Settings.IndentChars = ("\t");
Settings.Encoding = Encoding.UTF8;
using (XmlWriter Writer = XmlWriter.Create(MS, Settings))
{
XS.Serialize(Writer, this);
}
MS.Flush();
string String = Encoding.UTF8.GetString(MS.ToArray());
File.WriteAllText(OutPath, String);
}
}
}
}