// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using EpicGames.Core;
namespace UnrealBuildTool
{
///
/// The version format for .uproject files. This rarely changes now; project descriptors should maintain backwards compatibility automatically.
///
enum ProjectDescriptorVersion
{
///
/// Invalid
///
Invalid = 0,
///
/// Initial version
///
Initial = 1,
///
/// Adding SampleNameHash
///
NameHash = 2,
///
/// Unifying plugin/project files (since abandoned, but backwards compatibility maintained)
///
ProjectPluginUnification = 3,
///
/// This needs to be the last line, so we can calculate the value of Latest below
///
LatestPlusOne,
///
/// The latest plugin descriptor version
///
Latest = LatestPlusOne - 1
}
///
/// In-memory representation of a .uproject file
///
public class ProjectDescriptor
{
///
/// Descriptor version number.
///
public int FileVersion;
///
/// The engine to open this project with.
///
public string? EngineAssociation;
///
/// Category to show under the project browser
///
public string? Category;
///
/// Description to show in the project browser
///
public string? Description;
///
/// List of all modules associated with this project
///
public ModuleDescriptor[]? Modules;
///
/// List of plugins for this project (may be enabled/disabled)
///
public PluginReferenceDescriptor[]? Plugins;
///
/// Array of additional root directories
///
public List AdditionalRootDirectories = new List();
///
/// List of additional plugin directories to scan for available plugins
///
public List AdditionalPluginDirectories = new List();
///
/// Array of platforms that this project is targeting
///
public string[]? TargetPlatforms;
///
/// A hash that is used to determine if the project was forked from a sample
///
public uint EpicSampleNameHash;
///
/// Steps to execute before creating rules assemblies in this project
///
public CustomBuildSteps? InitSteps;
///
/// Steps to execute before building targets in this project
///
public CustomBuildSteps? PreBuildSteps;
///
/// Steps to execute before building targets in this project
///
public CustomBuildSteps? PostBuildSteps;
///
/// Indicates if this project is an Enterprise project
///
public bool IsEnterpriseProject;
///
/// Indicates that enabled by default engine plugins should not be enabled unless explicitly enabled by the project or target files.
///
public bool DisableEnginePluginsByDefault;
///
/// Constructor.
///
public ProjectDescriptor()
{
FileVersion = (int)ProjectDescriptorVersion.Latest;
IsEnterpriseProject = false;
DisableEnginePluginsByDefault = false;
}
///
/// Constructor
///
/// Raw JSON object to parse
/// Base directory for resolving relative paths
///
public ProjectDescriptor(JsonObject RawObject, DirectoryReference BaseDir, FileReference JsonFilePath)
{
// Read the version
if (!RawObject.TryGetIntegerField("FileVersion", out FileVersion))
{
if (!RawObject.TryGetIntegerField("ProjectFileVersion", out FileVersion))
{
throw new BuildException("Project does not contain a valid FileVersion entry");
}
}
// Check it's not newer than the latest version we can parse
if (FileVersion > (int)PluginDescriptorVersion.Latest)
{
throw new BuildException("Project descriptor appears to be in a newer version ({0}) of the file format that we can load (max version: {1}).", FileVersion, (int)ProjectDescriptorVersion.Latest);
}
// Read simple fields
RawObject.TryGetStringField("EngineAssociation", out EngineAssociation);
RawObject.TryGetStringField("Category", out Category);
RawObject.TryGetStringField("Description", out Description);
RawObject.TryGetBoolField("Enterprise", out IsEnterpriseProject);
RawObject.TryGetBoolField("DisableEnginePluginsByDefault", out DisableEnginePluginsByDefault);
// Read the modules
JsonObject[]? ModulesArray;
if (RawObject.TryGetObjectArrayField("Modules", out ModulesArray))
{
Modules = Array.ConvertAll(ModulesArray, x => ModuleDescriptor.FromJsonObject(x, JsonFilePath));
}
// Read the plugins
JsonObject[]? PluginsArray;
if (RawObject.TryGetObjectArrayField("Plugins", out PluginsArray))
{
Plugins = Array.ConvertAll(PluginsArray, x => PluginReferenceDescriptor.FromJsonObject(x));
}
// Read the additional root directories
string[]? RootDirectoryStrings;
if (RawObject.TryGetStringArrayField("AdditionalRootDirectories", out RootDirectoryStrings))
{
AdditionalRootDirectories.AddRange(RootDirectoryStrings.Select(x => DirectoryReference.Combine(BaseDir, x)));
}
// Read the additional plugin directories
string[]? PluginDirectoryStrings;
if (RawObject.TryGetStringArrayField("AdditionalPluginDirectories", out PluginDirectoryStrings))
{
AdditionalPluginDirectories.AddRange(PluginDirectoryStrings.Select(x => DirectoryReference.Combine(BaseDir, x)));
}
// Read the target platforms
RawObject.TryGetStringArrayField("TargetPlatforms", out TargetPlatforms);
// Get the sample name hash
RawObject.TryGetUnsignedIntegerField("EpicSampleNameHash", out EpicSampleNameHash);
// Read the init, pre and post-build steps
CustomBuildSteps.TryRead(RawObject, "InitSteps", out InitSteps);
CustomBuildSteps.TryRead(RawObject, "PreBuildSteps", out PreBuildSteps);
CustomBuildSteps.TryRead(RawObject, "PostBuildSteps", out PostBuildSteps);
}
///
/// Creates a plugin descriptor from a file on disk
///
/// The filename to read
/// New plugin descriptor
public static ProjectDescriptor FromFile(FileReference FileName)
{
try
{
JsonObject RawObject = JsonObject.Read(FileName);
ProjectDescriptor Descriptor = new ProjectDescriptor(RawObject, FileName.Directory, FileName);
if (Descriptor.Modules != null)
{
foreach (ModuleDescriptor Module in Descriptor.Modules)
{
Module.Validate(FileName);
}
}
return Descriptor;
}
catch (JsonException ex)
{
throw new JsonException($"{ex.Message} (in {FileName})", FileName.FullName, ex.LineNumber, ex.BytePositionInLine, ex);
}
}
///
/// Creates a plugin descriptor based on a directory, searching for best uproject
///
/// The directory to search for a uproject
/// New plugin descriptor
public static ProjectDescriptor FromDirectory(DirectoryReference DirectoryName)
{
FileReference ProjectFile = FileReference.Combine(DirectoryName, DirectoryName.GetDirectoryName() + ".uproject");
if (FileReference.Exists(ProjectFile))
{
return ProjectDescriptor.FromFile(ProjectFile);
}
// find any uproject file, in case the name doesn't match
IEnumerable FoundProjects = DirectoryReference.EnumerateFiles(DirectoryName, "*.uproject", SearchOption.TopDirectoryOnly);
if (FoundProjects.Count() == 0)
{
throw new FileNotFoundException($"Unable to find a .uproject file in {DirectoryName}");
}
if (FoundProjects.Count() > 1)
{
Log.TraceWarningOnce("Found multiple uproject files in {0}, choosing {1}", DirectoryName, FoundProjects.First().GetFileName());
}
return ProjectDescriptor.FromFile(FoundProjects.First());
}
///
/// If the descriptor has either additional plugin directories or additional root directories
/// then it is considered to have additional paths. The additional paths will be relative
/// to the provided directory.
///
/// The directory set to add the paths too.
/// The directory which is used to setup the additional paths
public void AddAdditionalPaths(List RootDirectories, DirectoryReference ProjectDir)
{
RootDirectories.AddRange(AdditionalRootDirectories);
RootDirectories.AddRange(AdditionalPluginDirectories);
}
///
/// Saves the descriptor to disk
///
/// The filename to write to
public void Save(FileReference FileName)
{
using (JsonWriter Writer = new JsonWriter(FileName))
{
Writer.WriteObjectStart();
Write(Writer, FileName.Directory);
Writer.WriteObjectEnd();
}
}
///
/// Writes the plugin descriptor to an existing Json writer
///
/// The writer to receive plugin data
/// Base directory to save paths relative to
public void Write(JsonWriter Writer, DirectoryReference BaseDir)
{
Writer.WriteValue("FileVersion", (int)ProjectDescriptorVersion.Latest);
Writer.WriteValue("EngineAssociation", EngineAssociation);
Writer.WriteValue("Category", Category);
Writer.WriteValue("Description", Description);
if (DisableEnginePluginsByDefault)
{
Writer.WriteValue("DisableEnginePluginsByDefault", DisableEnginePluginsByDefault);
}
// Write the enterprise flag
if (IsEnterpriseProject)
{
Writer.WriteValue("Enterprise", IsEnterpriseProject);
}
// Write the module list
ModuleDescriptor.WriteArray(Writer, "Modules", Modules);
// Write the plugin list
PluginReferenceDescriptor.WriteArray(Writer, "Plugins", Plugins);
// Write the custom module roots
if (AdditionalRootDirectories.Count > 0)
{
Writer.WriteStringArrayField("AdditionalRootDirectories", AdditionalRootDirectories.Select(x => x.MakeRelativeTo(BaseDir).Replace(Path.DirectorySeparatorChar, '/')));
}
// Write out the additional plugin directories to scan
if (AdditionalPluginDirectories.Count > 0)
{
Writer.WriteStringArrayField("AdditionalPluginDirectories", AdditionalPluginDirectories.Select(x => x.MakeRelativeTo(BaseDir).Replace(Path.DirectorySeparatorChar, '/')));
}
// Write the target platforms
if (TargetPlatforms != null && TargetPlatforms.Length > 0)
{
Writer.WriteStringArrayField("TargetPlatforms", TargetPlatforms);
}
// If it's a signed sample, write the name hash
if (EpicSampleNameHash != 0)
{
Writer.WriteValue("EpicSampleNameHash", (uint)EpicSampleNameHash);
}
// Write the custom build steps
InitSteps?.Write(Writer, "InitSteps");
PreBuildSteps?.Write(Writer, "PreBuildSteps");
PostBuildSteps?.Write(Writer, "PostBuildSteps");
}
}
}