Files
UnrealEngineUWP/Engine/Extras/VisualStudioWorkspace/UnrealBuildTool-5.0.patch
joe kirchoff 318c389593 Patches for Visual Studio workspace support in old versions
[CL 31275433 by joe kirchoff in ue5-main branch]
2024-02-07 18:03:23 -05:00

829 lines
34 KiB
Diff

From 53e7e3f327d27608575225420cd23829acfcd69b Mon Sep 17 00:00:00 2001
From: Henrique Fernandes Baggio <hebaggio@microsoft.com>
Date: Wed, 17 Jan 2024 19:49:54 -0800
Subject: [PATCH] [UE5.0] Project Generator for VS Workspace support
Adding a new Project Generator for Visual Studio that will be used for
the new support to opening `.uproject` files directly in the IDE without
generating the traditional `.sln` and .vcxproj files.
This is based on the QueryMode API in `ue5-main` and adapted to the
GenerateProjectFiles flow. The project files will contain information for each
Target+Configuration+Platform combinations that are used the project.
Visual Studio will automatically invoke this new generator during the flow to
open a `.uproject` file directly.
---
.../Configuration/UEBuildTarget.cs | 2 +-
.../Modes/GenerateProjectFilesMode.cs | 4 +
.../Platform/Windows/VCToolChain.cs | 26 ++
.../ProjectFiles/ProjectFileGenerator.cs | 5 +-
.../VSWorkspaceProjectFile.cs | 394 ++++++++++++++++++
.../VSWorkspaceProjectFileGenerator.cs | 248 +++++++++++
.../UnrealBuildTool/ToolChain/UEToolChain.cs | 20 +
7 files changed, 696 insertions(+), 3 deletions(-)
create mode 100644 Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioWorkspace/VSWorkspaceProjectFile.cs
create mode 100644 Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioWorkspace/VSWorkspaceProjectFileGenerator.cs
diff --git a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs
index 25e98e1729cfb..04395105aaf64 100644
--- a/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs
+++ b/Engine/Source/Programs/UnrealBuildTool/Configuration/UEBuildTarget.cs
@@ -2249,7 +2249,7 @@ List<RestrictedFolder> GetRestrictedFolders(FileReference File)
/// Creates a toolchain for the current target. May be overridden by the target rules.
/// </summary>
/// <returns>New toolchain instance</returns>
- private UEToolChain CreateToolchain(UnrealTargetPlatform Platform)
+ public UEToolChain CreateToolchain(UnrealTargetPlatform Platform)
{
if (Rules.ToolChainName == null)
{
diff --git a/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs b/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs
index 5ac8f51d12f4f..a6e0cde49cfa2 100644
--- a/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs
+++ b/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs
@@ -35,6 +35,7 @@ class GenerateProjectFilesMode : ToolMode
[CommandLine("-EddieProjectFiles", Value = nameof(ProjectFileFormat.Eddie))]
[CommandLine("-VSCode", Value = nameof(ProjectFileFormat.VisualStudioCode))]
[CommandLine("-VSMac", Value = nameof(ProjectFileFormat.VisualStudioMac))]
+ [CommandLine("-VSWorkspace", Value = nameof(ProjectFileFormat.VisualStudioWorkspace))]
[CommandLine("-CLion", Value = nameof(ProjectFileFormat.CLion))]
[CommandLine("-Rider", Value = nameof(ProjectFileFormat.Rider))]
#if __VPROJECT_AVAILABLE__
@@ -202,6 +203,9 @@ public override int Execute(CommandLineArguments Arguments)
case ProjectFileFormat.VisualStudioCode:
Generator = new VSCodeProjectFileGenerator(ProjectFile);
break;
+ case ProjectFileFormat.VisualStudioWorkspace:
+ Generator = new VSWorkspaceProjectFileGenerator(ProjectFile, Arguments);
+ break;
case ProjectFileFormat.CLion:
Generator = new CLionGenerator(ProjectFile);
break;
diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs
index 80ec89d70e37a..287e8abd3e78f 100644
--- a/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs
+++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Windows/VCToolChain.cs
@@ -47,6 +47,11 @@ public VCToolChain(ReadOnlyTargetRules Target)
}
}
+ public override FileReference? GetCppCompilerPath()
+ {
+ return EnvVars.CompilerPath;
+ }
+
/// <summary>
/// Prepares the environment for building
/// </summary>
@@ -209,6 +214,27 @@ public static void AddSourceDependsFile(List<string> Arguments, FileItem SourceD
Arguments.Add($"/clang:-MD /clang:-MF\"{SourceDependsFileString}\"");
}
+ public override IEnumerable<string> GetGlobalCommandLineArgs(CppCompileEnvironment CompileEnvironment)
+ {
+ List<string> Arguments = new List<string>();
+ AppendCLArguments_Global(new CppCompileEnvironment(CompileEnvironment), Arguments);
+ return Arguments;
+ }
+
+ public override IEnumerable<string> GetCPPCommandLineArgs(CppCompileEnvironment CompileEnvironment)
+ {
+ List<string> Arguments = new List<string>();
+ AppendCLArguments_CPP(CompileEnvironment, Arguments);
+ return Arguments;
+ }
+
+ public override IEnumerable<string> GetCCommandLineArgs(CppCompileEnvironment CompileEnvironment)
+ {
+ List<string> Arguments = new List<string>();
+ AppendCLArguments_C(Arguments);
+ return Arguments;
+ }
+
protected virtual void AppendCLArguments_Global(CppCompileEnvironment CompileEnvironment, List<string> Arguments)
{
// Suppress generation of object code for unreferenced inline functions. Enabling this option is more standards compliant, and causes a big reduction
diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/ProjectFileGenerator.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/ProjectFileGenerator.cs
index 1e8e7e609c166..4276988ed1374 100644
--- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/ProjectFileGenerator.cs
+++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/ProjectFileGenerator.cs
@@ -132,6 +132,7 @@ enum ProjectFileFormat
VisualStudio,
VisualStudio2019,
VisualStudio2022,
+ VisualStudioWorkspace,
XCode,
Eddie,
VisualStudioCode,
@@ -2297,7 +2298,7 @@ private ProjectFile FindProjectForModule(FileReference CurModuleFile, List<FileR
/// <param name="GameProjects">Map of game folder name to all of the game projects we created</param>
/// <param name="ProgramProjects">Map of program names to all of the program projects we created</param>
/// <param name="RulesAssemblies">Map of RuleAssemblies to their base folders</param>
- private void AddProjectsForAllTargets(
+ protected void AddProjectsForAllTargets(
PlatformProjectGeneratorCollection PlatformProjectGenerators,
List<FileReference> AllGames,
List<FileReference> AllTargetFiles,
@@ -2619,7 +2620,7 @@ protected void AddProjectsForMods(List<ProjectFile> GameProjects, out List<Proje
/// </summary>
/// <param name="BaseName">The base name for the project file</param>
/// <returns>Full path to the project file</returns>
- protected FileReference GetProjectLocation(string BaseName)
+ protected virtual FileReference GetProjectLocation(string BaseName)
{
return FileReference.Combine(IntermediateProjectFilesPath, BaseName + ProjectFileExtension);
}
diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioWorkspace/VSWorkspaceProjectFile.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioWorkspace/VSWorkspaceProjectFile.cs
new file mode 100644
index 0000000000000..05c5473a616db
--- /dev/null
+++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioWorkspace/VSWorkspaceProjectFile.cs
@@ -0,0 +1,394 @@
+// 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 EpicGames.Core;
+using Microsoft.Extensions.Logging;
+using UnrealBuildBase;
+
+namespace UnrealBuildTool
+{
+ internal class VSWorkspaceProjectFile : ProjectFile
+ {
+ private readonly DirectoryReference RootPath;
+ private readonly HashSet<TargetType> TargetTypes;
+ private readonly CommandLineArguments Arguments;
+ private VCProjectFileSettings Settings;
+ private bool bUsePrecompiled;
+
+ /// <summary>
+ /// Collection of output files for this project
+ /// </summary>
+ public List<ExportedTargetInfo> ExportedTargetProjects { get; set; } = new List<ExportedTargetInfo>();
+
+ public VSWorkspaceProjectFile(FileReference InProjectFilePath, DirectoryReference BaseDir,
+ DirectoryReference RootPath, HashSet<TargetType> TargetTypes, CommandLineArguments Arguments, VCProjectFileSettings Settings, bool bUsePrecompiled)
+ : base(InProjectFilePath, BaseDir)
+ {
+ this.RootPath = RootPath;
+ this.TargetTypes = TargetTypes;
+ this.Arguments = Arguments;
+ this.Settings = Settings;
+ this.bUsePrecompiled = bUsePrecompiled;
+ }
+
+ /// <summary>
+ /// Write project file info in JSON file.
+ /// For every combination of <c>UnrealTargetPlatform</c>, <c>UnrealTargetConfiguration</c> and <c>TargetType</c>
+ /// will be generated separate JSON file.
+ /// </summary>
+ public bool WriteProjectFile(List<UnrealTargetPlatform> InPlatforms,
+ List<UnrealTargetConfiguration> InConfigurations,
+ PlatformProjectGeneratorCollection PlatformProjectGenerators, JsonWriterStyle Minimize)
+ {
+ DirectoryReference ProjectRootFolder = RootPath;
+
+ Dictionary<FileReference, (UEBuildTarget BuildTarget, bool bBuildByDefault)> FileToTarget = new Dictionary<FileReference, (UEBuildTarget, bool)>();
+
+ foreach (UnrealTargetPlatform Platform in InPlatforms)
+ {
+ foreach (UnrealTargetConfiguration Configuration in InConfigurations)
+ {
+ foreach (ProjectTarget ProjectTarget in ProjectTargets.OfType<ProjectTarget>())
+ {
+ 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;
+ }
+
+ bool bBuildByDefault = ShouldBuildByDefaultForSolutionTargets && ProjectTarget.SupportedPlatforms.Contains(Platform);
+
+ string DefaultArchitecture = UEBuildPlatform
+ .GetBuildPlatform(Platform)
+ .GetDefaultArchitecture(ProjectTarget.UnrealProjectFilePath);
+
+ TargetDescriptor TargetDesc = new TargetDescriptor(ProjectTarget.UnrealProjectFilePath, ProjectTarget.Name,
+ Platform, Configuration, DefaultArchitecture, Arguments);
+
+ try
+ {
+ FileReference OutputFile = FileReference.Combine(ProjectRootFolder,
+ $"{ProjectTarget.TargetFilePath.GetFileNameWithoutAnyExtensions()}_{Configuration}_{Platform}.json");
+
+ UEBuildTarget BuildTarget = UEBuildTarget.Create(TargetDesc, false, false, false);
+ FileToTarget.Add(OutputFile, (BuildTarget, bBuildByDefault));
+ }
+ catch (Exception Ex)
+ {
+ Log.TraceWarning("Exception while generating include data for Target:{Target}, Platform: {Platform}, Configuration: {Configuration}", TargetDesc.Name, Platform.ToString(), Configuration.ToString());
+ Log.TraceWarning("{Ex}", Ex.ToString());
+ }
+ }
+ }
+ }
+
+ foreach (var Entry in FileToTarget)
+ {
+ var OutputFile = Entry.Key;
+ var BuildTarget = Entry.Value.BuildTarget;
+ var bBuildByDefault = Entry.Value.bBuildByDefault;
+
+ try
+ {
+ BuildTarget.PreBuildSetup();
+
+ ExportedTargetInfo TargetInfo = ExportTarget(BuildTarget, bBuildByDefault, PlatformProjectGenerators);
+
+ DirectoryReference.CreateDirectory(OutputFile.Directory);
+
+ File.WriteAllText(OutputFile.FullName, JsonSerializer.Serialize(TargetInfo, options: new JsonSerializerOptions()
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = Minimize == JsonWriterStyle.Readable,
+ }));
+
+ ExportedTargetProjects.Add(TargetInfo);
+ }
+ catch (Exception Ex)
+ {
+ Log.TraceWarning("Exception while generating include data for Target:{Target}, Platform: {Platform}, Configuration: {Configuration}",
+ BuildTarget.AppName, BuildTarget.Platform.ToString(), BuildTarget.Configuration.ToString());
+ Log.TraceWarning("{Ex}", Ex.ToString());
+ }
+ }
+
+ return true;
+ }
+
+ private ExportedTargetInfo ExportTarget(
+ UEBuildTarget Target, bool bBuildByDefault, PlatformProjectGeneratorCollection PlatformProjectGenerators)
+ {
+ ExportedTargetInfo TargetInfo = new ExportedTargetInfo()
+ {
+ 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)
+ };
+
+ UEToolChain TargetToolChain = Target.CreateToolchain(Target.Platform);
+ CppCompileEnvironment GlobalCompileEnvironment = Target.CreateCompileEnvironmentForProjectFiles();
+
+ HashSet<string> ModuleNames = new HashSet<string>();
+ foreach (UEBuildBinary Binary in Target.Binaries)
+ {
+ CppCompileEnvironment BinaryCompileEnvironment = Binary.CreateBinaryCompileEnvironment(GlobalCompileEnvironment);
+ IEnumerable<UEBuildModuleCPP> CandidateModules = Binary.Modules.Where(x => x is UEBuildModuleCPP).Cast<UEBuildModuleCPP>();
+
+ foreach (var ModuleCpp in CandidateModules)
+ {
+ if (!ModuleNames.Add(ModuleCpp.Name))
+ {
+ continue;
+ }
+
+ CppCompileEnvironment ModuleCompileEnvironment = ModuleCpp.CreateCompileEnvironmentForIntellisense(Target.Rules, BinaryCompileEnvironment);
+ 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 ExportedModuleInfo ExportModule(UEBuildModuleCPP Module, UEToolChain TargetToolChain, CppCompileEnvironment ModuleCompileEnvironment)
+ {
+ ExportedModuleInfo Result = new ExportedModuleInfo()
+ {
+ 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.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)
+ {
+ string VCIncludePaths = VCToolChain.GetVCIncludePaths(ModuleCompileEnvironment.Platform, WindowsCompiler.VisualStudio2022, null);
+ Result.IncludePaths.AddRange(VCIncludePaths.Split(";"));
+ }
+
+ Result.Defines.AddRange(Module.PublicDefinitions);
+ Result.Defines.AddRange(Module.Rules.PrivateDefinitions);
+ Result.Defines.AddRange(Module.Rules.bTreatAsEngineModule ? Array.Empty<string>() : Module.Rules.Target.ProjectDefinitions);
+ Result.Defines.AddRange(Module.GetEmptyApiMacros());
+
+ var 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)
+ {
+ if (!BuildHostPlatform.Current.Platform.IsInGroup(UnrealPlatformGroup.Windows))
+ {
+ Log.TraceWarning("Unsupported platform for Build Information: {Platform}", BuildHostPlatform.Current.Platform.ToString());
+ return null;
+ }
+
+ if (IsStubProject)
+ {
+ return null;
+ }
+
+ ProjectTarget ProjectTarget = ProjectTargets.OfType<ProjectTarget>().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);
+
+ string BuildArguments = GetBuildArguments(ProjGenerator, ProjectTarget, Target, UProjectPath);
+
+ return new TargetBuildInfo()
+ {
+ BuildCmd = $"{GetBuildScript("Build")} {BuildArguments}",
+ RebuildCmd = $"{GetBuildScript("Rebuild")} {BuildArguments}",
+ CleanCmd = $"{GetBuildScript("Clean")} {BuildArguments}",
+ PrimaryOutput = Target.Binaries[0].OutputFilePath.FullName,
+ BuildByDefault = bBuildByDefault,
+ };
+ }
+
+ private string GetBuildScript(string Cmd)
+ {
+ DirectoryReference BatchFilesDirectory = DirectoryReference.Combine(Unreal.EngineDirectory, "Build", "BatchFiles");
+ return EscapePath(FileReference.Combine(BatchFilesDirectory, $"{Cmd}.bat").FullName);
+ }
+
+ private string GetBuildArguments(
+ PlatformProjectGenerator? ProjGenerator, ProjectTarget ProjectTarget, UEBuildTarget Target, string UProjectPath)
+ {
+ UnrealTargetConfiguration Configuration = Target.Configuration;
+ UnrealTargetPlatform Platform = Target.Platform;
+ string TargetName = ProjectTarget.TargetFilePath.GetFileNameWithoutAnyExtensions();
+ TargetRules TargetRulesObject = ProjectTarget.TargetRules!;
+
+ // This is the standard UE based project NMake build line:
+ // ..\..\Build\BatchFiles\Build.bat <TARGETNAME> <PLATFORM> <CONFIGURATION>
+ // ie ..\..\Build\BatchFiles\Build.bat BlankProgram Win64 Debug
+
+ StringBuilder BuildArguments = new StringBuilder();
+
+ BuildArguments.AppendFormat("{0} {1} {2}", TargetName, Configuration, Platform);
+ if (IsForeignProject)
+ {
+ BuildArguments.AppendFormat(" -Project={0}", UProjectPath);
+ }
+
+ List<string> ExtraTargets = new List<string>();
+ if (!bUsePrecompiled)
+ {
+ if (TargetRulesObject.Type == TargetType.Editor && Settings.bEditorDependsOnShaderCompileWorker && !Unreal.IsEngineInstalled())
+ {
+ ExtraTargets.Add("ShaderCompileWorker Win64 Development");
+ }
+ if (TargetRulesObject.bWithLiveCoding && Settings.bBuildLiveCodingConsole && !Unreal.IsEngineInstalled() && TargetRulesObject.Name != "LiveCodingConsole")
+ {
+ ExtraTargets.Add(TargetRulesObject.bUseDebugLiveCodingConsole ? "LiveCodingConsole Win64 Debug" : "LiveCodingConsole Win64 Development");
+ }
+ }
+
+ if (ExtraTargets.Count > 0)
+ {
+ BuildArguments.Replace("\"", "\\\"");
+ BuildArguments.Insert(0, "-Target=\"");
+ BuildArguments.Append("\"");
+ foreach (string ExtraTarget in ExtraTargets)
+ {
+ BuildArguments.AppendFormat(" -Target=\"{0} -Quiet\"", ExtraTarget);
+ }
+ }
+
+ if (bUsePrecompiled)
+ {
+ BuildArguments.Append(" -UsePrecompiled");
+ }
+
+ // Always wait for the mutex between UBT invocations, so that building the whole solution doesn't fail.
+ BuildArguments.Append(" -WaitMutex");
+
+ // Always include a flag to format log messages for MSBuild
+ BuildArguments.Append(" -FromMsBuild");
+
+ if (Settings.bAddFastPDBToProjects)
+ {
+ // Pass Fast PDB option to make use of Visual Studio's /DEBUG:FASTLINK option
+ BuildArguments.Append(" -FastPDB");
+ }
+
+ if (ProjGenerator != null)
+ {
+ BuildArguments.Append(ProjGenerator.GetExtraBuildArguments(Platform, Configuration));
+ }
+
+ return BuildArguments.ToString();
+ }
+
+ private static void WriteCommand(JsonWriter Writer, string CommandName, string Command, string Arguments)
+ {
+ Writer.WriteObjectStart(CommandName);
+ Writer.WriteValue("Command", Command);
+ Writer.WriteValue("Args", Arguments);
+ Writer.WriteObjectEnd();
+ }
+
+ 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<string, ExportedModuleInfo> ModuleToCompileSettings { get; set; } = new Dictionary<string, ExportedModuleInfo>();
+ public Dictionary<string, string> DirToModule { get; set; } = new Dictionary<string, string>();
+ }
+
+ 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<string> IncludePaths { get; set; } = new List<string>();
+ public List<string> Defines { get; set; } = new List<string>();
+ public string? Standard { get; set; }
+ public List<string> ForcedIncludes { get; set; } = new List<string>();
+ public string? CompilerPath { get; set; }
+ public List<string> CompilerArgs { get; set; } = new List<string>();
+ public Dictionary<string, List<string>> CompilerAdditionalArgs { get; set; } = new Dictionary<string, List<string>>();
+ 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; }
+ }
+ }
+}
diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioWorkspace/VSWorkspaceProjectFileGenerator.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioWorkspace/VSWorkspaceProjectFileGenerator.cs
new file mode 100644
index 0000000000000..454e291171e00
--- /dev/null
+++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioWorkspace/VSWorkspaceProjectFileGenerator.cs
@@ -0,0 +1,248 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using EpicGames.Core;
+using Microsoft.Extensions.Logging;
+using UnrealBuildBase;
+
+namespace UnrealBuildTool
+{
+ class VSWorkspaceProjectFileGenerator : ProjectFileGenerator
+ {
+ public override string ProjectFileExtension => ".json";
+
+ // These properties are used by Visual Studio to determine where to read the project files.
+ // So they must remain constant.
+ private const string VSUnrealWorkspaceFileName = ".vs-unreal-workspace";
+ private const string ProjectFilesFolder = "VisualStudio";
+
+ private readonly CommandLineArguments Arguments;
+
+ /// <summary>
+ /// List of deprecated platforms.
+ /// Don't generate project model for these platforms unless they are specified in "Platforms" console arguments.
+ /// </summary>
+ /// <returns></returns>
+ private readonly HashSet<UnrealTargetPlatform> DeprecatedPlatforms = new HashSet<UnrealTargetPlatform>();
+
+ /// <summary>
+ /// Platforms to generate project files for
+ /// </summary>
+ [CommandLine("-Platforms=", ListSeparator = '+')]
+ HashSet<UnrealTargetPlatform> Platforms = new HashSet<UnrealTargetPlatform>();
+
+ /// <summary>
+ /// Target types to generate project files for
+ /// </summary>
+ [CommandLine("-TargetTypes=", ListSeparator = '+')]
+ HashSet<TargetType> TargetTypes = new HashSet<TargetType>();
+
+ /// <summary>
+ /// Target configurations to generate project files for
+ /// </summary>
+ [CommandLine("-TargetConfigurations=", ListSeparator = '+')]
+ HashSet<UnrealTargetConfiguration> TargetConfigurations = new HashSet<UnrealTargetConfiguration>();
+
+ /// <summary>
+ /// Projects to generate project files for
+ /// </summary>
+ [CommandLine("-ProjectNames=", ListSeparator = '+')]
+ HashSet<string> ProjectNames = new HashSet<string>();
+
+ /// <summary>
+ /// Should format JSON files in human readable form, or use packed one without indents
+ /// </summary>
+ [CommandLine("-Minimize", Value = "Compact")]
+ private JsonWriterStyle Minimize = JsonWriterStyle.Readable;
+
+ /// <summary>
+ /// The settings object
+ /// </summary>
+ protected VCProjectFileSettings Settings = new VCProjectFileSettings();
+
+ public VSWorkspaceProjectFileGenerator(FileReference? InOnlyGameProject,
+ CommandLineArguments InArguments)
+ : base(InOnlyGameProject)
+ {
+ Arguments = InArguments;
+ Arguments.ApplyTo(this);
+ XmlConfig.ApplyTo(Settings);
+ }
+
+ public override bool ShouldGenerateIntelliSenseData() => true;
+
+ public override void CleanProjectFiles(DirectoryReference InPrimaryProjectDirectory, string InPrimaryProjectName,
+ DirectoryReference InIntermediateProjectFilesDirectory)
+ {
+ DirectoryReference.Delete(InPrimaryProjectDirectory);
+ }
+
+ protected override void ConfigureProjectFileGeneration(string[] Arguments, ref bool IncludeAllPlatforms)
+ {
+ base.ConfigureProjectFileGeneration(Arguments, ref IncludeAllPlatforms);
+ }
+
+ protected override ProjectFile AllocateProjectFile(FileReference InitFilePath, DirectoryReference BaseDir)
+ {
+ VSWorkspaceProjectFile projectFile = new VSWorkspaceProjectFile(InitFilePath, BaseDir, InitFilePath.Directory,
+ TargetTypes, Arguments, Settings, bUsePrecompiled);
+ return projectFile;
+ }
+
+ protected override bool WriteProjectFiles(PlatformProjectGeneratorCollection PlatformProjectGenerators)
+ {
+ using ProgressWriter Progress = new ProgressWriter("Writing project files...", true);
+ List<ProjectFile> ProjectsToGenerate = new List<ProjectFile>(GeneratedProjectFiles);
+ if (ProjectNames.Any())
+ {
+ ProjectsToGenerate = ProjectsToGenerate.Where(Project =>
+ ProjectNames.Contains(Project.ProjectFilePath.GetFileNameWithoutAnyExtensions())).ToList();
+ }
+
+ int TotalProjectFileCount = ProjectsToGenerate.Count;
+
+ HashSet<UnrealTargetPlatform> PlatformsToGenerate = new HashSet<UnrealTargetPlatform>(SupportedPlatforms);
+ if (Platforms.Any())
+ {
+ PlatformsToGenerate.IntersectWith(Platforms);
+ }
+
+ List<UnrealTargetPlatform> FilteredPlatforms = PlatformsToGenerate.Where(Platform =>
+ {
+ // Skip deprecated unless explicitly specified in the command line.
+ return (!DeprecatedPlatforms.Contains(Platform) || Platforms.Contains(Platform))
+ && UEBuildPlatform.IsPlatformAvailable(Platform);
+ }).ToList();
+
+ HashSet<UnrealTargetConfiguration> ConfigurationsToGenerate = new HashSet<UnrealTargetConfiguration>(SupportedConfigurations);
+ if (TargetConfigurations.Any())
+ {
+ ConfigurationsToGenerate.IntersectWith(TargetConfigurations);
+ }
+
+ List<UnrealTargetConfiguration> Configurations = ConfigurationsToGenerate.ToList();
+
+ for (int ProjectFileIndex = 0; ProjectFileIndex < ProjectsToGenerate.Count; ++ProjectFileIndex)
+ {
+ VSWorkspaceProjectFile? CurrentProject = ProjectsToGenerate[ProjectFileIndex] as VSWorkspaceProjectFile;
+ if (CurrentProject == null)
+ {
+ return false;
+ }
+
+ if (!CurrentProject.WriteProjectFile(FilteredPlatforms, Configurations, PlatformProjectGenerators, Minimize))
+ {
+ return false;
+ }
+
+ Progress.Write(ProjectFileIndex + 1, TotalProjectFileCount);
+ }
+
+ Progress.Write(TotalProjectFileCount, TotalProjectFileCount);
+
+ return true;
+ }
+
+ public override bool GenerateProjectFiles(PlatformProjectGeneratorCollection PlatformProjectGenerators,
+ String[] Arguments)
+ {
+ bool IncludeAllPlatforms = true;
+ ConfigureProjectFileGeneration(Arguments, ref IncludeAllPlatforms);
+
+ if (bGeneratingGameProjectFiles || Unreal.IsEngineInstalled())
+ {
+ PrimaryProjectPath = OnlyGameProject!.Directory;
+ PrimaryProjectName = OnlyGameProject.GetFileNameWithoutExtension();
+
+ IntermediateProjectFilesPath =
+ DirectoryReference.Combine(PrimaryProjectPath, "Intermediate", "ProjectFiles");
+ }
+
+ SetupSupportedPlatformsAndConfigurations(IncludeAllPlatforms: true, out string SupportedPlatformNames);
+ Log.TraceVerbose("Supported platforms: {Platforms}", SupportedPlatformNames);
+
+ List<FileReference> AllGames = FindGameProjects();
+
+ {
+ // Find all of the target files.
+ List<FileReference> AllTargetFiles = DiscoverTargets(AllGames);
+
+ // If there are multiple targets of a given type for a project, use the order to determine which one gets a suffix.
+ AllTargetFiles = AllTargetFiles.OrderBy(x => x.FullName, StringComparer.OrdinalIgnoreCase).ToList();
+
+ AddProjectsForAllTargets(
+ PlatformProjectGenerators,
+ AllGames,
+ AllTargetFiles,
+ Arguments,
+ out ProjectFile? EngineProject,
+ out List<ProjectFile>? GameProjects,
+ out Dictionary<FileReference, ProjectFile>? ProgramProjects,
+ out Dictionary<RulesAssembly, DirectoryReference>? RulesAssemblies);
+
+ AddAllGameProjects(GameProjects);
+ }
+
+ WriteProjectFiles(PlatformProjectGenerators);
+ WritePrimaryProjectFile(UBTProject, PlatformProjectGenerators);
+
+ return true;
+ }
+
+ protected override bool WritePrimaryProjectFile(ProjectFile? UBTProject,
+ PlatformProjectGeneratorCollection PlatformProjectGenerators)
+ {
+ try
+ {
+ FileReference PrimaryProjectFile = FileReference.Combine(
+ IntermediateProjectFilesPath, ProjectFilesFolder, VSUnrealWorkspaceFileName);
+
+ DirectoryReference.CreateDirectory(PrimaryProjectFile.Directory);
+
+ // Collect all the resulting project files and aggregate the target-level data
+ var AggregatedProjectInfo = GeneratedProjectFiles
+ .Where(Project => Project is VSWorkspaceProjectFile)
+ .OfType<VSWorkspaceProjectFile>()
+ .SelectMany(Project => Project.ExportedTargetProjects)
+ .GroupBy(TargetProject => TargetProject.TargetName)
+ .Select(g => (g.Key, Target: new
+ {
+ TargetType = g.Select(i => i.TargetType).Distinct().Single(),
+ TargetPath = g.Select(i => i.TargetPath).Distinct().Single(),
+ ProjectPath = g.Select(i => i.ProjectPath).Distinct().Single(),
+ Configurations = g.Select(i => i.Configuration).Distinct().ToList(),
+ Platforms = g.Select(i => i.Platform).Distinct().ToList(),
+ }));
+
+ // The inner Targets object is needed for schema compatibility with the Query Mode API.
+ var Result = new
+ {
+ Targets = AggregatedProjectInfo.ToDictionary(item => item.Key, item => item.Target)
+ };
+
+ File.WriteAllText(PrimaryProjectFile.FullName, JsonSerializer.Serialize(Result, new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = Minimize == JsonWriterStyle.Readable,
+ }));
+ }
+ catch (Exception Ex)
+ {
+ Log.TraceWarning("Exception while writing root project file: {Ex}", Ex.ToString());
+ return false;
+ }
+
+ return true;
+ }
+
+ /// <inheritdoc />
+ protected override FileReference GetProjectLocation(string BaseName)
+ {
+ return FileReference.Combine(IntermediateProjectFilesPath, ProjectFilesFolder, BaseName + ProjectFileExtension);
+ }
+ }
+}
diff --git a/Engine/Source/Programs/UnrealBuildTool/ToolChain/UEToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/ToolChain/UEToolChain.cs
index e8b40fff208d9..4f4fff19c8551 100644
--- a/Engine/Source/Programs/UnrealBuildTool/ToolChain/UEToolChain.cs
+++ b/Engine/Source/Programs/UnrealBuildTool/ToolChain/UEToolChain.cs
@@ -34,6 +34,12 @@ public static DirectoryReference GetModuleInterfaceDir(DirectoryReference Output
return DirectoryReference.Combine(OutputDir, "Ifc");
}
+ // Return the path to the cpp compiler that will be used by this toolchain.
+ public virtual FileReference? GetCppCompilerPath()
+ {
+ return null;
+ }
+
public abstract CPPOutput CompileCPPFiles(CppCompileEnvironment CompileEnvironment, List<FileItem> InputFiles, DirectoryReference OutputDir, string ModuleName, IActionGraphBuilder Graph);
public virtual CPPOutput CompileRCFiles(CppCompileEnvironment Environment, List<FileItem> InputFiles, DirectoryReference OutputDir, IActionGraphBuilder Graph)
@@ -65,6 +71,20 @@ public virtual FileItem[] LinkAllFiles(LinkEnvironment LinkEnvironment, bool bBu
return LinkFile != null ? new FileItem[] { LinkFile } : new FileItem[] { };
}
+ public virtual IEnumerable<string> GetGlobalCommandLineArgs(CppCompileEnvironment CompileEnvironment)
+ {
+ return Array.Empty<string>();
+ }
+
+ public virtual IEnumerable<string> GetCPPCommandLineArgs(CppCompileEnvironment CompileEnvironment)
+ {
+ return Array.Empty<string>();
+ }
+
+ public virtual IEnumerable<string> GetCCommandLineArgs(CppCompileEnvironment CompileEnvironment)
+ {
+ return Array.Empty<string>();
+ }
/// <summary>
/// Get the name of the response file for the current linker environment and output file