// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using EpicGames.Core;
using Microsoft.Extensions.Logging;
using UnrealBuildBase;
namespace UnrealBuildTool
{
///
/// Holds information about the current engine version
///
[Serializable]
public class BuildVersion
{
///
/// The major engine version (5 for UE5)
///
public int MajorVersion;
///
/// The minor engine version
///
public int MinorVersion;
///
/// The hotfix/patch version
///
public int PatchVersion;
///
/// The changelist that the engine is being built from
///
public int Changelist;
///
/// The changelist that the engine maintains compatibility with
///
public int CompatibleChangelist;
///
/// Whether the changelist numbers are a licensee changelist
///
public bool IsLicenseeVersion;
///
/// Whether the current build is a promoted build, that is, built strictly from a clean sync of the given changelist
///
public bool IsPromotedBuild;
///
/// Name of the current branch, with '/' characters escaped as '+'
///
public string? BranchName;
///
/// The current build id. This will be generated automatically whenever engine binaries change if not set in the default Engine/Build/Build.version.
///
public string? BuildId;
///
/// The build version string
///
public string? BuildVersionString;
///
/// Optional URL for a continuous integration job associated with this build version. (e.g. the job that build a set of binaries)
///
public string? BuildURL;
///
/// Returns the value which can be used as the compatible changelist. Requires that the regular changelist is also set, and defaults to the
/// regular changelist if a specific compatible changelist is not set.
///
public int EffectiveCompatibleChangelist => (Changelist != 0 && CompatibleChangelist != 0) ? CompatibleChangelist : Changelist;
///
/// Try to read a version file from disk
///
/// Path to the version file
/// The version information
/// True if the version was read successfully, false otherwise
public static bool TryRead(FileReference FileName, [NotNullWhen(true)] out BuildVersion? Version)
{
JsonObject? Object;
if (!JsonObject.TryRead(FileName, out Object))
{
Version = null;
return false;
}
return TryParse(Object, out Version);
}
///
/// Get the default path to the build.version file on disk
///
/// Path to the Build.version file
public static FileReference GetDefaultFileName()
{
return FileReference.Combine(Unreal.EngineDirectory, "Build", "Build.version");
}
///
/// Get the default path for a target's version file.
///
/// The output directory for the executable. For MacOS, this is the directory containing the app bundle.
/// Name of the target being built
/// Platform the target is being built for
/// The configuration being built
/// Architecture of the target being built
/// Path to the target's version file
public static FileReference GetFileNameForTarget(DirectoryReference OutputDirectory, string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, UnrealArchitectures Architectures)
{
// Get the architecture suffix. Platforms have the option of overriding whether to include this string in filenames.
string ArchitectureSuffix = "";
if (UnrealArchitectureConfig.ForPlatform(Platform).RequiresArchitectureFilenames(Architectures))
{
ArchitectureSuffix = Architectures.ToString();
}
// Build the output filename
if (String.IsNullOrEmpty(ArchitectureSuffix) && Configuration == UnrealTargetConfiguration.Development)
{
return FileReference.Combine(OutputDirectory, String.Format("{0}.version", TargetName));
}
else
{
return FileReference.Combine(OutputDirectory, String.Format("{0}-{1}-{2}{3}.version", TargetName, Platform.ToString(), Configuration.ToString(), ArchitectureSuffix));
}
}
///
/// Parses a build version from a JsonObject
///
/// The object to read from
/// The resulting version field
/// True if the build version could be read, false otherwise
public static bool TryParse(JsonObject Object, [NotNullWhen(true)] out BuildVersion? Version)
{
BuildVersion NewVersion = new BuildVersion();
if (!Object.TryGetIntegerField("MajorVersion", out NewVersion.MajorVersion) || !Object.TryGetIntegerField("MinorVersion", out NewVersion.MinorVersion) || !Object.TryGetIntegerField("PatchVersion", out NewVersion.PatchVersion))
{
Version = null;
return false;
}
Object.TryGetIntegerField("Changelist", out NewVersion.Changelist);
Object.TryGetIntegerField("CompatibleChangelist", out NewVersion.CompatibleChangelist);
int IsLicenseeVersionInt;
Object.TryGetIntegerField("IsLicenseeVersion", out IsLicenseeVersionInt);
NewVersion.IsLicenseeVersion = IsLicenseeVersionInt != 0;
int IsPromotedBuildInt;
Object.TryGetIntegerField("IsPromotedBuild", out IsPromotedBuildInt);
NewVersion.IsPromotedBuild = IsPromotedBuildInt != 0;
Object.TryGetStringField("BranchName", out NewVersion.BranchName);
Object.TryGetStringField("BuildId", out NewVersion.BuildId);
Object.TryGetStringField("BuildVersion", out NewVersion.BuildVersionString);
Object.TryGetStringField("BuildURL", out NewVersion.BuildURL);
Version = NewVersion;
return true;
}
///
/// Exports this object as Json
///
/// The filename to write to
public void Write(FileReference FileName)
{
using (StreamWriter Writer = new StreamWriter(FileName.FullName))
{
Write(Writer);
}
}
///
/// Exports this object as Json
///
/// The filename to write to
/// Logger for output
public void WriteIfModified(FileReference FileName, ILogger Logger)
{
using StringWriter Writer = new StringWriter();
Write(Writer);
Utils.WriteFileIfChanged(FileName, Writer.ToString(), Logger);
}
///
/// Exports this object as Json
///
/// Writer for output text
public void Write(TextWriter Writer)
{
using (JsonWriter OtherWriter = new JsonWriter(Writer))
{
OtherWriter.WriteObjectStart();
WriteProperties(OtherWriter);
OtherWriter.WriteObjectEnd();
}
}
///
/// Exports this object as Json
///
/// The json writer to receive the version settings
/// True if the build version could be read, false otherwise
public void WriteProperties(JsonWriter Writer)
{
Writer.WriteValue("MajorVersion", MajorVersion);
Writer.WriteValue("MinorVersion", MinorVersion);
Writer.WriteValue("PatchVersion", PatchVersion);
Writer.WriteValue("Changelist", Changelist);
Writer.WriteValue("CompatibleChangelist", CompatibleChangelist);
Writer.WriteValue("IsLicenseeVersion", IsLicenseeVersion ? 1 : 0);
Writer.WriteValue("IsPromotedBuild", IsPromotedBuild ? 1 : 0);
Writer.WriteValue("BranchName", BranchName);
if (!String.IsNullOrEmpty(BuildId))
{
Writer.WriteValue("BuildId", BuildId);
}
if (!String.IsNullOrEmpty(BuildVersionString))
{
Writer.WriteValue("BuildVersion", BuildVersionString);
}
if (!String.IsNullOrEmpty(BuildURL))
{
Writer.WriteValue("BuildURL", BuildURL);
}
}
}
///
/// Read-only wrapper for a BuildVersion instance
///
public class ReadOnlyBuildVersion
{
///
/// The inner build version
///
private BuildVersion Inner;
///
/// Cached copy of the current build version
///
private static ReadOnlyBuildVersion? CurrentCached;
///
/// Constructor
///
/// The writable build version instance
public ReadOnlyBuildVersion(BuildVersion Inner)
{
this.Inner = Inner;
}
///
/// Gets the current build version
///
public static ReadOnlyBuildVersion Current
{
get
{
if (CurrentCached == null)
{
FileReference File = BuildVersion.GetDefaultFileName();
if (!FileReference.Exists(File))
{
throw new BuildException("Version file is missing ({0})", File);
}
BuildVersion? Version;
if (!BuildVersion.TryRead(File, out Version))
{
throw new BuildException("Unable to read version file ({0}). Check that this file is present and well-formed JSON.", File);
}
CurrentCached = new ReadOnlyBuildVersion(Version);
}
return CurrentCached;
}
}
///
/// Accessors for fields on the inner BuildVersion instance
///
#region Read-only accessor properties
#pragma warning disable CS1591
public int MajorVersion => Inner.MajorVersion;
public int MinorVersion => Inner.MinorVersion;
public int PatchVersion => Inner.PatchVersion;
public int Changelist => Inner.Changelist;
public int CompatibleChangelist => Inner.CompatibleChangelist;
public int EffectiveCompatibleChangelist => Inner.EffectiveCompatibleChangelist;
public bool IsLicenseeVersion => Inner.IsLicenseeVersion;
public bool IsPromotedBuild => Inner.IsPromotedBuild;
public string? BranchName => Inner.BranchName;
public string? BuildVersionString => Inner.BuildVersionString;
public string? BuildURL => Inner.BuildURL;
#pragma warning restore C1591
#endregion
}
}