2022-05-25 17:54:39 -04:00
// 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 ;
using UnrealBuildBase ;
namespace UnrealBuildTool
{
class VSCodeProject : ProjectFile
{
public VSCodeProject ( FileReference InitFilePath , DirectoryReference BaseDir )
: base ( InitFilePath , BaseDir )
{
}
public override bool WriteProjectFile ( List < UnrealTargetPlatform > InPlatforms , List < UnrealTargetConfiguration > InConfigurations , PlatformProjectGeneratorCollection PlatformProjectGenerators )
{
return true ;
}
}
class VSCodeProjectFileGenerator : ProjectFileGenerator
{
private UnrealTargetPlatform HostPlatform = BuildHostPlatform . Current . Platform ;
private bool bForeignProject ;
private DirectoryReference ProjectRoot ;
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 ;
/// <summary>
/// Whether VS Code project generation should include debug configurations to allow attaching to already running processes
/// </summary>
[XmlConfigFile(Name = "AddDebugAttachConfig")]
private bool bAddDebugAttachConfig = false ;
/// <summary>
/// Whether VS Code project generation should include debug configurations to allow core dump debugging
/// </summary>
[XmlConfigFile(Name = "AddDebugCoreConfig")]
private bool bAddDebugCoreConfig = 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 = ProjectRoot ;
}
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 ( "/" , "\\" ) ;
}
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 , ProjectRoot ) ;
}
else
{
return MakeQuotedPathString ( InRef , bInAbsolute ? EPathType . Absolute : EPathType . Relative , ProjectRoot ) ;
}
}
public VSCodeProjectFileGenerator ( FileReference ? InOnlyGameProject )
: base ( InOnlyGameProject )
{
ProjectRoot = Unreal . RootDirectory ;
}
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 ( JsonWriter . EscapeString ( Value ) ) + "," ) ;
}
public void AddUnnamedField ( string Value )
{
Lines . Add ( TabString + Quoted ( JsonWriter . EscapeString ( 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 InPrimaryProjectDirectory , string InPrimaryProjectName , DirectoryReference InIntermediateProjectFilesPath )
{
}
public override bool ShouldGenerateIntelliSenseData ( )
{
return true ;
}
protected override ProjectFile AllocateProjectFile ( FileReference InitFilePath , DirectoryReference BaseDir )
{
return new VSCodeProject ( InitFilePath , BaseDir ) ;
}
protected override bool WritePrimaryProjectFile ( ProjectFile ? UBTProject , PlatformProjectGeneratorCollection PlatformProjectGenerators )
{
DirectoryReference VSCodeDir = DirectoryReference . Combine ( PrimaryProjectPath , ".vscode" ) ;
DirectoryReference . CreateDirectory ( VSCodeDir ) ;
bForeignProject = ! VSCodeDir . IsUnderDirectory ( ProjectRoot ) ;
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 ) ;
WriteTasksFile ( ProjectData , VSCodeDir ) ;
WriteLaunchFile ( ProjectData , VSCodeDir ) ;
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 VsCodeDirectory = DirectoryReference . Combine ( Unreal . RootDirectory , ".vscode" ) ;
WriteCppPropertiesFile ( VsCodeDirectory , ProjectData ) ;
}
return true ;
}
private class BuildTarget
{
public readonly string Name ;
public readonly TargetType Type ;
public readonly UnrealTargetPlatform Platform ;
public readonly UnrealTargetConfiguration Configuration ;
public readonly CppStandardVersion CppStandard ;
public readonly FileReference ? CompilerPath ;
public readonly DirectoryReference ? SysRootPath ;
public readonly Dictionary < DirectoryReference , string > ModuleCommandLines ;
public BuildTarget ( string InName , TargetType InType , UnrealTargetPlatform InPlatform , UnrealTargetConfiguration InConfiguration , CppStandardVersion InCppStandard , FileReference ? InCompilerPath , DirectoryReference ? InSysRootPath , Dictionary < DirectoryReference , string > InModulesCommandLines )
{
Name = InName ;
Type = InType ;
Platform = InPlatform ;
Configuration = InConfiguration ;
CppStandard = InCppStandard ;
CompilerPath = InCompilerPath ;
SysRootPath = InSysRootPath ;
ModuleCommandLines = InModulesCommandLines ;
}
public override string ToString ( )
{
return Name . ToString ( ) + " " + Type . ToString ( ) ;
}
}
protected override void AddTargetForIntellisense ( UEBuildTarget Target )
{
base . AddTargetForIntellisense ( Target ) ;
bool UsingClang = true ;
FileReference ? CompilerPath = null ;
DirectoryReference ? SysRootPath = null ;
if ( OperatingSystem . IsWindows ( ) )
{
VCEnvironment Environment = VCEnvironment . Create ( WindowsPlatform . GetDefaultCompiler ( null , Target . Rules . WindowsPlatform . Architecture ) , Target . Platform , Target . Rules . WindowsPlatform . Architecture , null , Target . Rules . WindowsPlatform . WindowsSdkVersion , null ) ;
CompilerPath = FileReference . FromString ( Environment . CompilerPath . FullName ) ;
UsingClang = false ;
}
else if ( OperatingSystem . IsLinux ( ) )
{
CompilerPath = FileReference . FromString ( LinuxCommon . WhichClang ( ) ) ;
string? InternalSDKPath = UEBuildPlatform . GetSDK ( UnrealTargetPlatform . Linux ) ? . GetInternalSDKPath ( ) ;
if ( ! string . IsNullOrEmpty ( InternalSDKPath ) )
{
SysRootPath = DirectoryReference . FromString ( InternalSDKPath ) ;
}
}
else if ( OperatingSystem . IsMacOS ( ) )
{
MacToolChainSettings Settings = new MacToolChainSettings ( false ) ;
CompilerPath = FileReference . FromString ( Settings . ToolchainDir + "clang++" ) ;
SysRootPath = DirectoryReference . FromString ( Settings . BaseSDKDir + "/MacOSX" + Settings . MacOSSDKVersion + ".sdk" ) ;
}
else
{
throw new Exception ( "Unknown platform " + HostPlatform . ToString ( ) ) ;
}
// 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 ( ) ;
foreach ( FileReference ForceIncludeFile in ForceIncludePaths )
{
CommandBuilder . AppendFormat ( "{0} \"{1}\" {2}" , UsingClang ? "-include" : "/FI" , ForceIncludeFile . FullName , Environment . NewLine ) ;
}
foreach ( string Definition in ModuleCompileEnvironment . Definitions )
{
CommandBuilder . AppendFormat ( "{0} \"{1}\" {2}" , UsingClang ? "-D" : "/D" , Definition , Environment . NewLine ) ;
}
foreach ( DirectoryReference IncludePath in ModuleCompileEnvironment . UserIncludePaths )
{
CommandBuilder . AppendFormat ( "{0} \"{1}\" {2}" , UsingClang ? "-I" : "/I" , IncludePath , Environment . NewLine ) ;
}
foreach ( DirectoryReference IncludePath in ModuleCompileEnvironment . SystemIncludePaths )
{
CommandBuilder . AppendFormat ( "{0} \"{1}\" {2}" , UsingClang ? "-I" : "/I" , IncludePath , Environment . NewLine ) ;
}
ModuleDirectoryToCompileCommand . Add ( Module . ModuleDirectory , CommandBuilder . ToString ( ) ) ;
}
}
BuildTargets . Add ( new BuildTarget ( Target . TargetName , Target . TargetType , Target . Platform , Target . Configuration , GlobalCompileEnvironment . CppStandard , CompilerPath , SysRootPath , 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 BuildProduct ( FileReference OutputFile )
{
this . OutputFile = OutputFile ;
}
}
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 Project ( string Name , ProjectFile SourceProject )
{
this . Name = Name ;
this . SourceProject = SourceProject ;
}
}
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 )
{
ProjectData ProjectData = new ProjectData ( ) ;
foreach ( ProjectFile Project in InProjects )
{
// Create new project record
ProjectData . Project NewProject = new ProjectData . Project ( Project . ProjectFilePath . GetFileNameWithoutExtension ( ) , Project ) ;
ProjectData . AllProjects . Add ( NewProject ) ;
// Add into the correct easy-access list
if ( Project is VSCodeProject )
{
foreach ( ProjectTarget Target in Project . ProjectTargets . OfType < ProjectTarget > ( ) )
{
UnrealTargetConfiguration [ ] Configs = ( UnrealTargetConfiguration [ ] ) 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 ) ;
}
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 ) )
{
NewTarget . BuildProducts . Add ( new ProjectData . BuildProduct ( GetExecutableFilename ( Project , Target , Platform , Config ) )
{
Platform = Platform ,
Config = Config ,
UProjectFile = Target . UnrealProjectFilePath ,
OutputType = ProjectData . EOutputType . Exe ,
CSharpInfo = null
} ) ;
}
}
}
}
}
ProjectData . NativeProjects . Add ( NewProject ) ;
}
else
{
VCSharpProjectFile VCSharpProject = ( VCSharpProjectFile ) Project ;
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 ( OutputFile )
{
Platform = HostPlatform ,
Config = Config ,
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" ) ;
{
HashSet < FileReference > AllSourceFiles = new HashSet < FileReference > ( ) ;
Dictionary < DirectoryReference , string > AllModuleCommandLines = new Dictionary < DirectoryReference , string > ( ) ;
FileReference ? CompilerPath = null ;
DirectoryReference ? SysRootPath = null ;
CppStandardVersion CppStandard = CppStandardVersion . Default ;
foreach ( ProjectData . Project Project in Projects . AllProjects )
{
AllSourceFiles . UnionWith ( Project . SourceProject . SourceFiles . Select ( x = > x . Reference ) ) ;
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 ;
string Name = string . Format ( "{0} {1} {2} {3} ({4})" , ProjectTarget . Name , ProjectTarget . Type , BuildTarget . Platform , BuildTarget . Configuration , Project . Name ) ;
WriteConfiguration ( Name , Project . Name , Project . SourceProject . SourceFiles . Select ( x = > x . Reference ) , BuildTarget . CppStandard , BuildTarget . CompilerPath ! , BuildTarget . SysRootPath , BuildTarget . ModuleCommandLines , OutFile , OutputDirectory ) ;
CompilerPath = BuildTarget . CompilerPath ;
foreach ( KeyValuePair < DirectoryReference , string > Pair in BuildTarget . ModuleCommandLines )
{
if ( ! AllModuleCommandLines . ContainsKey ( Pair . Key ) )
{
AllModuleCommandLines [ Pair . Key ] = Pair . Value ;
}
}
if ( BuildTarget . CppStandard > CppStandard )
{
CppStandard = BuildTarget . CppStandard ;
}
if ( BuildTarget . SysRootPath ! = null )
{
SysRootPath = BuildTarget . SysRootPath ;
}
}
}
string DefaultConfigName ;
if ( HostPlatform = = UnrealTargetPlatform . Linux )
{
DefaultConfigName = "Linux" ;
}
else if ( HostPlatform = = UnrealTargetPlatform . Mac )
{
DefaultConfigName = "Mac" ;
}
else
{
DefaultConfigName = "Win32" ;
}
WriteConfiguration ( DefaultConfigName , "Default" , AllSourceFiles , CppStandard , CompilerPath ! , SysRootPath , AllModuleCommandLines , OutFile , OutputDirectory ) ;
}
OutFile . EndArray ( ) ;
}
OutFile . EndRootObject ( ) ;
OutFile . Write ( FileReference . Combine ( OutputDirectory , "c_cpp_properties.json" ) ) ;
}
private void WriteConfiguration ( string Name , string ProjectName , IEnumerable < FileReference > SourceFiles , CppStandardVersion CppStandard , FileReference CompilerPath , DirectoryReference ? SysRootPath , Dictionary < DirectoryReference , string > ModuleCommandLines , JsonFile OutFile , DirectoryReference OutputDirectory )
{
OutFile . BeginObject ( ) ;
OutFile . AddField ( "name" , Name ) ;
OutFile . AddField ( "compilerPath" , CompilerPath . FullName ) ;
if ( HostPlatform = = UnrealTargetPlatform . Mac )
{
string SysRoot = SysRootPath ! = null ? SysRootPath . FullName : "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" ;
OutFile . BeginArray ( "compilerArgs" ) ;
{
OutFile . AddUnnamedField ( "-isysroot" ) ;
OutFile . AddUnnamedField ( SysRoot ) ;
}
OutFile . EndArray ( ) ;
OutFile . BeginArray ( "macFrameworkPath" ) ;
{
OutFile . AddUnnamedField ( SysRoot + "/System/Library/Frameworks" ) ;
}
OutFile . EndArray ( ) ;
}
else if ( SysRootPath ! = null )
{
OutFile . BeginArray ( "compilerArgs" ) ;
{
OutFile . AddUnnamedField ( "-isysroot" ) ;
OutFile . AddUnnamedField ( SysRootPath . FullName ) ;
}
OutFile . EndArray ( ) ;
}
switch ( CppStandard )
{
case CppStandardVersion . Cpp14 :
OutFile . AddField ( "cStandard" , "c11" ) ;
OutFile . AddField ( "cppStandard" , "c++14" ) ;
break ;
case CppStandardVersion . Cpp17 :
OutFile . AddField ( "cStandard" , "c17" ) ;
OutFile . AddField ( "cppStandard" , "c++17" ) ;
break ;
case CppStandardVersion . Cpp20 :
case CppStandardVersion . Latest :
OutFile . AddField ( "cStandard" , "c17" ) ;
OutFile . AddField ( "cppStandard" , "c++20" ) ;
break ;
default :
throw new BuildException ( $"Unsupported C++ standard type set: {CppStandard}" ) ;
}
if ( HostPlatform = = UnrealTargetPlatform . Win64 )
{
OutFile . AddField ( "intelliSenseMode" , "msvc-x64" ) ;
}
else
{
OutFile . AddField ( "intelliSenseMode" , "clang-x64" ) ;
}
FileReference CompileCommands = FileReference . Combine ( OutputDirectory , string . Format ( "compileCommands_{0}.json" , ProjectName ) ) ;
WriteCompileCommands ( CompileCommands , SourceFiles , CompilerPath , ModuleCommandLines ) ;
OutFile . AddField ( "compileCommands" , MakePathString ( CompileCommands , bInAbsolute : true , bForceSkipQuotes : true ) ) ;
OutFile . EndObject ( ) ;
}
private void WriteNativeTaskDeployAndroid ( ProjectData . Project InProject , JsonFile OutFile , ProjectData . Target Target , ProjectData . BuildProduct BuildProduct )
{
if ( BuildProduct . UProjectFile = = null )
{
return ;
}
string [ ] ConfigTypes = new string [ ] { "Cook+Deploy" , "Cook" , "Deploy" } ;
foreach ( string ConfigType in ConfigTypes )
{
OutFile . BeginObject ( ) ;
{
string TaskName = String . Format ( "{0} {1} {2} {3}" , Target . Name , BuildProduct . Platform . ToString ( ) , BuildProduct . Config , ConfigType ) ;
OutFile . AddField ( "label" , TaskName ) ;
OutFile . AddField ( "group" , "build" ) ;
if ( HostPlatform = = UnrealTargetPlatform . Win64 )
{
OutFile . AddField ( "command" , MakePathString ( FileReference . Combine ( ProjectRoot , "Engine" , "Build" , "BatchFiles" , "RunUAT.bat" ) ) ) ;
}
else
{
OutFile . AddField ( "command" , MakePathString ( FileReference . Combine ( ProjectRoot , "Engine" , "Build" , "BatchFiles" , "RunUAT.sh" ) ) ) ;
}
OutFile . BeginArray ( "args" ) ;
{
OutFile . AddUnnamedField ( "BuildCookRun" ) ;
OutFile . AddUnnamedField ( "-ScriptsForProject=" + BuildProduct . UProjectFile . ToNormalizedPath ( ) ) ;
OutFile . AddUnnamedField ( "-Project=" + BuildProduct . UProjectFile . ToNormalizedPath ( ) ) ;
OutFile . AddUnnamedField ( "-noP4" ) ;
OutFile . AddUnnamedField ( String . Format ( "-ClientConfig={0}" , BuildProduct . Config . ToString ( ) ) ) ;
OutFile . AddUnnamedField ( String . Format ( "-ServerConfig={0}" , BuildProduct . Config . ToString ( ) ) ) ;
OutFile . AddUnnamedField ( "-NoCompileEditor" ) ;
OutFile . AddUnnamedField ( "-utf8output" ) ;
OutFile . AddUnnamedField ( String . Format ( "-Platform={0}" , BuildProduct . Platform . ToString ( ) ) ) ;
OutFile . AddUnnamedField ( String . Format ( "-TargetPlatform={0}" , BuildProduct . Platform . ToString ( ) ) ) ;
OutFile . AddUnnamedField ( "-ini:Game:[/Script/UnrealEd.ProjectPackagingSettings]:BlueprintNativizationMethod=Disabled" ) ;
OutFile . AddUnnamedField ( "-Compressed" ) ;
OutFile . AddUnnamedField ( "-IterativeCooking" ) ;
OutFile . AddUnnamedField ( "-IterativeDeploy" ) ;
switch ( ConfigType )
{
case "Cook+Deploy" :
{
OutFile . AddUnnamedField ( "-Cook" ) ;
OutFile . AddUnnamedField ( "-Stage" ) ;
OutFile . AddUnnamedField ( "-Deploy" ) ;
break ;
}
case "Cook" :
{
OutFile . AddUnnamedField ( "-Cook" ) ;
break ;
}
case "Deploy" :
{
OutFile . AddUnnamedField ( "-DeploySoToDevice" ) ;
OutFile . AddUnnamedField ( "-SkipCook" ) ;
OutFile . AddUnnamedField ( "-Stage" ) ;
OutFile . AddUnnamedField ( "-Deploy" ) ;
break ;
}
}
}
OutFile . EndArray ( ) ;
OutFile . BeginArray ( "dependsOn" ) ;
{
switch ( ConfigType )
{
case "Cook+Deploy" :
case "Cook" :
{
OutFile . AddUnnamedField ( String . Format ( "{0}Editor {1} Development Build" , Target . Name , HostPlatform . ToString ( ) ) ) ;
OutFile . AddUnnamedField ( String . Format ( "{0} {1} {2} Build" , Target . Name , BuildProduct . Platform . ToString ( ) , BuildProduct . Config ) ) ;
break ;
}
default :
{
OutFile . AddUnnamedField ( String . Format ( "{0} {1} {2} Build" , Target . Name , BuildProduct . Platform . ToString ( ) , BuildProduct . Config ) ) ;
break ;
}
}
}
OutFile . EndArray ( ) ;
OutFile . AddField ( "type" , "shell" ) ;
OutFile . BeginObject ( "options" ) ;
{
OutFile . AddField ( "cwd" , MakeUnquotedPathString ( ProjectRoot , EPathType . Absolute ) ) ;
}
OutFile . EndObject ( ) ;
}
OutFile . EndObject ( ) ;
}
}
private void WriteCompileCommands ( FileReference CompileCommandsFile , IEnumerable < FileReference > SourceFiles ,
FileReference CompilerPath , 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 ( ) ;
DirectoryReference ResponseFileDir = DirectoryReference . Combine ( CompileCommandsFile . Directory , CompileCommandsFile . GetFileNameWithoutExtension ( ) ) ;
DirectoryReference . CreateDirectory ( ResponseFileDir ) ;
Dictionary < DirectoryReference , FileReference ? > DirectoryToResponseFile = new Dictionary < DirectoryReference , FileReference ? > ( ) ;
foreach ( KeyValuePair < DirectoryReference , string > Pair in ModuleCommandLines )
{
FileReference ResponseFile = FileReference . Combine ( ResponseFileDir , String . Format ( "{0}.{1}.rsp" , Pair . Key . GetDirectoryName ( ) , DirectoryToResponseFile . Count ) ) ;
FileReference . WriteAllText ( ResponseFile , Pair . Value ) ;
DirectoryToResponseFile . Add ( Pair . Key , ResponseFile ) ;
}
foreach ( FileReference File in SourceFiles . OrderBy ( x = > x . FullName ) )
{
DirectoryReference Directory = File . Directory ;
FileReference ? ResponseFile = null ;
if ( ! DirectoryToResponseFile . TryGetValue ( Directory , out ResponseFile ) )
{
for ( DirectoryReference ? ParentDir = Directory ; ParentDir ! = null & & ParentDir ! = Unreal . RootDirectory ; ParentDir = ParentDir . ParentDirectory )
{
if ( DirectoryToResponseFile . TryGetValue ( ParentDir , out ResponseFile ) )
{
break ;
}
}
DirectoryToResponseFile [ Directory ] = ResponseFile ;
}
if ( ResponseFile = = 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 , bInAbsolute : true , bForceSkipQuotes : true ) ) ;
Writer . WriteArrayStart ( "arguments" ) ;
Writer . WriteValue ( MakePathString ( CompilerPath , bInAbsolute : true , bForceSkipQuotes : true ) ) ;
Writer . WriteValue ( $"@{MakePathString(ResponseFile, bInAbsolute: true, bForceSkipQuotes: true)}" ) ;
Writer . WriteArrayEnd ( ) ;
Writer . WriteValue ( "directory" , Unreal . 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 ( ProjectRoot , "Engine" , "Build" , "BatchFiles" , Command + ".bat" ) ) ) ;
CleanParam = null ;
}
else
{
OutFile . AddField ( "command" , MakePathString ( FileReference . Combine ( ProjectRoot , "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 ( ProjectRoot , EPathType . Absolute ) ) ;
}
OutFile . EndObject ( ) ;
}
OutFile . EndObject ( ) ;
if ( BuildProduct . Platform = = UnrealTargetPlatform . Android & & BaseCommand . Equals ( "Build" ) )
{
WriteNativeTaskDeployAndroid ( InProject , OutFile , Target , BuildProduct ) ;
}
}
}
}
}
private void WriteCSharpTask ( ProjectData . Project InProject , JsonFile OutFile )
{
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 ( ! RuntimePlatform . IsWindows )
{
OutFile . AddField ( "command" , MakePathString ( FileReference . Combine ( ProjectRoot , "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 ( ProjectRoot , EPathType . Absolute ) ) ;
}
OutFile . EndObject ( ) ;
OutFile . EndObject ( ) ;
}
}
}
}
private void WriteTasksFile ( ProjectData ProjectData , DirectoryReference VSCodeDir )
{
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 = Unreal . 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 WriteNativeLaunchConfigAndroidOculus ( ProjectData . Project InProject , JsonFile OutFile , ProjectData . Target Target , ProjectData . BuildProduct BuildProduct )
{
ConfigHierarchy Ini = ConfigCache . ReadHierarchy ( ConfigHierarchyType . Engine , DirectoryReference . FromFile ( BuildProduct . UProjectFile ) , BuildProduct . Platform ) ;
List < string > ? OculusMobileDevices ;
bool result = Ini . GetArray ( "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "PackageForOculusMobile" , out OculusMobileDevices ) ;
// Check if packaging for oculus
if ( ! result | | OculusMobileDevices = = null | | OculusMobileDevices . Count = = 0 )
{
return ;
}
// Get package name
string PackageName ;
Ini . GetString ( "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "PackageName" , out PackageName ) ;
if ( PackageName . Contains ( "[PROJECT]" ) )
{
// project name must start with a letter
if ( ! char . IsLetter ( Target . Name [ 0 ] ) )
{
Trace . TraceWarning ( "Package name segments must all start with a letter. Please replace [PROJECT] with a valid name" ) ;
}
string ProjectName = Target . Name ;
// hyphens not allowed so change them to underscores in project name
if ( ProjectName . Contains ( "-" ) )
{
Trace . TraceWarning ( "Project name contained hyphens, converted to underscore" ) ;
ProjectName = ProjectName . Replace ( "-" , "_" ) ;
}
// check for special characters
for ( int Index = 0 ; Index < ProjectName . Length ; Index + + )
{
char c = ProjectName [ Index ] ;
if ( c ! = '.' & & c ! = '_' & & ! char . IsLetterOrDigit ( c ) )
{
Trace . TraceWarning ( "Project name contains illegal characters (only letters, numbers, and underscore allowed); please replace [PROJECT] with a valid name" ) ;
ProjectName . Replace ( c , '_' ) ;
}
}
PackageName = PackageName . Replace ( "[PROJECT]" , ProjectName ) ;
}
// Get store version
int StoreVersion = 1 ;
int StoreVersionArm64 = 1 ;
int StoreVersionArmV7 = 1 ;
int StoreVersionOffsetArm64 = 0 ;
int StoreVersionOffsetArmV7 = 0 ;
Ini . GetInt32 ( "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "StoreVersion" , out StoreVersion ) ;
Ini . GetInt32 ( "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "StoreVersionOffsetArm64" , out StoreVersionOffsetArm64 ) ;
Ini . GetInt32 ( "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "StoreVersionOffsetArmV7" , out StoreVersionOffsetArmV7 ) ;
StoreVersionArm64 = StoreVersion + StoreVersionOffsetArm64 ;
StoreVersionArmV7 = StoreVersion + StoreVersionOffsetArmV7 ;
DirectoryReference SymbolPathArm64 = DirectoryReference . Combine (
BuildProduct . OutputFile . Directory ,
Target . Name + "_Symbols_v" + StoreVersionArm64 . ToString ( ) ,
Target . Name + "-arm64" ) ;
DirectoryReference SymbolPathArmV7 = DirectoryReference . Combine (
BuildProduct . OutputFile . Directory ,
Target . Name + "_Symbols_v" + StoreVersionArmV7 . ToString ( ) ,
Target . Name + "-armv7" ) ;
string LaunchTaskName = String . Format ( "{0} {1} {2} Deploy" , Target . Name , BuildProduct . Platform , BuildProduct . Config ) ;
List < string > ConfigTypes = new List < string > ( ) ;
ConfigTypes . Add ( "Launch" ) ;
if ( BuildProduct . Config = = UnrealTargetConfiguration . Development )
{
ConfigTypes . Add ( "Attach" ) ;
}
foreach ( string ConfigType in ConfigTypes )
{
OutFile . BeginObject ( ) ;
{
OutFile . AddField ( "name" , Target . Name + " Oculus (" + BuildProduct . Config . ToString ( ) + ") " + ConfigType ) ;
OutFile . AddField ( "request" , ConfigType . ToLowerInvariant ( ) ) ;
if ( ConfigType = = "Launch" )
{
OutFile . AddField ( "preLaunchTask" , LaunchTaskName ) ;
}
OutFile . AddField ( "type" , "fb-lldb" ) ;
OutFile . BeginObject ( "android" ) ;
{
OutFile . BeginObject ( "application" ) ;
{
OutFile . AddField ( "package" , PackageName ) ;
OutFile . AddField ( "activity" , "com.epicgames.unreal.GameActivity" ) ;
}
OutFile . EndObject ( ) ;
OutFile . BeginObject ( "lldbConfig" ) ;
{
OutFile . BeginArray ( "librarySearchPaths" ) ;
OutFile . AddUnnamedField ( "\\\"" + SymbolPathArm64 . ToNormalizedPath ( ) + "\\\"" ) ;
OutFile . AddUnnamedField ( "\\\"" + SymbolPathArmV7 . ToNormalizedPath ( ) + "\\\"" ) ;
OutFile . EndArray ( ) ;
OutFile . BeginArray ( "lldbPreTargetCreateCommands" ) ;
FileReference DataFormatters = FileReference . Combine ( ProjectRoot , "Engine" , "Extras" , "LLDBDataFormatters" , "UEDataFormatters_2ByteChars.py" ) ;
OutFile . AddUnnamedField ( "command script import \\\"" + DataFormatters . FullName . Replace ( "\\" , "/" ) + "\\\"" ) ;
OutFile . EndArray ( ) ;
OutFile . BeginArray ( "lldbPostTargetCreateCommands" ) ;
//on Oculus devices, we use SIGILL for input redirection, so the debugger shouldn't catch it.
OutFile . AddUnnamedField ( "process handle --pass true --stop false --notify true SIGILL" ) ;
OutFile . EndArray ( ) ;
}
OutFile . EndObject ( ) ;
}
OutFile . EndObject ( ) ;
}
OutFile . EndObject ( ) ;
}
}
private void WriteNativeLaunchConfig ( ProjectData . Project InProject , JsonFile OutFile , ProjectData . Target Target , ProjectData . BuildProduct BuildProduct )
{
bool bIsLinux = BuildProduct . Platform = = UnrealTargetPlatform . Linux ;
List < string > Types = new List < string > ( ) ;
Types . Add ( "Launch" ) ;
if ( bAddDebugAttachConfig & & bIsLinux )
{
Types . Add ( "Attach" ) ;
}
if ( bAddDebugCoreConfig & & bIsLinux )
{
Types . Add ( "Debug Core" ) ;
}
string LaunchTaskName = String . Format ( "{0} {1} {2} Build" , Target . Name , BuildProduct . Platform , BuildProduct . Config ) ;
foreach ( string Type in Types )
{
OutFile . BeginObject ( ) ;
{
OutFile . AddField ( "name" , Type + " " + Target . Name + " (" + BuildProduct . Config . ToString ( ) + ")" ) ;
OutFile . AddField ( "request" , ( Type = = "Attach" ) ? "attach" : "launch" ) ;
OutFile . AddField ( "program" , MakeUnquotedPathString ( BuildProduct . OutputFile , EPathType . Absolute ) ) ;
switch ( Type )
{
case "Launch" :
OutFile . AddField ( "preLaunchTask" , LaunchTaskName ) ;
break ;
case "Debug Core" :
OutFile . AddField ( "coreDumpPath" , "${input:coreFileName}" ) ;
break ;
case "Attach" :
OutFile . AddField ( "processId" , "${command:pickProcess}" ) ;
break ;
}
if ( Type ! = "Attach" )
{
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 ( ProjectRoot , EPathType . Absolute ) ) ;
if ( HostPlatform = = UnrealTargetPlatform . Win64 )
{
OutFile . AddField ( "stopAtEntry" , false ) ;
OutFile . AddField ( "console" , "externalTerminal" ) ;
OutFile . AddField ( "type" , "cppvsdbg" ) ;
OutFile . AddField ( "visualizerFile" , MakeUnquotedPathString ( FileReference . Combine ( ProjectRoot , "Engine" , "Extras" , "VisualStudioDebugging" , "Unreal.natvis" ) , EPathType . Absolute ) ) ;
}
else if ( HostPlatform = = UnrealTargetPlatform . Linux )
{
OutFile . AddField ( "type" , "cppdbg" ) ;
OutFile . AddField ( "visualizerFile" , MakeUnquotedPathString ( FileReference . Combine ( ProjectRoot , "Engine" , "Extras" , "VisualStudioDebugging" , "Unreal.natvis" ) , EPathType . Absolute ) ) ;
OutFile . AddField ( "showDisplayString" , true ) ;
}
else
{
OutFile . AddField ( "type" , "lldb" ) ;
}
if ( UnrealBuildTool . OriginalCompilationRootDirectory ! = ProjectRoot )
{
OutFile . BeginObject ( "sourceFileMap" ) ;
{
OutFile . AddField (
MakeUnquotedPathString ( UnrealBuildTool . OriginalCompilationRootDirectory , EPathType . Absolute ) ,
MakeUnquotedPathString ( ProjectRoot , EPathType . Absolute ) ) ;
}
OutFile . EndObject ( ) ;
}
}
OutFile . EndObject ( ) ;
}
}
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 )
{
WriteNativeLaunchConfig ( InProject , OutFile , Target , BuildProduct ) ;
}
else if ( BuildProduct . Platform = = UnrealTargetPlatform . Android )
{
WriteNativeLaunchConfigAndroidOculus ( InProject , OutFile , Target , BuildProduct ) ;
}
}
}
}
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 = ProjectRoot ;
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 )
{
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 , DirectoryReference VSCodeDir )
{
JsonFile OutFile = new JsonFile ( ) ;
OutFile . BeginRootObject ( ) ;
{
OutFile . AddField ( "version" , "0.2.0" ) ;
if ( bAddDebugCoreConfig )
{
OutFile . BeginArray ( "inputs" ) ;
OutFile . BeginObject ( ) ;
OutFile . AddField ( "id" , "coreFileName" ) ;
OutFile . AddField ( "type" , "command" ) ;
OutFile . AddField ( "command" , "filePicker.pick" ) ;
OutFile . BeginObject ( "args" ) ;
OutFile . AddField ( "masks" , "core*" ) ;
OutFile . BeginObject ( "display" ) ;
OutFile . AddField ( "type" , "fileRelativePath" ) ;
OutFile . AddField ( "detail" , "filePath" ) ;
OutFile . EndObject ( ) ;
OutFile . AddField ( "output" , "filePath" ) ;
OutFile . EndObject ( ) ;
OutFile . EndObject ( ) ;
OutFile . EndArray ( ) ;
}
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 ( bGeneratingGameProjectFiles )
{
Args . Add ( "-project=" + MakeUnquotedPathString ( OnlyGameProject ! , EPathType . Absolute ) ) ;
Args . Add ( "-game" ) ;
}
if ( bIncludeEngineSource )
{
Args . Add ( "-engine" ) ;
}
if ( bIncludeDotNetPrograms )
{
Args . Add ( "-dotnet" ) ;
PreLaunchTask = "UnrealBuildTool " + HostPlatform . ToString ( ) + " Development Build" ;
}
FileReference RunUbtPath = FileReference . Combine ( ProjectRoot , "Engine" , "Build" , "BatchFiles" , "RunUBT.bat" ) ;
WriteSingleCSharpLaunchConfig (
OutFile ,
"Generate Project Files" ,
PreLaunchTask ,
RunUbtPath ,
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 . OfType < ProjectTarget > ( ) )
{
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 : Unreal . 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 ( PrimaryProjectPath , ".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 ( Unreal . 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 ( ! RuntimePlatform . IsWindows )
{
WorkspaceFile . AddUnnamedField ( "vadimcn.vscode-lldb" ) ;
WorkspaceFile . AddUnnamedField ( "ms-vscode.mono-debug" ) ;
WorkspaceFile . AddUnnamedField ( "dfarley1.file-picker" ) ;
}
}
WorkspaceFile . EndArray ( ) ;
}
WorkspaceFile . EndObject ( ) ;
WorkspaceFile . EndRootObject ( ) ;
string? WorkspaceName = bForeignProject ? GameProjectName : "UE5" ;
WorkspaceFile . Write ( FileReference . Combine ( PrimaryProjectPath , WorkspaceName + ".code-workspace" ) ) ;
}
private void GetExcludePathsCPP ( DirectoryReference BaseDir , List < string > PathsToExclude )
{
string [ ] DirAllowList = { "Binaries" , "Build" , "Config" , "Plugins" , "Source" , "Private" , "Public" , "Internal" , "Classes" , "Resources" } ;
foreach ( DirectoryReference SubDir in DirectoryReference . EnumerateDirectories ( BaseDir , "*" , SearchOption . TopDirectoryOnly ) )
{
if ( Array . Find ( DirAllowList , 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 [ ] DenyList =
{
"obj" ,
"bin"
} ;
foreach ( string DenyListDir in DenyList )
{
string ExcludePath = Path . Combine ( BaseDir , DenyListDir ) ;
if ( ! PathsToExclude . Contains ( ExcludePath ) )
{
PathsToExclude . Add ( ExcludePath ) ;
}
}
}
}
}