// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { /// /// Queries information about the targets supported by a project /// [ToolMode("QueryTargets", ToolModeOptions.XmlConfig | ToolModeOptions.BuildPlatformsHostOnly | ToolModeOptions.SingleInstance)] class QueryTargetsMode : ToolMode { /// /// Path to the project file to query /// [CommandLine("-Project=")] FileReference? ProjectFile = null; /// /// Path to the output file to receive information about the targets /// [CommandLine("-Output=")] FileReference? OutputFile = null; /// /// Write out all targets, even if a default is specified in the BuildSettings section of the Default*.ini files. /// [CommandLine("-IncludeAllTargets")] bool bIncludeAllTargets = false; /// /// Write all targets from parent assemblies (useful for EngineRules which can be chained) /// [CommandLine("-IncludeParentAssembly", Value ="true")] [CommandLine("-DontIncludeParentAssembly", Value = "false")] bool bIncludeParentAssembly = true; /// /// Execute the mode /// /// Command line arguments /// /// public override Task ExecuteAsync(CommandLineArguments Arguments, ILogger Logger) { Arguments.ApplyTo(this); // Create the build configuration object, and read the settings BuildConfiguration BuildConfiguration = new BuildConfiguration(); XmlConfig.ApplyTo(BuildConfiguration); Arguments.ApplyTo(BuildConfiguration); // Ensure the path to the output file is valid if (OutputFile == null) { OutputFile = GetDefaultOutputFile(ProjectFile); } // Create the rules assembly RulesAssembly Assembly; if (ProjectFile != null) { Assembly = RulesCompiler.CreateProjectRulesAssembly(ProjectFile, BuildConfiguration.bUsePrecompiled, BuildConfiguration.bSkipRulesCompile, BuildConfiguration.bForceRulesCompile, Logger); } else { Assembly = RulesCompiler.CreateEngineRulesAssembly(BuildConfiguration.bUsePrecompiled, BuildConfiguration.bSkipRulesCompile, BuildConfiguration.bForceRulesCompile, Logger); } // Write information about these targets WriteTargetInfo(ProjectFile, Assembly, OutputFile, Arguments, Logger, bIncludeAllTargets, bIncludeParentAssembly); Logger.LogInformation("Written {OutputFile}", OutputFile); return Task.FromResult(0); } /// /// Gets the path to the target info output file /// /// Project file being queried /// Path to the output file public static FileReference GetDefaultOutputFile(FileReference? ProjectFile) { if (ProjectFile == null) { return FileReference.Combine(Unreal.EngineDirectory, "Intermediate", "TargetInfo.json"); } else { return FileReference.Combine(ProjectFile.Directory, "Intermediate", "TargetInfo.json"); } } /// /// Writes information about the targets in an assembly to a file /// /// The project file for the targets being built /// The rules assembly for this target /// Output file to write to /// /// Logger for output /// Include all targets even if a default target is specified for a given target type. /// Include all targets from parent assemblies (useful for EngineRules which can be chained) public static void WriteTargetInfo(FileReference? ProjectFile, RulesAssembly Assembly, FileReference OutputFile, CommandLineArguments Arguments, ILogger Logger, bool bIncludeAllTargets = true, bool bIncludeParentAssembly = false) { // Construct all the targets in this assembly List TargetNames = new List(); Assembly.GetAllTargetNames(TargetNames, bIncludeParentAssembly); // Generate json using System.IO.StringWriter StringWriter = new(); using (JsonWriter Writer = new(StringWriter)) { Writer.WriteObjectStart(); Writer.WriteArrayStart("Targets"); foreach (string TargetName in TargetNames) { // skip target rules that are platform extension or platform group specializations string[] TargetPathSplit = TargetName.Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries); if (TargetPathSplit.Length > 1 && (UnrealTargetPlatform.IsValidName(TargetPathSplit.Last()) || UnrealPlatformGroup.IsValidName(TargetPathSplit.Last()))) { continue; } // Construct the rules object TargetRules TargetRules; try { UnrealArchitectures Architectures = UnrealArchitectureConfig.ForPlatform(BuildHostPlatform.Current.Platform).ActiveArchitectures(ProjectFile, TargetName); TargetRules = Assembly.CreateTargetRules(TargetName, BuildHostPlatform.Current.Platform, UnrealTargetConfiguration.Development, Architectures, ProjectFile, Arguments, Logger, ValidationOptions: TargetRulesValidationOptions.ValidateNothing); } catch (Exception Ex) { Logger.LogWarning("Unable to construct target rules for {TargetName}", TargetName); Logger.LogDebug("{Ex}", ExceptionUtils.FormatException(Ex)); continue; } // Is this a default target? bool? bIsDefaultTarget = null; if (ProjectFile != null) { string? DefaultTargetName = ProjectFileGenerator.GetProjectDefaultTargetNameForType(ProjectFile.Directory, TargetRules.Type); // GetProjectDefaultTargetNameForType returns if (DefaultTargetName != null) { bIsDefaultTarget = DefaultTargetName == TargetName; } } // If we don't want all targets, skip over non-defaults. if (!bIncludeAllTargets && bIsDefaultTarget.HasValue && !bIsDefaultTarget.Value) { continue; } // Get the path to the target FileReference? path = Assembly.GetTargetFileName(TargetName); // Write the target info Writer.WriteObjectStart(); Writer.WriteValue("Name", TargetName); if (path != null) { Writer.WriteValue("Path", path.MakeRelativeTo(OutputFile.Directory)); } Writer.WriteValue("Type", TargetRules.Type.ToString()); if (bIncludeAllTargets && bIsDefaultTarget.HasValue) { Writer.WriteValue("DefaultTarget", bIsDefaultTarget.Value); } Writer.WriteObjectEnd(); } Writer.WriteArrayEnd(); Writer.WriteObjectEnd(); } // Write the output file DirectoryReference.CreateDirectory(OutputFile.Directory); FileReference.WriteAllTextIfDifferent(OutputFile, StringWriter.ToString()); // even if the contents don't change, we need to update the last write time (this is safer to do in parallel, unlike WriteAllText) FileReference.SetLastWriteTimeUtc(OutputFile, DateTime.UtcNow); } } }