Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/BuildGraph/Condition.cs
Andrew Grant 0b4257e23a Copying //UE4/Orion-Staging to //UE4/Main (Source //Orion/Dev-General @ 2927258)
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 2927181 on 2016/03/29 by Dmitry.Rekman

	(Optionally) exclude idle time from server FPS charts.

	- Time spent waiting for the next frame in order to hit capped FPS can be optionally excluded by using t.FPSChart.ExcludeIdleTime (set to 1 for servers).
	- Server FPS charts analytics events and log output will include the information if idle time was excluded.

	- Also: added a log line each time we detect a server hitch for easier pin-pointing them in the log.

	#rb Paul.Moore
	#codereview Paul.Moore, Michael.Noland
	#tests Ran Linux server and Windows client on compatible content.

Change 2927084 on 2016/03/29 by Ben.Marsh

	BuildGraph: Don't allow triggers to run until all their order dependencies are complete. Just because a downstream node doesn't have a dependency on an upstream node via temp storage doesn't mean it can run immediately.

	#rb none
	#tests none

Change 2927060 on 2016/03/29 by Michael.Noland

	Renamed GPU analytics event from GPU to DesktopGPU to reflect that it is the default desktop adapter and not the one we initialized (which is GPUAdapter)
	Updated text/log based FPS chart events to print out GPUAdapter instead (with DesktopGPU in parens if they differ, e.g., in an optimus setup)
	#rb marcus.wassmer
	#tests Ran and did some fps charts

Change 2927048 on 2016/03/29 by Michael.Noland

	HLOD: Removed an unused cvar r.HLODEnabled (everything is done thru r.HLOD)
	#tests Compiled and ran Paragon
	#rb marcus.wassmer

Change 2926920 on 2016/03/29 by Ben.Marsh

	BuildGraph: Update schema with Rename task.

Change 2926911 on 2016/03/29 by Ben.Marsh

	BuildGraph: Add a task which can rename files matching a given wildcard. Syntax is: <Rename Files="*.txt" To="*.md"> or <Rename Files="Engine/Build/..." From="*.txt" To="*.md"/>

	#rb none
	#tests none

Change 2926908 on 2016/03/29 by Andrew.Grant

	Fix for CDO properties of renamed blueprints not being applied
	#rb none
	#tests loaded Origin map (renamed from Playgo3) and verified properties are applied.

Change 2926799 on 2016/03/29 by Jason.Bestimt

	#ORION_DG - Merge MAIN (23) @ CL# 2926780

	#RB:none
	#Tests:none

Change 2926663 on 2016/03/29 by david.nikdel

	#ROBOMERGE-OBO: jason.bestimt
	#ROBOMERGE-SOURCE: CL 2926660 in //Orion/Release-0.23/... via CL 2926662
	#ROBOMERGE-BOT: ORION (Main -> Dev-General)

	#ORION_23 - Potential fix for Cook failures

	"Fix shelved in 2926635, tested in Dev-Blueprints. Could not run any GEditor related logic safely in ShutdownModule because of the same destruction issue orders that caused the bug in the first place. I will chat with Editor team about nulling out GEditor the same way we null out GUnrealEd."

	#RB:none
	#Tests: none

	[CodeReviewed]: andrew.grant, dan.oconnor

Change 2926510 on 2016/03/29 by Andrew.Grant

	Potential fix for OR-18207 - editor becomes unresponsive (audio deadlock)
	#rb none
	#tests compiled

Change 2926495 on 2016/03/29 by Rob.Cannaday

	Change storing HTTP requests as raw pointers to weak pointers with validity being checked via Pinning it
	#jira FORT-18947
	#jira OR-17695
	#tests golden path
	#rb eric.newman

Change 2926427 on 2016/03/29 by Josh.Markiewicz

	#UE4 - fixed typo
	#rb none
	#tests none

Change 2926250 on 2016/03/29 by Martin.Mittring

	fixed OR-18489 HERO: IGGY: RMB on E ability causes blinding hair effect
	#rb:Chris.Bunner
	#codereview:Brian.Karis

