// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
using AutomationTool;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using UnrealBuildTool;
namespace AutomationScripts.Automation
{
[Help("List labels valid for this branch.")]
[Help("type", "Type of labels to list. Could be one of: all, promotable and promoted. Default: all.")]
[Help("ticks", "Print ticks along with the label name.")]
[Help("game=GameName", "Name of the game to filter labels. If not set then assuming shared promotable.")]
[RequireP4]
[DoesNotNeedP4CL]
class UnrealSyncList : BuildCommand
{
enum QueryType
{
All,
Promotable,
Promoted
}
public override void ExecuteBuild()
{
var GameName = ParseParamValue("game");
var QueryType = GetTypeParam();
var BranchPath = CommandUtils.GetDirectoryName(P4Env.BuildRootP4);
var Ticks = ParseParam("ticks");
var BranchAndGameName = string.IsNullOrWhiteSpace(GameName)
? string.Format("branch {0} shared-promotable", BranchPath)
: string.Format("branch {0} and game {1}", BranchPath, GameName);
if (string.IsNullOrWhiteSpace(BranchPath))
{
throw new AutomationException("The branch path is not set. Something went wrong.");
}
if (QueryType == QueryType.Promoted)
{
Log("Promoted labels for {0}.", BranchAndGameName);
Print(GetPromotedLabels(BranchPath, GameName), Ticks);
}
else if(QueryType == QueryType.Promotable)
{
Log("Promotable labels for {0}.", BranchAndGameName);
Print(GetPromotableLabels(BranchPath, GameName), Ticks);
}
else
{
Log("All labels for {0}.", BranchPath);
Print(GetBranchLabels(BranchPath), Ticks);
}
}
///
/// Gets promoted label list for given branch.
///
/// A branch path.
/// The game name for which provide the label. If null or empty then provides shared-promotable label.
/// List of promoted labels for given branch path.
public static P4Label[] GetPromotedLabels(string BranchPath, string GameName)
{
return P4.GetLabels(BranchPath + "/Promoted" + (GameName != null ? ("-" + GameName) : "") + "-CL-*");
}
///
/// Gets promoted label list for given branch.
///
/// A branch path.
/// The game name for which provide the label. If null or empty then provides shared-promotable label.
/// List of promoted labels for given branch path.
public static P4Label[] GetPromotableLabels(string BranchPath, string GameName)
{
var Combined = P4.GetLabels(BranchPath + "/Promot*" + (GameName != null ? ("-" + GameName) : "") + "-CL-*");
Combined.OrderByDescending((Label) => Label.Date);
var Output = new List();
foreach(var PossiblePromotable in Combined)
{
if(PossiblePromotable.Name.StartsWith(BranchPath + "/Promoted"))
{
break;
}
if (PossiblePromotable.Name.StartsWith(BranchPath + "/Promotable"))
{
Output.Add(PossiblePromotable);
}
// else skip
}
return Output.ToArray();
}
///
/// Prints the labels.
///
/// Labels to print.
/// Print ticks along with label name?
public void Print(P4Label[] Labels, bool Ticks)
{
foreach(var Label in Labels.OrderByDescending((Label) => Label.Date))
{
Log(Label.Name + (Ticks ? (" " + Label.Date.Ticks.ToString()) : ""));
}
}
///
/// Gets labels list for given branch.
///
/// A branch path.
/// List of labels for given branch path.
public P4Label[] GetBranchLabels(string BranchPath)
{
return P4.GetLabels(BranchPath + "/*");
}
///
/// Parses type param.
///
/// Enum value of the query type param.
private QueryType GetTypeParam()
{
var TypeString = ParseParamValue("type");
if(string.IsNullOrWhiteSpace(TypeString))
{
return QueryType.All;
}
switch (TypeString.ToLower())
{
case "all":
return QueryType.All;
case "promotable":
return QueryType.Promotable;
case "promoted":
return QueryType.Promoted;
default:
throw new AutomationException("Unsupported query type. Allowed are: all, promotable and promoted.");
}
}
}
[Help("Syncs promotable build. Use either -game or -label parameter.")]
[Help("artist", "Artist sync i.e. sync content to head and all the rest to promoted label.")]
[Help("preview", "This option makes that syncs are done in the preview mode (i.e. p4 sync -n).")]
[Help("game=GameName", "Name of the game to sync. If not set then shared promotable will be synced.")]
[Help("label=LabelName", "Promotable label name to sync to.")]
[RequireP4]
[DoesNotNeedP4CL]
class UnrealSync : BuildCommand
{
public override void ExecuteBuild()
{
var Preview = ParseParam("preview");
var ArtistSync = ParseParam("artist");
var BranchPath = CommandUtils.GetDirectoryName(P4Env.BuildRootP4);
var LabelParam = ParseParamValue("label");
var GameName = ParseParamValue("game");
if(GameName == null)
{
GameName = "";
}
string ProgramSyncLabelName = null;
if (!string.IsNullOrWhiteSpace(LabelParam))
{
if (!LabelParam.StartsWith(BranchPath) || !P4.ValidateLabelContent(LabelParam))
{
throw new AutomationException("Label {0} either doesn't exist or is not valid for the current branch path {1}.", LabelParam, BranchPath);
}
ProgramSyncLabelName = LabelParam;
}
else
{
ProgramSyncLabelName = GetLatestPromotedLabel(BranchPath, GameName, true);
}
if (ProgramSyncLabelName == null)
{
throw new AutomationException("Label for {0} was not found.",
string.IsNullOrWhiteSpace(GameName)
? string.Format("branch {0} shared-promotable", BranchPath)
: string.Format("branch {0} and game {1}", BranchPath, GameName));
}
SyncToLabel(BranchPath, ProgramSyncLabelName, ArtistSync, Preview);
}
///
/// Picks latest label from the provided group and returns its name.
///
/// Labels to chose from.
/// If the method should skip labels that has no tagged files in it.
/// The name of the label. If none was valid returns null.
public static string PickLatest(P4Label[] Labels, bool bVerifyContent)
{
if (Labels.Length == 0)
{
return null;
}
var OrderedLabels = Labels.OrderByDescending((Label) => Label.Date);
if (bVerifyContent)
{
foreach (var Label in OrderedLabels)
{
if (P4.ValidateLabelContent(Label.Name))
{
return Label.Name;
}
}
// Haven't found valid label.
return null;
}
return OrderedLabels.First().Name;
}
///
/// Get latest promoted label given branch and game name.
///
/// The branch path of the label.
/// The game name for which provide the label. If null or empty then provides shared-promotable label.
/// Verify if label tags at least one file.
/// Label name if it exists, null otherwise.
public static string GetLatestPromotedLabel(string BranchPath, string GameName, bool bVerifyContent)
{
if (string.IsNullOrWhiteSpace(GameName))
{
GameName = null;
}
return PickLatest(UnrealSyncList.GetPromotedLabels(BranchPath, GameName), bVerifyContent);
}
///
/// Syncs to given label.
///
/// Current branch path.
/// Label name to sync.
/// Perform artist sync? (binaries to label, content to latest)
/// Perform preview sync? (p4 sync -n)
private void SyncToLabel(string BranchPath, string LabelName, bool bArtistSync = true, bool bPreview = false)
{
var ProgramRevisionSpec = "@" + LabelName;
List SyncSteps;
if (bArtistSync)
{
// Get latest CL number to sync cause @head can change during
// different syncs and it could create integrity problems in
// workspace.
var ContentRevisionSpec = "@" + P4.GetLatestCLNumber().ToString();
var GameName = ParseGameNameFromLabel(LabelName);
var ArtistSyncRulesPath = string.Format("{0}/{1}/Build/ArtistSyncRules.xml",
BranchPath, string.IsNullOrWhiteSpace(GameName) ? "Samples" : GameName);
var SyncRules = string.Join("\n", P4.P4Print(ArtistSyncRulesPath + "#head"));
if (string.IsNullOrWhiteSpace(SyncRules))
{
throw new AutomationException("The path {0} is not valid or file is empty.", ArtistSyncRulesPath);
}
SyncSteps = GenerateSyncSteps(SyncRules, ContentRevisionSpec, ProgramRevisionSpec);
}
else
{
SyncSteps = new List();
SyncSteps.Add("/..." + ProgramRevisionSpec); // all files to label
}
foreach (var SyncStep in SyncSteps)
{
P4.Sync((bPreview ? "-n " : "") + BranchPath + SyncStep);
}
}
///
/// Generates sync steps based on the sync rules xml content.
///
/// ArtistSyncRules.xml content.
/// Revision spec to which sync the content. Different for artist sync.
/// Revision spec to which sync everything except content.
/// An array of sync steps to perform.
private List GenerateSyncSteps(string SyncRules, string ContentRevisionSpec, string ProgramRevisionSpec)
{
var SyncRulesDocument = new XmlDocument();
SyncRulesDocument.LoadXml(SyncRules);
var RuleNodes = SyncRulesDocument.DocumentElement.SelectNodes("/ArtistSyncRules/Rules/string");
var OutputList = new List();
foreach (XmlNode Node in RuleNodes)
{
var SyncStep = Node.InnerText.Replace("%LABEL_TO_SYNC_TO%", ProgramRevisionSpec);
// If there was no label in sync step.
// TODO: This is hack for messy ArtistSyncRules.xml format.
// Needs to be changed soon.
if (!SyncStep.Contains("@"))
{
SyncStep += ContentRevisionSpec;
}
OutputList.Add(SyncStep);
}
return OutputList;
}
/* Pattern used for parsing game name from label name. */
private static readonly Regex GameNameFromLabelParsingPattern = new Regex(@"^.+/Promot(ed|able)-((?\w+)-)?CL.+$", RegexOptions.Compiled);
///
/// Tries to parse game name from label name.
///
/// The label name to parse from.
/// Game name if found. Null otherwise.
private string ParseGameNameFromLabel(string LabelName)
{
return GameNameFromLabelParsingPattern.Match(LabelName).Groups["game"].Value;
}
}
}