You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden #rb none ========================== MAJOR FEATURES + CHANGES ========================== Change 3021930 on 2016/06/21 by Ben.Marsh BuildGraph: Better diagnostic message if the source directory for copies does not exist. Change 3022391 on 2016/06/21 by Ben.Marsh Rework copy task slightly so that all code paths result in files being tagged. Change 3026592 on 2016/06/24 by Ben.Marsh BuildGraph: Add a ForEach element, which will assign a local property to each of a semicolon separated list of values, and expand the elements within it. Added an example in Properties.xml. Change 3028708 on 2016/06/27 by Matthew.Griffin Converting Engine build process to BuildGraph script Added Tag Receipts task to retrieve list of build products/dependencies from *.target files. Changed Pak File task so that you can specify an existing response file, rather than creating one from the file list. Changed base task so that you can resolve filespec from a list of file patterns if you already have them separated, which was the case with wildcards in runtime dependencies. Added EngineMajorVersion, EngineMinorVersion and EnginePatchVersion as default properties available to BuildGraph Added FinalizeInstalledBuild command to write out InstalledBuild.txt file and config entries for installed platforms Included .exe.config and exe.mdb files to build products of CsCompile task if they exist Added TagReferences option to CsCompile so that you can get any external references projects have that need to be included when staging Added RunOptions parameter to SpawnTask, so that you can specify these for the exe you want to run Added missing Runtime Dependency for ICU on Mac Change 3030209 on 2016/06/28 by Matthew.Griffin Renamed EngineBuild.xml to InstalledEngineBuild.xml to make its purpose more clear. Removed reference to xcodeunlock.sh from Mac Installed build dependencies as the file itself has been deleted. Added myself to list of notifiers for failures in the UE4 Binary build. Change 3034068 on 2016/06/30 by Ben.Marsh BuildGraph: Change scoping rules for properties. Local properties can no longer shadow global properties with the same name (or vice versa), and local properties are always modified in the scope that they were first declared, rather than being re-declared in a narrower scope. Change 3034070 on 2016/06/30 by Ben.Marsh BuildGraph: Warn when referencing a property which is not defined, and add new attributes to the <Property> element to set the default value for a property if it's not already set, and validating that it's one of a list of valid values if it is (eg. <Property Name="WithWin64" Restrict="true;false" Default="false"/>). Change 3034110 on 2016/06/30 by Matthew.Griffin Updated Installed Build so that properties are consistently named Exceptions and that the right versions are used Added Filter and Exception properties for each target platform to add any files that can't be figured out via dependencies Added Default values for various properties used across Engine build scripts - IsReleaseBranch, IsPreflight, OutputDir, BuildLabel, WithWin64 etc. Tagged Generated Includes from each target so that they can be included in Installed Build Added additional Android architectures to Shipping build Changed SwarmCoordinator to build for Any CPU Removed Local HostPlatform property from DDC nodes Changed Installed Build target platforms to use Do blocks so that we only have to check With... property once Reordered stripping and signing process so that we use the Exception check in less places Change 3035499 on 2016/07/01 by Ben.Marsh BuildGraph: Remove the <Local> element, and just make all <Property> declarations scoped. Also add an error if a property is later declared in a parent scope, since the earlier assignment won't be visible to the later one. Change 3035520 on 2016/07/01 by Ben.Marsh BuildGraph: Add support for <, <=, >, >= operators in condition expressions. Change 3035666 on 2016/07/01 by Matthew.Griffin Added more parameters to Chunk and Label Build tasks Updated all remaining uses of Local to Property in Installed Build script Made sure Feature Packs use paths compatible with Mac and also changed the node to use a ForEach element Change 3037020 on 2016/07/04 by Matthew.Griffin Ensured that TempStorageFileList uses forward slashes as its path separators so that it's easily used on Mac and Windows Was causing the results of the Make Feature Packs node to be tagged using Windows style paths, meaning they would throw an error if you tried to copy them on Mac Change 3037052 on 2016/07/04 by Ben.Marsh Move FJsonValue::ErrorMessage into cpp file, since it depends on the log class defined in Json.h (which includes it). Change 3037283 on 2016/07/05 by Matthew.Griffin Removed EnterScope and LeaveScope from ReadGraphBody so that included files are treated as being in the same scope (allows use of properties across files) Change 3037547 on 2016/07/05 by Ben.Marsh UAT: Allow CommandUtils.Run() to check directories listed in the PATH environment variable for the executable before failing. Change 3037552 on 2016/07/05 by Ben.Marsh BuildGraph: Add an <Unzip> task, which extracts a zip file to an output directory. Change 3039109 on 2016/07/06 by Matthew.Griffin Moved tagging of UAT build products to the Installed Build step as it's the only thing that needs them Moved Strip and Sign filters to the filters file, made sure they're used for all operations and added stripping back to UE4Editor nodes Changed BuildPatchTool to be built in shipping mode Changed all C# projects to be compiled for AnyCPU as they ended up in different output folders otherwise Added all files referenced by C# projects to avoid having to filter them manually Changed filters to get files included for Linux closer to the old pattern Changed Build DDC command to ignore empty entries in FeaturePacks list, don't want to fail the process if a list begins with a ; Changed UE4Game to use shipping PhysX libs for Shipping builds Added glut32.dll as a Runtime Dependency for PhysX Added libsteam_api.so as a Runtime Dependency for Steamworks on Linux Change 3039676 on 2016/07/06 by Ben.Marsh Core: Move definitions for FORCEINLINE'd FMath functions into UnrealMathUtility. Prevents link errors if including one without the other. Change 3039681 on 2016/07/06 by Ben.Marsh Core: Move implementation of GetTypeHash(FTimespan) into CPP file, to remove implicit dependency on the inline implementation of GetTypeHash(int64) being included. Change 3039735 on 2016/07/06 by Ben.Marsh Core: Move USE_DELEGATE_TRYGETBOUNDFUNCTIONNAME into a separate header, so delegate headers can be included separately. Change 3039878 on 2016/07/06 by Ben.Marsh Core: Move FOperatorFunctionID out of TOperatorJumpTable to allow MSVC to compile it and catch errors before the template is instantiated. Change 3040156 on 2016/07/06 by Ben.Marsh Core: Move FDateTime::GetTypeHash() into cpp file to eliminate dependency on TypeHash.h being included before it. Change 3041009 on 2016/07/07 by Matthew.Griffin Changed UE4Game to only use shipping PhysX libraries on Windows Change 3041015 on 2016/07/07 by Leigh.Swift UBT: Support creating C# programs that will be included in the UE4.sln Programs list. To have your program listed, remove the sln file that may have been created for you, and add a file named "UE4CSharp.prog" next to your csproj file. Change 3041234 on 2016/07/07 by Matthew.Griffin Added building of Launcher Samples to BuildGraph system Added Command to Build Sample projects, which distills to temp directory, builds DDC if needed and then chunks/posts to MCP Change 3041244 on 2016/07/07 by Ben.Marsh Core: Change PlatformIncludes.h to include all the individual PlatformMemory.h, PlatformTime.h, etc... headers rather than including separate per-platform headers which include them all. Makes it much easier to optimize header file usage, and eliminates redundant typedefs in the individual Platform*.h files. Also fixes some headers that previously didn't compile. Change 3042518 on 2016/07/08 by Matthew.Griffin Added content modifiers to those notified about Sample failures Throw exception if RocketPromoteBuild tries to promote all samples Throw exceptions for missing parameters in BuildLauncherSample command, corrected EngineDir parameter name. Change 3042545 on 2016/07/08 by Ben.Marsh Core: Push/Pop defines for MAX_uint8, MAX_uint16, MAX_uint32, MAX_int32 around Windows.h includes, so we don't need to be careful about the order in which we include NumericLimits.h. Change 3042546 on 2016/07/08 by Ben.Marsh Core: Put standard CRT includes into their own header, so we can include it without taking all of PlatformIncludes.h (and make any platform-specific additions as needed) Change 3042548 on 2016/07/08 by Ben.Marsh Core: Include PlatformCompilerSetup headers from Platform.h, as well as all the defaults for non-platform overriden defines. Allows including Platform.h to get all the basic types, defines and compile environment set up without having to include a large number of system headers or unnecessary functionality. Change 3044424 on 2016/07/11 by Ben.Marsh Merge fixes for QFE installer (CL 3044412) from 4.11 branch. Change 3044584 on 2016/07/11 by Ben.Marsh Core: Move FMath::FormatIntToHumanReadable() to UnrealMath.cpp, since it's a very large/expensive function to try to inline (and introduce a FString dependency for) Change 3044603 on 2016/07/11 by Matthew.Griffin Added PS4 and XboxOne to installed build as options that will always be disabled by default Standardised some of the agent names Removed logging from the Installed Build nodes as it takes a huge amount of time to write out the list for little reward Change 3044608 on 2016/07/11 by Ben.Marsh Core: Split out definition of SIMD VectorRegister class into its own header, so it's not forcibly included with UnrealMathUtility. Change 3044638 on 2016/07/11 by Matthew.Griffin Added internal build jobs for all games with compile, cook and package nodes. Added Documentation, Localization and NonUnity steps. Change 3045959 on 2016/07/12 by Matthew.Griffin Removed Aggregates from Installed Build script as they weren't used/necessary. Change 3045961 on 2016/07/12 by Matthew.Griffin Fixed various issues with Full Build Switch to build non-client/server configurations for some games Included PS4 and Xbox game targets in our internal monolithics aggregate Added Requirements for steps that need UHT, SCW etc. Added list of Packaged Game Nodes that we can build up as they're defined Added targets that were previously in the Internal Tools nodes Changed APIDocTool to build Release as that's what the solution uses and made use of the path created for it Removed -clean from the NonUnity targets as that doesn't actually build anything Changed mail notifications so that individual nodes are used for content modifiers, not every preceeding node too Change 3047068 on 2016/07/12 by Ben.Marsh BuildGraph: Reduce the amount of log output when compiling a C# project; use /verbosity:minimal and /nolog, as Visual Studio does. Change 3047298 on 2016/07/12 by Ben.Marsh EC: Add a workspace setting specifying that it should be synced incrementally. Change 3047626 on 2016/07/13 by Matthew.Griffin Added PackageToNetwork property, which will default to false, which determines whether to put staged builds on the P: drive or within the LocalBuilds folder of the root dir Also changed WorldExplorers to use P:/Builds/Friday instead of WEX, as no one is now clearing up the WEX folder regularly Change 3047762 on 2016/07/13 by Matthew.Griffin Added -nodebuginfo to all compile tasks with -precompile to reduce the size of libs produced Added plugin intermediates to list of files excluded from installed build [CL 3047809 by Ben Marsh in Main branch]
672 lines
24 KiB
C#
672 lines
24 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>
|
|
/// Tag to be applied to any non-private references the projects have
|
|
/// (i.e. those that are external and not copied into the output dir)
|
|
/// </summary>
|
|
[TaskParameter(Optional = true, ValidationType = TaskParameterValidationType.TagList)]
|
|
public string TagReferences;
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
Arguments.Add("/verbosity:minimal");
|
|
Arguments.Add("/nologo");
|
|
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;
|
|
HashSet<FileReference> ProjectReferences;
|
|
if(!FindBuildProducts(ProjectFiles, Properties, out ProjectBuildProducts, out ProjectReferences))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Apply the optional tag to the produced archive
|
|
foreach(string TagName in FindTagNamesFromList(Parameters.Tag))
|
|
{
|
|
FindOrAddTagSet(TagNameToFileSet, TagName).UnionWith(ProjectBuildProducts);
|
|
}
|
|
|
|
if (!String.IsNullOrEmpty(Parameters.TagReferences))
|
|
{
|
|
// Apply the optional tag to any references
|
|
foreach (string TagName in FindTagNamesFromList(Parameters.TagReferences))
|
|
{
|
|
FindOrAddTagSet(TagNameToFileSet, TagName).UnionWith(ProjectReferences);
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
foreach (string TagName in FindTagNamesFromList(Parameters.Tag))
|
|
{
|
|
yield return TagName;
|
|
}
|
|
|
|
foreach (string TagName in FindTagNamesFromList(Parameters.TagReferences))
|
|
{
|
|
yield return TagName;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Find all the build products created by compiling the given project file
|
|
/// </summary>
|
|
/// <param name="ProjectFiles">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>
|
|
/// <param name="OutReferences">Receives a set of non-private references 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, out HashSet<FileReference> OutReferences)
|
|
{
|
|
// 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;
|
|
OutReferences = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Find all the build products and references
|
|
HashSet<FileReference> BuildProducts = new HashSet<FileReference>();
|
|
HashSet<FileReference> References = 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(KeyValuePair<FileReference, bool> Reference in ProjectInfo.References)
|
|
{
|
|
FileReference OtherAssembly = Reference.Key;
|
|
if (Reference.Value)
|
|
{
|
|
// Add reference from the output dir
|
|
FileReference OutputFile = FileReference.Combine(OutputDir, OtherAssembly.GetFileName());
|
|
BuildProducts.Add(OutputFile);
|
|
|
|
FileReference SymbolFile = OtherAssembly.ChangeExtension(".pdb");
|
|
if(SymbolFile.Exists())
|
|
{
|
|
BuildProducts.Add(OutputFile.ChangeExtension(".pdb"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Add reference directly
|
|
References.Add(OtherAssembly);
|
|
FileReference SymbolFile = OtherAssembly.ChangeExtension(".pdb");
|
|
if(SymbolFile.Exists())
|
|
{
|
|
References.Add(SymbolFile);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
OutReferences = References;
|
|
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"));
|
|
FileReference ExeConfig = FileReference.Combine(OutputDir, AssemblyName + ".exe.config");
|
|
if (ExeConfig.Exists()) { BuildProducts.Add(ExeConfig); }
|
|
FileReference ExeMDB = FileReference.Combine(OutputDir, AssemblyName + "exe.mdb");
|
|
if (ExeMDB.Exists()) { BuildProducts.Add(ExeMDB); }
|
|
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();
|
|
}
|
|
}
|
|
}
|