Created
November 7, 2018 20:55
-
-
Save TimTinkers/31b35a50e3aaddb5889137fc87fea1d5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Xml; | |
using System.Xml.Linq; | |
using System.Diagnostics; | |
using System.IO; | |
using Tools.DotNETCommon; | |
namespace UnrealBuildTool | |
{ | |
/* UnrealPluginLanguage (UPL) is a simple XML-based language for manipulating XML and returning | |
* strings. It contains an <init> section which is evaluated once per architecture before any | |
* other sections. The state is maintained and carried forward to the next section evaluated | |
* so the order the sections are executed matters. | |
* | |
* While UPL is a general system for modifying and quering XML it is specifically used to allow | |
* plug-ins to effect the global configuration of the package that they are a part of. For | |
* example, this allows a plug-in to modify an Android's APK AndroidManfiest.xml file or an | |
* IOS IPA's plist file. UBT will also query a plug-in's UPL xml file for strings to be included | |
* in files that must be common to the package such as some .java files on Android. | |
* | |
* If you need to see the instructions executed in your plugin context add the following to | |
* enable tracing: | |
* | |
* <trace enable="true"/> | |
* | |
* After this instuction all the nodes actually executed in your context will be written to the | |
* log until you do a <trace enable="false"/>. You can also get a dump of all the variables in | |
* your context with this command: | |
* | |
* <dumpvars/> | |
* | |
* Bool, Int, and String variable types are supported. Any attribute may reference a variable | |
* and will be replaced with the string equivalent before evaluation using this syntax: | |
* | |
* $B(name) = boolean variable "name"'s value | |
* $I(name) = integer variable "name"'s value | |
* $S(name) = string variable "name"'s value | |
* $E(name) = element variable "name"'s value | |
* | |
* The following variables are initialized automatically: | |
* | |
* $S(Output) = the output returned for evaluating the section (initialized to Input) | |
* $S(Architecture) = target architecture (armeabi-armv7a, arm64-v8a, x86, x86_64) | |
* $S(PluginDir) = directory the XML file was loaded from | |
* $S(EngineDir) = engine directory | |
* $S(BuildDir) = project's platform appropriate build directory (within the Intermediate folder) | |
* $S(Configuration) = configuration type (Debug, DebugGame, Development, Test, Shipping) | |
* $B(Distribution) = true if distribution build | |
* | |
* Note: with the exception of the above variables, all are in the context of the plugin to | |
* prevent namespace collision; trying to set a new value to any of the above, with the | |
* exception of Output, will only affect the current context. | |
* | |
* The following nodes allow manipulation of variables: | |
* | |
* <setBool result="" value=""/> | |
* <setInt result="" value=""/> | |
* <setString result="" value=""/> | |
* <setElement result="" value=""/> | |
* <setElement result="" value="" text=""/> | |
* <setElement result="" xml=""/> | |
* | |
* <setElement> with value creates an empty XML element with the tag set to value. | |
* <setElement> with value and text creates an XML element with tag set to value of unparsed text. | |
* <setElement> with xml will parse the XML provided. Remember to escape any special characters! | |
* | |
* Variables may also be set from a property in an ini file: | |
* | |
* <setBoolFromProperty result="" ini="" section="" property="" default=""/> | |
* <setBoolFromPropertyContains result="" ini="" section="" property="" default="" contains=""/> | |
* <setIntFromProperty result="" ini="" section="" property="" default=""/> | |
* <setStringFromProperty result="" ini="" section="" property="" default=""/> | |
* | |
* Strings may also be set from an environment variable using the <setStringFromEnvVar> node. | |
* The environment variable must be specified as the 'value' attribute and wrapped in a pair | |
* of '%' characters. | |
* | |
* <setStringFromEnvVar result="" value=""/> | |
* | |
* You can also check if a specific environment variable is defined (again, wrapped in '%' characters): | |
* | |
* <setBoolEnvVarDefined result="" value=""/> | |
* | |
* A general example for using environment variable nodes: | |
* | |
* <setBoolEnvVarDefined result="bHasNDK" value="%NDKROOT%"/> | |
* <setStringFromEnvVar result="NDKPath" value="%NDKROOT%"/> | |
* | |
* Boolean variables may also be set to the result of applying operators: | |
* | |
* <setBoolNot result="" source=""/> | |
* <setBoolAnd result="" arg1="" arg2=""/> | |
* <setBoolOr result="" arg1="" arg2=""/> | |
* <setBoolIsEqual result="" arg1="" arg2=""/> | |
* <setBoolIsLess result="" arg1="" arg2=""/> | |
* <setBoolIsLessEqual result="" arg1="" arg2=""/> | |
* <setBoolIsGreater result="" arg1="" arg2=""/> | |
* <setBoolIsGreaterEqual result="" arg1="" arg2=""/> | |
* <setBoolFromPropertyContains result="" ini="" section="" property="" contains=""/> | |
* | |
* Integer variables may use these arithmetic operations: | |
* | |
* <setIntAdd result="" arg1="" arg2=""/> | |
* <setIntSubtract result="" arg1="" arg2=""/> | |
* <setIntMultiply result="" arg1="" arg2=""/> | |
* <setIntDivide result="" arg1="" arg2=""/> | |
* | |
* Strings are manipulated with the following: | |
* | |
* <setStringAdd result="" arg1="" arg2=""/> | |
* <setStringSubstring result="" source="" start="" length=""/> | |
* <setStringReplace result="" source="" find="" with=""/> | |
* | |
* String length may be retrieved with: | |
* | |
* <setIntLength result="" source=""/> | |
* | |
* The index of a search string may be found in source with: | |
* | |
* <setIntFindString result="" source="" find=""/> | |
* | |
* The following shortcut string comparisons may also be used instead of using <setIntFindString> | |
* and checking the result: | |
* | |
* <setBoolStartsWith result="" source="" find=""/> | |
* <setBoolEndsWith result="" source="" find=""/> | |
* <setBoolContains result="" source="" find=""/> | |
* | |
* Messages are written to the log with this node: | |
* | |
* <log text=""/> | |
* | |
* Conditional execution uses the following form: | |
* | |
* <if condition=""> | |
* <true> | |
* <!-- executes if boolean variable in condition is true --> | |
* </true> | |
* <false> | |
* <!-- executes if boolean variable in condition is false --> | |
* </false> | |
* </if> | |
* | |
* The <true> and <false> blocks are optional. The condition must be in a boolean variable. | |
* The boolean operator nodes may be combined to create a final state for more complex | |
* conditions: | |
* | |
* <setBoolNot result="notDistribution" source="$B(Distribution)/> | |
* <setBoolIsEqual result="isX86" arg1="$S(Architecture)" arg2="x86"/> | |
* <setBoolIsEqual result="isX86_64" arg2="$S(Architecture)" arg2="x86_64/"> | |
* <setBoolOr result="isIntel" arg1="$B(isX86)" arg2="$B(isX86_64)"/> | |
* <setBoolAnd result="intelAndNotDistribution" arg1="$B(isIntel)" arg2="$B(notDistribution)"/> | |
* <if condition="intelAndNotDistribution"> | |
* <true> | |
* <!-- do something for Intel if not a distribution build --> | |
* </true> | |
* </if> | |
* | |
* Note the "isIntel" could also be done like this: | |
* | |
* <setStringSubstring result="subarch" source="$S(Architecture)" start="0" length="3"/> | |
* <setBoolEquals result="isIntel" arg1="$S(subarch)" arg2="x86"/> | |
* | |
* Two shortcut nodes are available for conditional execution: | |
* | |
* <isArch arch="armeabi-armv7"> | |
* <!-- do stuff --> | |
* </isArch> | |
* | |
* is the equivalent of: | |
* | |
* <setBoolEquals result="temp" arg1="$S(Architecture)" arg2="armeabi-armv7"> | |
* <if condition="temp"> | |
* <true> | |
* <!-- do stuff --> | |
* </true> | |
* </if> | |
* | |
* and | |
* | |
* <isDistribution> | |
* <!-- do stuff --> | |
* </isDistribution> | |
* | |
* is the equivalent of: | |
* | |
* <if condition="Distribution"> | |
* <!-- do stuff --> | |
* </if> | |
* | |
* Execution may be stopped with: | |
* | |
* <return/> | |
* | |
* Loops may be created using these nodes: | |
* | |
* <while condition=""> | |
* <!-- do stuff --> | |
* </while> | |
* | |
* <break/> | |
* <continue/> | |
* | |
* The <while> body will execute until the condition is false or a <break/> is hit. The | |
* <continue/> will restart execution of the loop if the condition is still true or exit. | |
* | |
* Note: <break/> outside a <while> body will act the same as <return/> | |
* | |
* Here is an example loop which writes 1 to 5 to the log, skipping 3. Note the update of the | |
* while condition should be done before the continue otherwise it may not exit. | |
* | |
* <setInt result="index" value="0"/> | |
* <setBool result="loopRun" value="true"/> | |
* <while condition="loopRun"> | |
* <setIntAdd result="index" arg1="$I(index)" arg2="1"/> | |
* <setBoolIsLess result="loopRun" arg1="$I(index)" arg2="5"/> | |
* <setBoolIsEqual result="indexIs3" arg1="$I(index)" arg2="3"/> | |
* <if condition="indexIs3"> | |
* <true> | |
* <continue/> | |
* </true> | |
* </if> | |
* <log text="$I(index)"/> | |
* </while> | |
* | |
* It is possible to use variable replacement in generating the result variable | |
* name as well. This makes the creation of arrays in loops possible: | |
* | |
* <setString result="array_$I(index)" value="element $I(index) in array"/> | |
* | |
* This may be retrieved using the following (value is treated as the variable | |
* name): | |
* | |
* <setStringFrom result="out" value="array_$I(index)"/> | |
* | |
* For boolean and integer types, you may use <setBoolFrom/> and <setIntFrom/>. | |
* | |
* Nodes for inserting text into the section are as follows: | |
* | |
* <insert> body </insert> | |
* <insertNewline/> | |
* <insertValue value=""/> | |
* <loadLibrary name="" failmsg=""/> | |
* | |
* The first one will insert either text or nodes into the returned section | |
* string. Please note you must use escaped characters for: | |
* | |
* < = < | |
* > = > | |
* & = & | |
* | |
* <insertValue value=""/> evaluates variables in value before insertion. If value contains | |
* double quote ("), you must escape it with ". | |
* | |
* <loadLibrary name="" failmsg=""/> is a shortcut to insert a system.LoadLibrary try/catch | |
* block with an optional logged message for failure to load case. | |
* | |
* You can do a search and replace in the Output with: | |
* | |
* <replace find="" with=""/> | |
* | |
* Note you can also manipulate the actual $S(Output) directly, the above are more efficient: | |
* | |
* <setStringAdd result="Input" arg1="$S(Output)" arg2="sample\n"/> | |
* <setStringReplace result="Input" source="$S(Output)" find=".LAUNCH" with=".INFO"/> | |
* | |
* XML manipulation uses the following nodes: | |
* | |
* <addElement tag="" name=""/> | |
* <addElements tag=""> body </addElements> | |
* <removeElement tag=""/> | |
* <setStringFromTag result="" tag=""/> | |
* <setStringFromAttribute result="" tag="" name=""/> | |
* <setStringFromTagText result="" tag=""/> | |
* <addAttribute tag="" name="" value=""/> | |
* <removeAttribute tag="" name=""/> | |
* <loopElements tag=""> instructions </loopElements> | |
* | |
* The current element is referenced with tag="$". Element variables are referenced with $varname | |
* since using $E(varname) will be expanded to the string equivalent of the XML. | |
* | |
* addElement, addElements, and removeElement by default are applied to all matching tags. An | |
* optional once="true" attribute may be added to only apply to first matching tag. | |
* | |
* <uses-permission>, <uses-feature>, and <uses-library> are updated with: | |
* | |
* <addPermission android:name="" .. /> | |
* <addFeature android:name="" .. /> | |
* <addLibrary android:name="" .. /> | |
* | |
* Any attributes in the above commands are copied to the element added to the manifest so you | |
* can do the following, for example: | |
* | |
* <addFeature android:name="android.hardware.usb.host" android:required="true"/> | |
* | |
* Finally, these nodes allow copying of files useful for staging jar and so files: | |
* | |
* <copyFile src="" dst="" force=""/> | |
* <copyDir src="" dst="" force=""/> | |
* | |
* If force is false the file(s) are replaced only if length or timestamp don't match. Default is true. | |
* | |
* The following should be used as the base for the src and dst paths: | |
* | |
* $S(PluginDir) = directory the XML file was loaded from | |
* $S(EngineDir) = engine directory | |
* $S(BuildDir) = project's platform appropriate build directory | |
* | |
* While it is possible to write outside the APK directory, it is not recommended. | |
* | |
* If you must remove files (like development-only files from distribution builds) you can | |
* use this node: | |
* | |
* <deleteFiles filespec=""/> | |
* | |
* It is restricted to only removing files from the BuildDir. Here is example usage to remove | |
* the Oculus Signature Files (osig) from the assets directory: | |
* | |
* <deleteFiles filespec="assets/oculussig_*"/> | |
* | |
* The following sections are evaluated during the packaging or deploy stages: | |
* | |
* ** For all platforms ** | |
* <!-- init section is always evaluated once per architecture --> | |
* <init> </init> | |
* | |
* ** Android Specific sections ** | |
* <!-- optional updates applied to AndroidManifest.xml --> | |
* <androidManifestUpdates> </androidManifestUpdates> | |
* | |
* <!-- optional additions to proguard --> | |
* <proguardAdditions> </proguardAdditions> | |
* | |
* <!-- optional AAR imports additions --> | |
* <AARImports> </AARImports> | |
* | |
* <!-- optional base build.gradle additions --> | |
* <baseBuildGradleAdditions> </baseBuildGradleAdditions> | |
* | |
* <!-- optional base build.gradle buildscript additions --> | |
* <buildscriptGradleAdditions> </buildscriptGradleAdditions> | |
* | |
* <!-- optional app build.gradle additions --> | |
* <buildGradleAdditions> </buildGradleAdditions> | |
* | |
* <!-- optional additions to generated build.xml before ${sdk.dir}/tools/ant/build.xml import --> | |
* <buildXmlPropertyAdditions> </buildXmlPropertyAdditions> | |
* | |
* <!-- optional files or directories to copy or delete from Intermediate/Android/APK before ndk-build --> | |
* <prebuildCopies> </prebuildCopies> | |
* | |
* <!-- optional files or directories to copy or delete from Intermediate/Android/APK after ndk-build --> | |
* <resourceCopies> </resourceCopies> | |
* | |
* <!-- optional files or directories to copy or delete from Intermediate/Android/APK before Gradle --> | |
* <gradleCopies> </gradleCopies> | |
* | |
* <!-- optional properties to add to gradle.properties --> | |
* <gradleProperties> </gradleProperties> | |
* | |
* <!-- optional parameters to add to Gradle commandline (prefix with a space or will run into previous parameter(s)) --> | |
* <gradleParameters> </gradleParameters> | |
* | |
* <!-- optional minimum SDK API level required --> | |
* <minimumSDKAPI> </minimumSDKAPI> | |
* | |
* <!-- optional additions to the GameActivity imports in GameActivity.java --> | |
* <gameActivityImportAdditions> </gameActivityImportAdditions> | |
* | |
* <!-- optional additions to the GameActivity after imports in GameActivity.java --> | |
* <gameActivityPostImportAdditions> </gameActivityPostImportAdditions> | |
* | |
* <!-- optional additions to the GameActivity class implements in GameActivity.java (end each line with a comma) --> | |
* <gameActivityImplementsAdditions> </gameActivityImplementsAdditions> | |
* | |
* <!-- optional additions to the GameActivity class body in GameActivity.java --> | |
* <gameActivityClassAdditions> </gameActivityOnClassAdditions> | |
* | |
* <!-- optional additions to GameActivity onCreate metadata reading in GameActivity.java --> | |
* <gameActivityReadMetadata> </gameActivityReadMetadata> | |
* | |
* <!-- optional additions to GameActivity onCreate in GameActivity.java --> | |
* <gameActivityOnCreateAdditions> </gameActivityOnCreateAdditions> | |
* | |
* <!-- optional additions to GameActivity onDestroy in GameActivity.java --> | |
* <gameActivityOnDestroyAdditions> </gameActivityOnDestroyAdditions> | |
* | |
* <!-- optional additions to GameActivity onStart in GameActivity.java --> | |
* <gameActivityOnStartAdditions> </gameActivityOnStartAdditions> | |
* | |
* <!-- optional additions to GameActivity onStop in GameActivity.java --> | |
* <gameActivityOnStopAdditions> </gameActivityOnStopAdditions> | |
* | |
* <!-- optional additions to GameActivity onPause in GameActivity.java --> | |
* <gameActivityOnPauseAdditions> </gameActivityOnPauseAdditions> | |
* | |
* <!-- optional additions to GameActivity onResume in GameActivity.java --> | |
* <gameActivityOnResumeAdditions> </gameActivityOnResumeAdditions> | |
* | |
* <!-- optional additions to GameActivity onNewIntent in GameActivity.java --> | |
* <gameActivityOnNewIntentAdditions> </gameActivityOnNewIntentAdditions> | |
* | |
* <!-- optional additions to GameActivity onActivityResult in GameActivity.java --> | |
* <gameActivityOnActivityResultAdditions> </gameActivityOnActivityResultAdditions> | |
* | |
* <!-- optional libraries to load in GameActivity.java before libUE4.so --> | |
* <soLoadLibrary> </soLoadLibrary> | |
* | |
* | |
* Here is the complete list of supported nodes: | |
* | |
* <isArch arch=""> | |
* <isDistribution> | |
* <if> => <true> / <false> | |
* <while condition=""> | |
* <return/> | |
* <break/> | |
* <continue/> | |
* <log text=""/> | |
* <insert> </insert> | |
* <insertValue value=""/> | |
* <replace find="" with""/> | |
* <copyFile src="" dst=""/> | |
* <copyDir src="" dst=""/> | |
* <loadLibrary name="" failmsg=""/> | |
* <setBool result="" value=""/> | |
* <setBoolEnvVarDefined result="" value=""/> | |
* <setBoolFrom result="" value=""/> | |
* <setBoolFromProperty result="" ini="" section="" property="" default=""/> | |
* <setBoolFromPropertyContains result="" ini="" section="" property="" contains=""/> | |
* <setBoolNot result="" source=""/> | |
* <setBoolAnd result="" arg1="" arg2=""/> | |
* <setBoolOr result="" arg1="" arg2=""/> | |
* <setBoolIsEqual result="" arg1="" arg2=""/> | |
* <setBoolIsLess result="" arg1="" arg2=""/> | |
* <setBoolIsLessEqual result="" arg1="" arg2=""/> | |
* <setBoolIsGreater result="" arg1="" arg2=""/> | |
* <setBoolIsGreaterEqual result="" arg1="" arg2=""/> | |
* <setInt result="" value=""/> | |
* <setIntFrom result="" value=""/> | |
* <setIntFromProperty result="" ini="" section="" property="" default=""/> | |
* <setIntAdd result="" arg1="" arg2=""/> | |
* <setIntSubtract result="" arg1="" arg2=""/> | |
* <setIntMultiply result="" arg1="" arg2=""/> | |
* <setIntDivide result="" arg1="" arg2=""/> | |
* <setIntLength result="" source=""/> | |
* <setIntFindString result="" source="" find=""/> | |
* <setString result="" value=""/> | |
* <setStringFrom result="" value=""/> | |
* <setStringFromEnvVar result="" value=""/> | |
* <setStringFromProperty result="" ini="" section="" property="" default=""/> | |
* <setStringAdd result="" arg1="" arg2=""/> | |
* <setStringSubstring result="" source="" index="" length=""/> | |
* <setStringReplace result="" source="" find="" with=""/> | |
* | |
*/ | |
class UnrealPluginLanguage | |
{ | |
/** The merged XML program to run */ | |
private XDocument XDoc; | |
/** XML namespace */ | |
private XNamespace XMLNameSpace; | |
private string XMLRootDefinition; | |
private UnrealTargetPlatform TargetPlatform; | |
/** Trace flag to enable debugging */ | |
static private bool bGlobalTrace = false; | |
/** Project file reference */ | |
private FileReference ProjectFile; | |
static private XDocument XMLDummy = XDocument.Parse("<manifest></manifest>"); | |
private class UPLContext | |
{ | |
/** Variable state */ | |
public Dictionary<string, bool> BoolVariables; | |
public Dictionary<string, int> IntVariables; | |
public Dictionary<string, string> StringVariables; | |
public Dictionary<string, XElement> ElementVariables; | |
/** Local context trace */ | |
public bool bTrace; | |
public UPLContext(string Architecture, string PluginDir) | |
{ | |
BoolVariables = new Dictionary<string, bool>(); | |
IntVariables = new Dictionary<string, int>(); | |
StringVariables = new Dictionary<string, string>(); | |
ElementVariables = new Dictionary<string, XElement>(); | |
if (PluginDir == null || PluginDir == "") | |
{ | |
PluginDir = "."; | |
} | |
StringVariables["PluginDir"] = PluginDir.Replace("\\", "/"); | |
StringVariables["AbsPluginDir"] = Path.GetFullPath(PluginDir).Replace("\\", "/"); | |
StringVariables["Architecture"] = Architecture; | |
bTrace = false; | |
} | |
} | |
private UPLContext GlobalContext; | |
private Dictionary<string, UPLContext> Contexts; | |
private int ContextIndex; | |
private String LastError; | |
public UnrealPluginLanguage(FileReference InProjectFile, List<string> InXMLFiles, List<string> InArchitectures, string InXMLNameSpace, string InRootDefinition, UnrealTargetPlatform InTargetPlatform) | |
{ | |
ProjectFile = InProjectFile; | |
LastError = null; | |
Contexts = new Dictionary<string, UPLContext>(); | |
GlobalContext = new UPLContext("", ""); | |
ContextIndex = 0; | |
XMLNameSpace = InXMLNameSpace; | |
XMLRootDefinition = InRootDefinition; | |
TargetPlatform = InTargetPlatform; | |
string PathPrefix = Path.GetFileName(Directory.GetCurrentDirectory()).Equals("Source") ? ".." : "Engine"; | |
XDoc = XDocument.Parse("<root " + InRootDefinition + "></root>"); | |
foreach (string Basename in InXMLFiles) | |
{ | |
string Filename = Path.Combine(PathPrefix, Basename.Replace("\\", "/")); | |
Log.TraceInformation("UPL: {0}", Filename); | |
if (File.Exists(Filename)) | |
{ | |
string PluginDir = Path.GetDirectoryName(Filename); | |
try | |
{ | |
XDocument MergeDoc = XDocument.Load(Filename); | |
MergeXML(MergeDoc, PluginDir, InArchitectures); | |
} | |
catch (Exception e) | |
{ | |
LastError = String.Format("Unreal Plugin file {0} parsing failed! {1}", Filename, e); | |
Log.TraceError("\n{0}", LastError); | |
} | |
} | |
else | |
{ | |
LastError = String.Format("Unreal Plugin file {0} missing!", Filename); | |
Log.TraceError("\n{0}", LastError); | |
Log.TraceInformation("\nCWD: {0}", Directory.GetCurrentDirectory()); | |
} | |
} | |
} | |
public String GetLastError() { return LastError; } | |
public bool GetTrace() { return bGlobalTrace; } | |
public void SetTrace() { bGlobalTrace = true; } | |
public void ClearTrace() { bGlobalTrace = false; } | |
public string GetUPLHash() | |
{ | |
return XDoc.ToString().GetHashCode().ToString(); | |
} | |
public bool MergeXML(XDocument MergeDoc, string PluginDir, List<string> Architectures) | |
{ | |
if (MergeDoc == null) | |
{ | |
return false; | |
} | |
// create a context for each architecture | |
ContextIndex++; | |
foreach (string Architecture in Architectures) | |
{ | |
UPLContext Context = new UPLContext(Architecture, PluginDir); | |
Contexts[Architecture + "_" + ContextIndex] = Context; | |
} | |
// merge in the nodes | |
foreach (XElement Element in MergeDoc.Root.Elements()) | |
{ | |
XElement Parent = XDoc.Root.Element(Element.Name); | |
if (Parent != null) | |
{ | |
XElement Entry = new XElement("Context", new XAttribute("index", ContextIndex.ToString())); | |
Entry.Add(Element.Elements()); | |
Parent.Add(Entry); | |
} | |
else | |
{ | |
XElement Entry = new XElement("Context", new XAttribute("index", ContextIndex.ToString())); | |
Entry.Add(Element.Elements()); | |
XElement Base = new XElement(Element.Name); | |
Base.Add(Entry); | |
XDoc.Root.Add(Base); | |
} | |
} | |
return true; | |
} | |
public void SaveXML(string Filename) | |
{ | |
if (XDoc != null) | |
{ | |
XDoc.Save(Filename); | |
} | |
} | |
private string DumpContext(UPLContext Context) | |
{ | |
StringBuilder Text = new StringBuilder(); | |
foreach (KeyValuePair<string, bool> Variable in Context.BoolVariables) | |
{ | |
Text.AppendLine(string.Format("\tbool {0} = {1}", Variable.Key, Variable.Value.ToString().ToLower())); | |
} | |
foreach (KeyValuePair<string, int> Variable in Context.IntVariables) | |
{ | |
Text.AppendLine(string.Format("\tint {0} = {1}", Variable.Key, Variable.Value)); | |
} | |
foreach (KeyValuePair<string, string> Variable in Context.StringVariables) | |
{ | |
Text.AppendLine(string.Format("\tstring {0} = {1}", Variable.Key, Variable.Value)); | |
} | |
foreach (KeyValuePair<string, XElement> Variable in Context.ElementVariables) | |
{ | |
Text.AppendLine(string.Format("\telement {0} = {1}", Variable.Key, Variable.Value)); | |
} | |
return Text.ToString(); | |
} | |
public string DumpVariables() | |
{ | |
string Result = "Global Context:\n" + DumpContext(GlobalContext); | |
foreach (KeyValuePair<string, UPLContext> Context in Contexts) | |
{ | |
Result += "Context " + Context.Key + ": " + Context.Value.StringVariables["PluginDir"] + "\n" + DumpContext(Context.Value); | |
} | |
return Result; | |
} | |
private bool GetCondition(UPLContext Context, XElement Node, string Condition, out bool Result) | |
{ | |
Result = false; | |
if (!Context.BoolVariables.TryGetValue(Condition, out Result)) | |
{ | |
if (!GlobalContext.BoolVariables.TryGetValue(Condition, out Result)) | |
{ | |
Log.TraceWarning("\nMissing condition '{0}' in '{1}' (skipping instruction)", Condition, TraceNodeString(Node)); | |
return false; | |
} | |
} | |
return true; | |
} | |
private string ExpandVariables(UPLContext Context, string InputString) | |
{ | |
string Result = InputString; | |
for (int Idx = Result.IndexOf("$B("); Idx != -1; Idx = Result.IndexOf("$B(", Idx)) | |
{ | |
// Find the end of the variable name | |
int EndIdx = Result.IndexOf(')', Idx + 3); | |
if (EndIdx == -1) | |
{ | |
break; | |
} | |
// Extract the variable name from the string | |
string Name = Result.Substring(Idx + 3, EndIdx - (Idx + 3)); | |
// Find the value for it, either from the dictionary or the environment block | |
bool Value; | |
if (!Context.BoolVariables.TryGetValue(Name, out Value)) | |
{ | |
if (!GlobalContext.BoolVariables.TryGetValue(Name, out Value)) | |
{ | |
Idx = EndIdx + 1; | |
continue; | |
} | |
} | |
// Replace the variable, or skip past it | |
Result = Result.Substring(0, Idx) + Value.ToString().ToLower() + Result.Substring(EndIdx + 1); | |
} | |
for (int Idx = Result.IndexOf("$I("); Idx != -1; Idx = Result.IndexOf("$I(", Idx)) | |
{ | |
// Find the end of the variable name | |
int EndIdx = Result.IndexOf(')', Idx + 3); | |
if (EndIdx == -1) | |
{ | |
break; | |
} | |
// Extract the variable name from the string | |
string Name = Result.Substring(Idx + 3, EndIdx - (Idx + 3)); | |
// Find the value for it, either from the dictionary or the environment block | |
int Value; | |
if (!Context.IntVariables.TryGetValue(Name, out Value)) | |
{ | |
if (!GlobalContext.IntVariables.TryGetValue(Name, out Value)) | |
{ | |
Idx = EndIdx + 1; | |
continue; | |
} | |
} | |
// Replace the variable, or skip past it | |
Result = Result.Substring(0, Idx) + Value.ToString() + Result.Substring(EndIdx + 1); | |
} | |
for (int Idx = Result.IndexOf("$S("); Idx != -1; Idx = Result.IndexOf("$S(", Idx)) | |
{ | |
// Find the end of the variable name | |
int EndIdx = Result.IndexOf(')', Idx + 3); | |
if (EndIdx == -1) | |
{ | |
break; | |
} | |
// Extract the variable name from the string | |
string Name = Result.Substring(Idx + 3, EndIdx - (Idx + 3)); | |
// Find the value for it, either from the dictionary or the environment block | |
string Value; | |
if (!Context.StringVariables.TryGetValue(Name, out Value)) | |
{ | |
if (!GlobalContext.StringVariables.TryGetValue(Name, out Value)) | |
{ | |
Idx = EndIdx + 1; | |
continue; | |
} | |
} | |
// Replace the variable, or skip past it | |
Result = Result.Substring(0, Idx) + Value + Result.Substring(EndIdx + 1); | |
} | |
for (int Idx = Result.IndexOf("$E("); Idx != -1; Idx = Result.IndexOf("$E(", Idx)) | |
{ | |
// Find the end of the variable name | |
int EndIdx = Result.IndexOf(')', Idx + 3); | |
if (EndIdx == -1) | |
{ | |
break; | |
} | |
// Extract the variable name from the string | |
string Name = Result.Substring(Idx + 3, EndIdx - (Idx + 3)); | |
// Find the value for it, either from the dictionary or the environment block | |
XElement Value; | |
if (!Context.ElementVariables.TryGetValue(Name, out Value)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Name, out Value)) | |
{ | |
Idx = EndIdx + 1; | |
continue; | |
} | |
} | |
// Replace the variable, or skip past it | |
Result = Result.Substring(0, Idx) + Value + Result.Substring(EndIdx + 1); | |
} | |
return Result; | |
} | |
private string TraceNodeString(XElement Node) | |
{ | |
string Result = Node.Name.ToString(); | |
foreach (XAttribute Attrib in Node.Attributes()) | |
{ | |
Result += " " + Attrib.ToString(); | |
} | |
return Result; | |
} | |
private bool StringToBool(string Input) | |
{ | |
if (Input == null) | |
{ | |
return false; | |
} | |
Input = Input.ToLower(); | |
return !(Input.Equals("0") || Input.Equals("false") || Input.Equals("off") || Input.Equals("no")); | |
} | |
private int StringToInt(string Input, XElement Node) | |
{ | |
int Result = 0; | |
if (!int.TryParse(Input, out Result)) | |
{ | |
Log.TraceWarning("\nInvalid integer '{0}' in '{1}' (defaulting to 0)", Input, TraceNodeString(Node)); | |
} | |
return Result; | |
} | |
private string GetAttribute(UPLContext Context, XElement Node, string AttributeName, bool bExpand = true, bool bRequired = true, string Fallback = null) | |
{ | |
XAttribute Attribute = Node.Attribute(AttributeName); | |
if (Attribute == null) | |
{ | |
if (bRequired) | |
{ | |
Log.TraceWarning("\nMissing attribute '{0}' in '{1}' (skipping instruction)", AttributeName, TraceNodeString(Node)); | |
} | |
return Fallback; | |
} | |
string Result = Attribute.Value; | |
return bExpand ? ExpandVariables(Context, Result) : Result; | |
} | |
private string GetAttributeWithNamespace(UPLContext Context, XElement Node, XNamespace Namespace, string AttributeName, bool bExpand = true, bool bRequired = true, string Fallback = null) | |
{ | |
XAttribute Attribute = Node.Attribute(Namespace + AttributeName); | |
if (Attribute == null) | |
{ | |
if (bRequired) | |
{ | |
Log.TraceWarning("\nMissing attribute '{0}' in '{1}' (skipping instruction)", AttributeName, TraceNodeString(Node)); | |
} | |
return Fallback; | |
} | |
string Result = Attribute.Value; | |
return bExpand ? ExpandVariables(Context, Result) : Result; | |
} | |
static private Dictionary<string, ConfigCacheIni_UPL> ConfigCache = null; | |
private ConfigCacheIni_UPL GetConfigCacheIni_UPL(string baseIniName) | |
{ | |
if (ConfigCache == null) | |
{ | |
ConfigCache = new Dictionary<string, ConfigCacheIni_UPL>(); | |
} | |
ConfigCacheIni_UPL config = null; | |
if (!ConfigCache.TryGetValue(baseIniName, out config)) | |
{ | |
// note: use our own ConfigCacheIni since EngineConfiguration.cs only parses RequiredSections! | |
config = ConfigCacheIni_UPL.CreateConfigCacheIni_UPL(TargetPlatform, baseIniName, DirectoryReference.FromFile(ProjectFile)); | |
ConfigCache.Add(baseIniName, config); | |
} | |
return config; | |
} | |
private static bool FilesAreDifferent(string SourceFilename, string DestFilename) | |
{ | |
// source must exist | |
FileInfo SourceInfo = new FileInfo(SourceFilename); | |
if (!SourceInfo.Exists) | |
{ | |
Log.TraceInformation("File {0} does not exist", SourceFilename); | |
return false; | |
} | |
// different if destination doesn't exist | |
FileInfo DestInfo = new FileInfo(DestFilename); | |
if (!DestInfo.Exists) | |
{ | |
return true; | |
} | |
// file lengths differ? | |
if (SourceInfo.Length != DestInfo.Length) | |
{ | |
return true; | |
} | |
// validate timestamps | |
TimeSpan Diff = DestInfo.LastWriteTimeUtc - SourceInfo.LastWriteTimeUtc; | |
if (Diff.TotalSeconds < -1 || Diff.TotalSeconds > 1) | |
{ | |
return true; | |
} | |
// could check actual bytes just to be sure, but good enough | |
return false; | |
} | |
private static void CopyFileDirectory(string SourceDir, string DestDir, bool bForce = false) | |
{ | |
if (!Directory.Exists(SourceDir)) | |
{ | |
return; | |
} | |
string[] Files = Directory.GetFiles(SourceDir, "*.*", SearchOption.AllDirectories); | |
foreach (string Filename in Files) | |
{ | |
// make the dst filename with the same structure as it was in SourceDir | |
string DestFilename = Path.Combine(DestDir, Utils.MakePathRelativeTo(Filename, SourceDir)); | |
if (bForce || FilesAreDifferent(Filename, DestFilename)) | |
{ | |
if (File.Exists(DestFilename)) | |
{ | |
File.SetAttributes(DestFilename, FileAttributes.Normal); | |
File.Delete(DestFilename); | |
} | |
// make the subdirectory if needed | |
string DestSubdir = Path.GetDirectoryName(DestFilename); | |
if (!Directory.Exists(DestSubdir)) | |
{ | |
Directory.CreateDirectory(DestSubdir); | |
} | |
File.Copy(Filename, DestFilename); | |
// remove any read only flags and keep timestamp | |
FileInfo DestFileInfo = new FileInfo(DestFilename); | |
DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly; | |
File.SetLastWriteTimeUtc(DestFilename, File.GetLastWriteTimeUtc(Filename)); | |
} | |
} | |
} | |
private static void DeleteFiles(string Filespec) | |
{ | |
string BaseDir = Path.GetDirectoryName(Filespec); | |
string Mask = Path.GetFileName(Filespec); | |
if (!Directory.Exists(BaseDir)) | |
{ | |
return; | |
} | |
string[] Files = Directory.GetFiles(BaseDir, Mask, SearchOption.TopDirectoryOnly); | |
foreach (string Filename in Files) | |
{ | |
File.SetAttributes(Filename, FileAttributes.Normal); | |
File.Delete(Filename); | |
Log.TraceInformation("\nDeleted file {0}", Filename); | |
} | |
} | |
private void AddAttribute(XElement Element, string Name, string Value) | |
{ | |
XAttribute Attribute; | |
int Index = Name.IndexOf(":"); | |
if (Index >= 0) | |
{ | |
Name = Name.Substring(Index + 1); | |
Attribute = Element.Attribute(XMLNameSpace + Name); | |
} | |
else | |
{ | |
Attribute = Element.Attribute(Name); | |
} | |
if (Attribute != null) | |
{ | |
Attribute.SetValue(Value); | |
} | |
else | |
{ | |
if (Index >= 0) | |
{ | |
Element.Add(new XAttribute(XMLNameSpace + Name, Value)); | |
} | |
else | |
{ | |
Element.Add(new XAttribute(Name, Value)); | |
} | |
} | |
} | |
private void RemoveAttribute(XElement Element, string Name) | |
{ | |
XAttribute Attribute; | |
int Index = Name.IndexOf(":"); | |
if (Index >= 0) | |
{ | |
Name = Name.Substring(Index + 1); | |
Attribute = Element.Attribute(XMLNameSpace + Name); | |
} | |
else | |
{ | |
Attribute = Element.Attribute(Name); | |
} | |
if (Attribute != null) | |
{ | |
Attribute.Remove(); | |
} | |
} | |
private void AddElements(XElement Target, XElement Source) | |
{ | |
if (Source.HasElements) | |
{ | |
foreach (XElement Index in Source.Elements()) | |
{ | |
// if (Target.Element(Index.Name) == null) | |
{ | |
Target.Add(Index); | |
} | |
} | |
} | |
} | |
public string ProcessPluginNode(string Architecture, string NodeName, string Input) | |
{ | |
return ProcessPluginNode(Architecture, NodeName, Input, ref XMLDummy); | |
} | |
public string ProcessPluginNode(string Architecture, string NodeName, string Input, ref XDocument XMLWork) | |
{ | |
// add all instructions to execution list | |
Stack<XElement> ExecutionStack = new Stack<XElement>(); | |
XElement StartNode = XDoc.Root.Element(NodeName); | |
if (StartNode != null) | |
{ | |
foreach (XElement Instruction in StartNode.Elements().Reverse()) | |
{ | |
ExecutionStack.Push(Instruction); | |
} | |
} | |
if (ExecutionStack.Count == 0) | |
{ | |
return Input; | |
} | |
Stack<UPLContext> ContextStack = new Stack<UPLContext>(); | |
UPLContext CurrentContext = GlobalContext; | |
// update Output in global context | |
GlobalContext.StringVariables["Output"] = Input; | |
Stack<XElement> ElementStack = new Stack<XElement>(); | |
XElement CurrentElement = XMLWork.Elements().First(); | |
// run the instructions | |
while (ExecutionStack.Count > 0) | |
{ | |
XElement Node = ExecutionStack.Pop(); | |
if (bGlobalTrace || CurrentContext.bTrace) | |
{ | |
Log.TraceInformation("Execute: '{0}'", TraceNodeString(Node)); | |
} | |
switch (Node.Name.ToString()) | |
{ | |
case "trace": | |
{ | |
string Enable = GetAttribute(CurrentContext, Node, "enable"); | |
if (Enable != null) | |
{ | |
CurrentContext.bTrace = StringToBool(Enable); | |
if (!bGlobalTrace && CurrentContext.bTrace) | |
{ | |
Log.TraceInformation("Context: '{0}' using Architecture='{1}', NodeName='{2}', Input='{3}'", CurrentContext.StringVariables["PluginDir"], Architecture, NodeName, Input); | |
} | |
} | |
} | |
break; | |
case "dumpvars": | |
{ | |
if (!bGlobalTrace && !CurrentContext.bTrace) | |
{ | |
Log.TraceInformation("Context: '{0}' using Architecture='{1}', NodeName='{2}', Input='{3}'", CurrentContext.StringVariables["PluginDir"], Architecture, NodeName, Input); | |
} | |
Log.TraceInformation("Variables:\n{0}", DumpContext(CurrentContext)); | |
} | |
break; | |
case "Context": | |
{ | |
ContextStack.Push(CurrentContext); | |
string index = GetAttribute(CurrentContext, Node, "index"); | |
CurrentContext = Contexts[Architecture + "_" + index]; | |
ExecutionStack.Push(new XElement("PopContext")); | |
foreach (XElement instruction in Node.Elements().Reverse()) | |
{ | |
ExecutionStack.Push(instruction); | |
} | |
if (bGlobalTrace || CurrentContext.bTrace) | |
{ | |
Log.TraceInformation("Context: '{0}' using Architecture='{1}', NodeName='{2}', Input='{3}'", CurrentContext.StringVariables["PluginDir"], Architecture, NodeName, Input); | |
} | |
} | |
break; | |
case "PopContext": | |
{ | |
CurrentContext = ContextStack.Pop(); | |
} | |
break; | |
case "PopElement": | |
{ | |
CurrentElement = ElementStack.Pop(); | |
} | |
break; | |
case "isArch": | |
{ | |
string arch = GetAttribute(CurrentContext, Node, "arch"); | |
if (arch != null && arch.Equals(Architecture)) | |
{ | |
foreach (XElement instruction in Node.Elements().Reverse()) | |
{ | |
ExecutionStack.Push(instruction); | |
} | |
} | |
} | |
break; | |
case "isDistribution": | |
{ | |
bool Result = false; | |
if (GetCondition(CurrentContext, Node, "Distribution", out Result)) | |
{ | |
if (Result) | |
{ | |
foreach (XElement Instruction in Node.Elements().Reverse()) | |
{ | |
ExecutionStack.Push(Instruction); | |
} | |
} | |
} | |
} | |
break; | |
case "if": | |
{ | |
bool Result; | |
if (GetCondition(CurrentContext, Node, GetAttribute(CurrentContext, Node, "condition"), out Result)) | |
{ | |
XElement ResultNode = Node.Element(Result ? "true" : "false"); | |
if (ResultNode != null) | |
{ | |
foreach (XElement Instruction in ResultNode.Elements().Reverse()) | |
{ | |
ExecutionStack.Push(Instruction); | |
} | |
} | |
} | |
} | |
break; | |
case "while": | |
{ | |
bool Result; | |
if (GetCondition(CurrentContext, Node, GetAttribute(CurrentContext, Node, "condition"), out Result)) | |
{ | |
if (Result) | |
{ | |
IEnumerable<XElement> ResultNode = Node.Elements(); | |
if (ResultNode != null) | |
{ | |
ExecutionStack.Push(Node); | |
foreach (XElement Instruction in ResultNode.Reverse()) | |
{ | |
ExecutionStack.Push(Instruction); | |
} | |
} | |
} | |
} | |
} | |
break; | |
case "return": | |
{ | |
while (ExecutionStack.Count > 0) | |
{ | |
ExecutionStack.Pop(); | |
} | |
} | |
break; | |
case "break": | |
{ | |
// remove up to while (or acts like a return if outside by removing everything) | |
while (ExecutionStack.Count > 0) | |
{ | |
Node = ExecutionStack.Pop(); | |
if (Node.Name.ToString().Equals("while")) | |
{ | |
break; | |
} | |
} | |
} | |
break; | |
case "continue": | |
{ | |
// remove up to while (or acts like a return if outside by removing everything) | |
while (ExecutionStack.Count > 0) | |
{ | |
Node = ExecutionStack.Pop(); | |
if (Node.Name.ToString().Equals("while")) | |
{ | |
ExecutionStack.Push(Node); | |
break; | |
} | |
} | |
} | |
break; | |
case "log": | |
{ | |
string Text = GetAttribute(CurrentContext, Node, "text"); | |
if (Text != null) | |
{ | |
Log.TraceInformation("{0}", Text); | |
} | |
} | |
break; | |
case "loopElements": | |
{ | |
string Tag = GetAttribute(CurrentContext, Node, "tag"); | |
ElementStack.Push(CurrentElement); | |
ExecutionStack.Push(new XElement("PopElement")); | |
IEnumerable<XElement> WorkList = (Tag == "$") ? CurrentElement.Elements().Reverse() : CurrentElement.Descendants(Tag).Reverse(); | |
foreach (XElement WorkNode in WorkList) | |
{ | |
foreach (XElement Instruction in Node.Elements().Reverse()) | |
{ | |
ExecutionStack.Push(Instruction); | |
} | |
ElementStack.Push(WorkNode); | |
ExecutionStack.Push(new XElement("PopElement")); | |
} | |
} | |
break; | |
case "addAttribute": | |
{ | |
string Tag = GetAttribute(CurrentContext, Node, "tag"); | |
string Name = GetAttribute(CurrentContext, Node, "name"); | |
string Value = GetAttribute(CurrentContext, Node, "value"); | |
if (Tag != null && Name != null && Value != null) | |
{ | |
if (Tag.StartsWith("$")) | |
{ | |
XElement Target = CurrentElement; | |
if (Tag.Length > 1) | |
{ | |
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target)) | |
{ | |
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node)); | |
continue; | |
} | |
} | |
} | |
AddAttribute(Target, Name, Value); | |
} | |
else | |
{ | |
if (CurrentElement.Name.ToString().Equals(Tag)) | |
{ | |
AddAttribute(CurrentElement, Name, Value); | |
} | |
foreach (XElement WorkNode in CurrentElement.Descendants(Tag)) | |
{ | |
AddAttribute(WorkNode, Name, Value); | |
} | |
} | |
} | |
} | |
break; | |
case "removeAttribute": | |
{ | |
string Tag = GetAttribute(CurrentContext, Node, "tag"); | |
string Name = GetAttribute(CurrentContext, Node, "name"); | |
if (Tag != null && Name != null) | |
{ | |
if (Tag.StartsWith("$")) | |
{ | |
XElement Target = CurrentElement; | |
if (Tag.Length > 1) | |
{ | |
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target)) | |
{ | |
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node)); | |
continue; | |
} | |
} | |
} | |
RemoveAttribute(Target, Name); | |
} | |
else | |
{ | |
if (CurrentElement.Name.ToString().Equals(Tag)) | |
{ | |
RemoveAttribute(CurrentElement, Name); | |
} | |
foreach (XElement WorkNode in CurrentElement.Descendants(Tag)) | |
{ | |
RemoveAttribute(WorkNode, Name); | |
} | |
} | |
} | |
} | |
break; | |
case "addPermission": | |
{ | |
string Name = GetAttributeWithNamespace(CurrentContext, Node, XMLNameSpace, "name"); | |
if (Name != null) | |
{ | |
// make sure it isn't already added | |
bool bFound = false; | |
foreach (XElement Element in XMLWork.Descendants("uses-permission")) | |
{ | |
XAttribute Attribute = Element.Attribute(XMLNameSpace + "name"); | |
if (Attribute != null) | |
{ | |
if (Attribute.Value == Name) | |
{ | |
bFound = true; | |
break; | |
} | |
} | |
} | |
// add it if not found | |
if (!bFound) | |
{ | |
// Get the attributes and apply any variable expansion needed | |
List<XAttribute> AttributeList = Node.Attributes().ToList(); | |
foreach (XAttribute Attribute in AttributeList) | |
{ | |
string NewValue = ExpandVariables(CurrentContext, Attribute.Value); | |
Attribute.SetValue(NewValue); | |
} | |
XMLWork.Element("manifest").Add(new XElement("uses-permission", AttributeList)); | |
} | |
} | |
} | |
break; | |
case "removePermission": | |
{ | |
string Name = GetAttributeWithNamespace(CurrentContext, Node, XMLNameSpace, "name"); | |
if (Name != null) | |
{ | |
foreach (XElement Element in XMLWork.Descendants("uses-permission")) | |
{ | |
XAttribute Attribute = Element.Attribute(XMLNameSpace + "name"); | |
if (Attribute != null) | |
{ | |
if (Attribute.Value == Name) | |
{ | |
Element.Remove(); | |
break; | |
} | |
} | |
} | |
} | |
} | |
break; | |
case "addFeature": | |
{ | |
string Name = GetAttributeWithNamespace(CurrentContext, Node, XMLNameSpace, "name"); | |
if (Name != null) | |
{ | |
// make sure it isn't already added | |
bool bFound = false; | |
foreach (XElement Element in XMLWork.Descendants("uses-feature")) | |
{ | |
XAttribute Attribute = Element.Attribute(XMLNameSpace + "name"); | |
if (Attribute != null) | |
{ | |
if (Attribute.Value == Name) | |
{ | |
bFound = true; | |
break; | |
} | |
} | |
} | |
// add it if not found | |
if (!bFound) | |
{ | |
// Get the attributes and apply any variable expansion needed | |
List<XAttribute> AttributeList = Node.Attributes().ToList(); | |
foreach (XAttribute Attribute in AttributeList) | |
{ | |
string NewValue = ExpandVariables(CurrentContext, Attribute.Value); | |
Attribute.SetValue(NewValue); | |
} | |
XMLWork.Element("manifest").Add(new XElement("uses-feature", AttributeList)); | |
} | |
} | |
} | |
break; | |
case "removeFeature": | |
{ | |
string Name = GetAttributeWithNamespace(CurrentContext, Node, XMLNameSpace, "name"); | |
if (Name != null) | |
{ | |
foreach (XElement Element in XMLWork.Descendants("uses-feature")) | |
{ | |
XAttribute Attribute = Element.Attribute(XMLNameSpace + "name"); | |
if (Attribute != null) | |
{ | |
if (Attribute.Value == Name) | |
{ | |
Element.Remove(); | |
break; | |
} | |
} | |
} | |
} | |
} | |
break; | |
case "addLibrary": | |
{ | |
string Name = GetAttributeWithNamespace(CurrentContext, Node, XMLNameSpace, "name"); | |
if (Name != null) | |
{ | |
// make sure it isn't already added | |
bool bFound = false; | |
foreach (XElement Element in XMLWork.Descendants("uses-library")) | |
{ | |
XAttribute Attribute = Element.Attribute(XMLNameSpace + "name"); | |
if (Attribute != null) | |
{ | |
if (Attribute.Value == Name) | |
{ | |
bFound = true; | |
break; | |
} | |
} | |
} | |
// add it if not found | |
if (!bFound) | |
{ | |
// Get the attributes and apply any variable expansion needed | |
List<XAttribute> AttributeList = Node.Attributes().ToList(); | |
foreach (XAttribute Attribute in AttributeList) | |
{ | |
string NewValue = ExpandVariables(CurrentContext, Attribute.Value); | |
Attribute.SetValue(NewValue); | |
} | |
XMLWork.Element("manifest").Element("application").Add(new XElement("uses-library", AttributeList)); | |
} | |
} | |
} | |
break; | |
case "removeLibrary": | |
{ | |
string Name = GetAttributeWithNamespace(CurrentContext, Node, XMLNameSpace, "name"); | |
if (Name != null) | |
{ | |
foreach (XElement Element in XMLWork.Descendants("uses-library")) | |
{ | |
XAttribute Attribute = Element.Attribute(XMLNameSpace + "name"); | |
if (Attribute != null) | |
{ | |
if (Attribute.Value == Name) | |
{ | |
Element.Remove(); | |
break; | |
} | |
} | |
} | |
} | |
} | |
break; | |
case "removeElement": | |
{ | |
string Tag = GetAttribute(CurrentContext, Node, "tag"); | |
bool bOnce = StringToBool(GetAttribute(CurrentContext, Node, "once", true, false)); | |
if (Tag != null) | |
{ | |
if (Tag == "$") | |
{ | |
XElement Parent = CurrentElement.Parent; | |
CurrentElement.Remove(); | |
CurrentElement = Parent; | |
} | |
else | |
{ | |
// use a list since Remove() may modify it | |
foreach (XElement Element in XMLWork.Descendants(Tag).ToList()) | |
{ | |
Element.Remove(); | |
if (bOnce) | |
{ | |
break; | |
} | |
} | |
} | |
} | |
} | |
break; | |
case "addElement": | |
{ | |
string Tag = GetAttribute(CurrentContext, Node, "tag"); | |
string Name = GetAttribute(CurrentContext, Node, "name"); | |
bool bOnce = StringToBool(GetAttribute(CurrentContext, Node, "once", true, false)); | |
if (Tag != null && Name != null) | |
{ | |
XElement Element; | |
if (!CurrentContext.ElementVariables.TryGetValue(Name, out Element)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Name, out Element)) | |
{ | |
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Name, TraceNodeString(Node)); | |
continue; | |
} | |
} | |
if (Tag.StartsWith("$")) | |
{ | |
XElement Target = CurrentElement; | |
if (Tag.Length > 1) | |
{ | |
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target)) | |
{ | |
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node)); | |
continue; | |
} | |
} | |
} | |
Target.Add(new XElement(Element)); | |
} | |
else | |
{ | |
if (CurrentElement.Name.ToString().Equals(Tag)) | |
{ | |
CurrentElement.Add(new XElement(Element)); | |
} | |
// make sure we don't recurse forever if Tag is in Element | |
List<XElement> AddSet = new List<XElement>(); | |
foreach (XElement WorkNode in CurrentElement.Descendants(Tag)) | |
{ | |
AddSet.Add(WorkNode); | |
if (bOnce) | |
{ | |
break; | |
} | |
} | |
foreach (XElement WorkNode in AddSet) | |
{ | |
WorkNode.Add(new XElement(Element)); | |
} | |
} | |
} | |
} | |
break; | |
case "addElements": | |
{ | |
string Tag = GetAttribute(CurrentContext, Node, "tag"); | |
bool bOnce = StringToBool(GetAttribute(CurrentContext, Node, "once", true, false)); | |
if (Tag != null) | |
{ | |
if (Tag.StartsWith("$")) | |
{ | |
XElement Target = CurrentElement; | |
if (Tag.Length > 1) | |
{ | |
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Target)) | |
{ | |
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node)); | |
continue; | |
} | |
} | |
} | |
AddElements(Target, Node); | |
} | |
else | |
{ | |
if (CurrentElement.Name.ToString().Equals(Tag)) | |
{ | |
AddElements(CurrentElement, Node); | |
} | |
// make sure we don't recurse forever if Tag is in Node | |
List<XElement> AddSet = new List<XElement>(); | |
foreach (XElement WorkNode in CurrentElement.Descendants(Tag)) | |
{ | |
AddSet.Add(WorkNode); | |
if (bOnce) | |
{ | |
break; | |
} | |
} | |
foreach (XElement WorkNode in AddSet) | |
{ | |
AddElements(WorkNode, Node); | |
} | |
} | |
} | |
} | |
break; | |
case "insert": | |
{ | |
if (Node.HasElements) | |
{ | |
foreach (XElement Element in Node.Elements()) | |
{ | |
string Value = Element.ToString().Replace(" " + XMLRootDefinition + " ", ""); | |
GlobalContext.StringVariables["Output"] += Value + "\n"; | |
} | |
} | |
else | |
{ | |
string Value = Node.Value.ToString(); | |
// trim trailing tabs | |
int Index = Value.Length; | |
while (Index > 0 && Value[Index - 1] == '\t') | |
{ | |
Index--; | |
} | |
if (Index < Value.Length) | |
{ | |
Value = Value.Substring(0, Index); | |
} | |
// trim leading newlines | |
Index = 0; | |
while (Index < Value.Length && Value[Index] == '\n') | |
{ | |
Index++; | |
} | |
if (Index < Value.Length) | |
{ | |
GlobalContext.StringVariables["Output"] += Value.Substring(Index); | |
} | |
} | |
} | |
break; | |
case "insertNewline": | |
GlobalContext.StringVariables["Output"] += "\n"; | |
break; | |
case "insertValue": | |
{ | |
string Value = GetAttribute(CurrentContext, Node, "value"); | |
if (Value != null) | |
{ | |
GlobalContext.StringVariables["Output"] += Value; | |
} | |
} | |
break; | |
case "replace": | |
{ | |
string Find = GetAttribute(CurrentContext, Node, "find"); | |
string With = GetAttribute(CurrentContext, Node, "with"); | |
if (Find != null && With != null) | |
{ | |
GlobalContext.StringVariables["Output"] = GlobalContext.StringVariables["Output"].Replace(Find, With); | |
} | |
} | |
break; | |
case "copyFile": | |
{ | |
string Src = GetAttribute(CurrentContext, Node, "src"); | |
string Dst = GetAttribute(CurrentContext, Node, "dst"); | |
bool bForce = StringToBool(GetAttribute(CurrentContext, Node, "force", true, false, "true")); | |
if (Src != null && Dst != null) | |
{ | |
if (File.Exists(Src)) | |
{ | |
// check to see if newer than last time we copied | |
if (bForce || FilesAreDifferent(Src, Dst)) | |
{ | |
if (File.Exists(Dst)) | |
{ | |
File.SetAttributes(Dst, FileAttributes.Normal); | |
File.Delete(Dst); | |
} | |
Directory.CreateDirectory(Path.GetDirectoryName(Dst)); | |
File.Copy(Src, Dst, true); | |
Log.TraceInformation("\nFile {0} copied to {1}", Src, Dst); | |
// remove any read only flags and keep timestamp | |
FileInfo DestFileInfo = new FileInfo(Dst); | |
DestFileInfo.Attributes = DestFileInfo.Attributes & ~FileAttributes.ReadOnly; | |
File.SetLastWriteTimeUtc(Dst, File.GetLastWriteTimeUtc(Src)); | |
} | |
} | |
else | |
{ | |
Log.TraceInformation("\nFile {0} does not exist, not copied!", Src); | |
} | |
} | |
} | |
break; | |
case "copyDir": | |
{ | |
string Src = GetAttribute(CurrentContext, Node, "src"); | |
string Dst = GetAttribute(CurrentContext, Node, "dst"); | |
bool bForce = StringToBool(GetAttribute(CurrentContext, Node, "force", true, false, "true")); | |
if (Src != null && Dst != null) | |
{ | |
CopyFileDirectory(Src, Dst, bForce); | |
Log.TraceInformation("\nDirectory {0} copied to {1}", Src, Dst, bForce); | |
} | |
} | |
break; | |
case "deleteFiles": | |
{ | |
string Filespec = GetAttribute(CurrentContext, Node, "filespec"); | |
if (Filespec != null) | |
{ | |
if (Filespec.Contains(":") || Filespec.Contains("..")) | |
{ | |
Log.TraceInformation("\nFilespec {0} not allowed; ignored.", Filespec); | |
} | |
else | |
{ | |
// force relative to BuildDir (and only from global context so someone doesn't try to be clever) | |
DeleteFiles(Path.Combine(GlobalContext.StringVariables["BuildDir"], Filespec)); | |
} | |
} | |
} | |
break; | |
case "loadLibrary": | |
{ | |
string Name = GetAttribute(CurrentContext, Node, "name"); | |
string FailMsg = GetAttribute(CurrentContext, Node, "failmsg", true, false); | |
if (Name != null) | |
{ | |
string Work = "\t\ttry\n" + | |
"\t\t{\n" + | |
"\t\t\tSystem.loadLibrary(\"" + Name + "\");\n" + | |
"\t\t}\n" + | |
"\t\tcatch (java.lang.UnsatisfiedLinkError e)\n" + | |
"\t\t{\n"; | |
if (FailMsg != null) | |
{ | |
Work += "\t\t\tLog.debug(e.toString());\n"; | |
Work += "\t\t\tLog.debug(\"" + FailMsg + "\");\n"; | |
} | |
GlobalContext.StringVariables["Output"] += Work + "\t\t}\n"; | |
} | |
} | |
break; | |
case "setBool": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "false"); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = StringToBool(Value); | |
} | |
} | |
break; | |
case "setBoolEnvVarDefined": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value"); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = (Value != null && Environment.ExpandEnvironmentVariables(Value).Length > 0); | |
} | |
} | |
break; | |
case "setBoolFrom": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "false"); | |
if (Result != null) | |
{ | |
Value = ExpandVariables(CurrentContext, "$B(" + Value + ")"); | |
CurrentContext.BoolVariables[Result] = StringToBool(Value); | |
} | |
} | |
break; | |
case "setBoolFromProperty": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Ini = GetAttribute(CurrentContext, Node, "ini"); | |
string Section = GetAttribute(CurrentContext, Node, "section"); | |
string Property = GetAttribute(CurrentContext, Node, "property"); | |
string DefaultVal = GetAttribute(CurrentContext, Node, "default", true, false, "false"); | |
if (Result != null && Ini != null && Section != null && Property != null) | |
{ | |
bool Value = StringToBool(DefaultVal); | |
ConfigCacheIni_UPL ConfigIni = GetConfigCacheIni_UPL(Ini); | |
if (ConfigIni != null) | |
{ | |
if (!ConfigIni.GetBool(Section, Property, out Value)) | |
{ | |
Value = StringToBool(DefaultVal); | |
} | |
} | |
CurrentContext.BoolVariables[Result] = Value; | |
} | |
} | |
break; | |
case "setBoolFromPropertyContains": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Ini = GetAttribute(CurrentContext, Node, "ini"); | |
string Section = GetAttribute(CurrentContext, Node, "section"); | |
string Property = GetAttribute(CurrentContext, Node, "property"); | |
string DefaultVal = GetAttribute(CurrentContext, Node, "default", true, false, "false"); | |
string Contains = GetAttribute(CurrentContext, Node, "contains", true, true, ""); | |
if (Result != null && Ini != null && Section != null && Property != null) | |
{ | |
bool Value = StringToBool(DefaultVal); | |
ConfigCacheIni_UPL ConfigIni = GetConfigCacheIni_UPL(Ini); | |
if (ConfigIni != null) | |
{ | |
List<string> StringList; | |
if (ConfigIni.GetArray(Section, Property, out StringList)) | |
{ | |
Value = false; | |
foreach (string Entry in StringList) | |
{ | |
if (Entry.Equals(Contains)) | |
{ | |
Value = true; | |
break; | |
} | |
} | |
} | |
} | |
CurrentContext.BoolVariables[Result] = Value; | |
} | |
} | |
break; | |
case "setBoolContains": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Source = GetAttribute(CurrentContext, Node, "source", true, false, ""); | |
string Find = GetAttribute(CurrentContext, Node, "find", true, false, ""); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = Source.Contains(Find); | |
} | |
} | |
break; | |
case "setBoolStartsWith": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Source = GetAttribute(CurrentContext, Node, "source", true, false, ""); | |
string Find = GetAttribute(CurrentContext, Node, "find", true, false, ""); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = Source.StartsWith(Find); | |
} | |
} | |
break; | |
case "setBoolEndsWith": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Source = GetAttribute(CurrentContext, Node, "source", true, false, ""); | |
string Find = GetAttribute(CurrentContext, Node, "find", true, false, ""); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = Source.EndsWith(Find); | |
} | |
} | |
break; | |
case "setBoolNot": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Source = GetAttribute(CurrentContext, Node, "source", true, false, "false"); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = !StringToBool(Source); | |
} | |
} | |
break; | |
case "setBoolAnd": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "false"); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "false"); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = StringToBool(Arg1) && StringToBool(Arg2); | |
} | |
} | |
break; | |
case "setBoolOr": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "false"); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "false"); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = StringToBool(Arg1) || StringToBool(Arg2); | |
} | |
} | |
break; | |
case "setBoolIsEqual": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, ""); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, ""); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = Arg1.Equals(Arg2); | |
} | |
} | |
break; | |
case "setBoolIsLess": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = (StringToInt(Arg1, Node) < StringToInt(Arg2, Node)); | |
} | |
} | |
break; | |
case "setBoolIsLessEqual": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = (StringToInt(Arg1, Node) <= StringToInt(Arg2, Node)); | |
} | |
} | |
break; | |
case "setBoolIsGreater": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = (StringToInt(Arg1, Node) > StringToInt(Arg2, Node)); | |
} | |
} | |
break; | |
case "setBoolIsGreaterEqual": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false); | |
if (Result != null) | |
{ | |
CurrentContext.BoolVariables[Result] = (StringToInt(Arg1, Node) >= StringToInt(Arg2, Node)); | |
} | |
} | |
break; | |
case "setInt": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "0"); | |
if (Result != null) | |
{ | |
CurrentContext.IntVariables[Result] = StringToInt(Value, Node); | |
} | |
} | |
break; | |
case "setIntFrom": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "0"); | |
if (Result != null) | |
{ | |
Value = ExpandVariables(CurrentContext, "$I(" + Value + ")"); | |
CurrentContext.IntVariables[Result] = StringToInt(Value, Node); | |
} | |
} | |
break; | |
case "setIntFromProperty": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Ini = GetAttribute(CurrentContext, Node, "ini"); | |
string Section = GetAttribute(CurrentContext, Node, "section"); | |
string Property = GetAttribute(CurrentContext, Node, "property"); | |
string DefaultVal = GetAttribute(CurrentContext, Node, "default", true, false, "0"); | |
if (Result != null && Ini != null && Section != null && Property != null) | |
{ | |
int Value = StringToInt(DefaultVal, Node); | |
ConfigCacheIni_UPL ConfigIni = GetConfigCacheIni_UPL(Ini); | |
if (ConfigIni != null) | |
{ | |
ConfigIni.GetInt32(Section, Property, out Value); | |
} | |
CurrentContext.IntVariables[Result] = Value; | |
} | |
} | |
break; | |
case "setIntAdd": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "0"); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "0"); | |
if (Result != null) | |
{ | |
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node) + StringToInt(Arg2, Node); | |
} | |
} | |
break; | |
case "setIntSubtract": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "0"); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "0"); | |
if (Result != null) | |
{ | |
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node) - StringToInt(Arg2, Node); | |
} | |
} | |
break; | |
case "setIntMultiply": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "1"); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "1"); | |
if (Result != null) | |
{ | |
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node) * StringToInt(Arg2, Node); | |
} | |
} | |
break; | |
case "setIntDivide": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, "1"); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, "1"); | |
if (Result != null) | |
{ | |
int Denominator = StringToInt(Arg2, Node); | |
if (Denominator == 0) | |
{ | |
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node); | |
} | |
else | |
{ | |
CurrentContext.IntVariables[Result] = StringToInt(Arg1, Node) / Denominator; | |
} | |
} | |
} | |
break; | |
case "setIntLength": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Source = GetAttribute(CurrentContext, Node, "source", true, false, ""); | |
if (Result != null) | |
{ | |
CurrentContext.IntVariables[Result] = Source.Length; | |
} | |
} | |
break; | |
case "setIntFindString": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Source = GetAttribute(CurrentContext, Node, "source", true, false, ""); | |
string Find = GetAttribute(CurrentContext, Node, "find", true, false, ""); | |
if (Result != null) | |
{ | |
CurrentContext.IntVariables[Result] = Source.IndexOf(Find); | |
} | |
} | |
break; | |
case "setString": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value", true, false, ""); | |
if (Result != null) | |
{ | |
if (Result == "Output") | |
{ | |
GlobalContext.StringVariables["Output"] = Value; | |
} | |
else | |
{ | |
CurrentContext.StringVariables[Result] = Value; | |
} | |
} | |
} | |
break; | |
case "setStringFrom": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value", true, false, "0"); | |
if (Result != null) | |
{ | |
Value = ExpandVariables(CurrentContext, "$S(" + Value + ")"); | |
CurrentContext.StringVariables[Result] = Value; | |
} | |
} | |
break; | |
case "setStringFromEnvVar": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value"); | |
if (Result != null && Value != null) | |
{ | |
CurrentContext.StringVariables[Result] = Environment.ExpandEnvironmentVariables(Value); | |
} | |
} | |
break; | |
case "setStringFromTag": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Tag = GetAttribute(CurrentContext, Node, "tag", true, false, "$"); | |
if (Result != null) | |
{ | |
XElement Element = CurrentElement; | |
if (Tag.StartsWith("$")) | |
{ | |
if (Tag.Length > 1) | |
{ | |
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element)) | |
{ | |
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node)); | |
continue; | |
} | |
} | |
} | |
} | |
CurrentContext.StringVariables[Result] = Element.Name.ToString(); | |
} | |
} | |
break; | |
case "setStringFromAttribute": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Tag = GetAttribute(CurrentContext, Node, "tag"); | |
string Name = GetAttribute(CurrentContext, Node, "name"); | |
if (Result != null && Tag != null && Name != null) | |
{ | |
XElement Element = CurrentElement; | |
if (Tag.StartsWith("$")) | |
{ | |
if (Tag.Length > 1) | |
{ | |
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element)) | |
{ | |
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node)); | |
continue; | |
} | |
} | |
} | |
} | |
XAttribute Attribute; | |
int Index = Name.IndexOf(":"); | |
if (Index >= 0) | |
{ | |
Name = Name.Substring(Index + 1); | |
Attribute = Element.Attribute(XMLNameSpace + Name); | |
} | |
else | |
{ | |
Attribute = Element.Attribute(Name); | |
} | |
CurrentContext.StringVariables[Result] = (Attribute != null) ? Attribute.Value : ""; | |
} | |
} | |
break; | |
case "setStringFromTagText": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Tag = GetAttribute(CurrentContext, Node, "tag", true, false, "$"); | |
if (Result != null) | |
{ | |
XElement Element = CurrentElement; | |
if (Tag.StartsWith("$")) | |
{ | |
if (Tag.Length > 1) | |
{ | |
if (!CurrentContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element)) | |
{ | |
if (!GlobalContext.ElementVariables.TryGetValue(Tag.Substring(1), out Element)) | |
{ | |
Log.TraceWarning("\nMissing element variable '{0}' in '{1}' (skipping instruction)", Tag, TraceNodeString(Node)); | |
continue; | |
} | |
} | |
} | |
} | |
if (Element.Value == null) | |
{ | |
Log.TraceWarning("\nExpected text in element '{0}' in '{1}' but found none (skipping instruction)", Element.Name.ToString(), TraceNodeString(Node)); | |
continue; | |
} | |
CurrentContext.StringVariables[Result] = Element.Value; | |
} | |
} | |
break; | |
case "setStringFromProperty": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Ini = GetAttribute(CurrentContext, Node, "ini"); | |
string Section = GetAttribute(CurrentContext, Node, "section"); | |
string Property = GetAttribute(CurrentContext, Node, "property"); | |
string DefaultVal = GetAttribute(CurrentContext, Node, "default", true, false, ""); | |
if (Result != null && Ini != null && Section != null && Property != null) | |
{ | |
string Value = DefaultVal; | |
ConfigCacheIni_UPL ConfigIni = GetConfigCacheIni_UPL(Ini); | |
if (ConfigIni != null) | |
{ | |
if (!ConfigIni.GetString(Section, Property, out Value)) | |
{ | |
// If the string was not found in the config, Value will have been set to an empty string | |
// Set it back to the DefaultVal | |
Value = DefaultVal; | |
} | |
} | |
if (Result == "Output") | |
{ | |
GlobalContext.StringVariables["Output"] = Value; | |
} | |
else | |
{ | |
CurrentContext.StringVariables[Result] = Value; | |
} | |
} | |
} | |
break; | |
case "setStringAdd": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Arg1 = GetAttribute(CurrentContext, Node, "arg1", true, false, ""); | |
string Arg2 = GetAttribute(CurrentContext, Node, "arg2", true, false, ""); | |
if (Result != null) | |
{ | |
string Value = Arg1 + Arg2; | |
if (Result == "Output") | |
{ | |
GlobalContext.StringVariables["Output"] = Value; | |
} | |
else | |
{ | |
CurrentContext.StringVariables[Result] = Value; | |
} | |
} | |
} | |
break; | |
case "setStringSubstring": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Source = GetAttribute(CurrentContext, Node, "source", true, false, ""); | |
string Start = GetAttribute(CurrentContext, Node, "start", true, false, "0"); | |
string Length = GetAttribute(CurrentContext, Node, "length", true, false, "0"); | |
if (Result != null && Source != null) | |
{ | |
int Index = StringToInt(Start, Node); | |
int Count = StringToInt(Length, Node); | |
Index = (Index < 0) ? 0 : (Index > Source.Length) ? Source.Length : Index; | |
Count = (Index + Count > Source.Length) ? Source.Length - Index : Count; | |
string Value = Source.Substring(Index, Count); | |
if (Result == "Output") | |
{ | |
GlobalContext.StringVariables["Output"] = Value; | |
} | |
else | |
{ | |
CurrentContext.StringVariables[Result] = Value; | |
} | |
} | |
} | |
break; | |
case "setStringReplace": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Source = GetAttribute(CurrentContext, Node, "source", true, false, ""); | |
string Find = GetAttribute(CurrentContext, Node, "find"); | |
string With = GetAttribute(CurrentContext, Node, "with", true, false, ""); | |
if (Result != null && Find != null) | |
{ | |
string Value = Source.Replace(Find, With); | |
if (Result == "Output") | |
{ | |
GlobalContext.StringVariables["Output"] = Value; | |
} | |
else | |
{ | |
CurrentContext.StringVariables[Result] = Value; | |
} | |
} | |
} | |
break; | |
case "setElement": | |
{ | |
string Result = GetAttribute(CurrentContext, Node, "result"); | |
string Value = GetAttribute(CurrentContext, Node, "value", true, false); | |
string Text = GetAttribute(CurrentContext, Node, "text", true, false); | |
string Parse = GetAttribute(CurrentContext, Node, "xml", true, false); | |
if (Result != null) | |
{ | |
if (Value != null) | |
{ | |
XElement NewElement = new XElement(Value); | |
if (Text != null) | |
{ | |
NewElement.Value = Text; | |
} | |
CurrentContext.ElementVariables[Result] = NewElement; | |
} | |
else if (Parse != null) | |
{ | |
try | |
{ | |
CurrentContext.ElementVariables[Result] = XElement.Parse(Parse); | |
} | |
catch (Exception e) | |
{ | |
Log.TraceError("\nXML parsing {0} failed! {1} (skipping instruction)", Parse, e); | |
} | |
} | |
} | |
} | |
break; | |
default: | |
Log.TraceWarning("\nUnknown command: {0}", Node.Name); | |
break; | |
} | |
} | |
return GlobalContext.StringVariables["Output"]; | |
} | |
public void Init(List<string> Architectures, bool bDistribution, string EngineDirectory, string BuildDirectory, string ProjectDirectory, string Configuration) | |
{ | |
GlobalContext.BoolVariables["Distribution"] = bDistribution; | |
GlobalContext.StringVariables["Configuration"] = Configuration; | |
GlobalContext.StringVariables["EngineDir"] = EngineDirectory.Replace("\\", "/"); | |
GlobalContext.StringVariables["BuildDir"] = BuildDirectory.Replace("\\", "/"); | |
GlobalContext.StringVariables["ProjectDir"] = ProjectDirectory.Replace("\\", "/"); | |
if (GlobalContext.StringVariables["EngineDir"].Length < 1) | |
{ | |
GlobalContext.StringVariables["EngineDir"] = "./"; | |
} | |
if (GlobalContext.StringVariables["BuildDir"].Length < 1) | |
{ | |
GlobalContext.StringVariables["BuildDir"] = "./"; | |
} | |
if (GlobalContext.StringVariables["ProjectDir"].Length < 1) | |
{ | |
GlobalContext.StringVariables["ProjectDir"] = "./"; | |
} | |
GlobalContext.StringVariables["AbsEngineDir"] = Path.GetFullPath(GlobalContext.StringVariables["EngineDir"]).Replace("\\", "/"); | |
GlobalContext.StringVariables["AbsBuildDir"] = Path.GetFullPath(GlobalContext.StringVariables["BuildDir"]).Replace("\\", "/"); | |
GlobalContext.StringVariables["AbsProjectDir"] = Path.GetFullPath(GlobalContext.StringVariables["ProjectDir"]).Replace("\\", "/"); | |
foreach (string Arch in Architectures) | |
{ | |
Log.TraceInformation("UPL Init: {0}", Arch); | |
ProcessPluginNode(Arch, "init", ""); | |
} | |
if (bGlobalTrace) | |
{ | |
Log.TraceInformation("\nVariables:\n{0}", DumpVariables()); | |
} | |
} | |
public void SetGlobalContextVariable(string VariableName, bool Value) | |
{ | |
if (VariableName != null) | |
{ | |
GlobalContext.BoolVariables[VariableName] = Value; | |
} | |
} | |
public void SetGlobalContextVariable(string VariableName, int Value) | |
{ | |
if (VariableName != null) | |
{ | |
GlobalContext.IntVariables[VariableName] = Value; | |
} | |
} | |
public void SetGlobalContextVariable(string VariableName, string Value) | |
{ | |
if (VariableName != null && Value != null) | |
{ | |
GlobalContext.StringVariables[VariableName] = Value; | |
} | |
} | |
} | |
/// <summary> | |
/// Equivalent of FConfigCacheIni_UPL. Parses ini files. This version reads ALL sections since ConfigCacheIni_UPL does NOT | |
/// </summary> | |
class ConfigCacheIni_UPL | |
{ | |
/// <summary> | |
/// Exception when parsing ini files | |
/// </summary> | |
public class IniParsingException : Exception | |
{ | |
public IniParsingException(string Message) | |
: base(Message) | |
{ } | |
public IniParsingException(string Format, params object[] Args) | |
: base(String.Format(Format, Args)) | |
{ } | |
} | |
/// <summary> | |
/// command class for being able to create config caches over and over without needing to read the ini files | |
/// </summary> | |
public class Command | |
{ | |
public string TrimmedLine; | |
} | |
class SectionCommand : Command | |
{ | |
public FileReference Filename; | |
public int LineIndex; | |
} | |
class KeyValueCommand : Command | |
{ | |
public string Key; | |
public string Value; | |
public ParseAction LastAction; | |
} | |
// cached ini files | |
static Dictionary<string, List<Command>> FileCache = new Dictionary<string, List<Command>>(); | |
static Dictionary<string, ConfigCacheIni_UPL> IniCache = new Dictionary<string, ConfigCacheIni_UPL>(); | |
static Dictionary<string, ConfigCacheIni_UPL> BaseIniCache = new Dictionary<string, ConfigCacheIni_UPL>(); | |
// static creation functions for ini files | |
public static ConfigCacheIni_UPL CreateConfigCacheIni_UPL(UnrealTargetPlatform Platform, string BaseIniName, DirectoryReference ProjectDirectory, DirectoryReference EngineDirectory = null) | |
{ | |
if (EngineDirectory == null) | |
{ | |
EngineDirectory = UnrealBuildTool.EngineDirectory; | |
} | |
// cache base ini for use as the seed for the rest | |
if (!BaseIniCache.ContainsKey(BaseIniName)) | |
{ | |
BaseIniCache.Add(BaseIniName, new ConfigCacheIni_UPL(UnrealTargetPlatform.Unknown, BaseIniName, null, EngineDirectory, EngineOnly: true)); | |
} | |
// build the new ini and cache it for later re-use | |
ConfigCacheIni_UPL BaseCache = BaseIniCache[BaseIniName]; | |
string Key = GetIniPlatformName(Platform) + BaseIniName + EngineDirectory.FullName + (ProjectDirectory != null ? ProjectDirectory.FullName : ""); | |
if (!IniCache.ContainsKey(Key)) | |
{ | |
IniCache.Add(Key, new ConfigCacheIni_UPL(Platform, BaseIniName, ProjectDirectory, EngineDirectory, BaseCache: BaseCache)); | |
} | |
return IniCache[Key]; | |
} | |
/// <summary> | |
/// List of values (or a single value) | |
/// </summary> | |
public class IniValues : List<string> | |
{ | |
public IniValues() | |
{ | |
} | |
public IniValues(IniValues Other) | |
: base(Other) | |
{ | |
} | |
public override string ToString() | |
{ | |
return String.Join(",", ToArray()); | |
} | |
} | |
/// <summary> | |
/// Ini section (map of keys and values) | |
/// </summary> | |
public class IniSection : Dictionary<string, IniValues> | |
{ | |
public IniSection() | |
: base(StringComparer.InvariantCultureIgnoreCase) | |
{ } | |
public IniSection(IniSection Other) | |
: this() | |
{ | |
foreach (KeyValuePair<string, IniValues> Pair in Other) | |
{ | |
Add(Pair.Key, new IniValues(Pair.Value)); | |
} | |
} | |
public override string ToString() | |
{ | |
return "IniSection"; | |
} | |
} | |
/// <summary> | |
/// True if we are loading a hierarchy of config files that should be merged together | |
/// </summary> | |
bool bIsMergingConfigs; | |
/// <summary> | |
/// All sections parsed from ini file | |
/// </summary> | |
Dictionary<string, IniSection> Sections; | |
/// <summary> | |
/// Constructor. Parses a single ini file. No Platform settings, no engine hierarchy. Do not use this with ini files that have hierarchy! | |
/// </summary> | |
/// <param name="Filename">The ini file to load</param> | |
public ConfigCacheIni_UPL(FileReference Filename) | |
{ | |
Init(Filename); | |
} | |
/// <summary> | |
/// Constructor. Parses ini hierarchy for the specified project. No Platform settings. | |
/// </summary> | |
/// <param name="BaseIniName">Ini name (Engine, Editor, etc)</param> | |
/// <param name="ProjectDirectory">Project path</param> | |
/// <param name="EngineDirectory"></param> | |
public ConfigCacheIni_UPL(string BaseIniName, string ProjectDirectory, string EngineDirectory = null) | |
{ | |
Init(UnrealTargetPlatform.Unknown, BaseIniName, (ProjectDirectory == null) ? null : new DirectoryReference(ProjectDirectory), (EngineDirectory == null) ? null : new DirectoryReference(EngineDirectory)); | |
} | |
/// <summary> | |
/// Constructor. Parses ini hierarchy for the specified project. No Platform settings. | |
/// </summary> | |
/// <param name="BaseIniName">Ini name (Engine, Editor, etc)</param> | |
/// <param name="ProjectDirectory">Project path</param> | |
/// <param name="EngineDirectory"></param> | |
public ConfigCacheIni_UPL(string BaseIniName, DirectoryReference ProjectDirectory, DirectoryReference EngineDirectory = null) | |
{ | |
Init(UnrealTargetPlatform.Unknown, BaseIniName, ProjectDirectory, EngineDirectory); | |
} | |
/// <summary> | |
/// Constructor. Parses ini hierarchy for the specified platform and project. | |
/// </summary> | |
/// <param name="ProjectDirectory">Project path</param> | |
/// <param name="Platform">Target platform</param> | |
/// <param name="BaseIniName">Ini name (Engine, Editor, etc)</param> | |
/// <param name="EngineDirectory"></param> | |
public ConfigCacheIni_UPL(UnrealTargetPlatform Platform, string BaseIniName, string ProjectDirectory, string EngineDirectory = null) | |
{ | |
Init(Platform, BaseIniName, (ProjectDirectory == null) ? null : new DirectoryReference(ProjectDirectory), (EngineDirectory == null) ? null : new DirectoryReference(EngineDirectory)); | |
} | |
/// <summary> | |
/// Constructor. Parses ini hierarchy for the specified platform and project. | |
/// </summary> | |
/// <param name="ProjectDirectory">Project path</param> | |
/// <param name="Platform">Target platform</param> | |
/// <param name="BaseIniName">Ini name (Engine, Editor, etc)</param> | |
/// <param name="EngineDirectory"></param> | |
/// <param name="EngineOnly"></param> | |
/// <param name="BaseCache"></param> | |
public ConfigCacheIni_UPL(UnrealTargetPlatform Platform, string BaseIniName, DirectoryReference ProjectDirectory, DirectoryReference EngineDirectory = null, bool EngineOnly = false, ConfigCacheIni_UPL BaseCache = null) | |
{ | |
Init(Platform, BaseIniName, ProjectDirectory, EngineDirectory, EngineOnly, BaseCache); | |
} | |
private void InitCommon() | |
{ | |
Sections = new Dictionary<string, IniSection>(StringComparer.InvariantCultureIgnoreCase); | |
} | |
private void Init(FileReference IniFileName) | |
{ | |
InitCommon(); | |
bIsMergingConfigs = false; | |
ParseIniFile(IniFileName); | |
} | |
private void Init(UnrealTargetPlatform Platform, string BaseIniName, DirectoryReference ProjectDirectory, DirectoryReference EngineDirectory, bool EngineOnly = false, ConfigCacheIni_UPL BaseCache = null) | |
{ | |
InitCommon(); | |
bIsMergingConfigs = true; | |
if (EngineDirectory == null) | |
{ | |
EngineDirectory = UnrealBuildTool.EngineDirectory; | |
} | |
if (BaseCache != null) | |
{ | |
foreach (KeyValuePair<string, IniSection> Pair in BaseCache.Sections) | |
{ | |
Sections.Add(Pair.Key, new IniSection(Pair.Value)); | |
} | |
} | |
if (EngineOnly) | |
{ | |
foreach (FileReference IniFileName in EnumerateEngineIniFileNames(EngineDirectory, BaseIniName)) | |
{ | |
if (FileReference.Exists(IniFileName)) | |
{ | |
ParseIniFile(IniFileName); | |
} | |
} | |
} | |
else | |
{ | |
foreach (FileReference IniFileName in EnumerateCrossPlatformIniFileNames(ProjectDirectory, EngineDirectory, Platform, BaseIniName, BaseCache != null)) | |
{ | |
if (FileReference.Exists(IniFileName)) | |
{ | |
ParseIniFile(IniFileName); | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// Finds a section in INI | |
/// </summary> | |
/// <param name="SectionName"></param> | |
/// <returns>Found section or null</returns> | |
public IniSection FindSection(string SectionName) | |
{ | |
IniSection Section; | |
Sections.TryGetValue(SectionName, out Section); | |
return Section; | |
} | |
/// <summary> | |
/// Finds values associated with the specified key (does not copy the list) | |
/// </summary> | |
private bool GetList(string SectionName, string Key, out IniValues Value) | |
{ | |
bool Result = false; | |
IniSection Section = FindSection(SectionName); | |
Value = null; | |
if (Section != null) | |
{ | |
if (Section.TryGetValue(Key, out Value)) | |
{ | |
Result = true; | |
} | |
} | |
return Result; | |
} | |
/// <summary> | |
/// Gets all values associated with the specified key | |
/// </summary> | |
/// <param name="SectionName">Section where the key is located</param> | |
/// <param name="Key">Key name</param> | |
/// <param name="Value">Copy of the list containing all values associated with the specified key</param> | |
/// <returns>True if the key exists</returns> | |
public bool GetArray(string SectionName, string Key, out List<string> Value) | |
{ | |
Value = null; | |
IniValues ValueList; | |
bool Result = GetList(SectionName, Key, out ValueList); | |
if (Result) | |
{ | |
Value = new List<string>(ValueList); | |
} | |
return Result; | |
} | |
/// <summary> | |
/// Gets a single string value associated with the specified key. | |
/// </summary> | |
/// <param name="SectionName">Section name</param> | |
/// <param name="Key">Key name</param> | |
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param> | |
/// <returns>True if the key exists</returns> | |
public bool GetString(string SectionName, string Key, out string Value) | |
{ | |
Value = String.Empty; | |
IniValues ValueList; | |
bool Result = GetList(SectionName, Key, out ValueList); | |
if (Result && ValueList != null && ValueList.Count > 0) | |
{ | |
Value = ValueList[0]; | |
Result = true; | |
} | |
else | |
{ | |
Result = false; | |
} | |
return Result; | |
} | |
/// <summary> | |
/// Gets a single bool value associated with the specified key. | |
/// </summary> | |
/// <param name="SectionName">Section name</param> | |
/// <param name="Key">Key name</param> | |
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param> | |
/// <returns>True if the key exists</returns> | |
public bool GetBool(string SectionName, string Key, out bool Value) | |
{ | |
Value = false; | |
string TextValue; | |
bool Result = GetString(SectionName, Key, out TextValue); | |
if (Result) | |
{ | |
// C# Boolean type expects "False" or "True" but since we're not case sensitive, we need to suppor that manually | |
if (String.Compare(TextValue, "true", true) == 0 || String.Compare(TextValue, "1") == 0) | |
{ | |
Value = true; | |
} | |
else if (String.Compare(TextValue, "false", true) == 0 || String.Compare(TextValue, "0") == 0) | |
{ | |
Value = false; | |
} | |
else | |
{ | |
// Failed to parse | |
Result = false; | |
} | |
} | |
return Result; | |
} | |
/// <summary> | |
/// Gets a single Int32 value associated with the specified key. | |
/// </summary> | |
/// <param name="SectionName">Section name</param> | |
/// <param name="Key">Key name</param> | |
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param> | |
/// <returns>True if the key exists</returns> | |
public bool GetInt32(string SectionName, string Key, out int Value) | |
{ | |
Value = 0; | |
string TextValue; | |
bool Result = GetString(SectionName, Key, out TextValue); | |
if (Result) | |
{ | |
Result = Int32.TryParse(TextValue, out Value); | |
} | |
return Result; | |
} | |
/// <summary> | |
/// Gets a single GUID value associated with the specified key. | |
/// </summary> | |
/// <param name="SectionName">Section name</param> | |
/// <param name="Key">Key name</param> | |
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param> | |
/// <returns>True if the key exists</returns> | |
public bool GetGUID(string SectionName, string Key, out Guid Value) | |
{ | |
Value = Guid.Empty; | |
string TextValue; | |
bool Result = GetString(SectionName, Key, out TextValue); | |
if (Result) | |
{ | |
string HexString = ""; | |
if (TextValue.Contains("A=") && TextValue.Contains("B=") && TextValue.Contains("C=") && TextValue.Contains("D=")) | |
{ | |
char[] Separators = new char[] { '(', ')', '=', ',', ' ', 'A', 'B', 'C', 'D' }; | |
string[] ComponentValues = TextValue.Split(Separators, StringSplitOptions.RemoveEmptyEntries); | |
if (ComponentValues.Length == 4) | |
{ | |
for (int ComponentIndex = 0; ComponentIndex < 4; ComponentIndex++) | |
{ | |
int IntegerValue; | |
Result &= Int32.TryParse(ComponentValues[ComponentIndex], out IntegerValue); | |
HexString += IntegerValue.ToString("X8"); | |
} | |
} | |
} | |
else | |
{ | |
HexString = TextValue; | |
} | |
try | |
{ | |
Value = Guid.ParseExact(HexString, "N"); | |
Result = true; | |
} | |
catch (Exception) | |
{ | |
Result = false; | |
} | |
} | |
return Result; | |
} | |
/// <summary> | |
/// Gets a single float value associated with the specified key. | |
/// </summary> | |
/// <param name="SectionName">Section name</param> | |
/// <param name="Key">Key name</param> | |
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param> | |
/// <returns>True if the key exists</returns> | |
public bool GetSingle(string SectionName, string Key, out float Value) | |
{ | |
Value = 0.0f; | |
string TextValue; | |
bool Result = GetString(SectionName, Key, out TextValue); | |
if (Result) | |
{ | |
Result = Single.TryParse(TextValue, out Value); | |
} | |
return Result; | |
} | |
/// <summary> | |
/// Gets a single double value associated with the specified key. | |
/// </summary> | |
/// <param name="SectionName">Section name</param> | |
/// <param name="Key">Key name</param> | |
/// <param name="Value">Value associated with the specified key. If the key has more than one value, only the first one is returned</param> | |
/// <returns>True if the key exists</returns> | |
public bool GetDouble(string SectionName, string Key, out double Value) | |
{ | |
Value = 0.0; | |
string TextValue; | |
bool Result = GetString(SectionName, Key, out TextValue); | |
if (Result) | |
{ | |
Result = Double.TryParse(TextValue, out Value); | |
} | |
return Result; | |
} | |
private static bool ExtractPath(string Source, out string Path) | |
{ | |
int start = Source.IndexOf('"'); | |
int end = Source.LastIndexOf('"'); | |
if (start != 1 && end != -1 && start < end) | |
{ | |
++start; | |
Path = Source.Substring(start, end - start); | |
return true; | |
} | |
else | |
{ | |
Path = ""; | |
} | |
return false; | |
} | |
public bool GetPath(string SectionName, string Key, out string Value) | |
{ | |
string temp; | |
if (GetString(SectionName, Key, out temp)) | |
{ | |
return ExtractPath(temp, out Value); | |
} | |
else | |
{ | |
Value = ""; | |
} | |
return false; | |
} | |
/// <summary> | |
/// List of actions that can be performed on a single line from ini file | |
/// </summary> | |
enum ParseAction | |
{ | |
None, | |
New, | |
Add, | |
Remove | |
} | |
/// <summary> | |
/// Checks what action should be performed on a single line from ini file | |
/// </summary> | |
private ParseAction GetActionForLine(ref string Line) | |
{ | |
if (String.IsNullOrEmpty(Line) || Line.StartsWith(";") || Line.StartsWith("//")) | |
{ | |
return ParseAction.None; | |
} | |
else if (Line.StartsWith("-")) | |
{ | |
Line = Line.Substring(1).TrimStart(); | |
return ParseAction.Remove; | |
} | |
else if (Line.StartsWith("+")) | |
{ | |
Line = Line.Substring(1).TrimStart(); | |
return ParseAction.Add; | |
} | |
else | |
{ | |
// We use Add rather than New when we're not merging config files together in order | |
// to mimic the behavior of the C++ config cache when loading a single file | |
return (bIsMergingConfigs) ? ParseAction.New : ParseAction.Add; | |
} | |
} | |
/// <summary> | |
/// Loads and parses ini file. | |
/// </summary> | |
public void ParseIniFile(FileReference Filename) | |
{ | |
String[] IniLines = null; | |
List<Command> Commands = null; | |
if (!FileCache.ContainsKey(Filename.FullName)) | |
{ | |
try | |
{ | |
IniLines = File.ReadAllLines(Filename.FullName); | |
Commands = new List<Command>(); | |
FileCache.Add(Filename.FullName, Commands); | |
} | |
catch (Exception ex) | |
{ | |
Log.TraceInformation("Error reading ini file: " + Filename + " Exception: " + ex.Message); | |
} | |
} | |
else | |
{ | |
Commands = FileCache[Filename.FullName]; | |
} | |
if (IniLines != null && Commands != null) | |
{ | |
IniSection CurrentSection = null; | |
// Line Index for exceptions | |
int LineIndex = 1; | |
bool bMultiLine = false; | |
string SingleValue = ""; | |
string Key = ""; | |
ParseAction LastAction = ParseAction.None; | |
// Parse each line | |
foreach (string Line in IniLines) | |
{ | |
string TrimmedLine = Line.Trim(); | |
// Multiline value support | |
bool bWasMultiLine = bMultiLine; | |
bMultiLine = TrimmedLine.EndsWith("\\"); | |
if (bMultiLine) | |
{ | |
TrimmedLine = TrimmedLine.Substring(0, TrimmedLine.Length - 1).TrimEnd(); | |
} | |
if (!bWasMultiLine) | |
{ | |
if (TrimmedLine.StartsWith("[")) | |
{ | |
CurrentSection = FindOrAddSection(TrimmedLine, Filename, LineIndex); | |
LastAction = ParseAction.None; | |
if (CurrentSection != null) | |
{ | |
SectionCommand Command = new SectionCommand(); | |
Command.Filename = Filename; | |
Command.LineIndex = LineIndex; | |
Command.TrimmedLine = TrimmedLine; | |
Commands.Add(Command); | |
} | |
} | |
else | |
{ | |
if (LastAction != ParseAction.None) | |
{ | |
throw new IniParsingException("Parsing new key/value pair when the previous one has not yet been processed ({0}, {1}) in {2}, line {3}: {4}", Key, SingleValue, Filename, LineIndex, TrimmedLine); | |
} | |
// Check if the line is empty or a comment, also remove any +/- markers | |
LastAction = GetActionForLine(ref TrimmedLine); | |
if (LastAction != ParseAction.None) | |
{ | |
/* if (CurrentSection == null) | |
{ | |
throw new IniParsingException("Trying to parse key/value pair that doesn't belong to any section in {0}, line {1}: {2}", Filename, LineIndex, TrimmedLine); | |
}*/ | |
ParseKeyValuePair(TrimmedLine, Filename, LineIndex, out Key, out SingleValue); | |
} | |
} | |
} | |
if (bWasMultiLine) | |
{ | |
SingleValue += TrimmedLine; | |
} | |
if (!bMultiLine && LastAction != ParseAction.None && CurrentSection != null) | |
{ | |
ProcessKeyValuePair(CurrentSection, Key, SingleValue, LastAction); | |
KeyValueCommand Command = new KeyValueCommand(); | |
Command.Key = Key; | |
Command.Value = SingleValue; | |
Command.LastAction = LastAction; | |
Commands.Add(Command); | |
LastAction = ParseAction.None; | |
SingleValue = ""; | |
Key = ""; | |
} | |
else if (CurrentSection == null) | |
{ | |
LastAction = ParseAction.None; | |
} | |
LineIndex++; | |
} | |
} | |
else if (Commands != null) | |
{ | |
IniSection CurrentSection = null; | |
// run each command | |
for (int Idx = 0; Idx < Commands.Count; ++Idx) | |
{ | |
Command Command = Commands[Idx]; | |
if (Command is SectionCommand) | |
{ | |
CurrentSection = FindOrAddSection((Command as SectionCommand).TrimmedLine, (Command as SectionCommand).Filename, (Command as SectionCommand).LineIndex); | |
} | |
else if (Command is KeyValueCommand) | |
{ | |
ProcessKeyValuePair(CurrentSection, (Command as KeyValueCommand).Key, (Command as KeyValueCommand).Value, (Command as KeyValueCommand).LastAction); | |
} | |
} | |
} | |
} | |
/// <summary> | |
/// Splits a line into key and value | |
/// </summary> | |
private static void ParseKeyValuePair(string TrimmedLine, FileReference Filename, int LineIndex, out string Key, out string Value) | |
{ | |
int AssignIndex = TrimmedLine.IndexOf('='); | |
if (AssignIndex < 0) | |
{ | |
throw new IniParsingException("Failed to find value when parsing {0}, line {1}: {2}", Filename, LineIndex, TrimmedLine); | |
} | |
Key = TrimmedLine.Substring(0, AssignIndex).Trim(); | |
if (String.IsNullOrEmpty(Key)) | |
{ | |
throw new IniParsingException("Empty key when parsing {0}, line {1}: {2}", Filename, LineIndex, TrimmedLine); | |
} | |
Value = TrimmedLine.Substring(AssignIndex + 1).Trim(); | |
if (Value.StartsWith("\"")) | |
{ | |
// Remove quotes | |
int QuoteEnd = Value.LastIndexOf('\"'); | |
if (QuoteEnd == 0) | |
{ | |
throw new IniParsingException("Mismatched quotes when parsing {0}, line {1}: {2}", Filename, LineIndex, TrimmedLine); | |
} | |
Value = Value.Substring(1, Value.Length - 2); | |
} | |
} | |
/// <summary> | |
/// Processes parsed key/value pair | |
/// </summary> | |
private static void ProcessKeyValuePair(IniSection CurrentSection, string Key, string SingleValue, ParseAction Action) | |
{ | |
switch (Action) | |
{ | |
case ParseAction.New: | |
{ | |
// New/replace | |
IniValues Value; | |
if (CurrentSection.TryGetValue(Key, out Value) == false) | |
{ | |
Value = new IniValues(); | |
CurrentSection.Add(Key, Value); | |
} | |
Value.Clear(); | |
Value.Add(SingleValue); | |
} | |
break; | |
case ParseAction.Add: | |
{ | |
IniValues Value; | |
if (CurrentSection.TryGetValue(Key, out Value) == false) | |
{ | |
Value = new IniValues(); | |
CurrentSection.Add(Key, Value); | |
} | |
Value.Add(SingleValue); | |
} | |
break; | |
case ParseAction.Remove: | |
{ | |
IniValues Value; | |
if (CurrentSection.TryGetValue(Key, out Value)) | |
{ | |
int ExistingIndex = Value.FindIndex(X => (String.Compare(SingleValue, X, true) == 0)); | |
if (ExistingIndex >= 0) | |
{ | |
Value.RemoveAt(ExistingIndex); | |
} | |
} | |
} | |
break; | |
} | |
} | |
/// <summary> | |
/// Finds an existing section or adds a new one | |
/// </summary> | |
private IniSection FindOrAddSection(string TrimmedLine, FileReference Filename, int LineIndex) | |
{ | |
int SectionEndIndex = TrimmedLine.IndexOf(']'); | |
if (SectionEndIndex != (TrimmedLine.Length - 1)) | |
{ | |
throw new IniParsingException("Mismatched brackets when parsing section name in {0}, line {1}: {2}", Filename, LineIndex, TrimmedLine); | |
} | |
string SectionName = TrimmedLine.Substring(1, TrimmedLine.Length - 2); | |
if (String.IsNullOrEmpty(SectionName)) | |
{ | |
throw new IniParsingException("Empty section name when parsing {0}, line {1}: {2}", Filename, LineIndex, TrimmedLine); | |
} | |
{ | |
IniSection CurrentSection; | |
if (Sections.TryGetValue(SectionName, out CurrentSection) == false) | |
{ | |
CurrentSection = new IniSection(); | |
Sections.Add(SectionName, CurrentSection); | |
} | |
return CurrentSection; | |
} | |
} | |
/// <summary> | |
/// Returns a list of INI filenames for the engine | |
/// </summary> | |
private static IEnumerable<FileReference> EnumerateEngineIniFileNames(DirectoryReference EngineDirectory, string BaseIniName) | |
{ | |
// Engine/Config/Base.ini (included in every ini type, required) | |
yield return FileReference.Combine(EngineDirectory, "Config", "Base.ini"); | |
// Engine/Config/Base* ini | |
yield return FileReference.Combine(EngineDirectory, "Config", "Base" + BaseIniName + ".ini"); | |
// Engine/Config/NotForLicensees/Base* ini | |
yield return FileReference.Combine(EngineDirectory, "Config", "NotForLicensees", "Base" + BaseIniName + ".ini"); | |
} | |
/// <summary> | |
/// Returns a list of INI filenames for the given project | |
/// </summary> | |
private static IEnumerable<FileReference> EnumerateCrossPlatformIniFileNames(DirectoryReference ProjectDirectory, DirectoryReference EngineDirectory, UnrealTargetPlatform Platform, string BaseIniName, bool SkipEngine) | |
{ | |
if (!SkipEngine) | |
{ | |
// Engine/Config/Base.ini (included in every ini type, required) | |
yield return FileReference.Combine(EngineDirectory, "Config", "Base.ini"); | |
// Engine/Config/Base* ini | |
yield return FileReference.Combine(EngineDirectory, "Config", "Base" + BaseIniName + ".ini"); | |
// Engine/Config/NotForLicensees/Base* ini | |
yield return FileReference.Combine(EngineDirectory, "Config", "NotForLicensees", "Base" + BaseIniName + ".ini"); | |
// NOTE: 4.7: See comment in GetSourceIniHierarchyFilenames() | |
// Engine/Config/NoRedist/Base* ini | |
// yield return Path.Combine(EngineDirectory, "Config", "NoRedist", "Base" + BaseIniName + ".ini"); | |
} | |
if (ProjectDirectory != null) | |
{ | |
// Game/Config/Default* ini | |
yield return FileReference.Combine(ProjectDirectory, "Config", "Default" + BaseIniName + ".ini"); | |
// Game/Config/NotForLicensees/Default* ini | |
yield return FileReference.Combine(ProjectDirectory, "Config", "NotForLicensees", "Default" + BaseIniName + ".ini"); | |
// Game/Config/NoRedist/Default* ini | |
yield return FileReference.Combine(ProjectDirectory, "Config", "NoRedist", "Default" + BaseIniName + ".ini"); | |
} | |
string PlatformName = GetIniPlatformName(Platform); | |
if (Platform != UnrealTargetPlatform.Unknown) | |
{ | |
// Engine/Config/Platform/Platform* ini | |
yield return FileReference.Combine(EngineDirectory, "Config", PlatformName, PlatformName + BaseIniName + ".ini"); | |
if (ProjectDirectory != null) | |
{ | |
// Game/Config/Platform/Platform* ini | |
yield return FileReference.Combine(ProjectDirectory, "Config", PlatformName, PlatformName + BaseIniName + ".ini"); | |
} | |
} | |
DirectoryReference UserSettingsFolder = Utils.GetUserSettingDirectory(); // Match FPlatformProcess::UserSettingsDir() | |
DirectoryReference PersonalFolder = null; // Match FPlatformProcess::UserDir() | |
if (BuildHostPlatform.Current.Platform == UnrealTargetPlatform.Mac) | |
{ | |
PersonalFolder = new DirectoryReference(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Documents")); | |
} | |
else if (Environment.OSVersion.Platform == PlatformID.Unix) | |
{ | |
PersonalFolder = new DirectoryReference(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Documents")); | |
} | |
else | |
{ | |
// Not all user accounts have a local application data directory (eg. SYSTEM, used by Jenkins for builds). | |
string PersonalFolderSetting = Environment.GetFolderPath(Environment.SpecialFolder.Personal); | |
if(!String.IsNullOrEmpty(PersonalFolderSetting)) | |
{ | |
PersonalFolder = new DirectoryReference(PersonalFolderSetting); | |
} | |
} | |
if(UserSettingsFolder != null) | |
{ | |
// <AppData>/UE4/EngineConfig/User* ini | |
yield return FileReference.Combine(UserSettingsFolder, "Unreal Engine", "Engine", "Config", "User" + BaseIniName + ".ini"); | |
} | |
if(PersonalFolder != null) | |
{ | |
// <Documents>/UE4/EngineConfig/User* ini | |
yield return FileReference.Combine(PersonalFolder, "Unreal Engine", "Engine", "Config", "User" + BaseIniName + ".ini"); | |
} | |
// Game/Config/User* ini | |
if (ProjectDirectory != null) | |
{ | |
yield return FileReference.Combine(ProjectDirectory, "Config", "User" + BaseIniName + ".ini"); | |
} | |
} | |
/// <summary> | |
/// Returns the platform name to use as part of platform-specific config files | |
/// </summary> | |
private static string GetIniPlatformName(UnrealTargetPlatform TargetPlatform) | |
{ | |
if (TargetPlatform == UnrealTargetPlatform.Win32 || TargetPlatform == UnrealTargetPlatform.Win64) | |
{ | |
return "Windows"; | |
} | |
else | |
{ | |
return Enum.GetName(typeof(UnrealTargetPlatform), TargetPlatform); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment