// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; using System.Threading.Tasks; using EpicGames.Core; using Microsoft.Extensions.Logging; using UnrealBuildBase; namespace UnrealBuildTool { internal class VSWorkspaceProjectFile : ProjectFile { private readonly DirectoryReference RootPath; private readonly HashSet TargetTypes; private readonly CommandLineArguments Arguments; /// /// Collection of output files for this project /// public List ExportedTargetProjects { get; set; } = new(); public VSWorkspaceProjectFile(FileReference InProjectFilePath, DirectoryReference BaseDir, DirectoryReference RootPath, HashSet TargetTypes, CommandLineArguments Arguments) : base(InProjectFilePath, BaseDir) { this.RootPath = RootPath; this.TargetTypes = TargetTypes; this.Arguments = Arguments; } /// /// 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) { DirectoryReference ProjectRootFolder = RootPath; HashSet ServerPlatforms = Utils.GetPlatformsInClass(UnrealPlatformClass.Server).ToHashSet(); foreach (UnrealTargetConfiguration Configuration in InConfigurations) { List Tasks = new List(); 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 Server for all invalid platforms if (ProjectTarget.TargetRules.Type == TargetType.Server && !ServerPlatforms.Contains(Platform)) { continue; } Task CreateTask = 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 { FileReference OutputFile = FileReference.Combine(ProjectRootFolder, $"{ProjectTarget.TargetFilePath.GetFileNameWithoutAnyExtensions()}_{Configuration}_{Platform}.json"); UEBuildTarget BuildTarget = UEBuildTarget.Create(TargetDesc, false, false, false, UnrealIntermediateEnvironment.GenerateProjectFiles, Logger); BuildTarget.PreBuildSetup(Logger); ExportedTargetInfo TargetInfo = ExportTarget(BuildTarget, bBuildByDefault, PlatformProjectGenerators, Logger); 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:{Target}, Platform: {Platform}, Configuration: {Configuration}", TargetDesc.Name, Platform.ToString(), Configuration.ToString()); Logger.LogWarning("{Ex}", Ex.ToString()); } }); Tasks.Add(CreateTask); } } Task.WaitAll(Tasks.ToArray()); } return true; } /// /// Write a Target to a JSON writer. Is array is empty, don't write anything /// /// /// /// /// Logger for output private ExportedTargetInfo ExportTarget( UEBuildTarget Target, bool bBuildByDefault, PlatformProjectGeneratorCollection PlatformProjectGenerators, ILogger Logger) { ExportedTargetInfo TargetInfo = new() { TargetName = Target.TargetName, TargetPath = Target.TargetRulesFile.FullName, ProjectPath = Target.ProjectFile?.FullName ?? String.Empty, TargetType = Target.TargetType.ToString(), Platform = Target.Platform.ToString(), Configuration = Target.Configuration.ToString(), BuildInfo = ExportBuildInfo(Target, 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; } /// /// 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.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, 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, null), 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, }; } 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 Configuration { 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 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; } } } }