// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Tools.DotNETCommon; namespace UnrealBuildTool { /// /// Describes all of the information needed to initialize a UEBuildTarget object /// class TargetDescriptor { public FileReference ProjectFile; public string Name; public UnrealTargetPlatform Platform; public UnrealTargetConfiguration Configuration; public string Architecture; public CommandLineArguments AdditionalArguments; /// /// Foreign plugin to compile against this target /// [CommandLine("-Plugin=")] public FileReference ForeignPlugin = null; /// /// Set of module names to compile. /// [CommandLine("-Module=")] public HashSet OnlyModuleNames = new HashSet(StringComparer.OrdinalIgnoreCase); /// /// Single file to compile /// [CommandLine("-SingleFile=")] public FileReference SingleFileToCompile = null; /// /// Whether to perform hot reload for this target /// [CommandLine("-NoHotReload", Value = nameof(HotReloadMode.Disabled))] [CommandLine("-ForceHotReload", Value = nameof(HotReloadMode.FromIDE))] [CommandLine("-LiveCoding", Value = nameof(HotReloadMode.LiveCoding))] public HotReloadMode HotReloadMode = HotReloadMode.Default; /// /// Map of module name to suffix for hot reloading from the editor /// public Dictionary HotReloadModuleNameToSuffix = new Dictionary(StringComparer.OrdinalIgnoreCase); /// /// Export the actions for the target to a file /// [CommandLine("-WriteActions=")] public List WriteActionFiles = new List(); /// /// Path to a file containing a list of modules that may be modified for live coding. /// [CommandLine("-LiveCodingModules=")] public FileReference LiveCodingModules = null; /// /// Path to the manifest for passing info about the output to live coding /// [CommandLine("-LiveCodingManifest=")] public FileReference LiveCodingManifest = null; /// /// Suppress messages about building this target /// [CommandLine("-Quiet")] public bool bQuiet; /// /// Constructor /// /// Path to the project file /// Name of the target to build /// Platform to build for /// Configuration to build /// Architecture to build for /// Other command-line arguments for the target public TargetDescriptor(FileReference ProjectFile, string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, string Architecture, CommandLineArguments Arguments) { this.ProjectFile = ProjectFile; this.Name = TargetName; this.Platform = Platform; this.Configuration = Configuration; this.Architecture = Architecture; // If there are any additional command line arguments List AdditionalArguments = new List(); if(Arguments != null) { // Apply the arguments to this object Arguments.ApplyTo(this); // Parse all the hot-reload module names foreach(string ModuleWithSuffix in Arguments.GetValues("-ModuleWithSuffix=")) { int SuffixIdx = ModuleWithSuffix.LastIndexOf(','); if(SuffixIdx == -1) { throw new BuildException("Missing suffix argument from -ModuleWithSuffix=Name,Suffix"); } string ModuleName = ModuleWithSuffix.Substring(0, SuffixIdx); int Suffix; if(!Int32.TryParse(ModuleWithSuffix.Substring(SuffixIdx + 1), out Suffix)) { throw new BuildException("Suffix for modules must be an integer"); } HotReloadModuleNameToSuffix[ModuleName] = Suffix; } // Pull out all the arguments that haven't been used so far for(int Idx = 0; Idx < Arguments.Count; Idx++) { if(!Arguments.HasBeenUsed(Idx)) { AdditionalArguments.Add(Arguments[Idx]); } } } this.AdditionalArguments = new CommandLineArguments(AdditionalArguments.ToArray()); } /// /// Parse a list of target descriptors from the command line /// /// Command-line arguments /// Whether to use a precompiled engine distribution /// Whether to skip compiling rules assemblies /// List of target descriptors public static List ParseCommandLine(CommandLineArguments Arguments, bool bUsePrecompiled, bool bSkipRulesCompile) { List TargetDescriptors = new List(); ParseCommandLine(Arguments, bUsePrecompiled, bSkipRulesCompile, TargetDescriptors); return TargetDescriptors; } /// /// Parse a list of target descriptors from the command line /// /// Command-line arguments /// Whether to use a precompiled engine distribution /// Whether to skip compiling rules assemblies /// Receives the list of parsed target descriptors public static void ParseCommandLine(CommandLineArguments Arguments, bool bUsePrecompiled, bool bSkipRulesCompile, List TargetDescriptors) { List TargetLists; Arguments = Arguments.Remove("-TargetList=", out TargetLists); List Targets; Arguments = Arguments.Remove("-Target=", out Targets); if(TargetLists.Count > 0 || Targets.Count > 0) { // Try to parse multiple arguments from a single command line foreach(string TargetList in TargetLists) { string[] Lines = File.ReadAllLines(TargetList); foreach(string Line in Lines) { string TrimLine = Line.Trim(); if(TrimLine.Length > 0 && TrimLine[0] != ';') { CommandLineArguments NewArguments = Arguments.Append(CommandLineArguments.Split(TrimLine)); ParseCommandLine(NewArguments, bUsePrecompiled, bSkipRulesCompile, TargetDescriptors); } } } foreach(string Target in Targets) { CommandLineArguments NewArguments = Arguments.Append(CommandLineArguments.Split(Target)); ParseCommandLine(NewArguments, bUsePrecompiled, bSkipRulesCompile, TargetDescriptors); } } else { // Otherwise just process the whole command line together ParseSingleCommandLine(Arguments, bUsePrecompiled, bSkipRulesCompile, TargetDescriptors); } } /// /// Parse a list of target descriptors from the command line /// /// Command-line arguments /// Whether to use a precompiled engine distribution /// Whether to skip compiling rules assemblies /// List of target descriptors public static void ParseSingleCommandLine(CommandLineArguments Arguments, bool bUsePrecompiled, bool bSkipRulesCompile, List TargetDescriptors) { List Platforms = new List(); List Configurations = new List(); List TargetNames = new List(); FileReference ProjectFile = Arguments.GetFileReferenceOrDefault("-Project=", null); // Settings for creating/using static libraries for the engine for (int ArgumentIndex = 0; ArgumentIndex < Arguments.Count; ArgumentIndex++) { string Argument = Arguments[ArgumentIndex]; if(Argument.Length > 0 && Argument[0] != '-') { // Mark this argument as used. We'll interpret it as one thing or another. Arguments.MarkAsUsed(ArgumentIndex); // Check if it's a project file argument if(Argument.EndsWith(".uproject", StringComparison.OrdinalIgnoreCase)) { FileReference NewProjectFile = new FileReference(Argument); if(ProjectFile != null && ProjectFile != NewProjectFile) { throw new BuildException("Multiple project files specified on command line (first {0}, then {1})", ProjectFile, NewProjectFile); } ProjectFile = new FileReference(Argument); continue; } // Split it into separate arguments string[] InlineArguments = Argument.Split('+'); // Try to parse them as platforms UnrealTargetPlatform ParsedPlatform; if(UnrealTargetPlatform.TryParse(InlineArguments[0], out ParsedPlatform)) { Platforms.Add(ParsedPlatform); for(int InlineArgumentIdx = 1; InlineArgumentIdx < InlineArguments.Length; InlineArgumentIdx++) { Platforms.Add(UnrealTargetPlatform.Parse(InlineArguments[InlineArgumentIdx])); } continue; } // Try to parse them as configurations UnrealTargetConfiguration ParsedConfiguration; if(Enum.TryParse(InlineArguments[0], true, out ParsedConfiguration)) { Configurations.Add(ParsedConfiguration); for(int InlineArgumentIdx = 1; InlineArgumentIdx < InlineArguments.Length; InlineArgumentIdx++) { string InlineArgument = InlineArguments[InlineArgumentIdx]; if(!Enum.TryParse(InlineArgument, true, out ParsedConfiguration)) { throw new BuildException("Invalid configuration '{0}'", InlineArgument); } Configurations.Add(ParsedConfiguration); } continue; } // Otherwise assume they are target names TargetNames.AddRange(InlineArguments); } } if (Platforms.Count == 0) { throw new BuildException("No platforms specified for target"); } if (Configurations.Count == 0) { throw new BuildException("No configurations specified for target"); } // Make sure the project file exists, and make sure we're using the correct case. if(ProjectFile != null) { FileInfo ProjectFileInfo = FileUtils.FindCorrectCase(ProjectFile.ToFileInfo()); if(!ProjectFileInfo.Exists) { throw new BuildException("Unable to find project '{0}'.", ProjectFile); } ProjectFile = new FileReference(ProjectFileInfo); } // Expand all the platforms, architectures and configurations foreach(UnrealTargetPlatform Platform in Platforms) { // Make sure the platform is valid if (!InstalledPlatformInfo.IsValid(null, Platform, null, EProjectType.Code, InstalledPlatformState.Downloaded)) { if (!InstalledPlatformInfo.IsValid(null, Platform, null, EProjectType.Code, InstalledPlatformState.Supported)) { throw new BuildException("The {0} platform is not supported from this engine distribution.", Platform); } else { throw new BuildException("Missing files required to build {0} targets. Enable {0} as an optional download component in the Epic Games Launcher.", Platform); } } // Parse the architecture parameter, or get the default for the platform List Architectures = new List(Arguments.GetValues("-Architecture=", '+')); if(Architectures.Count == 0) { Architectures.Add(UEBuildPlatform.GetBuildPlatform(Platform).GetDefaultArchitecture(ProjectFile)); } foreach(string Architecture in Architectures) { foreach(UnrealTargetConfiguration Configuration in Configurations) { // Create all the target descriptors for targets specified by type foreach(string TargetTypeString in Arguments.GetValues("-TargetType=")) { TargetType TargetType; if(!Enum.TryParse(TargetTypeString, out TargetType)) { throw new BuildException("Invalid target type '{0}'", TargetTypeString); } if (ProjectFile == null) { throw new BuildException("-TargetType=... requires a project file to be specified"); } else { TargetNames.Add(RulesCompiler.CreateProjectRulesAssembly(ProjectFile, bUsePrecompiled, bSkipRulesCompile).GetTargetNameByType(TargetType, Platform, Configuration, Architecture, ProjectFile)); } } // Make sure we could parse something if (TargetNames.Count == 0) { throw new BuildException("No target name was specified on the command-line."); } // Create all the target descriptors foreach(string TargetName in TargetNames) { // If a project file was not specified see if we can find one if (ProjectFile == null && NativeProjects.TryGetProjectForTarget(TargetName, out ProjectFile)) { Log.TraceVerbose("Found project file for {0} - {1}", TargetName, ProjectFile); } // Create the target descriptor TargetDescriptors.Add(new TargetDescriptor(ProjectFile, TargetName, Platform, Configuration, Architecture, Arguments)); } } } } } /// /// Try to parse the project file from the command line /// /// The command line arguments /// The project file that was parsed /// True if the project file was parsed, false otherwise public static bool TryParseProjectFileArgument(CommandLineArguments Arguments, out FileReference ProjectFile) { FileReference ExplicitProjectFile; if(Arguments.TryGetValue("-Project=", out ExplicitProjectFile)) { ProjectFile = ExplicitProjectFile; return true; } for(int Idx = 0; Idx < Arguments.Count; Idx++) { if(Arguments[Idx][0] != '-' && Arguments[Idx].EndsWith(".uproject", StringComparison.OrdinalIgnoreCase)) { Arguments.MarkAsUsed(Idx); ProjectFile = new FileReference(Arguments[Idx]); return true; } } if(UnrealBuildTool.IsProjectInstalled()) { ProjectFile = UnrealBuildTool.GetInstalledProjectFile(); return true; } ProjectFile = null; return false; } /// /// Parse a single argument value, of the form -Foo=Bar /// /// The argument to parse /// The argument prefix, eg. "-Foo=" /// Receives the value of the argument /// True if the argument could be parsed, false otherwise private static bool ParseArgumentValue(string Argument, string Prefix, out string Value) { if(Argument.StartsWith(Prefix, StringComparison.InvariantCultureIgnoreCase)) { Value = Argument.Substring(Prefix.Length); return true; } else { Value = null; return false; } } /// /// Format this object for the debugger /// /// String representation of this target descriptor public override string ToString() { StringBuilder Result = new StringBuilder(); Result.AppendFormat("{0} {1} {2}", Name, Platform, Configuration); if(!String.IsNullOrEmpty(Architecture)) { Result.AppendFormat(" -Architecture={0}", Architecture); } if(ProjectFile != null) { Result.AppendFormat(" -Project={0}", Utils.MakePathSafeToUseWithCommandLine(ProjectFile)); } if(AdditionalArguments != null && AdditionalArguments.Count > 0) { Result.AppendFormat(" {0}", AdditionalArguments); } return Result.ToString(); } } }