// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using EpicGames.Core; using Microsoft.Extensions.Logging; using OpenTracing.Util; using UnrealBuildBase; namespace UnrealBuildTool { // TODO: Namespacing? enum QueryType { Capabilities, AvailableTargets, TargetDetails, } internal class TargetIntellisenseInfo { internal class CompileSettings { public List IncludePaths { get; set; } = new(); public List Defines { get; set; } = new(); public string? Standard { get; set; } public List ForcedIncludes { get; set; } = new(); public string? CompilerPath { get; set; } public List CompilerArgs { get; set; } = new(); public string? WindowsSdkVersion { get; set; } } public Dictionary ModuleToCompileSettings = new(); public Dictionary DirToModule = new(); public UEBuildModule? FindModuleForFile(FileReference File) { DirectoryReference? Dir = File.Directory; while (Dir != null) { if (DirToModule.TryGetValue(Dir, out UEBuildModule? Module)) { return Module; } Dir = Dir.ParentDirectory; } return null; } public UEBuildModule? FindModuleForDirectory(DirectoryReference Directory) { DirectoryReference? Dir = Directory; while (Dir != null) { if (DirToModule.TryGetValue(Dir, out UEBuildModule? Module)) { return Module; } Dir = Dir.ParentDirectory; } return null; } } internal class TargetConfigs { public string ProjectPath { get; set; } = ""; public List Platforms { get; set; } = new(); public List Configurations { get; set; } = new(); } [ToolMode("Query", ToolModeOptions.BuildPlatforms | ToolModeOptions.XmlConfig | ToolModeOptions.UseStartupTraceListener)] class QueryMode : ToolMode { [CommandLine("-LogDirectory=")] public DirectoryReference? LogDirectory = null; [CommandLine("-Query=")] public QueryType? Query = null; [CommandLine("-IncludeEngineSource=")] public bool bIncludeEngineSource = true; [CommandLine("-Target=")] public string? TargetName; [CommandLine("-Configuration=")] public string? TargetConfiguration; [CommandLine("-Platform=")] public string? TargetPlatform; [CommandLine("-Indented")] public bool bIndented; private BuildConfiguration BuildConfiguration = new(); public override Task ExecuteAsync(CommandLineArguments Arguments, ILogger Logger) { Arguments.ApplyTo(this); if (LogDirectory == null) { LogDirectory = DirectoryReference.Combine(Unreal.EngineProgramSavedDirectory, "UnrealBuildTool"); } DirectoryReference.CreateDirectory(LogDirectory); FileReference LogFile = FileReference.Combine(LogDirectory, "Log_Query.txt"); // TODO: More history? Pass a log file path on the cmd line from extension and prune from there? Log.AddFileWriter("DefaultLogTraceListener", LogFile); XmlConfig.ApplyTo(BuildConfiguration); Arguments.ApplyTo(BuildConfiguration); // TODO: Document this hack ProjectFileGenerator.bGenerateProjectFiles = true; if (Query == null) { return Task.FromResult(0); } JsonSerializerOptions ResponseOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = bIndented, }; switch (Query) { case QueryType.Capabilities: Logger.LogInformation("QueryCapabilities"); return Task.FromResult(QueryCapabilities(Arguments, Logger, ResponseOptions)); case QueryType.AvailableTargets: Logger.LogInformation("QueryAvailableTargets"); return Task.FromResult(QueryAvailableTargets(Arguments, Logger, ResponseOptions)); case QueryType.TargetDetails: Logger.LogInformation("QueryTargetDetails"); return Task.FromResult(QueryTargetDetails(Arguments, Logger, ResponseOptions)); } return Task.FromResult(0); } private int QueryCapabilities(CommandLineArguments Arguments, ILogger Logger, JsonSerializerOptions JsonOptions) { var Reply = new { Queries = new List { QueryType.Capabilities.ToString(), QueryType.AvailableTargets.ToString(), QueryType.TargetDetails.ToString() } }; Console.WriteLine(JsonSerializer.Serialize(Reply, JsonOptions)); return 0; } private int QueryAvailableTargets(CommandLineArguments Arguments, ILogger Logger, JsonSerializerOptions JsonOptions) { try { GenerateProjectFilesMode.TryParseProjectFileArgument(Arguments, Logger, out FileReference? ProjectFileArg); List Platforms = new(); foreach (UnrealTargetPlatform Platform in UnrealTargetPlatform.GetValidPlatforms()) { // If there is a build platform present, add it to the SupportedPlatforms list UEBuildPlatform? BuildPlatform; if (UEBuildPlatform.TryGetBuildPlatform(Platform, out BuildPlatform)) { if (InstalledPlatformInfo.IsValidPlatform(Platform, EProjectType.Code)) { Platforms.Add(Platform); } } } List AllowedTargetConfigurations = new List(); AllowedTargetConfigurations = Enum.GetValues(typeof(UnrealTargetConfiguration)).Cast().ToList(); List Configurations = new(); foreach (UnrealTargetConfiguration CurConfiguration in AllowedTargetConfigurations) { if (CurConfiguration != UnrealTargetConfiguration.Unknown) { if (InstalledPlatformInfo.IsValidConfiguration(CurConfiguration, EProjectType.Code)) { Configurations.Add(CurConfiguration.ToString()); } } } List Projects = ProjectFileArg != null ? new List(new[] { ProjectFileArg }) : NativeProjects.EnumerateProjectFiles(Logger).ToList(); List AllTargetFiles = ProjectFileGenerator.DiscoverTargets(Projects, Logger, null, Platforms, bIncludeEngineSource: bIncludeEngineSource, bIncludeTempTargets: false); // TODO: Check valid configurations/platforms per target Dictionary Targets = new(); string? DefaultTarget = null; foreach (FileReference TargetFilePath in AllTargetFiles) { string TargetName = TargetFilePath.GetFileNameWithoutAnyExtensions(); FileReference? ProjectPath = Projects.FirstOrDefault(p => TargetFilePath.IsUnderDirectory(p.Directory)); Targets.Add(TargetName, new TargetConfigs() { ProjectPath = ProjectPath?.ToString() ?? "", Configurations = Configurations, Platforms = Platforms.Select(x => x.ToString()).ToList() }); if (DefaultTarget == null || TargetName == "UnrealEditor") { DefaultTarget = TargetName; } } var Reply = new { Targets = Targets, DefaultTarget = DefaultTarget, DefaultPlatform = Platforms[0].ToString(), DefaultConfiguration = UnrealTargetConfiguration.Development.ToString(), }; Console.WriteLine(JsonSerializer.Serialize(Reply, JsonOptions)); return 0; } catch (Exception e) { Logger.LogError("Failed to query available targets: {0}", e.Message); return 1; } } private int QueryTargetDetails(CommandLineArguments Arguments, ILogger Logger, JsonSerializerOptions JsonOptions) { if (TargetName == null || TargetConfiguration == null || TargetPlatform == null) { return 1; } GenerateProjectFilesMode.TryParseProjectFileArgument(Arguments, Logger, out FileReference? ProjectFileArg); List RawArgs = new List { TargetName, TargetConfiguration, TargetPlatform }; if (ProjectFileArg != null) { RawArgs.Add(ProjectFileArg.ToString()); } CommandLineArguments Args = new CommandLineArguments(RawArgs.ToArray()); List TargetDescriptors = new(); TargetDescriptor.ParseSingleCommandLine(Args, false, false, false, TargetDescriptors, Logger); if (TargetDescriptors.Count == 0) { // TOOD: Error return 1; } HashSet BrowseConfigurationFolders = new HashSet(); try { UEBuildTarget CurrentTarget; using (GlobalTracer.Instance.BuildSpan("UEBuildTarget.Create()").StartActive()) { bool bUsePrecompiled = false; CurrentTarget = UEBuildTarget.Create(TargetDescriptors[0], false, false, bUsePrecompiled, Logger); } TargetIntellisenseInfo CurrentTargetIntellisenseInfo = new TargetIntellisenseInfo(); GetBrowseConfigurationResponse CurrentBrowseConfiguration = new GetBrowseConfigurationResponse { Success = true }; // Partially duplicated from UEBuildTarget.Build because we just want to get C++ compile actions without running UHT // or generating link actions / full dependency graph CppConfiguration CppConfiguration = UEBuildTarget.GetCppConfiguration(CurrentTarget.Configuration); SourceFileMetadataCache MetadataCache = SourceFileMetadataCache.CreateHierarchy(null, Logger); CppCompileEnvironment GlobalCompileEnvironment = new CppCompileEnvironment(CurrentTarget.Platform, CppConfiguration, CurrentTarget.Architectures, MetadataCache); LinkEnvironment GlobalLinkEnvironment = new LinkEnvironment(GlobalCompileEnvironment.Platform, GlobalCompileEnvironment.Configuration, GlobalCompileEnvironment.Architectures); UEToolChain TargetToolChain = CurrentTarget.CreateToolchain(CurrentTarget.Platform); TargetToolChain.SetEnvironmentVariables(); CurrentTarget.SetupGlobalEnvironment(TargetToolChain, GlobalCompileEnvironment, GlobalLinkEnvironment); // TODO: For installed builds, filter out all the binaries that aren't in mods foreach (UEBuildBinary Binary in CurrentTarget.Binaries) { HashSet LinkEnvironmentVisitedModules = new HashSet(); CppCompileEnvironment BinaryCompileEnvironment = Binary.CreateBinaryCompileEnvironment(GlobalCompileEnvironment); CurrentBrowseConfiguration.Standard = BinaryCompileEnvironment.CppStandard.ToString(); CurrentBrowseConfiguration.WindowsSdkVersion = CurrentTarget.Rules.WindowsPlatform.WindowsSdkVersion; foreach (UEBuildModuleCPP Module in Binary.Modules.OfType()) { if (Module.Binary != null && Module.Binary != Binary) { continue; } CppCompileEnvironment ModuleCompileEnvironment = Module.CreateModuleCompileEnvironment(CurrentTarget.Rules, BinaryCompileEnvironment, Logger); foreach (DirectoryReference Dir in Module.ModuleDirectories) { BrowseConfigurationFolders.Add(Dir.ToString()); CurrentTargetIntellisenseInfo.DirToModule.TryAdd(Dir, Module); } if (Module.GeneratedCodeDirectory != null) { CurrentTargetIntellisenseInfo.DirToModule.TryAdd(Module.GeneratedCodeDirectory, Module); } foreach (DirectoryReference Dir in ModuleCompileEnvironment.SystemIncludePaths) { BrowseConfigurationFolders.Add(Dir.ToString()); } foreach (DirectoryReference Dir in ModuleCompileEnvironment.UserIncludePaths) { BrowseConfigurationFolders.Add(Dir.ToString()); } TargetIntellisenseInfo.CompileSettings Settings = new TargetIntellisenseInfo.CompileSettings(); if (OperatingSystem.IsWindows()) { if (CurrentTarget.Platform == UnrealTargetPlatform.Win64) { // TODO: Correct compiler Settings.IncludePaths.AddRange(VCToolChain.GetVCIncludePaths(UnrealTargetPlatform.Win64, WindowsCompiler.VisualStudio2022, null, null, Logger).Split(";")); } } Settings.IncludePaths.AddRange(ModuleCompileEnvironment.SystemIncludePaths.Select(x => x.ToString())); Settings.IncludePaths.AddRange(ModuleCompileEnvironment.UserIncludePaths.Select(x => x.ToString())); Settings.Defines = ModuleCompileEnvironment.Definitions; Settings.Standard = ModuleCompileEnvironment.CppStandard.ToString(); Settings.ForcedIncludes = ModuleCompileEnvironment.ForceIncludeFiles.Select(x => x.ToString()).ToList(); Settings.CompilerPath = TargetToolChain.GetCppCompilerPath()?.ToString(); Settings.WindowsSdkVersion = CurrentTarget.Rules.WindowsPlatform.WindowsSdkVersion; CurrentTargetIntellisenseInfo.ModuleToCompileSettings.Add(Module, Settings); } } CurrentBrowseConfiguration.Paths = BrowseConfigurationFolders.ToList(); var Result = new { DirToModule = CurrentTargetIntellisenseInfo.DirToModule.ToDictionary(x => x.Key.ToString(), x => x.Value.Name), ModuleToCompileSettings = CurrentTargetIntellisenseInfo.ModuleToCompileSettings.ToDictionary(x => x.Key.Name, x => x.Value), }; Console.WriteLine(JsonSerializer.Serialize(Result, JsonOptions)); return 0; } catch (Exception e) { Logger.LogError("Caught exception setting up target: {0}", e); return 1; } } } }