// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using EpicGames.Core; using UnrealBuildBase; using UnrealBuildTool; using Microsoft.Extensions.Logging; namespace AutomationTool { [Help("Builds the specified targets and configurations for the specified project.")] [Help("Example BuildTarget -project=QAGame -target=Editor+Game -platform=Win64+Android -configuration=Development.")] [Help("Note: Editor will only ever build for the current platform in a Development config and required tools will be included")] [Help("project=", "Project to build. Will search current path and paths in ueprojectdirs. If omitted will build vanilla UnrealEditor")] [Help("platform=Win64+Android", "Platforms to build, join multiple platforms using +")] [Help("configuration=Development+Test", "Configurations to build, join multiple configurations using +")] [Help("target=Editor+Game", "Targets to build, join multiple targets using +")] [Help("notools", "Don't build any tools (UnrealPak, Lightmass, ShaderCompiler, CrashReporter")] [Help("clean", "Do a clean build")] [Help("NoXGE", "Toggle to disable the distributed build process")] [Help("DisableUnity", "Toggle to disable the unity build system")] public class BuildTarget : BuildCommand { // exposed as a property so projects can derive and set this directly public string ProjectName { get; set; } public string Targets { get; set; } public string Platforms { get; set; } public string Configurations { get; set; } public bool Clean { get; set; } public bool NoTools { get; set; } public string UBTArgs { get; set; } public bool Preview { get; set; } // It would be nice to use SingleTargetProperties but we can't get rules // For UE types.. public class SimpleTargetInfo { public string TargetName { get; private set; } public TargetType Type { get; private set; } public SimpleTargetInfo(string InName, TargetType InType) { TargetName = InName; Type = InType; } } public BuildTarget() { Platforms = HostPlatform.Current.HostEditorPlatform.ToString(); Configurations = "Development"; UBTArgs = ""; } public override ExitCode Execute() { string[] Arguments = this.Params; ProjectName = ParseParamValue("project", ProjectName); Targets = ParseParamValue("target", Targets); Platforms = ParseParamValue("platform", Platforms); Configurations = ParseParamValue("configuration", Configurations); Clean = ParseParam("clean") || Clean; NoTools = ParseParam("NoTools") || NoTools; UBTArgs = ParseParamValue("ubtargs", UBTArgs); Preview = ParseParam("preview") || Preview; if (string.IsNullOrEmpty(Targets)) { throw new AutomationException("No target specified with -target. Use -help to see all options"); } IEnumerable TargetList = Targets.Split(new char[] { '+' }, StringSplitOptions.RemoveEmptyEntries); IEnumerable ConfigurationList = null; IEnumerable PlatformList = null; try { ConfigurationList = Configurations.Split(new char[] { '+' }, StringSplitOptions.RemoveEmptyEntries) .Select(C => (UnrealTargetConfiguration)Enum.Parse(typeof(UnrealTargetConfiguration), C, true)).ToArray(); } catch (Exception Ex) { Logger.LogError("Failed to parse configuration string. {Arg0}", Ex.Message); return ExitCode.Error_Arguments; } try { PlatformList = Platforms.Split(new char[] { '+' }, StringSplitOptions.RemoveEmptyEntries) .Select(C => { UnrealTargetPlatform Platform; if (!UnrealTargetPlatform.TryParse(C, out Platform)) { throw new AutomationException("No such platform {0}", C); } return Platform; }).ToArray(); } catch (Exception Ex) { Logger.LogError("Failed to parse configuration string. {Arg0}", Ex.Message); return ExitCode.Error_Arguments; } FileReference ProjectFile = null; if (!string.IsNullOrEmpty(ProjectName)) { // find the project ProjectFile = ProjectUtils.FindProjectFileFromName(ProjectName); if (ProjectFile == null) { throw new AutomationException("Unable to find uproject file for {0}", ProjectName); } } IEnumerable BuildTargets = TargetList.Select(T => ProjectTargetFromTargetName(T, ProjectFile, PlatformList, ConfigurationList)).ToArray(); bool ContainsEditor = BuildTargets.Where(T => T.Type == TargetType.Editor).Any(); bool SingleBuild = BuildTargets.Count() == 1 && PlatformList.Count() == 1 && ConfigurationList.Count() == 1; if (!SingleBuild || (ContainsEditor && !NoTools)) { UnrealBuild Build = new UnrealBuild(this); UnrealBuild.BuildAgenda Agenda = new UnrealBuild.BuildAgenda(); SimpleTargetInfo EditorTarget = BuildTargets.Where(T => T.Type == TargetType.Editor).FirstOrDefault(); IEnumerable OtherTargets = BuildTargets.Where(T => T.Type != TargetType.Editor); UnrealTargetPlatform CurrentPlatform = HostPlatform.Current.HostEditorPlatform; if (EditorTarget != null) { Agenda.AddTarget(EditorTarget.TargetName, CurrentPlatform, UnrealTargetConfiguration.Development, ProjectFile, UBTArgs); if (!NoTools) { Agenda.AddTarget("UnrealPak", CurrentPlatform, UnrealTargetConfiguration.Development, ProjectFile, UBTArgs); Agenda.AddTarget("ShaderCompileWorker", CurrentPlatform, UnrealTargetConfiguration.Development, ProjectFile, UBTArgs); Agenda.AddTarget("UnrealLightmass", CurrentPlatform, UnrealTargetConfiguration.Development, ProjectFile, UBTArgs); Agenda.AddTarget("InterchangeWorker", CurrentPlatform, UnrealTargetConfiguration.Development, ProjectFile, UBTArgs); Agenda.AddTarget("CrashReportClient", CurrentPlatform, UnrealTargetConfiguration.Shipping, ProjectFile, UBTArgs); Agenda.AddTarget("CrashReportClientEditor", CurrentPlatform, UnrealTargetConfiguration.Shipping, ProjectFile, UBTArgs); } } foreach (SimpleTargetInfo Target in OtherTargets) { bool IsServer = Target.Type == TargetType.Server; IEnumerable PlatformsToBuild = IsServer ? new UnrealTargetPlatform[] { CurrentPlatform } : PlatformList; foreach (UnrealTargetPlatform Platform in PlatformsToBuild) { foreach (UnrealTargetConfiguration Config in ConfigurationList) { Agenda.AddTarget(Target.TargetName, Platform, Config, ProjectFile, UBTArgs); } } } foreach (var Target in Agenda.Targets) { Logger.LogInformation("Will {Arg0}build {Target}", Clean ? "clean and " : "", Target); if (Clean) { Target.Clean = Clean; } } if (!Preview) { Build.Build(Agenda, InUpdateVersionFiles: false); } } else { UnrealTargetPlatform PlatformToBuild = PlatformList.First(); UnrealTargetConfiguration ConfigToBuild = ConfigurationList.First(); string TargetToBuild = BuildTargets.First().TargetName; if (!Preview) { // Compile the editor string CommandLine = CommandUtils.UBTCommandline(ProjectFile, TargetToBuild, PlatformToBuild, ConfigToBuild, UBTArgs); if (Clean) { CommandUtils.RunUBT(CommandUtils.CmdEnv, Unreal.UnrealBuildToolDllPath, CommandLine + " -clean"); } CommandUtils.RunUBT(CommandUtils.CmdEnv, Unreal.UnrealBuildToolDllPath, CommandLine); } else { Logger.LogInformation("Will {Arg0}build {TargetToBuild} {PlatformToBuild} {ConfigToBuild}", Clean ? "clean and " : "", TargetToBuild, PlatformToBuild, ConfigToBuild); } } return ExitCode.Success; } /// /// Takes a target type like "Editor" and returns the actual targetname used by the specified project. If no project is specified then /// the name of the UE types (e.g. UnrealEditor) is returned. /// /// /// /// /// /// public SimpleTargetInfo ProjectTargetFromTargetName(string InTargetName, FileReference InProjectFile, IEnumerable InPlatformList, IEnumerable InConfigurationList) { ProjectProperties Properties = InProjectFile != null ? ProjectUtils.GetProjectProperties(InProjectFile, InPlatformList.ToList(), InConfigurationList.ToList()) : null; SimpleTargetInfo ProjectTarget = null; IEnumerable AvailableTargets = null; if (Properties != null && Properties.bIsCodeBasedProject) { AvailableTargets = Properties.Targets.Select(T => new SimpleTargetInfo(T.TargetName, T.Rules.Type)); } else { // default UE targets AvailableTargets = new[] { new SimpleTargetInfo("UnrealEditor", TargetType.Editor), new SimpleTargetInfo("UnrealGame", TargetType.Game), new SimpleTargetInfo("UnrealClient", TargetType.Client), new SimpleTargetInfo("UnrealServer", TargetType.Server), }; } // If they asked for ShooterClient etc and that's there, just return that. ProjectTarget = AvailableTargets.FirstOrDefault(T => T.TargetName.Equals(InTargetName, StringComparison.OrdinalIgnoreCase)); if (ProjectTarget == null) { // find targets that use rules of the desired type IEnumerable MatchingTargetTypes = AvailableTargets .Where(T => T.Type.ToString().Equals(InTargetName, StringComparison.OrdinalIgnoreCase)); if (MatchingTargetTypes.Any()) { if (MatchingTargetTypes.Count() == 1) { ProjectTarget = MatchingTargetTypes.First(); } else { // if multiple targets, pick the one that starts with project name and contains the target type // (Some projects have multiple targets of a given type) ProjectTarget = MatchingTargetTypes .Where(T => string.CompareOrdinal(T.TargetName, 0, ProjectName, 0, 1) == 0) .Where(T => T.TargetName.IndexOf(InTargetName, StringComparison.OrdinalIgnoreCase) > 0) .FirstOrDefault(); // Try to find a target where the target type matches the target name exactly such as "Editor" or "Game" if (ProjectTarget == null && string.CompareOrdinal(InTargetName, "Program") != 0) { ProjectTarget = MatchingTargetTypes .Where(T => string.CompareOrdinal(T.Type.ToString(), InTargetName) == 0) .FirstOrDefault(); } } } } // If no target is found, try to build the provided target name. This enables programs to build (Ex. UnrealInsights). if(ProjectTarget == null) { ProjectTarget = new SimpleTargetInfo(InTargetName, TargetType.Program); } if (ProjectTarget == null) { throw new AutomationException("{0} is not a valid target in {1}", InTargetName, InProjectFile); } return ProjectTarget; } } [Help("Builds the editor for the specified project.")] [Help("Example BuildEditor -project=QAGame")] [Help("Note: Editor will only ever build for the current platform in a Development config and required tools will be included")] [Help("project=", "Project to build. Will search current path and paths in ueprojectdirs. If omitted will build vanilla UnrealEditor")] [Help("notools", "Don't build any tools (UHT, ShaderCompiler, CrashReporter")] class BuildEditor : BuildTarget { public BuildEditor() { Targets = "Editor"; } public override ExitCode Execute() { bool DoOpen = ParseParam("open"); ExitCode Status = base.Execute(); if (Status == ExitCode.Success && DoOpen) { OpenEditor OpenCmd = new OpenEditor(); OpenCmd.ProjectName = this.ProjectName; Status = OpenCmd.Execute(); } return Status; } } [Help("Builds the game for the specified project.")] [Help("Example BuildGame -project=QAGame -platform=Win64+Android -configuration=Development.")] [Help("project=", "Project to build. Will search current path and paths in ueprojectdirs.")] [Help("platform=Wind64+Android", "Platforms to build, join multiple platforms using +")] [Help("configuration=Development+Test", "Configurations to build, join multiple configurations using +")] [Help("notools", "Don't build any tools (UHT, ShaderCompiler, CrashReporter")] class BuildGame : BuildTarget { public BuildGame() { Targets = "Game"; } } [Help("Builds the server for the specified project.")] [Help("Example BuildServer -project=QAGame -platform=Win64 -configuration=Development.")] [Help("project=", "Project to build. Will search current path and paths in ueprojectdirs.")] [Help("platform=Win64", "Platforms to build, join multiple platforms using +")] [Help("configuration=Development+Test", "Configurations to build, join multiple configurations using +")] [Help("notools", "Don't build any tools (UHT, ShaderCompiler, CrashReporter")] class BuildServer : BuildTarget { public BuildServer() { Targets = "Server"; } } }