You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
========================== MAJOR FEATURES + CHANGES ========================== Change 2986511 on 2016/05/23 by Ben.Marsh UdpMessaging: Force references to symbols in the files containing UDP automated tests. Since plugins are compiled into static libraries before being linked into the executable, the linker will exclude any object files which don't have any referenced symbols. In non-unity builds, or adaptive unity builds with modified test files, this results in the tests not being linked in. Change 2993274 on 2016/05/27 by Ben.Marsh Fix UGS enumerating deleted .target.cs files when trying to detect editor target name. Change 2994265 on 2016/05/31 by Ben.Marsh Add info about setting up CIS integration and zipped editor builds in UGS. Change 2994275 on 2016/05/31 by Ben.Marsh PR #2443: [Unreal Game Sync] Added -project so shortcut or script can hint at the project file to open. (Contributed by paulevans) Change 2994287 on 2016/05/31 by Ben.Marsh UnrealGameSync: Add information about how UGS self-patches and updates. Change 2996928 on 2016/06/01 by Ben.Marsh UnrealGameSync: Fix trying to sync files which are open for branch or move/add. They don't exist on the server yet. Change 2997619 on 2016/06/02 by Ben.Marsh UAT: Fix PRX files not being remapped on PS4. All non-UFS should be allowed to be remapped, and UFS files can be if we're not using a PAK file. Change 2999769 on 2016/06/03 by Ben.Marsh UBT: Fix codepaths which assume that the current user account has a personal folder. The SYSTEM account (which Jenkins defaults to using) does not. Change 3004879 on 2016/06/07 by Ben.Marsh Remove copy of AWSSDK in NotForLicensees folder. Change 3004902 on 2016/06/07 by Ben.Marsh UAT: Switch MCP to use version of AWSSDK that's not in a NotForLicensees folder. Change 3005892 on 2016/06/08 by Ben.Marsh Add the GitHub promotion to the UE4 binary release build. Change 3016241 on 2016/06/16 by Ben.Marsh UGS: Always sync version files at the same changelist as everything else (rather than head revision) Change 3016446 on 2016/06/16 by Ben.Marsh PR #2279: Use MSBuild instead of DevEnv for solution builds (Contributed by FineRedMist) Change 3016472 on 2016/06/16 by Ben.Marsh PR #2442: pointing to the pull requests page of the repo (Contributed by KrishMunot) Change 3017694 on 2016/06/17 by Ben.Marsh EC: Produce an error if trying to sync back to a changelist more than 30 days before the most recent change. Meant to catch errors in entered CL fields from the dashboard. Can be overridden by specifying --allow-old-change in the build arguments. Change 3017695 on 2016/06/17 by Ben.Marsh UBT: Use a well defined order for parsing configuration files, rather than ignoring one file if another has a newer timestamp. Prevents confusing behavior where settings can be present, but are completely ignored. Now prioritizes the BuildConfiguration.xml file in the My Documents/Unreal Engine/UnrealBuildTool, followed by the one in AppData/Roaming/Unreal Engine/UnrealBuildTool. Both are added to the Config section of the solution if present, under different folders. #jira UE-24271 Change 3017698 on 2016/06/17 by Ben.Marsh Rename the <MsBuild> task to <CsCompile>, highlighting the fact that it only actually works on .csproj files (and not .sln files or other project types). #jira UEB-664 Change 3017701 on 2016/06/17 by Ben.Marsh BuildGraph: Relax a lot of the restrictions relating to using output tags from nodes. Output tags may contain an arbitrary set of files, including files which are also in other tags or produced by other nodes, but will not be written to temp storage more than once. The default tagged set of files for a node (eg. #MyNodeName) now includes all build products produced by that node. Temp storage now separates the storing of build products from the tags which reference them. A TempStorageFileList object is written for each output tag, which includes a list of files as well as a list of the storage blocks referenced by it. When a node depends on a tag, the TempStorageFileList is read first and used to determine which storage blocks to read. All tasks now have overloaded functions for returning the tags which they modify and/or reference, and errors are produced if an existing tag is modified, or referenced without being added as an input dependency. Change 3017714 on 2016/06/17 by Ben.Marsh BuildGraph: Allow specifying multiple tag names in the 'Tag' attribute of build tasks (or 'With' attribute of the 'Tag' task). Change 3018007 on 2016/06/17 by Ben.Marsh UBT: Add sections to the target receipt listing files which are required to build using precompiled binaries (as previously generated using -generateexternalfilelist), and runtime dependencies that may be required when using precompiled binaries but aren't actually required for the current target (which previously had to be specified through InstalledEngineFilters.ini). Tested by running UBT with arguments "UE4Game Win64 Development -precompile -xgeexport" and examining target receipt. #jira UE-28761 Change 3018322 on 2016/06/17 by Ben.Marsh PR #2518: Improvements for Clang on Windows (Contributed by Mattiwatti) Change 3018365 on 2016/06/17 by Ben.Marsh Misc: Fixes for warnings compiling ShaderCompileWorker on Clang Change 3018397 on 2016/06/17 by Ben.Marsh UnrealVS: Add an UnrealVS command to run a single-file-compile with UBT Change 3019421 on 2016/06/20 by Ben.Marsh Fix compilation of FreeType on Clang for Windows - there's an fttypes.h header already in the Windows SDK, so use a relative include path instead. Change 3019423 on 2016/06/20 by Ben.Marsh PR #2518: Improvements for Clang on Windows (Contributed by Mattiwatti) Change 3020377 on 2016/06/20 by Ben.Marsh UBT: Fix strings not being escaped before writing to JSON files. Change 3020378 on 2016/06/20 by Ben.Marsh UBT: Exclude precompiled files from the target receipt which are outside the engine and project directories. We don't need SDK libs being added to the precompile list. Change 3020966 on 2016/06/21 by Ben.Marsh EC: Allow scheduling builds at a certain offset past midnight, using the schedule formatted like "Every 20m from 04:00", and add scheduled builds of target platforms for dev streams every 4 hours. Also make the meaning of "Editor Only" and "Editor, Tools & Monolithics" build names consistent between Dev streams and Main, and add a new "Editor, Tools, Monolithics & DDC" build type includes additional DDC build for //UE4/Main. #rb none #lockdown Nick.Penwarden [CL 3020980 by Ben Marsh in Main branch]
622 lines
22 KiB
C#
622 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Xml;
|
|
using UnrealBuildTool;
|
|
|
|
namespace AutomationTool.Tasks
|
|
{
|
|
/// <summary>
|
|
/// Parameters for a task that compiles a C# project
|
|
/// </summary>
|
|
public class CsCompileTaskParameters
|
|
{
|
|
/// <summary>
|
|
/// The C# project file to be compile. More than one project file can be specified by separating with semicolons.
|
|
/// </summary>
|
|
[TaskParameter]
|
|
public string Project;
|
|
|
|
/// <summary>
|
|
/// The configuration to compile
|
|
/// </summary>
|
|
[TaskParameter(Optional = true)]
|
|
public string Configuration;
|
|
|
|
/// <summary>
|
|
/// The platform to compile
|
|
/// </summary>
|
|
[TaskParameter(Optional = true)]
|
|
public string Platform;
|
|
|
|
/// <summary>
|
|
/// Additional options to pass to the compiler
|
|
/// </summary>
|
|
[TaskParameter(Optional = true)]
|
|
public string Arguments;
|
|
|
|
/// <summary>
|
|
/// Directory containing output files
|
|
/// </summary>
|
|
[TaskParameter(Optional = true)]
|
|
public string OutputDir;
|
|
|
|
/// <summary>
|
|
/// Patterns for output files
|
|
/// </summary>
|
|
[TaskParameter(Optional = true)]
|
|
public string OutputFiles;
|
|
|
|
/// <summary>
|
|
/// Only enumerate build products; do not actually compile the projects.
|
|
/// </summary>
|
|
[TaskParameter(Optional = true)]
|
|
public bool EnumerateOnly;
|
|
|
|
/// <summary>
|
|
/// Tag to be applied to build products of this task
|
|
/// </summary>
|
|
[TaskParameter(Optional = true, ValidationType = TaskParameterValidationType.TagList)]
|
|
public string Tag;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Compile a C# project file
|
|
/// </summary>
|
|
[TaskElement("CsCompile", typeof(CsCompileTaskParameters))]
|
|
public class CsCompileTask : CustomTask
|
|
{
|
|
/// <summary>
|
|
/// Parameters for the task
|
|
/// </summary>
|
|
CsCompileTaskParameters Parameters;
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="InParameters">Parameters for this task</param>
|
|
public CsCompileTask(CsCompileTaskParameters InParameters)
|
|
{
|
|
Parameters = InParameters;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Execute the task.
|
|
/// </summary>
|
|
/// <param name="Job">Information about the current job</param>
|
|
/// <param name="BuildProducts">Set of build products produced by this node.</param>
|
|
/// <param name="TagNameToFileSet">Mapping from tag names to the set of files they include</param>
|
|
/// <returns>True if the task succeeded</returns>
|
|
public override bool Execute(JobContext Job, HashSet<FileReference> BuildProducts, Dictionary<string, HashSet<FileReference>> TagNameToFileSet)
|
|
{
|
|
// Get the project file
|
|
HashSet<FileReference> ProjectFiles = ResolveFilespec(CommandUtils.RootDirectory, Parameters.Project, TagNameToFileSet);
|
|
foreach(FileReference ProjectFile in ProjectFiles)
|
|
{
|
|
if(!ProjectFile.Exists())
|
|
{
|
|
CommandUtils.LogError("Couldn't find project file '{0}'", ProjectFile.FullName);
|
|
return false;
|
|
}
|
|
if(!ProjectFile.HasExtension(".csproj"))
|
|
{
|
|
CommandUtils.LogError("File '{0}' is not a C# project", ProjectFile.FullName);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get the default properties
|
|
Dictionary<string, string> Properties = new Dictionary<string,string>(StringComparer.InvariantCultureIgnoreCase);
|
|
if(!String.IsNullOrEmpty(Parameters.Platform))
|
|
{
|
|
Properties["Platform"] = Parameters.Platform;
|
|
}
|
|
if(!String.IsNullOrEmpty(Parameters.Configuration))
|
|
{
|
|
Properties["Configuration"] = Parameters.Configuration;
|
|
}
|
|
|
|
// Build the arguments and run the build
|
|
if(!Parameters.EnumerateOnly)
|
|
{
|
|
List<string> Arguments = new List<string>();
|
|
foreach(KeyValuePair<string, string> PropertyPair in Properties)
|
|
{
|
|
Arguments.Add(String.Format("/property:{0}={1}", CommandUtils.MakePathSafeToUseWithCommandLine(PropertyPair.Key), CommandUtils.MakePathSafeToUseWithCommandLine(PropertyPair.Value)));
|
|
}
|
|
if(!String.IsNullOrEmpty(Parameters.Arguments))
|
|
{
|
|
Arguments.Add(Parameters.Arguments);
|
|
}
|
|
foreach(FileReference ProjectFile in ProjectFiles)
|
|
{
|
|
CommandUtils.MsBuild(CommandUtils.CmdEnv, ProjectFile.FullName, String.Join(" ", Arguments), null);
|
|
}
|
|
}
|
|
|
|
// Try to figure out the output files
|
|
HashSet<FileReference> ProjectBuildProducts;
|
|
if(!FindBuildProducts(ProjectFiles, Properties, out ProjectBuildProducts))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Apply the optional tag to the produced archive
|
|
foreach(string TagName in FindTagNamesFromList(Parameters.Tag))
|
|
{
|
|
FindOrAddTagSet(TagNameToFileSet, TagName).UnionWith(ProjectBuildProducts);
|
|
}
|
|
|
|
// Merge them into the standard set of build products
|
|
BuildProducts.UnionWith(ProjectBuildProducts);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Output this task out to an XML writer.
|
|
/// </summary>
|
|
public override void Write(XmlWriter Writer)
|
|
{
|
|
Write(Writer, Parameters);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find all the tags which are used as inputs to this task
|
|
/// </summary>
|
|
/// <returns>The tag names which are read by this task</returns>
|
|
public override IEnumerable<string> FindConsumedTagNames()
|
|
{
|
|
return FindTagNamesFromFilespec(Parameters.Project);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find all the tags which are modified by this task
|
|
/// </summary>
|
|
/// <returns>The tag names which are modified by this task</returns>
|
|
public override IEnumerable<string> FindProducedTagNames()
|
|
{
|
|
return FindTagNamesFromList(Parameters.Tag);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find all the build products created by compiling the given project file
|
|
/// </summary>
|
|
/// <param name="ProjectFile">Initial project file to read. All referenced projects will also be read.</param>
|
|
/// <param name="InitialProperties">Mapping of property name to value</param>
|
|
/// <param name="OutBuildProducts">Receives a set of build products on success</param>
|
|
/// <returns>True if the build products were found, false otherwise.</returns>
|
|
static bool FindBuildProducts(HashSet<FileReference> ProjectFiles, Dictionary<string, string> InitialProperties, out HashSet<FileReference> OutBuildProducts)
|
|
{
|
|
// Read all the project information into a dictionary
|
|
Dictionary<FileReference, CsProjectInfo> FileToProjectInfo = new Dictionary<FileReference,CsProjectInfo>();
|
|
foreach(FileReference ProjectFile in ProjectFiles)
|
|
{
|
|
if(!ReadProjectsRecursively(ProjectFile, InitialProperties, FileToProjectInfo))
|
|
{
|
|
OutBuildProducts = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Find all the build products
|
|
HashSet<FileReference> BuildProducts = new HashSet<FileReference>();
|
|
foreach(KeyValuePair<FileReference, CsProjectInfo> Pair in FileToProjectInfo)
|
|
{
|
|
CsProjectInfo ProjectInfo = Pair.Value;
|
|
|
|
// Add the standard build products
|
|
DirectoryReference OutputDir = ProjectInfo.GetOutputDir(Pair.Key.Directory);
|
|
ProjectInfo.AddBuildProducts(OutputDir, BuildProducts);
|
|
|
|
// Add the referenced assemblies
|
|
foreach(FileReference OtherAssembly in ProjectInfo.References.Where(x => x.Value).Select(x => x.Key))
|
|
{
|
|
FileReference OutputFile = FileReference.Combine(OutputDir, OtherAssembly.GetFileName());
|
|
BuildProducts.Add(OutputFile);
|
|
|
|
FileReference SymbolFile = OtherAssembly.ChangeExtension(".pdb");
|
|
if(SymbolFile.Exists())
|
|
{
|
|
BuildProducts.Add(OutputFile.ChangeExtension(".pdb"));
|
|
}
|
|
}
|
|
|
|
// Add build products from all the referenced projects. MSBuild only copy the directly referenced build products, not recursive references or other assemblies.
|
|
foreach(CsProjectInfo OtherProjectInfo in ProjectInfo.ProjectReferences.Where(x => x.Value).Select(x => FileToProjectInfo[x.Key]))
|
|
{
|
|
OtherProjectInfo.AddBuildProducts(OutputDir, BuildProducts);
|
|
}
|
|
}
|
|
|
|
// Update the output set
|
|
OutBuildProducts = BuildProducts;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a project file, plus all the project files it references.
|
|
/// </summary>
|
|
/// <param name="File">Project file to read</param>
|
|
/// <param name="InitialProperties">Mapping of property name to value for the initial project</param>
|
|
/// <param name="FileToProjectInfo"></param>
|
|
/// <returns>True if the projects were read correctly, false (and prints an error to the log) if not</returns>
|
|
static bool ReadProjectsRecursively(FileReference File, Dictionary<string, string> InitialProperties, Dictionary<FileReference, CsProjectInfo> FileToProjectInfo)
|
|
{
|
|
// Early out if we've already read this project, return succes
|
|
if(FileToProjectInfo.ContainsKey(File))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Try to read this project
|
|
CsProjectInfo ProjectInfo;
|
|
if(!CsProjectInfo.TryRead(File, InitialProperties, out ProjectInfo))
|
|
{
|
|
CommandUtils.LogError("Couldn't read project '{0}'", File.FullName);
|
|
return false;
|
|
}
|
|
|
|
// Add it to the project lookup, and try to read all the projects it references
|
|
FileToProjectInfo.Add(File, ProjectInfo);
|
|
return ProjectInfo.ProjectReferences.Keys.All(x => ReadProjectsRecursively(x, new Dictionary<string, string>(), FileToProjectInfo));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Basic information from a preprocessed C# project file. Supports reading a project file, expanding simple conditions in it, parsing property values, assembly references and references to other projects.
|
|
/// </summary>
|
|
class CsProjectInfo
|
|
{
|
|
/// <summary>
|
|
/// Evaluated properties from the project file
|
|
/// </summary>
|
|
public Dictionary<string, string> Properties;
|
|
|
|
/// <summary>
|
|
/// Mapping of referenced assemblies to their 'CopyLocal' (aka 'Private') setting.
|
|
/// </summary>
|
|
public Dictionary<FileReference, bool> References = new Dictionary<FileReference,bool>();
|
|
|
|
/// <summary>
|
|
/// Mapping of referenced projects to their 'CopyLocal' (aka 'Private') setting.
|
|
/// </summary>
|
|
public Dictionary<FileReference, bool> ProjectReferences = new Dictionary<FileReference,bool>();
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="InProperties">Initial mapping of property names to values</param>
|
|
CsProjectInfo(Dictionary<string, string> InProperties)
|
|
{
|
|
Properties = new Dictionary<string,string>(InProperties);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolve the project's output directory
|
|
/// </summary>
|
|
/// <param name="BaseDirectory">Base directory to resolve relative paths to</param>
|
|
/// <returns>The configured output directory</returns>
|
|
public DirectoryReference GetOutputDir(DirectoryReference BaseDirectory)
|
|
{
|
|
string OutputPath;
|
|
if(Properties.TryGetValue("OutputPath", out OutputPath))
|
|
{
|
|
return DirectoryReference.Combine(BaseDirectory, OutputPath);
|
|
}
|
|
else
|
|
{
|
|
return BaseDirectory;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds build products from the project to the given set.
|
|
/// </summary>
|
|
/// <param name="OutputDir">Output directory for the build products. May be different to the project's output directory in the case that we're copying local to another project.</param>
|
|
/// <param name="BuildProducts">Set to receive the list of build products</param>
|
|
public bool AddBuildProducts(DirectoryReference OutputDir, HashSet<FileReference> BuildProducts)
|
|
{
|
|
string OutputType, AssemblyName;
|
|
if(Properties.TryGetValue("OutputType", out OutputType) && Properties.TryGetValue("AssemblyName", out AssemblyName))
|
|
{
|
|
switch(OutputType)
|
|
{
|
|
case "Exe":
|
|
BuildProducts.Add(FileReference.Combine(OutputDir, AssemblyName + ".exe"));
|
|
BuildProducts.Add(FileReference.Combine(OutputDir, AssemblyName + ".pdb"));
|
|
return true;
|
|
case "Library":
|
|
BuildProducts.Add(FileReference.Combine(OutputDir, AssemblyName + ".dll"));
|
|
BuildProducts.Add(FileReference.Combine(OutputDir, AssemblyName + ".pdb"));
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to read project information for the given file.
|
|
/// </summary>
|
|
/// <param name="File">The project file to read</param>
|
|
/// <param name="Properties">Initial set of property values</param>
|
|
/// <param name="OutProjectInfo">If successful, the parsed project info</param>
|
|
/// <returns>True if the project was read successfully, false otherwise</returns>
|
|
public static bool TryRead(FileReference File, Dictionary<string, string> Properties, out CsProjectInfo OutProjectInfo)
|
|
{
|
|
// Read the project file
|
|
XmlDocument Document = new XmlDocument();
|
|
Document.Load(File.FullName);
|
|
|
|
// Check the root element is the right type
|
|
// HashSet<FileReference> ProjectBuildProducts = new HashSet<FileReference>();
|
|
if(Document.DocumentElement.Name != "Project")
|
|
{
|
|
OutProjectInfo = null;
|
|
return false;
|
|
}
|
|
|
|
// Parse the basic structure of the document, updating properties and recursing into other referenced projects as we go
|
|
CsProjectInfo ProjectInfo = new CsProjectInfo(Properties);
|
|
foreach(XmlElement Element in Document.DocumentElement.ChildNodes.OfType<XmlElement>())
|
|
{
|
|
switch(Element.Name)
|
|
{
|
|
case "PropertyGroup":
|
|
if(EvaluateCondition(Element, ProjectInfo.Properties))
|
|
{
|
|
ParsePropertyGroup(Element, ProjectInfo.Properties);
|
|
}
|
|
break;
|
|
case "ItemGroup":
|
|
if(EvaluateCondition(Element, ProjectInfo.Properties))
|
|
{
|
|
ParseItemGroup(File.Directory, Element, ProjectInfo);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Return the complete project
|
|
OutProjectInfo = ProjectInfo;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a 'PropertyGroup' element.
|
|
/// </summary>
|
|
/// <param name="ParentElement">The parent 'PropertyGroup' element</param>
|
|
/// <param name="Properties">Dictionary mapping property names to values</param>
|
|
static void ParsePropertyGroup(XmlElement ParentElement, Dictionary<string, string> Properties)
|
|
{
|
|
// We need to know the overridden output type and output path for the selected configuration.
|
|
foreach(XmlElement Element in ParentElement.ChildNodes.OfType<XmlElement>())
|
|
{
|
|
if(EvaluateCondition(Element, Properties))
|
|
{
|
|
Properties[Element.Name] = ExpandProperties(Element.InnerText, Properties);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses an 'ItemGroup' element.
|
|
/// </summary>
|
|
/// <param name="BaseDirectory">Base directory to resolve relative paths against</param>
|
|
/// <param name="ParentElement">The parent 'ItemGroup' element</param>
|
|
/// <param name="ProjectInfo">Project info object to be updated</param>
|
|
static void ParseItemGroup(DirectoryReference BaseDirectory, XmlElement ParentElement, CsProjectInfo ProjectInfo)
|
|
{
|
|
// Parse any external assembly references
|
|
foreach(XmlElement ItemElement in ParentElement.ChildNodes.OfType<XmlElement>())
|
|
{
|
|
switch(ItemElement.Name)
|
|
{
|
|
case "Reference":
|
|
// Reference to an external assembly
|
|
if(EvaluateCondition(ItemElement, ProjectInfo.Properties))
|
|
{
|
|
ParseReference(BaseDirectory, ItemElement, ProjectInfo.References);
|
|
}
|
|
break;
|
|
case "ProjectReference":
|
|
// Reference to another project
|
|
if(EvaluateCondition(ItemElement, ProjectInfo.Properties))
|
|
{
|
|
ParseProjectReference(BaseDirectory, ItemElement, ProjectInfo.ProjectReferences);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses an assembly reference from a given 'Reference' element
|
|
/// </summary>
|
|
/// <param name="BaseDirectory">Directory to resolve relative paths against</param>
|
|
/// <param name="ParentElement">The parent 'Reference' element</param>
|
|
/// <param name="ProjectReferences">Dictionary of project files to a bool indicating whether the assembly should be copied locally to the referencing project.</param>
|
|
static void ParseReference(DirectoryReference BaseDirectory, XmlElement ParentElement, Dictionary<FileReference, bool> References)
|
|
{
|
|
string HintPath = GetChildElementString(ParentElement, "HintPath", null);
|
|
if(!String.IsNullOrEmpty(HintPath))
|
|
{
|
|
FileReference AssemblyFile = FileReference.Combine(BaseDirectory, HintPath);
|
|
bool bPrivate = GetChildElementBoolean(ParentElement, "Private", true);
|
|
References.Add(AssemblyFile, bPrivate);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a project reference from a given 'ProjectReference' element
|
|
/// </summary>
|
|
/// <param name="BaseDirectory">Directory to resolve relative paths against</param>
|
|
/// <param name="ParentElement">The parent 'ProjectReference' element</param>
|
|
/// <param name="ProjectReferences">Dictionary of project files to a bool indicating whether the outputs of the project should be copied locally to the referencing project.</param>
|
|
static void ParseProjectReference(DirectoryReference BaseDirectory, XmlElement ParentElement, Dictionary<FileReference, bool> ProjectReferences)
|
|
{
|
|
string IncludePath = ParentElement.GetAttribute("Include");
|
|
if(!String.IsNullOrEmpty(IncludePath))
|
|
{
|
|
FileReference ProjectFile = FileReference.Combine(BaseDirectory, IncludePath);
|
|
bool bPrivate = GetChildElementBoolean(ParentElement, "Private", true);
|
|
ProjectReferences[ProjectFile] = bPrivate;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the inner text of a child XML element
|
|
/// </summary>
|
|
/// <param name="ParentElement">The parent element to check</param>
|
|
/// <param name="Name">Name of the child element</param>
|
|
/// <param name="DefaultValue">Default value to return if the child element is missing</param>
|
|
/// <returns>The contents of the child element, or default value if it's not present</returns>
|
|
static string GetChildElementString(XmlElement ParentElement, string Name, string DefaultValue)
|
|
{
|
|
XmlElement ChildElement = ParentElement.ChildNodes.OfType<XmlElement>().FirstOrDefault(x => x.Name == Name);
|
|
if(ChildElement == null)
|
|
{
|
|
return DefaultValue;
|
|
}
|
|
else
|
|
{
|
|
return ChildElement.InnerText ?? DefaultValue;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a child XML element with the given name, and parse it as a boolean.
|
|
/// </summary>
|
|
/// <param name="ParentElement">Parent element to check</param>
|
|
/// <param name="Name">Name of the child element to look for</param>
|
|
/// <param name="DefaultValue">Default value to return if the element is missing or not a valid bool</param>
|
|
/// <returns>The parsed boolean, or the default value</returns>
|
|
static bool GetChildElementBoolean(XmlElement ParentElement, string Name, bool DefaultValue)
|
|
{
|
|
string Value = GetChildElementString(ParentElement, Name, null);
|
|
if(Value == null)
|
|
{
|
|
return DefaultValue;
|
|
}
|
|
else if(Value.Equals("True", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
else if(Value.Equals("False", StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return DefaultValue;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluate whether the optional MSBuild condition on an XML element evaluates to true. Currently only supports 'ABC' == 'DEF' style expressions, but can be expanded as needed.
|
|
/// </summary>
|
|
/// <param name="Element">The XML element to check</param>
|
|
/// <param name="Properties">Dictionary mapping from property names to values.</param>
|
|
/// <returns></returns>
|
|
static bool EvaluateCondition(XmlElement Element, Dictionary<string, string> Properties)
|
|
{
|
|
// Read the condition attribute. If it's not present, assume it evaluates to true.
|
|
string Condition = Element.GetAttribute("Condition");
|
|
if(String.IsNullOrEmpty(Condition))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Expand all the properties
|
|
Condition = ExpandProperties(Condition, Properties);
|
|
|
|
// Tokenize the condition
|
|
string[] Tokens = Tokenize(Condition);
|
|
|
|
// Try to evaluate it. We only support a very limited class of condition expressions at the moment, but it's enough to parse standard projects
|
|
bool bResult;
|
|
if(Tokens.Length == 3 && Tokens[0].StartsWith("'") && Tokens[1] == "==" && Tokens[2].StartsWith("'"))
|
|
{
|
|
bResult = String.Compare(Tokens[0], Tokens[2], StringComparison.InvariantCultureIgnoreCase) == 0;
|
|
}
|
|
else
|
|
{
|
|
throw new AutomationException("Couldn't parse condition in project file");
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Expand MSBuild properties within a string. If referenced properties are not in this dictionary, the process' environment variables are expanded. Unknown properties are expanded to an empty string.
|
|
/// </summary>
|
|
/// <param name="Text">The input string to expand</param>
|
|
/// <param name="Properties">Dictionary mapping from property names to values.</param>
|
|
/// <returns>String with all properties expanded.</returns>
|
|
static string ExpandProperties(string Text, Dictionary<string, string> Properties)
|
|
{
|
|
string NewText = Text;
|
|
for (int Idx = NewText.IndexOf("$("); Idx != -1; Idx = NewText.IndexOf("$(", Idx))
|
|
{
|
|
// Find the end of the variable name
|
|
int EndIdx = NewText.IndexOf(')', Idx + 2);
|
|
if (EndIdx != -1)
|
|
{
|
|
// Extract the variable name from the string
|
|
string Name = NewText.Substring(Idx + 2, EndIdx - (Idx + 2));
|
|
|
|
// Find the value for it, either from the dictionary or the environment block
|
|
string Value;
|
|
if(!Properties.TryGetValue(Name, out Value))
|
|
{
|
|
Value = Environment.GetEnvironmentVariable(Name) ?? "";
|
|
}
|
|
|
|
// Replace the variable, or skip past it
|
|
NewText = NewText.Substring(0, Idx) + Value + NewText.Substring(EndIdx + 1);
|
|
|
|
// Make sure we skip over the expanded variable; we don't want to recurse on it.
|
|
Idx += Value.Length;
|
|
}
|
|
}
|
|
return NewText;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Split an MSBuild condition into tokens
|
|
/// </summary>
|
|
/// <param name="Condition">The condition expression</param>
|
|
/// <returns>Array of the parsed tokens</returns>
|
|
static string[] Tokenize(string Condition)
|
|
{
|
|
List<string> Tokens = new List<string>();
|
|
for(int Idx = 0; Idx < Condition.Length; Idx++)
|
|
{
|
|
if(Idx + 1 < Condition.Length && Condition[Idx] == '=' && Condition[Idx + 1] == '=')
|
|
{
|
|
// "==" operator
|
|
Idx++;
|
|
Tokens.Add("==");
|
|
}
|
|
else if(Condition[Idx] == '\'')
|
|
{
|
|
// Quoted string
|
|
int StartIdx = Idx++;
|
|
while(Idx + 1 < Condition.Length && Condition[Idx] != '\'')
|
|
{
|
|
Idx++;
|
|
}
|
|
Tokens.Add(Condition.Substring(StartIdx, Idx - StartIdx));
|
|
}
|
|
else if(!Char.IsWhiteSpace(Condition[Idx]))
|
|
{
|
|
// Other token; assume a single character.
|
|
string Token = Condition.Substring(Idx, 1);
|
|
Tokens.Add(Token);
|
|
}
|
|
}
|
|
return Tokens.ToArray();
|
|
}
|
|
}
|
|
}
|