// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Text.Json; using System.Threading.Tasks; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; using static UnrealBuildTool.VCProjectFile; namespace UnrealBuildTool { internal class VSWorkspaceProjectFile : ProjectFile { private readonly HashSet TargetTypes; private readonly CommandLineArguments Arguments; /// /// Collection of output files for this project /// public List ExportedTargetProjects { get; set; } = new(); public DirectoryReference RootPath { get; } public VSWorkspaceProjectFile(FileReference InProjectFilePath, DirectoryReference BaseDir, DirectoryReference RootPath, HashSet TargetTypes, CommandLineArguments Arguments) : base(InProjectFilePath, BaseDir) { this.RootPath = RootPath; this.TargetTypes = TargetTypes; this.Arguments = Arguments; } /// /// Collect available configurations for the project storing it at availableConfigurations dictionary. /// /// /// Keys for the dictionary are available project names. /// /// /// /// /// /// /// public void CollectAvailableConfigurations(Dictionary availableConfigurations, List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators, JsonWriterStyle Minimize, ILogger Logger) { ProcessProjectFiles( InPlatforms, InConfigurations, PlatformProjectGenerators, // always provide null in here as we want to collect all available data. null, (UnrealTargetConfiguration Configuration, UnrealTargetPlatform Platform, ProjectTarget ProjectTarget) => { if (!availableConfigurations.TryGetValue(ProjectTarget.Name, out AvailableConfiguration? availableConfiguration)) { availableConfiguration = new AvailableConfiguration(); availableConfigurations.Add(ProjectTarget.Name, availableConfiguration); } availableConfiguration.TargetType = ProjectTarget.TargetRules?.Type.ToString() ?? string.Empty; availableConfiguration.Configurations.Add(Configuration.ToString()); availableConfiguration.Platforms.Add(Platform.ToString()); availableConfiguration.TargetPath = ProjectTarget.TargetFilePath.FullName; availableConfiguration.ProjectPath = ProjectTarget.UnrealProjectFilePath?.FullName ?? string.Empty; return Task.CompletedTask; }); } /// /// Write project file info in JSON file. /// For every combination of UnrealTargetPlatform, UnrealTargetConfiguration and TargetType /// will be generated separate JSON file. /// /// /// * TargetType.Editor will be generated for current platform only and will ignore UnrealTargetConfiguration.Test and UnrealTargetConfiguration.Shipping configurations /// * TargetType.Program will be generated for current platform only and UnrealTargetConfiguration.Development configuration only /// /// /// /// /// /// /// /// /// public bool WriteProjectFile(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators, JsonWriterStyle Minimize, ILogger Logger, Dictionary? ProjectGenerationDescriptionData, VCProjectFileFormat VCFormat) { DirectoryReference ProjectRootFolder = RootPath; ProcessProjectFiles( InPlatforms, InConfigurations, PlatformProjectGenerators, ProjectGenerationDescriptionData, (UnrealTargetConfiguration Configuration, UnrealTargetPlatform Platform, ProjectTarget ProjectTarget) => Task.Run(async () => { bool bBuildByDefault = ShouldBuildByDefaultForSolutionTargets && ProjectTarget.SupportedPlatforms.Contains(Platform); UnrealArchitectures ProjectArchitectures = UEBuildPlatform .GetBuildPlatform(Platform) .ArchitectureConfig.ActiveArchitectures(ProjectTarget.UnrealProjectFilePath, ProjectTarget.Name); TargetDescriptor TargetDesc = new(ProjectTarget.UnrealProjectFilePath, ProjectTarget.Name, Platform, Configuration, ProjectArchitectures, Arguments); TargetDesc.IntermediateEnvironment = UnrealIntermediateEnvironment.GenerateProjectFiles; try { foreach (var Architecture in TargetDesc.Architectures.Architectures) { UEBuildTarget BuildTarget = UEBuildTarget.Create(TargetDesc, false, false, false, UnrealIntermediateEnvironment.GenerateProjectFiles, Logger); BuildTarget.PreBuildSetup(Logger); ExportedTargetInfo TargetInfo = ExportTarget(BuildTarget, Architecture, bBuildByDefault, PlatformProjectGenerators, Logger, VCFormat); var sb = new StringBuilder(); sb.Append(ProjectTarget.TargetFilePath.GetFileNameWithoutAnyExtensions()); sb.Append("_"); sb.Append(TargetInfo.Configuration); sb.Append("_"); sb.Append(TargetInfo.Platform); if (!string.IsNullOrEmpty(TargetInfo.Architecture)) { sb.Append("_"); sb.Append(TargetInfo.Architecture); } sb.Append(".json"); FileReference OutputFile = FileReference.Combine(ProjectRootFolder, sb.ToString()); DirectoryReference.CreateDirectory(OutputFile.Directory); using FileStream Stream = new(OutputFile.FullName, FileMode.Create, FileAccess.Write); await JsonSerializer.SerializeAsync(Stream, TargetInfo, options: new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = Minimize == JsonWriterStyle.Readable, }); lock (ExportedTargetProjects) { ExportedTargetProjects.Add(TargetInfo); } } } catch (Exception Ex) { Logger.LogWarning("Exception while generating include data for Target:{0}, Platform: {1}, Configuration: {2}", TargetDesc.Name, Platform.ToString(), Configuration.ToString()); Logger.LogWarning("{0}", Ex.ToString()); } })); return true; } /// /// Filter configurations based on what is supported by the project. /// /// /// /// /// /// private void ProcessProjectFiles(List InPlatforms, List InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators, Dictionary? ProjectGenerationDescriptionData, Func Handler) { List Tasks = new List(); List<(UnrealTargetConfiguration, UnrealTargetPlatform, ProjectTarget)> ret = new List<(UnrealTargetConfiguration, UnrealTargetPlatform, ProjectTarget)>(); foreach (UnrealTargetConfiguration Configuration in InConfigurations) { foreach (UnrealTargetPlatform Platform in InPlatforms) { foreach (ProjectTarget ProjectTarget in ProjectTargets.OfType()) { if (TargetTypes.Any() && !TargetTypes.Contains(ProjectTarget.TargetRules!.Type)) { continue; } // Skip Programs for all configs except for current platform + Development & Debug configurations if (ProjectTarget.TargetRules!.Type == TargetType.Program && (BuildHostPlatform.Current.Platform != Platform || !(Configuration == UnrealTargetConfiguration.Development || Configuration == UnrealTargetConfiguration.Debug))) { continue; } // Skip Editor for all platforms except for current platform if (ProjectTarget.TargetRules.Type == TargetType.Editor && (BuildHostPlatform.Current.Platform != Platform || (Configuration == UnrealTargetConfiguration.Test || Configuration == UnrealTargetConfiguration.Shipping))) { continue; } // Skip invalid platforms for the current TargetType if (!ProjectTarget.SupportedPlatforms.Contains(Platform)) { continue; } if (ShouldSkipConfiguration(ProjectGenerationDescriptionData, Configuration, Platform, ProjectTarget)) { continue; } Tasks.Add(Handler(Configuration, Platform, ProjectTarget)); } } Task.WaitAll(Tasks.ToArray()); } } /// /// Write a Target to a JSON writer. Is array is empty, don't write anything /// /// /// /// /// /// Logger for output /// Project file format. private ExportedTargetInfo ExportTarget( UEBuildTarget Target, UnrealArch? Architecture, bool bBuildByDefault, PlatformProjectGeneratorCollection PlatformProjectGenerators, ILogger Logger, VCProjectFileFormat VCFormat) { ExportedTargetInfo TargetInfo = new() { TargetName = Target.TargetName, TargetPath = Target.TargetRulesFile.FullName, ProjectPath = Target.ProjectFile?.FullName ?? String.Empty, TargetType = Target.TargetType.ToString(), Platform = Target.Platform.ToString(), VCPlatform = GetVCPlatform(Target, Architecture, PlatformProjectGenerators, VCFormat), Configuration = Target.Configuration.ToString(), VCConfiguration = GetVCConfiguration(Target, Architecture, PlatformProjectGenerators, VCFormat), Architecture = GetArchitecture(Target, Architecture, PlatformProjectGenerators, VCFormat), BuildInfo = ExportBuildInfo(Target, Architecture, PlatformProjectGenerators, bBuildByDefault, Logger) }; UEToolChain TargetToolChain = Target.CreateToolchain(Target.Platform, Logger); CppCompileEnvironment GlobalCompileEnvironment = Target.CreateCompileEnvironmentForProjectFiles(Logger); HashSet ModuleNames = new(); foreach (UEBuildBinary Binary in Target.Binaries) { CppCompileEnvironment BinaryCompileEnvironment = Binary.CreateBinaryCompileEnvironment(GlobalCompileEnvironment); IEnumerable CandidateModules = Binary.Modules.Where(x => x is UEBuildModuleCPP).Cast(); foreach (UEBuildModuleCPP ModuleCpp in CandidateModules) { if (!ModuleNames.Add(ModuleCpp.Name)) { continue; } CppCompileEnvironment ModuleCompileEnvironment = ModuleCpp.CreateCompileEnvironmentForIntellisense(Target.Rules, BinaryCompileEnvironment, Logger); TargetInfo.ModuleToCompileSettings.Add(ModuleCpp.Name, ExportModule(ModuleCpp, TargetToolChain, ModuleCompileEnvironment)); foreach (DirectoryReference ModuleDirectory in ModuleCpp.ModuleDirectories) { TargetInfo.DirToModule.TryAdd(ModuleDirectory.FullName, ModuleCpp.Name); } if (ModuleCpp.GeneratedCodeDirectory != null) { TargetInfo.DirToModule.TryAdd(ModuleCpp.GeneratedCodeDirectory.FullName, ModuleCpp.Name); } } } return TargetInfo; } private static bool ShouldSkipConfiguration(Dictionary? ProjectGenerationDescriptionData, UnrealTargetConfiguration Configuration, UnrealTargetPlatform Platform, ProjectTarget ProjectTarget) { // No restrictions were provided, we should not skip anything. if (ProjectGenerationDescriptionData == null) { return false; } // Configs were provided, but we could not find it in the dictionary, so we should not skip it. if (!ProjectGenerationDescriptionData.TryGetValue(ProjectTarget.Name, out AvailableConfiguration? availableConfiguration)) { return true; } return !availableConfiguration.Configurations.Contains(Configuration.ToString()) || !availableConfiguration.Platforms.Contains(Platform.ToString()); } /// /// Write a Module to a JSON writer. If array is empty, don't write anything /// /// /// /// private static ExportedModuleInfo ExportModule(UEBuildModuleCPP Module, UEToolChain TargetToolChain, CppCompileEnvironment ModuleCompileEnvironment) { ExportedModuleInfo Result = new() { Name = Module.Name, Directory = Module.ModuleDirectory.FullName, Rules = Module.RulesFile.FullName, GeneratedCodeDirectory = Module.GeneratedCodeDirectory != null ? Module.GeneratedCodeDirectory.FullName : String.Empty, Standard = ModuleCompileEnvironment.CppStandard.ToString(), }; Result.IncludePaths.AddRange(Module.PublicIncludePaths.Select(x => x.FullName)); Result.IncludePaths.AddRange(Module.PublicSystemIncludePaths.Select(x => x.FullName)); Result.IncludePaths.AddRange(Module.InternalIncludePaths.Select(x => x.FullName)); Result.IncludePaths.AddRange(Module.LegacyPublicIncludePaths.Select(x => x.FullName)); Result.IncludePaths.AddRange(Module.LegacyParentIncludePaths.Select(x => x.FullName)); Result.IncludePaths.AddRange(Module.PrivateIncludePaths.Select(x => x.FullName)); Result.IncludePaths.AddRange(ModuleCompileEnvironment.UserIncludePaths.Select(x => x.FullName)); Result.IncludePaths.AddRange(Module.PublicSystemLibraryPaths.Select(x => x.FullName)); Result.IncludePaths.AddRange(Module.PublicSystemLibraries.Concat(Module.PublicLibraries.Select(x => x.FullName))); if (TargetToolChain is VCToolChain TargetVCToolChain) { Result.IncludePaths.AddRange(TargetVCToolChain.GetVCIncludePaths().Select(x => x.ToString())); } Result.Defines.AddRange(Module.PublicDefinitions); Result.Defines.AddRange(Module.Rules.PrivateDefinitions); Result.Defines.AddRange(Module.Rules.bTreatAsEngineModule ? Array.Empty() : Module.Rules.Target.ProjectDefinitions); Result.Defines.AddRange(Module.GetEmptyApiMacros()); List ForcedIncludes = ModuleCompileEnvironment.ForceIncludeFiles.ToList(); if (ModuleCompileEnvironment.PrecompiledHeaderAction == PrecompiledHeaderAction.Include) { FileItem IncludeHeader = FileItem.GetItemByFileReference(ModuleCompileEnvironment.PrecompiledHeaderIncludeFilename!); ForcedIncludes.Insert(0, IncludeHeader); Result.PrecompiledHeader = IncludeHeader.FullName; } Result.ForcedIncludes.AddRange(ForcedIncludes.Select(x => x.FullName)); Result.CompilerPath = TargetToolChain.GetCppCompilerPath()?.ToString(); Result.CompilerArgs.AddRange(TargetToolChain.GetGlobalCommandLineArgs(ModuleCompileEnvironment)); Result.CompilerAdditionalArgs.Add("c", TargetToolChain.GetCCommandLineArgs(ModuleCompileEnvironment).ToList()); Result.CompilerAdditionalArgs.Add("cpp", TargetToolChain.GetCPPCommandLineArgs(ModuleCompileEnvironment).ToList()); return Result; } private TargetBuildInfo? ExportBuildInfo(UEBuildTarget Target, UnrealArch? Architecture, PlatformProjectGeneratorCollection PlatformProjectGenerators, bool bBuildByDefault, ILogger Logger) { if (!BuildHostPlatform.Current.Platform.IsInGroup(UnrealPlatformGroup.Windows)) { Logger.LogWarning("Unsupported platform for Build Information: {Platform}", BuildHostPlatform.Current.Platform.ToString()); return null; } if (IsStubProject) { return null; } ProjectTarget ProjectTarget = ProjectTargets.OfType().Single(It => Target.TargetRulesFile == It.TargetFilePath); UnrealTargetPlatform Platform = Target.Platform; UnrealTargetConfiguration Configuration = Target.Configuration; string UProjectPath = ""; if (IsForeignProject) { UProjectPath = String.Format("\"{0}\"", ProjectTarget.UnrealProjectFilePath!.FullName); } PlatformProjectGenerator? ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(Platform, true); VCProjectFile.BuildCommandBuilder BuildCommandBuilder = new( new PlatformProjectGenerator.VSSettings(Platform, Configuration, VCProjectFileFormat.Default, Architecture), ProjectTarget, UProjectPath) { ProjectGenerator = ProjGenerator, bIsForeignProject = IsForeignProject }; string BuildArguments = BuildCommandBuilder.GetBuildArguments(); return new TargetBuildInfo() { BuildCmd = $"\"{BuildCommandBuilder.BuildScript.FullName}\" {BuildArguments}", RebuildCmd = $"\"{BuildCommandBuilder.RebuildScript.FullName}\" {BuildArguments}", CleanCmd = $"\"{BuildCommandBuilder.CleanScript.FullName}\" {BuildArguments}", PrimaryOutput = Target.Binaries[0].OutputFilePath.FullName, BuildByDefault = bBuildByDefault, }; } private string GetVCPlatform(UEBuildTarget Target, UnrealArch? Architecture, PlatformProjectGeneratorCollection Generators, VCProjectFileFormat VCFormat) { PlatformProjectGenerator? Generator = Generators.GetPlatformProjectGenerator(Target.Platform, true); if (Generator != null) { PlatformProjectGenerator.VSSettings VSSettings = new(Target.Platform, Target.Configuration, VCFormat, Architecture); if (Generator.HasVisualStudioSupport(VSSettings)) { return Generator.GetVisualStudioPlatformName(VSSettings); } return DefaultPlatformName; } return string.Empty; } private string GetVCConfiguration(UEBuildTarget Target, UnrealArch? Architecture, PlatformProjectGeneratorCollection Generators, VCProjectFileFormat VCFormat) { PlatformProjectGenerator? Generator = Generators.GetPlatformProjectGenerator(Target.Platform, true); if (Generator != null) { PlatformProjectGenerator.VSSettings VSSettings = new(Target.Platform, Target.Configuration, VCFormat, Architecture); if (Generator.HasVisualStudioSupport(VSSettings)) { if (!Generator.RequiresDistinctVisualStudioConfigurationName(VSSettings)) { return Target.Configuration.ToString(); } } return string.Format("{0}{1}_{2}", Target.Platform.ToString(), Architecture != null ? "_" + Architecture.ToString() : string.Empty, Target.Configuration.ToString()); ; } return string.Empty; } private string GetArchitecture(UEBuildTarget Target, UnrealArch? Architecture, PlatformProjectGeneratorCollection Generators, VCProjectFileFormat VCFormat) { PlatformProjectGenerator? Generator = Generators.GetPlatformProjectGenerator(Target.Platform, true); if (Generator != null) { PlatformProjectGenerator.VSSettings VSSettings = new(Target.Platform, Target.Configuration, VCFormat, Architecture); if (Generator.HasVisualStudioSupport(VSSettings)) { if (!Generator.RequiresDistinctVisualStudioConfigurationName(VSSettings)) { return string.Empty; } } if (Architecture != null) { var result = Architecture.ToString(); return result != null ? result : string.Empty; } } return string.Empty; } internal class ExportedTargetInfo { public string TargetName { get; set; } = String.Empty; public string TargetPath { get; set; } = String.Empty; public string ProjectPath { get; set; } = String.Empty; public string TargetType { get; set; } = String.Empty; public string Platform { get; set; } = String.Empty; public string VCPlatform { get; set; } = String.Empty; public string Configuration { get; set; } = String.Empty; public string VCConfiguration { get; set; } = String.Empty; public string Architecture { get; set; } = String.Empty; public TargetBuildInfo? BuildInfo { get; set; } public Dictionary ModuleToCompileSettings { get; set; } = new(); public Dictionary DirToModule { get; set; } = new(); } internal class ExportedModuleInfo { public string Name { get; set; } = String.Empty; public string Directory { get; set; } = String.Empty; public string Rules { get; set; } = String.Empty; public string GeneratedCodeDirectory { get; set; } = String.Empty; public List IncludePaths { get; set; } = new(); public List Defines { get; set; } = new(); public string? Standard { get; set; } public string? PrecompiledHeader { get; set; } public List ForcedIncludes { get; set; } = new(); public string? CompilerPath { get; set; } public List CompilerArgs { get; set; } = new(); public Dictionary> CompilerAdditionalArgs { get; set; } = new(); public string? WindowsSdkVersion { get; set; } } internal class TargetBuildInfo { public string BuildCmd { get; set; } = String.Empty; public string RebuildCmd { get; set; } = String.Empty; public string CleanCmd { get; set; } = String.Empty; public string PrimaryOutput { get; set; } = String.Empty; public bool BuildByDefault { get; internal set; } } } }