// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace UnrealBuildTool { /// /// Base class for file system objects (files or directories). /// [Serializable] public abstract class FileSystemReference { /// /// The path to this object. Stored as an absolute path, with O/S preferred separator characters, and no trailing slash for directories. /// public readonly string FullName; /// /// The canonical full name for this object. /// public readonly string CanonicalName; /// /// Constructs a filesystem object for the given path. /// public FileSystemReference(string InPath) { FullName = Path.GetFullPath(InPath).TrimEnd(Path.DirectorySeparatorChar); CanonicalName = FullName.ToLowerInvariant(); } /// /// Constructs a reference from the given FileSystemInfo. /// public FileSystemReference(FileSystemInfo InInfo) { FullName = InInfo.FullName; CanonicalName = FullName.ToLowerInvariant(); } /// /// Direct constructor for a path /// protected FileSystemReference(string InFullName, string InCanonicalName) { FullName = InFullName; CanonicalName = InCanonicalName; } /// /// Create a full path by concatenating multiple strings /// /// static protected string CombineStrings(DirectoryReference BaseDirectory, params string[] Fragments) { // Get the initial string to append to, and strip any root directory suffix from it StringBuilder NewFullName = new StringBuilder(BaseDirectory.FullName); if (NewFullName.Length > 0 && NewFullName[NewFullName.Length - 1] == Path.DirectorySeparatorChar) { NewFullName.Remove(NewFullName.Length - 1, 1); } // Scan through the fragments to append, appending them to a string and updating the base length as we go foreach (string Fragment in Fragments) { // Check if this fragment is an absolute path if ((Fragment.Length >= 2 && Fragment[1] == ':') || (Fragment.Length >= 1 && (Fragment[0] == Path.DirectorySeparatorChar || Fragment[0] == Path.AltDirectorySeparatorChar))) { // It is. Reset the new name to the full version of this path. NewFullName.Clear(); NewFullName.Append(Path.GetFullPath(Fragment).TrimEnd(Path.DirectorySeparatorChar)); } else { // Append all the parts of this fragment to the end of the existing path. int StartIdx = 0; while (StartIdx < Fragment.Length) { // Find the end of this fragment. We may have been passed multiple paths in the same string. int EndIdx = StartIdx; while (EndIdx < Fragment.Length && Fragment[EndIdx] != Path.DirectorySeparatorChar && Fragment[EndIdx] != Path.AltDirectorySeparatorChar) { EndIdx++; } // Ignore any empty sections, like leading or trailing slashes, and '.' directory references. int Length = EndIdx - StartIdx; if (Length == 0) { // Multiple directory separators in a row; illegal. throw new ArgumentException("Path fragment '{0}' contains invalid directory separators."); } else if (Length == 2 && Fragment[StartIdx] == '.' && Fragment[StartIdx + 1] == '.') { // Remove the last directory name for (int SeparatorIdx = NewFullName.Length - 1; SeparatorIdx >= 0; SeparatorIdx--) { if (NewFullName[SeparatorIdx] == Path.DirectorySeparatorChar) { NewFullName.Remove(SeparatorIdx, NewFullName.Length - SeparatorIdx); break; } } } else if (Length != 1 || Fragment[StartIdx] != '.') { // Append this fragment NewFullName.Append(Path.DirectorySeparatorChar); NewFullName.Append(Fragment, StartIdx, Length); } // Move to the next part StartIdx = EndIdx + 1; } } } // Append the directory separator if (NewFullName.Length == 0 || (NewFullName.Length == 2 && NewFullName[1] == ':')) { NewFullName.Append(Path.DirectorySeparatorChar); } // Set the new path variables return NewFullName.ToString(); } /// /// Checks whether this name has the given extension. /// /// The extension to check /// True if this name has the given extension, false otherwise public bool HasExtension(string Extension) { if (Extension.Length > 0 && Extension[0] != '.') { return HasExtension("." + Extension); } else { return CanonicalName.EndsWith(Extension.ToLowerInvariant()); } } /// /// Determines if the given object is at or under the given directory /// /// /// public bool IsUnderDirectory(DirectoryReference Other) { return CanonicalName.StartsWith(Other.CanonicalName) && (CanonicalName.Length == Other.CanonicalName.Length || CanonicalName[Other.CanonicalName.Length] == Path.DirectorySeparatorChar); } /// /// Creates a relative path from the given base directory /// /// The directory to create a relative path from /// A relative path from the given directory public string MakeRelativeTo(DirectoryReference Directory) { // Find how much of the path is common between the two paths. This length does not include a trailing directory separator character. int CommonDirectoryLength = -1; for (int Idx = 0; ; Idx++) { if (Idx == CanonicalName.Length) { // The two paths are identical. Just return the "." character. if (Idx == Directory.CanonicalName.Length) { return "."; } // Check if we're finishing on a complete directory name if (Directory.CanonicalName[Idx] == Path.DirectorySeparatorChar) { CommonDirectoryLength = Idx; } break; } else if (Idx == Directory.CanonicalName.Length) { // Check whether the end of the directory name coincides with a boundary for the current name. if (CanonicalName[Idx] == Path.DirectorySeparatorChar) { CommonDirectoryLength = Idx; } break; } else { // Check the two paths match, and bail if they don't. Increase the common directory length if we've reached a separator. if (CanonicalName[Idx] != Directory.CanonicalName[Idx]) { break; } if (CanonicalName[Idx] == Path.DirectorySeparatorChar) { CommonDirectoryLength = Idx; } } } // If there's no relative path, just return the absolute path if (CommonDirectoryLength == -1) { return FullName; } // Append all the '..' separators to get back to the common directory, then the rest of the string to reach the target item StringBuilder Result = new StringBuilder(); for (int Idx = CommonDirectoryLength + 1; Idx < Directory.CanonicalName.Length; Idx++) { // Move up a directory Result.Append(".."); Result.Append(Path.DirectorySeparatorChar); // Scan to the next directory separator while (Idx < Directory.CanonicalName.Length && Directory.CanonicalName[Idx] != Path.DirectorySeparatorChar) { Idx++; } } if (CommonDirectoryLength + 1 < FullName.Length) { Result.Append(FullName, CommonDirectoryLength + 1, FullName.Length - CommonDirectoryLength - 1); } return Result.ToString(); } /// /// Returns a string representation of this filesystem object /// /// Full path to the object public override string ToString() { return FullName; } } /// /// Representation of an absolute directory path. Allows fast hashing and comparisons. /// [Serializable] public class DirectoryReference : FileSystemReference, IEquatable { /// /// Default constructor. /// /// Path to this directory. public DirectoryReference(string InPath) : base(InPath) { } /// /// Construct a DirectoryReference from a DirectoryInfo object. /// /// Path to this file public DirectoryReference(DirectoryInfo InInfo) : base(InInfo) { } /// /// Constructor for creating a directory object directly from two strings. /// /// /// protected DirectoryReference(string InFullName, string InCanonicalName) : base(InFullName, InCanonicalName) { } /// /// Gets the top level directory name /// /// The name of the directory public string GetDirectoryName() { return Path.GetFileName(FullName); } /// /// Gets the directory containing this object /// /// A new directory object representing the directory containing this object public DirectoryReference ParentDirectory { get { if (IsRootDirectory()) { return null; } int ParentLength = CanonicalName.LastIndexOf(Path.DirectorySeparatorChar); if (ParentLength == 2 && CanonicalName[1] == ':') { ParentLength++; } return new DirectoryReference(FullName.Substring(0, ParentLength), CanonicalName.Substring(0, ParentLength)); } } /// /// Gets the parent directory for a file /// /// The file to get directory for /// The full directory name containing the given file public static DirectoryReference GetParentDirectory(FileReference File) { int ParentLength = File.CanonicalName.LastIndexOf(Path.DirectorySeparatorChar); return new DirectoryReference(File.FullName.Substring(0, ParentLength), File.CanonicalName.Substring(0, ParentLength)); } /// /// Creates the directory /// public void CreateDirectory() { Directory.CreateDirectory(FullName); } /// /// Checks whether the directory exists /// /// True if this directory exists public bool Exists() { return Directory.Exists(FullName); } /// /// Enumerate files from a given directory /// /// Sequence of file references public IEnumerable EnumerateFileReferences() { foreach (string FileName in Directory.EnumerateFiles(FullName)) { yield return FileReference.MakeFromNormalizedFullPath(FileName); } } /// /// Enumerate files from a given directory /// /// Sequence of file references public IEnumerable EnumerateFileReferences(string Pattern) { foreach (string FileName in Directory.EnumerateFiles(FullName, Pattern)) { yield return FileReference.MakeFromNormalizedFullPath(FileName); } } /// /// Enumerate files from a given directory /// /// Sequence of file references public IEnumerable EnumerateFileReferences(string Pattern, SearchOption Option) { foreach (string FileName in Directory.EnumerateFiles(FullName, Pattern, Option)) { yield return FileReference.MakeFromNormalizedFullPath(FileName); } } /// /// Enumerate subdirectories in a given directory /// /// Sequence of directory references public IEnumerable EnumerateDirectoryReferences() { foreach (string DirectoryName in Directory.EnumerateDirectories(FullName)) { yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName); } } /// /// Enumerate subdirectories in a given directory /// /// Sequence of directory references public IEnumerable EnumerateDirectoryReferences(string Pattern) { foreach (string DirectoryName in Directory.EnumerateDirectories(FullName, Pattern)) { yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName); } } /// /// Enumerate subdirectories in a given directory /// /// Sequence of directory references public IEnumerable EnumerateDirectoryReferences(string Pattern, SearchOption Option) { foreach (string DirectoryName in Directory.EnumerateDirectories(FullName, Pattern, Option)) { yield return DirectoryReference.MakeFromNormalizedFullPath(DirectoryName); } } /// /// Determines whether this path represents a root directory in the filesystem /// /// True if this path is a root directory, false otherwise public bool IsRootDirectory() { return CanonicalName[CanonicalName.Length - 1] == Path.DirectorySeparatorChar; } /// /// Combine several fragments with a base directory, to form a new directory name /// /// The base directory /// Fragments to combine with the base directory /// The new directory name public static DirectoryReference Combine(DirectoryReference BaseDirectory, params string[] Fragments) { string FullName = FileSystemReference.CombineStrings(BaseDirectory, Fragments); return new DirectoryReference(FullName, FullName.ToLowerInvariant()); } /// /// Compares two filesystem object names for equality. Uses the canonical name representation, not the display name representation. /// /// First object to compare. /// Second object to compare. /// True if the names represent the same object, false otherwise public static bool operator ==(DirectoryReference A, DirectoryReference B) { if ((object)A == null) { return (object)B == null; } else { return (object)B != null && A.CanonicalName == B.CanonicalName; } } /// /// Compares two filesystem object names for inequality. Uses the canonical name representation, not the display name representation. /// /// First object to compare. /// Second object to compare. /// False if the names represent the same object, true otherwise public static bool operator !=(DirectoryReference A, DirectoryReference B) { return !(A == B); } /// /// Compares against another object for equality. /// /// other instance to compare. /// True if the names represent the same object, false otherwise public override bool Equals(object Obj) { return (Obj is DirectoryReference) && ((DirectoryReference)Obj) == this; } /// /// Compares against another object for equality. /// /// other instance to compare. /// True if the names represent the same object, false otherwise public bool Equals(DirectoryReference Obj) { return Obj == this; } /// /// Returns a hash code for this object /// /// public override int GetHashCode() { return CanonicalName.GetHashCode(); } /// /// Helper function to create a remote directory reference. Unlike normal DirectoryReference objects, these aren't converted to a full path in the local filesystem. /// /// The absolute path in the remote file system /// New directory reference public static DirectoryReference MakeRemote(string AbsolutePath) { return new DirectoryReference(AbsolutePath, AbsolutePath.ToLowerInvariant()); } /// /// Helper function to create a directory reference from a raw platform path. The path provided *MUST* be exactly the same as that returned by Path.GetFullPath(). /// /// The absolute path in the file system /// New file reference public static DirectoryReference MakeFromNormalizedFullPath(string AbsolutePath) { return new DirectoryReference(AbsolutePath, AbsolutePath.ToLowerInvariant()); } /// /// Gets the parent directory for a file, or returns null if it's null. /// /// The file to create a directory reference for /// The directory containing the file public static DirectoryReference FromFile(FileReference File) { return (File == null)? null : File.Directory; } } /// /// Representation of an absolute file path. Allows fast hashing and comparisons. /// [Serializable] public class FileReference : FileSystemReference, IEquatable { /// /// Default constructor. /// /// Path to this file public FileReference(string InPath) : base(InPath) { } /// /// Construct a FileReference from a FileInfo object. /// /// Path to this file public FileReference(FileInfo InInfo) : base(InInfo) { } /// /// Default constructor. /// /// Path to this file protected FileReference(string InFullName, string InCanonicalName) : base(InFullName, InCanonicalName) { } /// /// Gets the file name without path information /// /// A string containing the file name public string GetFileName() { return Path.GetFileName(FullName); } /// /// Gets the file name without path information or an extension /// /// A string containing the file name without an extension public string GetFileNameWithoutExtension() { return Path.GetFileNameWithoutExtension(FullName); } /// /// Gets the file name without path or any extensions /// /// A string containing the file name without an extension public string GetFileNameWithoutAnyExtensions() { int StartIdx = FullName.LastIndexOf(Path.DirectorySeparatorChar) + 1; int EndIdx = FullName.IndexOf('.', StartIdx); if (EndIdx < StartIdx) { return FullName.Substring(StartIdx); } else { return FullName.Substring(StartIdx, EndIdx - StartIdx); } } /// /// Gets the extension for this filename /// /// A string containing the extension of this filename public string GetExtension() { return Path.GetExtension(FullName); } /// /// Change the file's extension to something else /// /// The new extension /// A FileReference with the same path and name, but with the new extension public FileReference ChangeExtension(string Extension) { string NewFullName = Path.ChangeExtension(FullName, Extension); return new FileReference(NewFullName, NewFullName.ToLowerInvariant()); } /// /// Gets the directory containing this file /// /// A new directory object representing the directory containing this object public DirectoryReference Directory { get { return DirectoryReference.GetParentDirectory(this); } } /// /// Determines whether the given filename exists /// /// True if it exists, false otherwise public bool Exists() { return File.Exists(FullName); } /// /// Deletes this file /// public void Delete() { File.Delete(FullName); } /// /// Combine several fragments with a base directory, to form a new filename /// /// The base directory /// Fragments to combine with the base directory /// The new file name public static FileReference Combine(DirectoryReference BaseDirectory, params string[] Fragments) { string FullName = FileSystemReference.CombineStrings(BaseDirectory, Fragments); return new FileReference(FullName, FullName.ToLowerInvariant()); } /// /// Append a string to the end of a filename /// /// The base file reference /// Suffix to be appended /// The new file reference public static FileReference operator +(FileReference A, string B) { return new FileReference(A.FullName + B, A.CanonicalName + B.ToLowerInvariant()); } /// /// Compares two filesystem object names for equality. Uses the canonical name representation, not the display name representation. /// /// First object to compare. /// Second object to compare. /// True if the names represent the same object, false otherwise public static bool operator ==(FileReference A, FileReference B) { if ((object)A == null) { return (object)B == null; } else { return (object)B != null && A.CanonicalName == B.CanonicalName; } } /// /// Compares two filesystem object names for inequality. Uses the canonical name representation, not the display name representation. /// /// First object to compare. /// Second object to compare. /// False if the names represent the same object, true otherwise public static bool operator !=(FileReference A, FileReference B) { return !(A == B); } /// /// Compares against another object for equality. /// /// other instance to compare. /// True if the names represent the same object, false otherwise public override bool Equals(object Obj) { return (Obj is FileReference) && ((FileReference)Obj) == this; } /// /// Compares against another object for equality. /// /// other instance to compare. /// True if the names represent the same object, false otherwise public bool Equals(FileReference Obj) { return Obj == this; } /// /// Returns a hash code for this object /// /// public override int GetHashCode() { return CanonicalName.GetHashCode(); } /// /// Helper function to create a remote file reference. Unlike normal FileReference objects, these aren't converted to a full path in the local filesystem, but are /// left as they are passed in. /// /// The absolute path in the remote file system /// New file reference public static FileReference MakeRemote(string AbsolutePath) { return new FileReference(AbsolutePath, AbsolutePath.ToLowerInvariant()); } /// /// Helper function to create a file reference from a raw platform path. The path provided *MUST* be exactly the same as that returned by Path.GetFullPath(). /// /// The absolute path in the file system /// New file reference public static FileReference MakeFromNormalizedFullPath(string AbsolutePath) { return new FileReference(AbsolutePath, AbsolutePath.ToLowerInvariant()); } } static class FileReferenceExtensionMethods { /// /// Manually serialize a file reference to a binary stream. /// /// Binary writer to write to public static void Write(this BinaryWriter Writer, FileReference File) { Writer.Write((File == null) ? String.Empty : File.FullName); } /// /// Serializes a file reference, using a lookup table to avoid serializing the same name more than once. /// /// The writer to save this reference to /// A file reference to output; may be null /// A lookup table that caches previous files that have been output, and maps them to unique id's. public static void Write(this BinaryWriter Writer, FileReference File, Dictionary FileToUniqueId) { int UniqueId; if (File == null) { Writer.Write(-1); } else if (FileToUniqueId.TryGetValue(File, out UniqueId)) { Writer.Write(UniqueId); } else { Writer.Write(FileToUniqueId.Count); Writer.Write(File); FileToUniqueId.Add(File, FileToUniqueId.Count); } } /// /// Manually deserialize a file reference from a binary stream. /// /// Binary reader to read from /// New FileReference object public static FileReference ReadFileReference(this BinaryReader Reader) { string FullName = Reader.ReadString(); return (FullName.Length == 0) ? null : FileReference.MakeFromNormalizedFullPath(FullName); } /// /// Deserializes a file reference, using a lookup table to avoid writing the same name more than once. /// /// The source to read from /// List of previously read file references. The index into this array is used in place of subsequent ocurrences of the file. /// The file reference that was read public static FileReference ReadFileReference(this BinaryReader Reader, List UniqueFiles) { int UniqueId = Reader.ReadInt32(); if (UniqueId == -1) { return null; } else if (UniqueId < UniqueFiles.Count) { return UniqueFiles[UniqueId]; } else { FileReference Result = Reader.ReadFileReference(); UniqueFiles.Add(Result); return Result; } } } }