// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using UnrealBuildBase; namespace UnrealBuildTool { /// /// Named cpp root folders /// public enum CppRootPathFolder { /// /// Project folder. Can be both inside and outside Root so this must be first /// Project, /// /// UE Root folder /// Root, /// /// Compiler folder /// Compiler, /// /// Toolchain folder (if needed) /// Toolchain, /// /// WindowsSDK folder (if needed) /// WinSDK, /// /// Additional platform SDK folder (if needed) /// PlatformSDK, /// /// Number of entries in enum /// Count, } /// /// Encapsulates the roots used for cache and vfs /// class CppRootPaths : IEnumerable<(uint id, DirectoryReference vfs, DirectoryReference local)> { private static readonly DirectoryReference _vfsRootPath = new(OperatingSystem.IsWindows() ? "Z:/UEVFS" : "/UEVFS"); /// /// Default constructor /// public CppRootPaths() { _lookup = []; _extras = []; _folderNames = []; } /// /// Archive reader constructor /// public CppRootPaths(BinaryArchiveReader Reader) { { int count = Reader.ReadInt(); _folderNames = []; for (int idx = 0; idx < count; idx++) { CppRootPathFolder folder = (CppRootPathFolder)Reader.ReadInt(); string name = Reader.ReadString()!; _folderNames[folder] = name; } } { int count = Reader.ReadInt(); _lookup = []; for (int idx = 0; idx < count; idx++) { CppRootPathFolder folder = (CppRootPathFolder)Reader.ReadInt(); DirectoryReference local = Reader.ReadCompactDirectoryReference(); _lookup.Add(folder, (AsVfsPath(folder), local)); } } { int count = Reader.ReadInt(); _extras = []; for (int idx = 0; idx < count; idx++) { string id = Reader.ReadString()!; DirectoryReference vfs = DirectoryReference.Combine(_vfsRootPath, id); DirectoryReference local = Reader.ReadCompactDirectoryReference(); _extras.Add(id, (vfs, local)); } } bUseVfs = Reader.ReadBool(); } /// /// Copy constructor /// public CppRootPaths(CppRootPaths Other) { _lookup = new(Other._lookup); _extras = new(Other._extras); _folderNames = new(Other._folderNames); bUseVfs = Other.bUseVfs; } /// /// Write root paths to archive /// public void Write(BinaryArchiveWriter Writer) { Writer.WriteDictionary(_folderNames, Key => Writer.WriteInt((int)Key), Value => Writer.WriteString(Value)); Writer.WriteDictionary(_lookup, Key => Writer.WriteInt((int)Key), Value => Writer.WriteCompactDirectoryReference(Value.local)); Writer.WriteDictionary(_extras, Key => Writer.WriteString(Key), Value => Writer.WriteCompactDirectoryReference(Value.local)); Writer.WriteBool(bUseVfs); } /// /// Set path for named roots /// public DirectoryReference this[CppRootPathFolder key] { set { if (key == CppRootPathFolder.Project) { string projectName = value.GetDirectoryName(); if (!projectName.StartsWith("Clang")) // Lol, there is a project called clang.. which collides with the compiler clang causing two roots to have the same path { AddFolderName(CppRootPathFolder.Project, value.GetDirectoryName()); } } _lookup[key] = (AsVfsPath(key), value); } } /// /// Enumerator for all roots /// public IEnumerator<(uint id, DirectoryReference vfs, DirectoryReference local)> GetEnumerator() { foreach (KeyValuePair kv in _lookup) { yield return ((uint)kv.Key, kv.Value.vfs, kv.Value.local); } uint index = (uint)CppRootPathFolder.Count; // Max of CppRootPathFolder foreach (KeyValuePair kv in _extras) { yield return (index++, kv.Value.vfs, kv.Value.local); } yield break; } /// /// Enumerator for all roots /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _lookup.GetEnumerator(); } /// /// Adds extra root path to the RootPaths table /// public void AddExtraPath((string id, string path)? extra) { if (extra == null) { return; } DirectoryReference? path = DirectoryReference.FromString(extra.Value.path); if (path == null) { return; } string id = extra.Value.id; if (_extras.TryGetValue(id, out (DirectoryReference vfs, DirectoryReference local) root)) { if (root.local != path) { throw new Exception($"Extra root path with id {id} already set. Current: {root.local.FullName} New: {path.FullName}"); } } else { DirectoryReference vfs = DirectoryReference.Combine(_vfsRootPath, id); _extras[id] = (vfs, path); } } /// /// Get a virtual filesystem overlay path if in use for a local path /// /// The path to convert /// Out param, the converted path /// True if the local path can be mapped to a virtual path when enabled, false otherwise public bool GetVfsOverlayPath(FileSystemReference reference, [NotNullWhen(true)] out string? vfsPath) { vfsPath = null; if (!bUseVfs) { return false; } foreach (KeyValuePair item in _lookup) { if (reference.IsUnderDirectory(item.Value.local)) { vfsPath = item.Value.vfs.FullName + reference.FullName.Substring(item.Value.local.FullName.Length); if (OperatingSystem.IsWindows()) { vfsPath = vfsPath.Replace('\\', '/'); } return true; } } foreach (KeyValuePair item in _extras) { if (reference.IsUnderDirectory(item.Value.local)) { vfsPath = item.Value.vfs.FullName + reference.FullName.Substring(item.Value.local.FullName.Length); if (OperatingSystem.IsWindows()) { vfsPath = vfsPath.Replace('\\', '/'); } return true; } } return false; } /// /// Always return a local file reference (convert from vfs if needed) /// public FileReference GetLocalPath(FileReference reference) { if (!bUseVfs) { return reference; } foreach (KeyValuePair item in _lookup) { if (reference.IsUnderDirectory(item.Value.vfs)) { return FileReference.FromString(item.Value.local.FullName + reference.FullName.Substring(item.Value.vfs.FullName.Length)); } } foreach (KeyValuePair item in _extras) { if (reference.IsUnderDirectory(item.Value.vfs)) { return FileReference.FromString(item.Value.local.FullName + reference.FullName.Substring(item.Value.vfs.FullName.Length)); } } return reference; } /// /// Populate from json objects /// public void Read(JsonObject Object) { if (Object.TryGetObjectArrayField("Lookup", out JsonObject[]? lookupItems)) { foreach (JsonObject item in lookupItems) { DirectoryReference local = new(item.GetStringField("Path")); if (item.TryGetEnumField("Root", out CppRootPathFolder folder)) { _lookup.Add(folder, (AsVfsPath(folder), local)); if (item.TryGetStringField("Root", out string? folderName)) { _folderNames.Add(folder, folderName); } } } } if (Object.TryGetObjectArrayField("Extras", out JsonObject[]? extraItems)) { foreach (JsonObject item in extraItems) { string id = item.GetStringField("Id"); DirectoryReference local = new(item.GetStringField("Path")); _extras.Add(id, (AsVfsPath(id), local)); } } if (Object.TryGetBoolField("bUseVfs", out bool useVfs)) { bUseVfs = useVfs; } } /// /// Writing json objects to json writer /// public void Write(JsonWriter writer) { if (_lookup.Any()) { writer.WriteArrayStart("Lookup"); foreach (KeyValuePair item in _lookup) { writer.WriteObjectStart(); writer.WriteEnumValue("Root", item.Key); writer.WriteValue("Path", item.Value.local.FullName); if (!_folderNames.TryGetValue(item.Key, out string? folderName)) { writer.WriteValue("Name", folderName); } writer.WriteObjectEnd(); } writer.WriteArrayEnd(); } if (_extras.Any()) { writer.WriteArrayStart("Extras"); foreach (KeyValuePair item in _extras) { writer.WriteObjectStart(); writer.WriteValue("Id", item.Key); writer.WriteValue("Path", item.Value.local.FullName); writer.WriteObjectEnd(); } writer.WriteArrayEnd(); } if (_folderNames.Any()) { writer.WriteArrayStart("Names"); foreach (KeyValuePair item in _folderNames) { writer.WriteObjectStart(); writer.WriteEnumValue("Root", item.Key); writer.WriteValue("Name", item.Value); writer.WriteObjectEnd(); } writer.WriteArrayEnd(); } writer.WriteValue("bUseVfs", bUseVfs); } /// /// Add an optional folder name to use in VFS paths rather than the enum name /// /// The folder enum to update /// The new string name public void AddFolderName(CppRootPathFolder folder, string name) { _folderNames[folder] = name; } private DirectoryReference AsVfsPath(CppRootPathFolder folder) { string? folderName; if (!_folderNames.TryGetValue(folder, out folderName)) { folderName = folder.ToString(); } return DirectoryReference.Combine(_vfsRootPath, folderName); } private static DirectoryReference AsVfsPath(string id) => DirectoryReference.Combine(_vfsRootPath, id); private Dictionary _folderNames { get; } private SortedDictionary _lookup { get; } private SortedDictionary _extras { get; } /// /// Set to true to make sure all created paths are using virtual path /// public bool bUseVfs { get; set; } } }