Change 2926224 on 2016/03/29 by Daniel.Lamb

	Fix for potenital threading issue with Console manager removing vars which could cause double free.
	#rb Robert.Manuszewski
	#test Orion cook

Change 2926174 on 2016/03/29 by Gareth.Martin

	Cloned fix for bUseMaterialPositionOffsetInStaticLighting crashing across from //UE4/Dev-Landscape/ to unblock people
	#rb
	#tests editor

Change 2925968 on 2016/03/29 by David.Nikdel

	#MCP #OSS
	- Read RedirectUrl from ini

	#RB: Eric.Newman
	#TESTS: compiled in another branch (merge over)
	#ROBOMERGE: Main

[CL 2929424 by Andrew Grant in Main branch]
2016-03-31 15:18:30 -04:00

331 lines
11 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AutomationTool
{
/// <summary>
/// Exception class thrown due to type and syntax errors in condition expressions
/// </summary>
class ConditionException : Exception
{
/// <summary>
/// Constructor; formats the exception message with the given String.Format() style parameters.
/// </summary>
/// <param name="Format">Formatting string, in String.Format syntax</param>
/// <param name="Args">Optional arguments for the string</param>
public ConditionException(string Format, params object[] Args) : base(String.Format(Format, Args))
{
}
}
/// <summary>
/// Class to evaluate condition expressions in build scripts, following this grammar:
///
/// or-expression ::= and-expression
/// | or-expression "Or" and-expression;
///
/// and-expression ::= comparison
/// | and-expression "And" comparison;
///
/// comparison ::= scalar
/// | scalar "==" scalar
/// | scalar "!=" scalar;
///
/// scalar ::= "(" or-expression ")"
/// | "!" scalar
/// | "Exists" "(" scalar ")"
/// | "HasTrailingSlash" "(" scalar ")"
/// | string
/// | identifier;
///
/// string ::= any sequence of characters terminated by single quotes (') or double quotes ("). Not escaped.
/// identifier ::= any sequence of letters, digits, or underscore characters.
///
/// The type of each subexpression is always a scalar, which are converted to expression-specific types (eg. booleans) as required.
/// Scalar values are case-insensitive strings. The identifier 'true' and the strings "true" and "True" are all identical scalars.
/// </summary>
static class Condition
{
/// <summary>
/// Sentinel added to the end of a sequence of tokens.
/// </summary>
const string EndToken = "<EOF>";
/// <summary>
/// Evaluates the given string as a condition. Throws a ConditionException on a type or syntax error.
/// </summary>
/// <param name="Text">The condition text</param>
/// <returns>The result of evaluating the condition</returns>
public static bool Evaluate(string Text)
{
List<string> Tokens = new List<string>();
Tokenize(Text, Tokens);
bool bResult = true;
if(Tokens.Count > 1)
{
int Idx = 0;
string Result = EvaluateOr(Tokens, ref Idx);
if(Tokens[Idx] != EndToken)
{
throw new ConditionException("Garbage after expression: {0}", String.Join("", Tokens.Skip(Idx)));
}
bResult = CoerceToBool(Result);
}
return bResult;
}
/// <summary>
/// Evaluates an "or-expression" production.
/// </summary>
/// <param name="Tokens">List of tokens in the expression</param>
/// <param name="Idx">Current position in the token stream. Will be incremented as tokens are consumed.</param>
/// <returns>A scalar representing the result of evaluating the expression.</returns>
static string EvaluateOr(List<string> Tokens, ref int Idx)
{
// <Condition> Or <Condition> Or...
string Result = EvaluateAnd(Tokens, ref Idx);
while(String.Compare(Tokens[Idx], "Or", true) == 0)
{
// Evaluate this condition. We use a binary OR here, because we want to parse everything rather than short-circuit it.
Idx++;
string Lhs = Result;
string Rhs = EvaluateAnd(Tokens, ref Idx);
Result = (CoerceToBool(Lhs) | CoerceToBool(Rhs))? "true" : "false";
}
return Result;
}
/// <summary>
/// Evaluates an "and-expression" production.
/// </summary>
/// <param name="Tokens">List of tokens in the expression</param>
/// <param name="Idx">Current position in the token stream. Will be incremented as tokens are consumed.</param>
/// <returns>A scalar representing the result of evaluating the expression.</returns>
static string EvaluateAnd(List<string> Tokens, ref int Idx)
{
// <Condition> And <Condition> And...
string Result = EvaluateComparison(Tokens, ref Idx);
while(String.Compare(Tokens[Idx], "And", true) == 0)
{
// Evaluate this condition. We use a binary AND here, because we want to parse everything rather than short-circuit it.
Idx++;
string Lhs = Result;
string Rhs = EvaluateComparison(Tokens, ref Idx);
Result = (CoerceToBool(Lhs) & CoerceToBool(Rhs))? "true" : "false";
}
return Result;
}
/// <summary>
/// Evaluates a "comparison" production.
/// </summary>
/// <param name="Tokens">List of tokens in the expression</param>
/// <param name="Idx">Current position in the token stream. Will be incremented as tokens are consumed.</param>
/// <returns>The result of evaluating the expression</returns>
static string EvaluateComparison(List<string> Tokens, ref int Idx)
{
// Scalar
// Scalar == Scalar
// Scalar != Scalar
string Result = EvaluateScalar(Tokens, ref Idx);
if(Tokens[Idx] == "==")
{
// Compare two scalars for equality
Idx++;
string Lhs = Result;
string Rhs = EvaluateScalar(Tokens, ref Idx);
Result = (String.Compare(Lhs, Rhs, true) == 0)? "true" : "false";
}
else if(Tokens[Idx] == "!=")
{
// Compare two scalars for inequality
Idx++;
string Lhs = Result;
string Rhs = EvaluateScalar(Tokens, ref Idx);
Result = (String.Compare(Lhs, Rhs, true) != 0)? "true" : "false";
}
return Result;
}
/// <summary>
/// Evaluates a "scalar" production.
/// </summary>
/// <param name="Tokens">List of tokens in the expression</param>
/// <param name="Idx">Current position in the token stream. Will be incremented as tokens are consumed.</param>
/// <returns>The result of evaluating the expression</returns>
static string EvaluateScalar(List<string> Tokens, ref int Idx)
{
string Result;
if(Tokens[Idx] == "(")
{
// Subexpression
Idx++;
Result = EvaluateOr(Tokens, ref Idx);
if(Tokens[Idx] != ")")
{
throw new ConditionException("Expected ')'");
}
Idx++;
}
else if(Tokens[Idx] == "!")
{
// Logical not
Idx++;
string Rhs = EvaluateScalar(Tokens, ref Idx);
Result = CoerceToBool(Rhs)? "false" : "true";
}
else if(String.Compare(Tokens[Idx], "Exists", true) == 0 && Tokens[Idx + 1] == "(")
{
// Check whether file or directory exists. Evaluate the argument as a subexpression.
Idx++;
string Argument = EvaluateScalar(Tokens, ref Idx);
Result = (File.Exists(Argument) || Directory.Exists(Argument))? "true" : "false";
}
else if(String.Compare(Tokens[Idx], "HasTrailingSlash", true) == 0 && Tokens[Idx + 1] == "(")
{
// Check whether the given string ends with a slash
Idx++;
string Argument = EvaluateScalar(Tokens, ref Idx);
Result = (Argument.Length > 0 && (Argument[Argument.Length - 1] == Path.DirectorySeparatorChar || Argument[Argument.Length - 1] == Path.AltDirectorySeparatorChar))? "true" : "false";
}
else
{
// Raw scalar. Remove quotes from strings, and allow literals and simple identifiers to pass through directly.
string Token = Tokens[Idx];
if(Token.Length >= 2 && (Token[0] == '\'' || Token[0] == '\"') && Token[Token.Length - 1] == Token[0])
{
Result = Token.Substring(1, Token.Length - 2);
Idx++;
}
else if(Char.IsLetterOrDigit(Token[0]) || Token[0] == '_')
{
Result = Token;
Idx++;
}
else
{
throw new ConditionException("Token '{0}' is not a valid scalar", Token);
}
}
return Result;
}
/// <summary>
/// Converts a scalar to a boolean value.
/// </summary>
/// <param name="Scalar">The scalar to convert</param>
/// <returns>The scalar converted to a boolean value.</returns>
static bool CoerceToBool(string Scalar)
{
bool Result;
if(String.Compare(Scalar, "true", true) == 0)
{
Result = true;
}
else if(String.Compare(Scalar, "false", true) == 0)
{
Result = false;
}
else
{
throw new ConditionException("Token '{0}' cannot be coerced to a bool", Scalar);
}
return Result;
}
/// <summary>
/// Splits an input string up into expression tokens.
/// </summary>
/// <param name="Text">Text to be converted into tokens</param>
/// <param name="Tokens">List to receive a list of tokens</param>
static void Tokenize(string Text, List<string> Tokens)
{
int Idx = 0;
while(Idx < Text.Length)
{
int EndIdx = Idx + 1;
if(!Char.IsWhiteSpace(Text[Idx]))
{
// Scan to the end of the current token
if(Char.IsNumber(Text[Idx]))
{
// Number
while(EndIdx < Text.Length && Char.IsNumber(Text[EndIdx]))
{
EndIdx++;
}
}
else if(Char.IsLetter(Text[Idx]) || Text[Idx] == '_')
{
// Identifier
while(EndIdx < Text.Length && (Char.IsLetterOrDigit(Text[EndIdx]) || Text[EndIdx] == '_'))
{
EndIdx++;
}
}
else if(Text[Idx] == '!' || Text[Idx] == '<' || Text[Idx] == '>' || Text[Idx] == '=')
{
// Operator that can be followed by an equals character
if(EndIdx < Text.Length && Text[EndIdx] == '=')
{
EndIdx++;
}
}
else if(Text[Idx] == '\'' || Text[Idx] == '\"')
{
// String
if(EndIdx < Text.Length)
{
EndIdx++;
while(EndIdx < Text.Length && Text[EndIdx - 1] != Text[Idx]) EndIdx++;
}
}
Tokens.Add(Text.Substring(Idx, EndIdx - Idx));
}
Idx = EndIdx;
}
Tokens.Add(EndToken);
}
/// <summary>
/// Test cases for conditions.
/// </summary>
public static void TestConditions()
{
TestCondition("1 == 2", false);
TestCondition("1 == 1", true);
TestCondition("1 != 2", true);
TestCondition("1 != 1", false);
TestCondition("'hello' == 'hello'", true);
TestCondition("'hello' == ('hello')", true);
TestCondition("'hello' == 'world'", false);
TestCondition("'hello' != ('world')", true);
TestCondition("true == ('true')", true);
TestCondition("true == ('True')", true);
TestCondition("true == ('false')", false);
TestCondition("true == !('False')", true);
TestCondition("true == 'true' and 'false' == 'False'", true);
TestCondition("true == 'true' and 'false' == 'true'", false);
TestCondition("true == 'false' or 'false' == 'false'", true);
TestCondition("true == 'false' or 'false' == 'true'", true);
}
/// <summary>
/// Helper method to evaluate a condition and check it's the expected result
/// </summary>
/// <param name="Condition">Condition to evaluate</param>
/// <param name="ExpectedResult">The expected result</param>
static void TestCondition(string Condition, bool ExpectedResult)
{
bool Result = Evaluate(Condition);
Console.WriteLine("{0}: {1} = {2}", (Result == ExpectedResult)? "PASS" : "FAIL", Condition, Result);
}
}
}