Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs
mark lintott 53511dcacd #jira UE-104748
#rb Ben.Marsh

Renamed UE4.natvis to Unreal.natvis
Fixed up Project File Generator within the Unreal Build Tool

[CL 15009828 by mark lintott in ue5-main branch]
2021-01-07 10:21:33 -04:00

1251 lines
39 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using EpicGames.Core;
using System.Diagnostics;
using System.Linq;
#nullable disable
namespace UnrealBuildTool
{
class VSCodeProjectFolder : MasterProjectFolder
{
public VSCodeProjectFolder(ProjectFileGenerator InitOwnerProjectFileGenerator, string InitFolderName)
: base(InitOwnerProjectFileGenerator, InitFolderName)
{
}
}
class VSCodeProject : ProjectFile
{
public VSCodeProject(FileReference InitFilePath)
: base(InitFilePath)
{
}
public override bool WriteProjectFile(List<UnrealTargetPlatform> InPlatforms, List<UnrealTargetConfiguration> InConfigurations, PlatformProjectGeneratorCollection PlatformProjectGenerators)
{
return true;
}
}
class VSCodeProjectFileGenerator : ProjectFileGenerator
{
private DirectoryReference VSCodeDir;
private UnrealTargetPlatform HostPlatform;
private bool bForeignProject;
private DirectoryReference UE4ProjectRoot;
private string FrameworkExecutableExtension = ".exe";
private string FrameworkLibraryExtension = ".dll";
private readonly List<BuildTarget> BuildTargets = new List<BuildTarget>();
/// <summary>
/// Includes all files in the generated workspace.
/// </summary>
[XmlConfigFile(Name = "IncludeAllFiles")]
private bool IncludeAllFiles = false;
private enum EPathType
{
Absolute,
Relative,
}
private enum EQuoteType
{
Single, // can be ignored on platforms that don't need it (windows atm)
Double,
}
private string CommonMakePathString(FileSystemReference InRef, EPathType InPathType, DirectoryReference InRelativeRoot)
{
if (InRelativeRoot == null)
{
InRelativeRoot = UE4ProjectRoot;
}
string Processed = InRef.ToString();
switch (InPathType)
{
case EPathType.Relative:
{
if (InRef.IsUnderDirectory(InRelativeRoot))
{
Processed = InRef.MakeRelativeTo(InRelativeRoot).ToString();
}
break;
}
default:
{
break;
}
}
if (HostPlatform == UnrealTargetPlatform.Win64)
{
Processed = Processed.Replace("\\", "\\\\");
Processed = Processed.Replace("/", "\\\\");
}
else
{
Processed = Processed.Replace('\\', '/');
}
return Processed;
}
private string MakeQuotedPathString(FileSystemReference InRef, EPathType InPathType, DirectoryReference InRelativeRoot = null, EQuoteType InQuoteType = EQuoteType.Double)
{
string Processed = CommonMakePathString(InRef, InPathType, InRelativeRoot);
if (Processed.Contains(" "))
{
if (HostPlatform == UnrealTargetPlatform.Win64 && InQuoteType == EQuoteType.Double)
{
Processed = "\\\"" + Processed + "\\\"";
}
else
{
Processed = "'" + Processed + "'";
}
}
return Processed;
}
private string MakeUnquotedPathString(FileSystemReference InRef, EPathType InPathType, DirectoryReference InRelativeRoot = null)
{
return CommonMakePathString(InRef, InPathType, InRelativeRoot);
}
private string MakePathString(FileSystemReference InRef, bool bInAbsolute = false, bool bForceSkipQuotes = false)
{
if (bForceSkipQuotes)
{
return MakeUnquotedPathString(InRef, bInAbsolute ? EPathType.Absolute : EPathType.Relative, UE4ProjectRoot);
}
else
{
return MakeQuotedPathString(InRef, bInAbsolute ? EPathType.Absolute : EPathType.Relative, UE4ProjectRoot);
}
}
public VSCodeProjectFileGenerator(FileReference InOnlyGameProject)
: base(InOnlyGameProject)
{
}
class JsonFile
{
public JsonFile()
{
}
public void BeginRootObject()
{
BeginObject();
}
public void EndRootObject()
{
EndObject();
if (TabString.Length > 0)
{
throw new Exception("Called EndRootObject before all objects and arrays have been closed");
}
}
public void BeginObject(string Name = null)
{
string Prefix = Name == null ? "" : Quoted(Name) + ": ";
Lines.Add(TabString + Prefix + "{");
TabString += "\t";
}
public void EndObject()
{
Lines[Lines.Count - 1] = Lines[Lines.Count - 1].TrimEnd(',');
TabString = TabString.Remove(TabString.Length - 1);
Lines.Add(TabString + "},");
}
public void BeginArray(string Name = null)
{
string Prefix = Name == null ? "" : Quoted(Name) + ": ";
Lines.Add(TabString + Prefix + "[");
TabString += "\t";
}
public void EndArray()
{
Lines[Lines.Count - 1] = Lines[Lines.Count - 1].TrimEnd(',');
TabString = TabString.Remove(TabString.Length - 1);
Lines.Add(TabString + "],");
}
public void AddField(string Name, bool Value)
{
Lines.Add(TabString + Quoted(Name) + ": " + Value.ToString().ToLower() + ",");
}
public void AddField(string Name, string Value)
{
Lines.Add(TabString + Quoted(Name) + ": " + Quoted(Value) + ",");
}
public void AddUnnamedField(string Value)
{
Lines.Add(TabString + Quoted(Value) + ",");
}
public void Write(FileReference File)
{
Lines[Lines.Count - 1] = Lines[Lines.Count - 1].TrimEnd(',');
FileReference.WriteAllLines(File, Lines.ToArray());
}
private string Quoted(string Value)
{
return "\"" + Value + "\"";
}
private List<string> Lines = new List<string>();
private string TabString = "";
}
override public string ProjectFileExtension
{
get
{
return ".vscode";
}
}
public override void CleanProjectFiles(DirectoryReference InMasterProjectDirectory, string InMasterProjectName, DirectoryReference InIntermediateProjectFilesPath)
{
}
public override bool ShouldGenerateIntelliSenseData()
{
return true;
}
protected override ProjectFile AllocateProjectFile(FileReference InitFilePath)
{
return new VSCodeProject(InitFilePath);
}
public override MasterProjectFolder AllocateMasterProjectFolder(ProjectFileGenerator InitOwnerProjectFileGenerator, string InitFolderName)
{
return new VSCodeProjectFolder(InitOwnerProjectFileGenerator, InitFolderName);
}
protected override bool WriteMasterProjectFile(ProjectFile UBTProject, PlatformProjectGeneratorCollection PlatformProjectGenerators)
{
VSCodeDir = DirectoryReference.Combine(MasterProjectPath, ".vscode");
DirectoryReference.CreateDirectory(VSCodeDir);
UE4ProjectRoot = UnrealBuildTool.RootDirectory;
bForeignProject = !VSCodeDir.IsUnderDirectory(UE4ProjectRoot);
HostPlatform = BuildHostPlatform.Current.Platform;
List<ProjectFile> Projects;
if (bForeignProject)
{
Projects = new List<ProjectFile>();
foreach (var Project in AllProjectFiles)
{
if (GameProjectName == Project.ProjectFilePath.GetFileNameWithoutAnyExtensions())
{
Projects.Add(Project);
break;
}
}
}
else
{
Projects = new List<ProjectFile>(AllProjectFiles);
}
Projects.Sort((A, B) => { return A.ProjectFilePath.GetFileName().CompareTo(B.ProjectFilePath.GetFileName()); });
ProjectData ProjectData = GatherProjectData(Projects, PlatformProjectGenerators);
WriteTasksFile(ProjectData);
WriteLaunchFile(ProjectData);
WriteWorkspaceIgnoreFile(Projects);
WriteCppPropertiesFile(VSCodeDir, ProjectData);
WriteWorkspaceFile();
if (bForeignProject && bIncludeEngineSource)
{
// for installed builds we need to write the cpp properties file under the installed engine as well for intellisense to work
DirectoryReference Ue4CodeDirectory = DirectoryReference.Combine(UnrealBuildTool.RootDirectory, ".vscode");
WriteCppPropertiesFile(Ue4CodeDirectory, ProjectData);
}
return true;
}
private class BuildTarget
{
public readonly string Name;
public readonly TargetType Type;
public readonly UnrealTargetPlatform Platform;
public readonly UnrealTargetConfiguration Configuration;
public readonly Dictionary<DirectoryReference, string> ModuleCommandLines;
public BuildTarget(string InName, TargetType InType, UnrealTargetPlatform InPlatform, UnrealTargetConfiguration InConfiguration, Dictionary<DirectoryReference, string> InModulesCommandLines)
{
Name = InName;
Type = InType;
Platform = InPlatform;
Configuration = InConfiguration;
ModuleCommandLines = InModulesCommandLines;
}
public override string ToString()
{
return Name.ToString() + " " + Type.ToString();
}
}
protected override void AddTargetForIntellisense(UEBuildTarget Target)
{
base.AddTargetForIntellisense(Target);
// we do not need to keep track of which binary the invocation belongs to, only which target, as such we join all binaries into a single set
Dictionary<DirectoryReference, string> ModuleDirectoryToCompileCommand = new Dictionary<DirectoryReference, string>();
// Generate a compile environment for each module in the binary
CppCompileEnvironment GlobalCompileEnvironment = Target.CreateCompileEnvironmentForProjectFiles();
foreach (UEBuildBinary Binary in Target.Binaries)
{
CppCompileEnvironment BinaryCompileEnvironment = Binary.CreateBinaryCompileEnvironment(GlobalCompileEnvironment);
foreach (UEBuildModuleCPP Module in Binary.Modules.OfType<UEBuildModuleCPP>())
{
CppCompileEnvironment ModuleCompileEnvironment = Module.CreateCompileEnvironmentForIntellisense(Target.Rules, BinaryCompileEnvironment);
List<FileReference> ForceIncludePaths = new List<FileReference>(ModuleCompileEnvironment.ForceIncludeFiles.Select(x => x.Location));
if (ModuleCompileEnvironment.PrecompiledHeaderIncludeFilename != null)
{
ForceIncludePaths.Add(ModuleCompileEnvironment.PrecompiledHeaderIncludeFilename);
}
StringBuilder CommandBuilder = new StringBuilder();
// Technically the command should include the compiler to use, but we only generate these for intellisense and thus the compiler is not needed
// And resolving the compiler when it would not be used is a waste of effort.
//CommandBuilder.AppendFormat("\"{0}\"", ClangPath.FullName);
foreach (FileReference ForceIncludeFile in ForceIncludePaths)
{
CommandBuilder.AppendFormat(" -include \"{0}\"", ForceIncludeFile.FullName);
}
foreach (string Definition in ModuleCompileEnvironment.Definitions)
{
CommandBuilder.AppendFormat(" -D\"{0}\"", Definition);
}
foreach (DirectoryReference IncludePath in ModuleCompileEnvironment.UserIncludePaths)
{
CommandBuilder.AppendFormat(" -I\"{0}\"", IncludePath);
}
foreach (DirectoryReference IncludePath in ModuleCompileEnvironment.SystemIncludePaths)
{
CommandBuilder.AppendFormat(" -I\"{0}\"", IncludePath);
}
ModuleDirectoryToCompileCommand.Add(Module.ModuleDirectory, CommandBuilder.ToString());
}
}
BuildTargets.Add(new BuildTarget(Target.TargetName, Target.TargetType, Target.Platform, Target.Configuration, ModuleDirectoryToCompileCommand));
}
private class ProjectData
{
public enum EOutputType
{
Library,
Exe,
WinExe, // some projects have this so we need to read it, but it will be converted across to Exe so no code should handle it!
}
public class BuildProduct
{
public FileReference OutputFile { get; set; }
public FileReference UProjectFile { get; set; }
public UnrealTargetConfiguration Config { get; set; }
public UnrealTargetPlatform Platform { get; set; }
public EOutputType OutputType { get; set; }
public CsProjectInfo CSharpInfo { get; set; }
public override string ToString()
{
return Platform.ToString() + " " + Config.ToString();
}
}
public class Target
{
public string Name;
public TargetType Type;
public List<BuildProduct> BuildProducts = new List<BuildProduct>();
public Target(Project InParentProject, string InName, TargetType InType)
{
Name = InName;
Type = InType;
InParentProject.Targets.Add(this);
}
public override string ToString()
{
return Name.ToString() + " " + Type.ToString();
}
}
public class Project
{
public string Name;
public ProjectFile SourceProject;
public List<Target> Targets = new List<Target>();
public override string ToString()
{
return Name;
}
}
public List<Project> NativeProjects = new List<Project>();
public List<Project> CSharpProjects = new List<Project>();
public List<Project> AllProjects = new List<Project>();
}
private ProjectData GatherProjectData(List<ProjectFile> InProjects, PlatformProjectGeneratorCollection PlatformProjectGenerators)
{
ProjectData ProjectData = new ProjectData();
foreach (ProjectFile Project in InProjects)
{
// Create new project record
ProjectData.Project NewProject = new ProjectData.Project();
NewProject.Name = Project.ProjectFilePath.GetFileNameWithoutExtension();
NewProject.SourceProject = Project;
ProjectData.AllProjects.Add(NewProject);
// Add into the correct easy-access list
if (Project is VSCodeProject)
{
foreach (ProjectTarget Target in Project.ProjectTargets)
{
Array Configs = Enum.GetValues(typeof(UnrealTargetConfiguration));
List<UnrealTargetPlatform> Platforms = new List<UnrealTargetPlatform>(Target.TargetRules.GetSupportedPlatforms());
ProjectData.Target NewTarget = new ProjectData.Target(NewProject, Target.TargetRules.Name, Target.TargetRules.Type);
if (HostPlatform != UnrealTargetPlatform.Win64)
{
Platforms.Remove(UnrealTargetPlatform.Win64);
Platforms.Remove(UnrealTargetPlatform.Win32);
}
foreach (UnrealTargetPlatform Platform in Platforms)
{
UEBuildPlatform.TryGetBuildPlatform(Platform, out UEBuildPlatform BuildPlatform);
if (SupportedPlatforms.Contains(Platform) && (BuildPlatform != null) && (BuildPlatform.HasRequiredSDKsInstalled() == SDKStatus.Valid))
{
foreach (UnrealTargetConfiguration Config in Configs)
{
if (MSBuildProjectFile.IsValidProjectPlatformAndConfiguration(Target, Platform, Config, PlatformProjectGenerators))
{
NewTarget.BuildProducts.Add(new ProjectData.BuildProduct
{
Platform = Platform,
Config = Config,
UProjectFile = Target.UnrealProjectFilePath,
OutputType = ProjectData.EOutputType.Exe,
OutputFile = GetExecutableFilename(Project, Target, Platform, Config),
CSharpInfo = null
});
}
}
}
}
}
ProjectData.NativeProjects.Add(NewProject);
}
else
{
VCSharpProjectFile VCSharpProject = Project as VCSharpProjectFile;
string ProjectName = Project.ProjectFilePath.GetFileNameWithoutExtension();
ProjectData.Target Target = new ProjectData.Target(NewProject, ProjectName, TargetType.Program);
UnrealTargetConfiguration[] Configs = { UnrealTargetConfiguration.Debug, UnrealTargetConfiguration.Development };
foreach (UnrealTargetConfiguration Config in Configs)
{
CsProjectInfo Info = VCSharpProject.GetProjectInfo(Config);
if (Info.Properties.ContainsKey("OutputPath"))
{
ProjectData.EOutputType OutputType;
string OutputTypeName;
if (Info.Properties.TryGetValue("OutputType", out OutputTypeName))
{
OutputType = (ProjectData.EOutputType)Enum.Parse(typeof(ProjectData.EOutputType), OutputTypeName);
}
else
{
OutputType = ProjectData.EOutputType.Library;
}
if (OutputType == ProjectData.EOutputType.WinExe)
{
OutputType = ProjectData.EOutputType.Exe;
}
FileReference OutputFile = null;
HashSet<FileReference> ProjectBuildProducts = new HashSet<FileReference>();
Info.FindCompiledBuildProducts(DirectoryReference.Combine(VCSharpProject.ProjectFilePath.Directory, Info.Properties["OutputPath"]), ProjectBuildProducts);
foreach (FileReference ProjectBuildProduct in ProjectBuildProducts)
{
if ((OutputType == ProjectData.EOutputType.Exe && ProjectBuildProduct.GetExtension() == FrameworkExecutableExtension) ||
(OutputType == ProjectData.EOutputType.Library && ProjectBuildProduct.GetExtension() == FrameworkLibraryExtension))
{
OutputFile = ProjectBuildProduct;
break;
}
}
if (OutputFile != null)
{
Target.BuildProducts.Add(new ProjectData.BuildProduct
{
Platform = HostPlatform,
Config = Config,
OutputFile = OutputFile,
OutputType = OutputType,
CSharpInfo = Info
});
}
}
}
ProjectData.CSharpProjects.Add(NewProject);
}
}
return ProjectData;
}
private void WriteCppPropertiesFile(DirectoryReference OutputDirectory, ProjectData Projects)
{
DirectoryReference.CreateDirectory(OutputDirectory);
JsonFile OutFile = new JsonFile();
OutFile.BeginRootObject();
{
OutFile.BeginArray("configurations");
{
foreach (ProjectData.Project Project in Projects.AllProjects)
{
foreach (ProjectData.Target ProjectTarget in Project.Targets)
{
BuildTarget BuildTarget = BuildTargets.FirstOrDefault(Target => Target.Name == ProjectTarget.Name);
// we do not generate intellisense for every target, as that just causes a lot of redundancy, as such we will not find a mapping for a lot of the targets
if (BuildTarget == null)
continue;
OutFile.BeginObject();
string Name = string.Format("{0} {1} {2} {3} ({4})", ProjectTarget.Name, ProjectTarget.Type, BuildTarget.Platform, BuildTarget.Configuration, Project.Name);
OutFile.AddField("name", Name);
if (HostPlatform == UnrealTargetPlatform.Win64)
{
OutFile.AddField("intelliSenseMode", "msvc-x64");
}
else
{
OutFile.AddField("intelliSenseMode", "clang-x64");
}
if (HostPlatform == UnrealTargetPlatform.Mac)
{
OutFile.BeginArray("macFrameworkPath");
{
OutFile.AddUnnamedField("/System/Library/Frameworks");
OutFile.AddUnnamedField("/Library/Frameworks");
}
OutFile.EndArray();
}
FileReference CompileCommands = FileReference.Combine(OutputDirectory, string.Format("compileCommands_{0}.json", Project.Name));
WriteCompileCommands(CompileCommands, Project.SourceProject.SourceFiles,BuildTarget.ModuleCommandLines);
OutFile.AddField("compileCommands", MakePathString(CompileCommands, bInAbsolute: true, bForceSkipQuotes: true));
OutFile.EndObject();
}
}
}
OutFile.EndArray();
}
OutFile.EndRootObject();
OutFile.Write(FileReference.Combine(OutputDirectory, "c_cpp_properties.json"));
}
private void WriteCompileCommands(FileReference CompileCommandsFile, List<ProjectFile.SourceFile> SourceFiles, Dictionary<DirectoryReference, string> ModuleCommandLines)
{
// this creates a compileCommands.json
// see VsCode Docs - https://code.visualstudio.com/docs/cpp/c-cpp-properties-schema-reference (compileCommands attribute)
// and the clang format description https://clang.llvm.org/docs/JSONCompilationDatabase.html
using (JsonWriter Writer = new JsonWriter(CompileCommandsFile))
{
Writer.WriteArrayStart();
Dictionary<DirectoryReference, string> DirectoryToIntellisenseCompilerCommand = ModuleCommandLines;
foreach (ProjectFile.SourceFile File in SourceFiles.OrderBy(x => x.Reference.FullName))
{
DirectoryReference Directory = File.Reference.Directory;
string CompilerCommand;
if (!DirectoryToIntellisenseCompilerCommand.TryGetValue(Directory, out CompilerCommand))
{
for (DirectoryReference ParentDir = Directory; ParentDir != null && ParentDir != UnrealBuildTool.RootDirectory; ParentDir = ParentDir.ParentDirectory)
{
if (DirectoryToIntellisenseCompilerCommand.TryGetValue(ParentDir, out CompilerCommand))
{
break;
}
}
DirectoryToIntellisenseCompilerCommand[Directory] = CompilerCommand;
}
if (CompilerCommand == null)
{
// no compiler command associated with the file, will happen for any file that is not a C++ file and is not an error
continue;
}
Writer.WriteObjectStart();
Writer.WriteValue("file", MakePathString(File.Reference, bInAbsolute: true, bForceSkipQuotes: true));
Writer.WriteValue("command", CompilerCommand);
Writer.WriteValue("directory", UnrealBuildTool.EngineSourceDirectory.ToString());
Writer.WriteObjectEnd();
}
Writer.WriteArrayEnd();
}
}
private void WriteNativeTask(ProjectData.Project InProject, JsonFile OutFile)
{
string[] Commands = { "Build", "Rebuild", "Clean" };
foreach (ProjectData.Target Target in InProject.Targets)
{
foreach (ProjectData.BuildProduct BuildProduct in Target.BuildProducts)
{
foreach (string BaseCommand in Commands)
{
string Command = BaseCommand == "Rebuild" ? "Build" : BaseCommand;
string TaskName = String.Format("{0} {1} {2} {3}", Target.Name, BuildProduct.Platform.ToString(), BuildProduct.Config, BaseCommand);
string CleanTaskName = String.Format("{0} {1} {2} {3}", Target.Name, BuildProduct.Platform.ToString(), BuildProduct.Config, "Clean");
OutFile.BeginObject();
{
OutFile.AddField("label", TaskName);
OutFile.AddField("group", "build");
string CleanParam = Command == "Clean" ? "-clean" : null;
if (HostPlatform == UnrealTargetPlatform.Win64)
{
OutFile.AddField("command", MakePathString(FileReference.Combine(UE4ProjectRoot, "Engine", "Build", "BatchFiles", Command + ".bat")));
CleanParam = null;
}
else
{
OutFile.AddField("command", MakePathString(FileReference.Combine(UE4ProjectRoot, "Engine", "Build", "BatchFiles", HostPlatform.ToString(), "Build.sh")));
if (Command == "Clean")
{
CleanParam = "-clean";
}
}
OutFile.BeginArray("args");
{
OutFile.AddUnnamedField(Target.Name);
OutFile.AddUnnamedField(BuildProduct.Platform.ToString());
OutFile.AddUnnamedField(BuildProduct.Config.ToString());
if (bForeignProject)
{
OutFile.AddUnnamedField(MakeUnquotedPathString(BuildProduct.UProjectFile, EPathType.Relative, null));
}
OutFile.AddUnnamedField("-waitmutex");
if (!string.IsNullOrEmpty(CleanParam))
{
OutFile.AddUnnamedField(CleanParam);
}
}
OutFile.EndArray();
OutFile.AddField("problemMatcher", "$msCompile");
if (!bForeignProject || BaseCommand == "Rebuild")
{
OutFile.BeginArray("dependsOn");
{
if (!bForeignProject)
{
if (Command == "Build" && Target.Type == TargetType.Editor)
{
OutFile.AddUnnamedField("ShaderCompileWorker " + HostPlatform.ToString() + " Development Build");
}
else
{
OutFile.AddUnnamedField("UnrealBuildTool " + HostPlatform.ToString() + " Development Build");
}
}
if (BaseCommand == "Rebuild")
{
OutFile.AddUnnamedField(CleanTaskName);
}
}
OutFile.EndArray();
}
OutFile.AddField("type", "shell");
OutFile.BeginObject("options");
{
OutFile.AddField("cwd", MakeUnquotedPathString(UE4ProjectRoot, EPathType.Absolute));
}
OutFile.EndObject();
}
OutFile.EndObject();
}
}
}
}
private void WriteCSharpTask(ProjectData.Project InProject, JsonFile OutFile)
{
VCSharpProjectFile ProjectFile = InProject.SourceProject as VCSharpProjectFile;
string[] Commands = { "Build", "Clean" };
foreach (ProjectData.Target Target in InProject.Targets)
{
foreach (ProjectData.BuildProduct BuildProduct in Target.BuildProducts)
{
foreach (string Command in Commands)
{
string TaskName = String.Format("{0} {1} {2} {3}", Target.Name, BuildProduct.Platform, BuildProduct.Config, Command);
OutFile.BeginObject();
{
OutFile.AddField("label", TaskName);
OutFile.AddField("group", "build");
if (Utils.IsRunningOnMono)
{
OutFile.AddField("command", MakePathString(FileReference.Combine(UE4ProjectRoot, "Engine", "Build", "BatchFiles", HostPlatform.ToString(), "RunDotnet.sh")));
}
else
{
OutFile.AddField("command", "dotnet");
}
OutFile.BeginArray("args");
{
OutFile.AddUnnamedField(Command.ToLower());
OutFile.AddUnnamedField("--configuration");
OutFile.AddUnnamedField(BuildProduct.Config.ToString());
OutFile.AddUnnamedField(MakeUnquotedPathString(BuildProduct.CSharpInfo.ProjectPath, EPathType.Absolute));
}
OutFile.EndArray();
}
OutFile.AddField("problemMatcher", "$msCompile");
OutFile.AddField("type", "shell");
OutFile.BeginObject("options");
{
OutFile.AddField("cwd", MakeUnquotedPathString(UE4ProjectRoot, EPathType.Absolute));
}
OutFile.EndObject();
OutFile.EndObject();
}
}
}
}
private void WriteTasksFile(ProjectData ProjectData)
{
JsonFile OutFile = new JsonFile();
OutFile.BeginRootObject();
{
OutFile.AddField("version", "2.0.0");
OutFile.BeginArray("tasks");
{
foreach (ProjectData.Project NativeProject in ProjectData.NativeProjects)
{
WriteNativeTask(NativeProject, OutFile);
}
foreach (ProjectData.Project CSharpProject in ProjectData.CSharpProjects)
{
WriteCSharpTask(CSharpProject, OutFile);
}
OutFile.EndArray();
}
}
OutFile.EndRootObject();
OutFile.Write(FileReference.Combine(VSCodeDir, "tasks.json"));
}
private FileReference GetExecutableFilename(ProjectFile Project, ProjectTarget Target, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration)
{
TargetRules TargetRulesObject = Target.TargetRules;
FileReference TargetFilePath = Target.TargetFilePath;
string TargetName = TargetFilePath == null ? Project.ProjectFilePath.GetFileNameWithoutExtension() : TargetFilePath.GetFileNameWithoutAnyExtensions();
string UBTPlatformName = Platform.ToString();
// Setup output path
UEBuildPlatform BuildPlatform = UEBuildPlatform.GetBuildPlatform(Platform);
// Figure out if this is a monolithic build
bool bShouldCompileMonolithic = BuildPlatform.ShouldCompileMonolithicBinary(Platform);
if (TargetRulesObject != null)
{
bShouldCompileMonolithic |= (Target.CreateRulesDelegate(Platform, Configuration).LinkType == TargetLinkType.Monolithic);
}
TargetType TargetRulesType = Target.TargetRules == null ? TargetType.Program : Target.TargetRules.Type;
// Get the output directory
DirectoryReference RootDirectory = UnrealBuildTool.EngineDirectory;
if (TargetRulesType != TargetType.Program && (bShouldCompileMonolithic || TargetRulesObject.BuildEnvironment == TargetBuildEnvironment.Unique))
{
if(Target.UnrealProjectFilePath != null)
{
RootDirectory = Target.UnrealProjectFilePath.Directory;
}
}
if (TargetRulesType == TargetType.Program)
{
if(Target.UnrealProjectFilePath != null)
{
RootDirectory = Target.UnrealProjectFilePath.Directory;
}
}
// Get the output directory
DirectoryReference OutputDirectory = DirectoryReference.Combine(RootDirectory, "Binaries", UBTPlatformName);
// Get the executable name (minus any platform or config suffixes)
string BinaryName;
if(Target.TargetRules.BuildEnvironment == TargetBuildEnvironment.Shared && TargetRulesType != TargetType.Program)
{
BinaryName = UEBuildTarget.GetAppNameForTargetType(TargetRulesType);
}
else
{
BinaryName = TargetName;
}
// Make the output file path
string BinaryFileName = UEBuildTarget.MakeBinaryFileName(BinaryName, Platform, Configuration, TargetRulesObject.Architecture, TargetRulesObject.UndecoratedConfiguration, UEBuildBinaryType.Executable);
string ExecutableFilename = FileReference.Combine(OutputDirectory, BinaryFileName).FullName;
// Include the path to the actual executable for a Mac app bundle
if (Platform == UnrealTargetPlatform.Mac && !Target.TargetRules.bIsBuildingConsoleApplication)
{
ExecutableFilename += ".app/Contents/MacOS/" + Path.GetFileName(ExecutableFilename);
}
return new FileReference(ExecutableFilename);
}
private void WriteNativeLaunchConfig(ProjectData.Project InProject, JsonFile OutFile)
{
foreach (ProjectData.Target Target in InProject.Targets)
{
foreach (ProjectData.BuildProduct BuildProduct in Target.BuildProducts)
{
if (BuildProduct.Platform == HostPlatform)
{
string LaunchTaskName = String.Format("{0} {1} {2} Build", Target.Name, BuildProduct.Platform, BuildProduct.Config);
OutFile.BeginObject();
{
OutFile.AddField("name", Target.Name + " (" + BuildProduct.Config.ToString() + ")");
OutFile.AddField("request", "launch");
OutFile.AddField("preLaunchTask", LaunchTaskName);
OutFile.AddField("program", MakeUnquotedPathString(BuildProduct.OutputFile, EPathType.Absolute));
OutFile.BeginArray("args");
{
if (Target.Type == TargetRules.TargetType.Editor)
{
if (InProject.Name != "UE5")
{
if (bForeignProject)
{
OutFile.AddUnnamedField(MakePathString(BuildProduct.UProjectFile, false, true));
}
else
{
OutFile.AddUnnamedField(InProject.Name);
}
}
}
}
OutFile.EndArray();
/*
DirectoryReference CWD = BuildProduct.OutputFile.Directory;
while (HostPlatform == UnrealTargetPlatform.Mac && CWD != null && CWD.ToString().Contains(".app"))
{
CWD = CWD.ParentDirectory;
}
if (CWD != null)
{
OutFile.AddField("cwd", MakePathString(CWD, true, true));
}
*/
OutFile.AddField("cwd", MakeUnquotedPathString(UE4ProjectRoot, EPathType.Absolute));
if (HostPlatform == UnrealTargetPlatform.Win64)
{
OutFile.AddField("stopAtEntry", false);
OutFile.AddField("externalConsole", true);
OutFile.AddField("type", "cppvsdbg");
OutFile.AddField("visualizerFile", MakeUnquotedPathString(FileReference.Combine(UE4ProjectRoot, "Engine", "Extras", "VisualStudioDebugging", "Unreal.natvis"), EPathType.Absolute));
}
else
{
OutFile.AddField("type", "lldb");
}
}
OutFile.EndObject();
}
}
}
}
private void WriteSingleCSharpLaunchConfig(JsonFile OutFile, string InTaskName, string InBuildTaskName, FileReference InExecutable, string[] InArgs)
{
OutFile.BeginObject();
{
OutFile.AddField("name", InTaskName);
OutFile.AddField("type", "coreclr");
OutFile.AddField("request", "launch");
if (!string.IsNullOrEmpty(InBuildTaskName))
{
OutFile.AddField("preLaunchTask", InBuildTaskName);
}
DirectoryReference CWD = UE4ProjectRoot;
OutFile.AddField("program", MakeUnquotedPathString(InExecutable, EPathType.Absolute));
OutFile.BeginArray("args");
{
if (InArgs != null)
{
foreach (string Arg in InArgs)
{
OutFile.AddUnnamedField(Arg);
}
}
}
OutFile.EndArray();
if (HostPlatform == UnrealTargetPlatform.Win64)
{
OutFile.AddField("console", "externalTerminal");
}
else
{
OutFile.AddField("console", "internalConsole");
OutFile.AddField("internalConsoleOptions", "openOnSessionStart");
}
OutFile.AddField("stopAtEntry", false);
OutFile.AddField("cwd", MakeUnquotedPathString(CWD, EPathType.Absolute));
}
OutFile.EndObject();
}
private void WriteCSharpLaunchConfig(ProjectData.Project InProject, JsonFile OutFile)
{
VCSharpProjectFile CSharpProject = InProject.SourceProject as VCSharpProjectFile;
foreach (ProjectData.Target Target in InProject.Targets)
{
foreach (ProjectData.BuildProduct BuildProduct in Target.BuildProducts)
{
if (BuildProduct.OutputType == ProjectData.EOutputType.Exe)
{
string TaskName = String.Format("{0} ({1})", Target.Name, BuildProduct.Config);
string BuildTaskName = String.Format("{0} {1} {2} Build", Target.Name, HostPlatform, BuildProduct.Config);
WriteSingleCSharpLaunchConfig(OutFile, TaskName, BuildTaskName, BuildProduct.OutputFile, null);
}
}
}
}
private void WriteLaunchFile(ProjectData ProjectData)
{
JsonFile OutFile = new JsonFile();
OutFile.BeginRootObject();
{
OutFile.AddField("version", "0.2.0");
OutFile.BeginArray("configurations");
{
foreach (ProjectData.Project Project in ProjectData.NativeProjects)
{
WriteNativeLaunchConfig(Project, OutFile);
}
foreach (ProjectData.Project Project in ProjectData.CSharpProjects)
{
WriteCSharpLaunchConfig(Project, OutFile);
}
}
// Add in a special task for regenerating project files
string PreLaunchTask = "";
List<string> Args = new List<string>();
Args.Add("-projectfiles");
Args.Add("-vscode");
if (bForeignProject)
{
Args.Add("-project=\\\"" + MakeUnquotedPathString(OnlyGameProject, EPathType.Absolute) + "\\\"");
Args.Add("-game");
Args.Add("-engine");
}
else
{
PreLaunchTask = "UnrealBuildTool " + HostPlatform.ToString() + " Development Build";
}
FileReference UbtPath = FileReference.Combine(UE4ProjectRoot, "Engine", "Binaries", "DotNET", "UnrealBuildTool", "UnrealBuildTool");
WriteSingleCSharpLaunchConfig(
OutFile,
"Generate Project Files",
PreLaunchTask,
UbtPath,
Args.ToArray()
);
OutFile.EndArray();
}
OutFile.EndRootObject();
OutFile.Write(FileReference.Combine(VSCodeDir, "launch.json"));
}
private void WriteWorkspaceIgnoreFile(List<ProjectFile> Projects)
{
List<string> PathsToExclude = new List<string>();
foreach (ProjectFile Project in Projects)
{
bool bFoundTarget = false;
foreach (ProjectTarget Target in Project.ProjectTargets)
{
if (Target.TargetFilePath != null)
{
DirectoryReference ProjDir = Target.TargetFilePath.Directory.GetDirectoryName() == "Source" ? Target.TargetFilePath.Directory.ParentDirectory : Target.TargetFilePath.Directory;
GetExcludePathsCPP(ProjDir, PathsToExclude);
DirectoryReference PluginRootDir = DirectoryReference.Combine(ProjDir, "Plugins");
WriteWorkspaceIgnoreFileForPlugins(PluginRootDir, PathsToExclude);
bFoundTarget = true;
}
}
if (!bFoundTarget)
{
GetExcludePathsCSharp(Project.ProjectFilePath.Directory.ToString(), PathsToExclude);
}
}
StringBuilder OutFile = new StringBuilder();
if (!IncludeAllFiles)
{
// TODO: Adding ignore patterns to .ignore hides files from Open File Dialog but it does not hide them in the File Explorer
// but using files.exclude with our full set of excludes breaks vscode for larger code bases so a verbose file explorer
// seems like less of an issue and thus we are not adding these to files.exclude.
// see https://github.com/microsoft/vscode/issues/109380 for discussions with vscode team
DirectoryReference WorkspaceRoot = bForeignProject ? Projects[0].BaseDir : UnrealBuildTool.RootDirectory;
string WorkspaceRootPath = WorkspaceRoot.ToString().Replace('\\', '/') + "/";
if (!bForeignProject)
{
OutFile.AppendLine(".vscode");
}
foreach (string PathToExclude in PathsToExclude)
{
OutFile.AppendLine(PathToExclude.Replace('\\', '/').Replace(WorkspaceRootPath, ""));
}
}
FileReference.WriteAllText(FileReference.Combine(MasterProjectPath, ".ignore"), OutFile.ToString());
}
private void WriteWorkspaceIgnoreFileForPlugins(DirectoryReference PluginBaseDir, List<string> PathsToExclude)
{
if (DirectoryReference.Exists(PluginBaseDir))
{
foreach (DirectoryReference SubDir in DirectoryReference.EnumerateDirectories(PluginBaseDir, "*", SearchOption.TopDirectoryOnly))
{
string[] UPluginFiles = Directory.GetFiles(SubDir.ToString(), "*.uplugin");
if (UPluginFiles.Length == 1)
{
DirectoryReference PluginDir = SubDir;
GetExcludePathsCPP(PluginDir, PathsToExclude);
}
else
{
WriteWorkspaceIgnoreFileForPlugins(SubDir, PathsToExclude);
}
}
}
}
private void WriteWorkspaceFile()
{
JsonFile WorkspaceFile = new JsonFile();
WorkspaceFile.BeginRootObject();
{
WorkspaceFile.BeginArray("folders");
{
// Add the directory in which which the code-workspace file exists.
// This is also known as ${workspaceRoot}
WorkspaceFile.BeginObject();
{
string ProjectName = bForeignProject ? GameProjectName : "UE5";
WorkspaceFile.AddField("name", ProjectName);
WorkspaceFile.AddField("path", ".");
}
WorkspaceFile.EndObject();
// If this project is outside the engine folder, add the root engine directory
if (bIncludeEngineSource && bForeignProject)
{
WorkspaceFile.BeginObject();
{
WorkspaceFile.AddField("name", "UE5");
WorkspaceFile.AddField("path", MakeUnquotedPathString(UnrealBuildTool.RootDirectory, EPathType.Absolute));
}
WorkspaceFile.EndObject();
}
}
WorkspaceFile.EndArray();
}
WorkspaceFile.BeginObject("settings");
{
// disable autodetect for typescript files to workaround slowdown in vscode as a result of parsing all files
WorkspaceFile.AddField("typescript.tsc.autoDetect", "off");
}
WorkspaceFile.EndObject();
WorkspaceFile.BeginObject("extensions");
{
// extensions is a set of recommended extensions that a user should install.
// Adding this section aids discovery of extensions which are helpful to have installed for Unreal development.
WorkspaceFile.BeginArray("recommendations");
{
WorkspaceFile.AddUnnamedField("ms-vscode.cpptools");
WorkspaceFile.AddUnnamedField("ms-dotnettools.csharp");
// If the platform we run the generator on uses mono, there are additional debugging extensions to add.
if (Utils.IsRunningOnMono)
{
WorkspaceFile.AddUnnamedField("vadimcn.vscode-lldb");
WorkspaceFile.AddUnnamedField("ms-vscode.mono-debug");
}
}
WorkspaceFile.EndArray();
}
WorkspaceFile.EndObject();
WorkspaceFile.EndRootObject();
string WorkspaceName = bForeignProject ? GameProjectName : "UE5";
WorkspaceFile.Write(FileReference.Combine(MasterProjectPath, WorkspaceName + ".code-workspace"));
}
private void GetExcludePathsCPP(DirectoryReference BaseDir, List<string> PathsToExclude)
{
string[] DirWhiteList = { "Binaries", "Build", "Config", "Plugins", "Source", "Private", "Public", "Classes", "Resources" };
foreach (DirectoryReference SubDir in DirectoryReference.EnumerateDirectories(BaseDir, "*", SearchOption.TopDirectoryOnly))
{
if (Array.Find(DirWhiteList, Dir => Dir == SubDir.GetDirectoryName()) == null)
{
string NewSubDir = SubDir.ToString();
if (!PathsToExclude.Contains(NewSubDir))
{
PathsToExclude.Add(NewSubDir);
}
}
}
}
private void GetExcludePathsCSharp(string BaseDir, List<string> PathsToExclude)
{
string[] BlackList =
{
"obj",
"bin"
};
foreach (string BlackListDir in BlackList)
{
string ExcludePath = Path.Combine(BaseDir, BlackListDir);
if (!PathsToExclude.Contains(ExcludePath))
{
PathsToExclude.Add(ExcludePath);
}
}
}
}
}