// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; using Tools.DotNETCommon; using UnrealBuildTool; namespace AutomationTool.Tasks { /// /// Parameters for a task that compiles a C# project /// public class CsCompileTaskParameters { /// /// The C# project file to be compile. More than one project file can be specified by separating with semicolons. /// [TaskParameter] public string Project; /// /// The configuration to compile /// [TaskParameter(Optional = true)] public string Configuration; /// /// The platform to compile /// [TaskParameter(Optional = true)] public string Platform; /// /// The target to build /// [TaskParameter(Optional = true)] public string Target; /// /// Additional options to pass to the compiler /// [TaskParameter(Optional = true)] public string Arguments; /// /// Only enumerate build products; do not actually compile the projects. /// [TaskParameter(Optional = true)] public bool EnumerateOnly; /// /// Tag to be applied to build products of this task /// [TaskParameter(Optional = true, ValidationType = TaskParameterValidationType.TagList)] public string Tag; /// /// 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) /// [TaskParameter(Optional = true, ValidationType = TaskParameterValidationType.TagList)] public string TagReferences; } /// /// Compiles C# project files, and their dependencies. /// [TaskElement("CsCompile", typeof(CsCompileTaskParameters))] public class CsCompileTask : CustomTask { /// /// Parameters for the task /// CsCompileTaskParameters Parameters; /// /// Constructor. /// /// Parameters for this task public CsCompileTask(CsCompileTaskParameters InParameters) { Parameters = InParameters; } /// /// Execute the task. /// /// Information about the current job /// Set of build products produced by this node. /// Mapping from tag names to the set of files they include public override void Execute(JobContext Job, HashSet BuildProducts, Dictionary> TagNameToFileSet) { // Get the project file HashSet ProjectFiles = ResolveFilespec(CommandUtils.RootDirectory, Parameters.Project, TagNameToFileSet); foreach(FileReference ProjectFile in ProjectFiles) { if(!FileReference.Exists(ProjectFile)) { throw new AutomationException("Couldn't find project file '{0}'", ProjectFile.FullName); } if(!ProjectFile.HasExtension(".csproj")) { throw new AutomationException("File '{0}' is not a C# project", ProjectFile.FullName); } } // Get the default properties Dictionary Properties = new Dictionary(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 Arguments = new List(); foreach(KeyValuePair 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); } if(!String.IsNullOrEmpty(Parameters.Target)) { Arguments.Add(String.Format("/target:{0}", CommandUtils.MakePathSafeToUseWithCommandLine(Parameters.Target))); } 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 ProjectBuildProducts; HashSet ProjectReferences; FindBuildProductsAndReferences(ProjectFiles, Properties, out ProjectBuildProducts, out ProjectReferences); // Apply the optional tag to the produced archive foreach(string TagName in FindTagNamesFromList(Parameters.Tag)) { FindOrAddTagSet(TagNameToFileSet, TagName).UnionWith(ProjectBuildProducts); } // Apply the optional tag to any references if (!String.IsNullOrEmpty(Parameters.TagReferences)) { foreach (string TagName in FindTagNamesFromList(Parameters.TagReferences)) { FindOrAddTagSet(TagNameToFileSet, TagName).UnionWith(ProjectReferences); } } // Merge them into the standard set of build products BuildProducts.UnionWith(ProjectBuildProducts); BuildProducts.UnionWith(ProjectReferences); } /// /// Output this task out to an XML writer. /// public override void Write(XmlWriter Writer) { Write(Writer, Parameters); } /// /// Find all the tags which are used as inputs to this task /// /// The tag names which are read by this task public override IEnumerable FindConsumedTagNames() { return FindTagNamesFromFilespec(Parameters.Project); } /// /// Find all the tags which are modified by this task /// /// The tag names which are modified by this task public override IEnumerable FindProducedTagNames() { foreach (string TagName in FindTagNamesFromList(Parameters.Tag)) { yield return TagName; } foreach (string TagName in FindTagNamesFromList(Parameters.TagReferences)) { yield return TagName; } } /// /// Find all the build products created by compiling the given project file /// /// Initial project file to read. All referenced projects will also be read. /// Mapping of property name to value /// Receives a set of build products on success /// Receives a set of non-private references on success static void FindBuildProductsAndReferences(HashSet ProjectFiles, Dictionary InitialProperties, out HashSet OutBuildProducts, out HashSet OutReferences) { // Find all the build products and references OutBuildProducts = new HashSet(); OutReferences = new HashSet(); // Read all the project information into a dictionary Dictionary FileToProjectInfo = new Dictionary(); foreach(FileReference ProjectFile in ProjectFiles) { // Read all the projects ReadProjectsRecursively(ProjectFile, InitialProperties, FileToProjectInfo); // Find all the outputs for each project foreach(KeyValuePair Pair in FileToProjectInfo) { CsProjectInfo ProjectInfo = Pair.Value; // Add all the build projects from this project DirectoryReference OutputDir = ProjectInfo.GetOutputDir(Pair.Key.Directory); ProjectInfo.FindBuildProducts(OutputDir, OutBuildProducts, FileToProjectInfo); // Add any files which are only referenced foreach (KeyValuePair Reference in ProjectInfo.References) { if(!Reference.Value) { CsProjectInfo.AddReferencedAssemblyAndSupportFiles(Reference.Key, OutReferences); } } } } OutBuildProducts.RemoveWhere(x => !FileReference.Exists(x)); OutReferences.RemoveWhere(x => !FileReference.Exists(x)); } /// /// Read a project file, plus all the project files it references. /// /// Project file to read /// Mapping of property name to value for the initial project /// /// True if the projects were read correctly, false (and prints an error to the log) if not static void ReadProjectsRecursively(FileReference File, Dictionary InitialProperties, Dictionary FileToProjectInfo) { // Early out if we've already read this project if (!FileToProjectInfo.ContainsKey(File)) { // Try to read this project CsProjectInfo ProjectInfo; if (!CsProjectInfo.TryRead(File, InitialProperties, out ProjectInfo)) { throw new AutomationException("Couldn't read project '{0}'", File.FullName); } // Add it to the project lookup, and try to read all the projects it references FileToProjectInfo.Add(File, ProjectInfo); foreach(FileReference ProjectReference in ProjectInfo.ProjectReferences.Keys) { if(!FileReference.Exists(ProjectReference)) { throw new AutomationException("Unable to find project '{0}' referenced by '{1}'", ProjectReference, File); } ReadProjectsRecursively(ProjectReference, InitialProperties, FileToProjectInfo); } } } } }