Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudioCode/VSCodeProjectFileGenerator.cs
Joakim Lindqvist e7039d3d35 UBT and UAT now use .NET Core instead of Framework and Mono. This means that we use the same runtime on Windows, Linux and Mac. Further benefits including newer C# features and a lot of intresting features for the future around AOT and Tiered compilation.
Some behavior changes:
Output paths - Both tools are now output to a subdirectory of Binaries/Dotnet, I believe most hardcoded paths have been fixed up but there may be tools that will fail because of this.
UAT Plugin Building - As .NET Core does not support AppDomain unloading, how we build the plugins has changed quite a bit, these are now built before UAT is started rather then by UAT itself. If you just start UAT via RunUAT.bat/sh this should just continue to work.

#rb ben.marsh

[CL 14834347 by Joakim Lindqvist in ue5-main branch]
2020-12-02 06:57:13 -04:00

1250 lines
39 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Tools.DotNETCommon;
using System.Diagnostics;
using System.Linq;
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)
{
var BuildPlatform = UEBuildPlatform.GetBuildPlatform(Platform, true);
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("--output");
OutFile.AddUnnamedField(MakePathString(BuildProduct.OutputFile.Directory));
}
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", "UE4.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);
}
}
}
}
}