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