// 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; }
}
}