2022-06-24 17:41:49 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Security.Cryptography ;
using System.Text ;
2023-05-30 18:38:07 -04:00
using System.Xml ;
2022-06-24 17:41:49 -04:00
using System.Xml.Linq ;
using EpicGames.Core ;
using Microsoft.Extensions.Logging ;
2023-05-30 18:38:07 -04:00
using UnrealBuildBase ;
2022-06-24 17:41:49 -04:00
namespace UnrealBuildTool
{
enum VCProjectFileFormat
{
Default , // Default to the best installed version, but allow SDKs to override
VisualStudio2022 ,
}
class VCProjectFileSettings
{
/// <summary>
/// The version of Visual Studio to generate project files for.
/// </summary>
[XmlConfigFile(Category = "VCProjectFileGenerator", Name = "Version")]
public VCProjectFileFormat ProjectFileFormat = VCProjectFileFormat . Default ;
/// <summary>
/// Puts the most common include paths in the IncludePath property in the MSBuild project. This significantly reduces Visual Studio
/// memory usage (measured 1.1GB -> 500mb), but seems to be causing issues with Visual Assist. Value here specifies maximum length
2022-06-27 09:55:34 -04:00
/// of the include path list in KB.
2022-06-24 17:41:49 -04:00
/// </summary>
[XmlConfigFile(Category = "VCProjectFileGenerator")]
public int MaxSharedIncludePaths = 24 * 1024 ;
/// <summary>
/// Semi-colon separated list of paths that should not be added to the projects include paths. Useful for omitting third-party headers
/// (e.g ThirdParty/WebRTC) from intellisense suggestions and reducing memory footprints.
/// </summary>
[XmlConfigFile(Category = "VCProjectFileGenerator")]
public string ExcludedIncludePaths = "" ;
/// <summary>
/// Semi-colon separated list of paths that should not be added to the projects. Useful for omitting third-party files
/// (e.g ThirdParty/WebRTC) from intellisense suggestions and reducing memory footprints.
/// </summary>
[XmlConfigFile(Category = "VCProjectFileGenerator")]
public string ExcludedFilePaths = "" ;
/// <summary>
/// Whether to write a solution option (suo) file for the sln.
/// </summary>
[XmlConfigFile(Category = "BuildConfiguration")]
public bool bWriteSolutionOptionFile = true ;
2022-08-26 16:33:56 -04:00
/// <summary>
/// Whether to write a .vsconfig file next to the sln to suggest components to install.
/// </summary>
[XmlConfigFile(Category = "BuildConfiguration")]
public bool bVsConfigFile = true ;
2022-06-24 17:41:49 -04:00
/// <summary>
/// Forces UBT to be built in debug configuration, regardless of the solution configuration
/// </summary>
[XmlConfigFile(Category = "VCProjectFileGenerator")]
public bool bBuildUBTInDebug = false ;
/// <summary>
/// Whether to add the -FastPDB option to build command lines by default.
/// </summary>
[XmlConfigFile(Category = "BuildConfiguration")]
public bool bAddFastPDBToProjects = false ;
/// <summary>
/// Whether to generate per-file intellisense data.
/// </summary>
[XmlConfigFile(Category = "BuildConfiguration")]
public bool bUsePerFileIntellisense = true ;
/// <summary>
/// Whether to include a dependency on ShaderCompileWorker when generating project files for the editor.
/// </summary>
[XmlConfigFile(Category = "BuildConfiguration")]
public bool bEditorDependsOnShaderCompileWorker = true ;
/// <summary>
/// Whether to include a dependency on LiveCodingConsole when building targets that support live coding.
/// </summary>
[XmlConfigFile(Category = "VCProjectFileGenerator")]
public bool bBuildLiveCodingConsole = false ;
2024-01-25 05:43:25 -05:00
/// <summary>
/// Whether to generate a project file for each individual target, and not include e.g. Editor/Client/Server in the Configuration.
/// </summary>
[XmlConfigFile(Category = "VCProjectFileGenerator")]
public bool bMakeProjectPerTarget = false ;
2022-06-24 17:41:49 -04:00
}
/// <summary>
/// Visual C++ project file generator implementation
/// </summary>
class VCProjectFileGenerator : ProjectFileGenerator
{
/// <summary>
/// The settings object
/// </summary>
protected VCProjectFileSettings Settings = new VCProjectFileSettings ( ) ;
2024-01-25 05:43:25 -05:00
/// <summary>
/// Set to true to enable a project for each target, and do not put the target type into the configuration
/// </summary>
protected override bool bMakeProjectPerTarget = > Settings . bMakeProjectPerTarget ;
2022-06-24 17:41:49 -04:00
/// <summary>
/// Override for the build tool to use in generated projects. If the compiler version is specified on the command line, we use the same argument on the
/// command line for generated projects.
/// </summary>
string? BuildToolOverride ;
/// <summary>
/// Default constructor
/// </summary>
/// <param name="InOnlyGameProject">The single project to generate project files for, or null</param>
/// <param name="InProjectFileFormat">Override the project file format to use</param>
/// <param name="InArguments">Additional command line arguments</param>
public VCProjectFileGenerator ( FileReference ? InOnlyGameProject , VCProjectFileFormat InProjectFileFormat , CommandLineArguments InArguments )
: base ( InOnlyGameProject )
{
XmlConfig . ApplyTo ( Settings ) ;
if ( InProjectFileFormat ! = VCProjectFileFormat . Default )
{
Settings . ProjectFileFormat = InProjectFileFormat ;
}
2024-01-26 17:21:00 -05:00
if ( InArguments . HasOption ( "-2022" ) )
2022-06-24 17:41:49 -04:00
{
BuildToolOverride = "-2022" ;
}
2022-08-29 18:18:18 -04:00
// Allow generating the solution even if the only installed toolchain is banned.
MicrosoftPlatformSDK . IgnoreToolchainErrors = true ;
2022-06-24 17:41:49 -04:00
}
public override string [ ] GetTargetArguments ( string [ ] Arguments )
{
2023-05-30 18:59:32 -04:00
return Arguments . Where ( s = > String . Equals ( s , BuildToolOverride , StringComparison . InvariantCultureIgnoreCase ) ) . ToArray ( ) ;
2022-06-24 17:41:49 -04:00
}
/// File extension for project files we'll be generating (e.g. ".vcxproj")
2023-05-30 18:59:32 -04:00
public override string ProjectFileExtension = > ".vcxproj" ;
2022-06-24 17:41:49 -04:00
/// <summary>
/// </summary>
public override void CleanProjectFiles ( DirectoryReference InPrimaryProjectDirectory , string InPrimaryProjectName , DirectoryReference InIntermediateProjectFilesDirectory , ILogger Logger )
{
FileReference PrimaryProjectFile = FileReference . Combine ( InPrimaryProjectDirectory , InPrimaryProjectName ) ;
FileReference PrimaryProjDeleteFilename = PrimaryProjectFile + ".sln" ;
if ( FileReference . Exists ( PrimaryProjDeleteFilename ) )
{
FileReference . Delete ( PrimaryProjDeleteFilename ) ;
}
PrimaryProjDeleteFilename = PrimaryProjectFile + ".sdf" ;
if ( FileReference . Exists ( PrimaryProjDeleteFilename ) )
{
FileReference . Delete ( PrimaryProjDeleteFilename ) ;
}
PrimaryProjDeleteFilename = PrimaryProjectFile + ".suo" ;
if ( FileReference . Exists ( PrimaryProjDeleteFilename ) )
{
FileReference . Delete ( PrimaryProjDeleteFilename ) ;
}
PrimaryProjDeleteFilename = PrimaryProjectFile + ".v11.suo" ;
if ( FileReference . Exists ( PrimaryProjDeleteFilename ) )
{
FileReference . Delete ( PrimaryProjDeleteFilename ) ;
}
PrimaryProjDeleteFilename = PrimaryProjectFile + ".v12.suo" ;
if ( FileReference . Exists ( PrimaryProjDeleteFilename ) )
{
FileReference . Delete ( PrimaryProjDeleteFilename ) ;
}
2022-08-26 16:33:56 -04:00
PrimaryProjDeleteFilename = FileReference . Combine ( InPrimaryProjectDirectory , ".vsconfig" ) ;
if ( FileReference . Exists ( PrimaryProjDeleteFilename ) )
{
FileReference . Delete ( PrimaryProjDeleteFilename ) ;
}
2022-06-24 17:41:49 -04:00
// Delete the project files folder
if ( DirectoryReference . Exists ( InIntermediateProjectFilesDirectory ) )
{
try
{
DirectoryReference . Delete ( InIntermediateProjectFilesDirectory , true ) ;
}
catch ( Exception Ex )
{
Logger . LogInformation ( "Error while trying to clean project files path {InIntermediateProjectFilesDirectory}. Ignored." , InIntermediateProjectFilesDirectory ) ;
Logger . LogInformation ( "\t{Message}" , Ex . Message ) ;
}
}
}
/// <summary>
/// Allocates a generator-specific project file object
/// </summary>
/// <param name="InitFilePath">Path to the project file</param>
/// <param name="BaseDir">The base directory for files within this project</param>
/// <returns>The newly allocated project file object</returns>
protected override ProjectFile AllocateProjectFile ( FileReference InitFilePath , DirectoryReference BaseDir )
{
2023-02-17 12:53:28 -05:00
return new VCProjectFile ( InitFilePath , BaseDir , Settings . ProjectFileFormat , bUsePrecompiled , bMakeProjectPerTarget , BuildToolOverride , Settings ) ;
2022-06-24 17:41:49 -04:00
}
/// "4.0", "12.0", or "14.0", etc...
2023-05-30 18:59:32 -04:00
public static string GetProjectFileToolVersionString ( VCProjectFileFormat ProjectFileFormat )
2022-06-24 17:41:49 -04:00
{
switch ( ProjectFileFormat )
{
case VCProjectFileFormat . VisualStudio2022 :
return "17.0" ;
}
2023-05-30 18:59:32 -04:00
return String . Empty ;
2022-06-24 17:41:49 -04:00
}
/// for instance: <PlatformToolset>v110</PlatformToolset>
2023-05-30 18:59:32 -04:00
public static string GetProjectFilePlatformToolsetVersionString ( VCProjectFileFormat ProjectFileFormat )
2022-06-24 17:41:49 -04:00
{
switch ( ProjectFileFormat )
{
case VCProjectFileFormat . VisualStudio2022 :
return "v143" ;
}
2023-05-30 18:59:32 -04:00
return String . Empty ;
2022-06-24 17:41:49 -04:00
}
2023-06-21 20:04:42 -04:00
public static WindowsCompiler GetCompilerForIntellisense ( VCProjectFileFormat ProjectFileFormat )
{
switch ( ProjectFileFormat )
{
case VCProjectFileFormat . VisualStudio2022 :
return WindowsCompiler . VisualStudio2022 ;
}
2024-01-26 17:21:00 -05:00
return WindowsCompiler . VisualStudio2022 ;
2023-06-21 20:04:42 -04:00
}
2023-05-30 18:59:32 -04:00
public static void AppendPlatformToolsetProperty ( StringBuilder VCProjectFileContent , VCProjectFileFormat ProjectFileFormat )
2022-06-24 17:41:49 -04:00
{
string ToolVersionString = GetProjectFileToolVersionString ( ProjectFileFormat ) ;
string PlatformToolsetVersionString = GetProjectFilePlatformToolsetVersionString ( ProjectFileFormat ) ;
VCProjectFileContent . AppendLine ( " <PlatformToolset>{0}</PlatformToolset>" , PlatformToolsetVersionString ) ;
}
2024-02-25 21:51:10 -05:00
// parses project ini for Android to get architecture(s) enabled
private static UnrealArchitectures GetAndroidProjectArchitectures ( FileReference ? ProjectFile , bool bGetAllSupported )
{
List < string > ActiveArches = new ( ) ;
// look in ini settings for what platforms to compile for
ConfigHierarchy Ini = ConfigCache . ReadHierarchy ( ConfigHierarchyType . Engine , DirectoryReference . FromFile ( ProjectFile ) , UnrealTargetPlatform . Android ) ;
bool bBuild ;
if ( Ini . GetBool ( "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "bBuildForArm64" , out bBuild ) & & bBuild )
{
ActiveArches . Add ( "arm64" ) ;
}
if ( Ini . GetBool ( "/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" , "bBuildForx8664" , out bBuild ) & & bBuild )
{
ActiveArches . Add ( "x64" ) ;
}
// we expect one to be specified
if ( ActiveArches . Count = = 0 )
{
ActiveArches . Add ( "arm64" ) ;
}
return new UnrealArchitectures ( ActiveArches ) ;
}
2023-08-02 20:27:20 -04:00
/// <summary>
/// Returns a list of architectures to generate unique VS platforms for.
/// </summary>
2024-02-25 21:51:10 -05:00
public static UnrealArchitectures ? GetPlatformArchitecturesToGenerate ( UEBuildPlatform BuildPlatform , ProjectTarget InProjectTarget )
2023-08-02 20:27:20 -04:00
{
2024-02-25 21:51:10 -05:00
if ( BuildPlatform . ArchitectureConfig . Mode = = UnrealArchitectureMode . SingleTargetLinkSeparately )
{
// this should only be Android at the moment
if ( BuildPlatform . Platform = = UnrealTargetPlatform . Android )
{
return InProjectTarget . UnrealProjectFilePath = = null ? BuildPlatform . ArchitectureConfig . AllSupportedArchitectures
: GetAndroidProjectArchitectures ( InProjectTarget . UnrealProjectFilePath , true ) ;
}
return BuildPlatform . ArchitectureConfig . AllSupportedArchitectures ;
}
return ( BuildPlatform . ArchitectureConfig . Mode = = UnrealArchitectureMode . OneTargetPerArchitecture ) ?
2023-08-11 12:50:25 -04:00
BuildPlatform . ArchitectureConfig . AllSupportedArchitectures : null ;
2023-08-02 20:27:20 -04:00
}
2022-06-24 17:41:49 -04:00
/// <inheritdoc/>
2024-04-02 20:29:22 -04:00
protected override void ConfigureProjectFileGeneration ( string [ ] Arguments , ref bool IncludeAllPlatforms , ILogger Logger )
2022-06-24 17:41:49 -04:00
{
// Call parent implementation first
base . ConfigureProjectFileGeneration ( Arguments , ref IncludeAllPlatforms , Logger ) ;
}
/// <summary>
/// Selects which platforms and build configurations we want in the project file
/// </summary>
/// <param name="IncludeAllPlatforms">True if we should include ALL platforms that are supported on this machine. Otherwise, only desktop platforms will be included.</param>
/// <param name="Logger"></param>
/// <param name="SupportedPlatformNames">Output string for supported platforms, returned as comma-separated values.</param>
protected override void SetupSupportedPlatformsAndConfigurations ( bool IncludeAllPlatforms , ILogger Logger , out string SupportedPlatformNames )
{
// Call parent implementation to figure out the actual platforms
base . SetupSupportedPlatformsAndConfigurations ( IncludeAllPlatforms , Logger , out SupportedPlatformNames ) ;
// If we have a non-default setting for visual studio, check the compiler exists. If not, revert to the default.
2022-11-17 16:58:58 -05:00
if ( Settings . ProjectFileFormat = = VCProjectFileFormat . VisualStudio2022 )
2022-08-31 19:25:13 -04:00
{
UnrealArch/UnrealArchitectures changes
- Creates the UnrealArchitectures class, which wraps a list of UnrealArch objects
- UnrealArch is a single architecture, expandable enum-like struct
- There is no more concept of "no/default architecture", there is always a valid active architecture when building
- Most uses of "string Architecture" are replaced with one of the two above, depending if multiple architectures are supported or not
- UnrealArch has some platform-extensions for platform-specific naming (like Linux adds in LinuxName that turns, for instance, Arm64 -> aarch64-unknown-linux-gnueabi, which is used in folder names, etc)
- UnrealArch has bIsX64 which can be used determine intel instruction set (as opposed to arm)
- TargetRules class has an "Architecture" accessor that will return a single architecture if the active architectures is a single architecture, or throw an exception if multiple. This is useful in a majority of the cases where a paltform can only have a single architecture active in TargetRules (microsoft platforms, for instance, will create separate targets when compiling multiple architectures at once)
- Added UnrealArchitectureConfig class, which contains all the architecture information for a platform (what architectures are supported, what ones are currently active for given project, etc)
#preflight 63c81fb5b065224750a1759e
#rb mike.fricker,roman.dzieciol,joe.kirchoff,dmytro.vovk,brandon.schaefer [various parts]
#p4v-preflight-copy 23562471
[CL 23829977 by josh adams in ue5-main branch]
2023-01-24 09:30:28 -05:00
if ( ! WindowsPlatform . HasCompiler ( WindowsCompiler . VisualStudio2022 , UnrealArch . X64 , Logger ) )
2022-08-31 19:25:13 -04:00
{
Logger . LogWarning ( "Visual Studio C++ 2022 installation not found - ignoring preferred project file format." ) ;
Settings . ProjectFileFormat = VCProjectFileFormat . Default ;
}
}
2022-06-24 17:41:49 -04:00
// Certain platforms override the project file format because their debugger add-ins may not yet support the latest
// version of Visual Studio. This is their chance to override that.
// ...but only if the user didn't override this via the command-line.
if ( Settings . ProjectFileFormat = = VCProjectFileFormat . Default )
{
// Enumerate all the valid installations. This list is already sorted by preference.
UnrealArch/UnrealArchitectures changes
- Creates the UnrealArchitectures class, which wraps a list of UnrealArch objects
- UnrealArch is a single architecture, expandable enum-like struct
- There is no more concept of "no/default architecture", there is always a valid active architecture when building
- Most uses of "string Architecture" are replaced with one of the two above, depending if multiple architectures are supported or not
- UnrealArch has some platform-extensions for platform-specific naming (like Linux adds in LinuxName that turns, for instance, Arm64 -> aarch64-unknown-linux-gnueabi, which is used in folder names, etc)
- UnrealArch has bIsX64 which can be used determine intel instruction set (as opposed to arm)
- TargetRules class has an "Architecture" accessor that will return a single architecture if the active architectures is a single architecture, or throw an exception if multiple. This is useful in a majority of the cases where a paltform can only have a single architecture active in TargetRules (microsoft platforms, for instance, will create separate targets when compiling multiple architectures at once)
- Added UnrealArchitectureConfig class, which contains all the architecture information for a platform (what architectures are supported, what ones are currently active for given project, etc)
#preflight 63c81fb5b065224750a1759e
#rb mike.fricker,roman.dzieciol,joe.kirchoff,dmytro.vovk,brandon.schaefer [various parts]
#p4v-preflight-copy 23562471
[CL 23829977 by josh adams in ue5-main branch]
2023-01-24 09:30:28 -05:00
List < VisualStudioInstallation > Installations = MicrosoftPlatformSDK . FindVisualStudioInstallations ( Logger ) . Where ( x = > WindowsPlatform . HasCompiler ( x . Compiler , UnrealArch . X64 , Logger ) ) . ToList ( ) ;
2022-06-24 17:41:49 -04:00
// Get the corresponding project file format
2022-10-17 12:07:45 -04:00
VCProjectFileFormat Format = VCProjectFileFormat . Default ;
2022-09-22 15:05:33 -04:00
foreach ( VisualStudioInstallation Installation in Installations )
2022-06-24 17:41:49 -04:00
{
2022-09-22 15:05:33 -04:00
if ( Installation . Compiler = = WindowsCompiler . VisualStudio2022 )
{
Format = VCProjectFileFormat . VisualStudio2022 ;
break ;
}
2022-06-24 17:41:49 -04:00
}
Settings . ProjectFileFormat = Format ;
2024-01-26 17:21:00 -05:00
bool DowngradeAvailable = false ; // ex: Installations.Any(x => x.Compiler == WindowsCompiler.VisualStudio2022);
2022-09-22 15:05:33 -04:00
2022-06-24 17:41:49 -04:00
// Allow the SDKs to override
foreach ( UnrealTargetPlatform SupportedPlatform in SupportedPlatforms )
{
UEBuildPlatform ? BuildPlatform ;
if ( UEBuildPlatform . TryGetBuildPlatform ( SupportedPlatform , out BuildPlatform ) )
{
// Don't worry about platforms that we're missing SDKs for
if ( BuildPlatform . HasRequiredSDKsInstalled ( ) = = SDKStatus . Valid )
{
VCProjectFileFormat ProposedFormat = BuildPlatform . GetRequiredVisualStudioVersion ( ) ;
if ( ProposedFormat ! = VCProjectFileFormat . Default )
{
// Reduce the Visual Studio version to the max supported by each platform we plan to include.
if ( Settings . ProjectFileFormat = = VCProjectFileFormat . Default | | ProposedFormat < Settings . ProjectFileFormat )
{
2022-09-20 00:48:51 -04:00
Logger . LogInformation ( "Available {SupportedPlatform} SDK does not support Visual Studio 2022." , SupportedPlatform ) ;
Version Version = BuildPlatform . GetVersionRequiredForVisualStudio ( VCProjectFileFormat . VisualStudio2022 ) ;
if ( Version > new Version ( ) )
{
Logger . LogInformation ( "Please update {SupportedPlatform} SDK to {Version} if Visual Studio 2022 support is desired." , SupportedPlatform , Version ) ;
}
2024-01-26 17:21:00 -05:00
if ( ! DowngradeAvailable )
2022-09-20 00:48:51 -04:00
{
2024-01-26 17:21:00 -05:00
Logger . LogInformation ( "Generated solution cannot be downgraded as no prior Visual Studio version is not installed. Please install the prior version of Visual Studio if {SupportedPlatform} SDK support is required." , SupportedPlatform ) ;
2022-09-20 00:48:51 -04:00
}
else
{
2024-01-26 17:21:00 -05:00
Logger . LogInformation ( "Downgrading generated solution to {ProposedFormat}." , ProposedFormat ) ;
Logger . LogInformation ( "To force {ProjectFileFormat} solutions to always be generated add the following to BuildConfiguration.xml:" , Settings . ProjectFileFormat ) ;
Logger . LogInformation ( " <VCProjectFileGenerator>\r\n <Version>{ProposedFormat}</Version>\r\n </VCProjectFileGenerator>" , Settings . ProjectFileFormat ) ;
2022-09-20 00:48:51 -04:00
Settings . ProjectFileFormat = ProposedFormat ;
}
2023-05-30 18:59:32 -04:00
Logger . LogInformation ( String . Empty ) ;
2022-06-24 17:41:49 -04:00
}
}
}
}
}
}
}
/// <summary>
/// Used to sort VC solution config names along with the config and platform values
/// </summary>
2022-09-16 15:53:01 -04:00
public class VCSolutionConfigCombination
2022-06-24 17:41:49 -04:00
{
/// <summary>
/// Visual Studio solution configuration name for this config+platform
/// </summary>
public string VCSolutionConfigAndPlatformName ;
/// <summary>
/// Configuration name
/// </summary>
public UnrealTargetConfiguration Configuration ;
/// <summary>
/// Platform name
/// </summary>
public UnrealTargetPlatform Platform ;
/// <summary>
/// The target type
/// </summary>
public TargetType TargetConfigurationName ;
2023-08-02 20:27:20 -04:00
/// <summary>
/// The target architecture
/// </summary>
public UnrealArch ? Architecture ;
2022-06-24 17:41:49 -04:00
public override string ToString ( )
{
2024-04-02 20:29:22 -04:00
return String . Format ( "{0}={1} {2} {3}{4}" , VCSolutionConfigAndPlatformName , Configuration , Platform , TargetConfigurationName , Architecture ! = null ? " " + Architecture : String . Empty ) ;
2022-06-24 17:41:49 -04:00
}
public VCSolutionConfigCombination ( string VCSolutionConfigAndPlatformName )
{
this . VCSolutionConfigAndPlatformName = VCSolutionConfigAndPlatformName ;
}
}
/// <summary>
/// Composes a string to use for the Visual Studio solution configuration, given a build configuration and target rules configuration name
/// </summary>
/// <param name="Configuration">The build configuration</param>
/// <param name="TargetType">The type of target being built</param>
2023-02-17 12:53:28 -05:00
/// <param name="bMakeProjectPerTarget">True if we are making one project per target type, instead of rolling them into the configs</param>
2022-06-24 17:41:49 -04:00
/// <returns>The generated solution configuration name</returns>
2023-02-17 12:53:28 -05:00
static string MakeSolutionConfigurationName ( UnrealTargetConfiguration Configuration , TargetType TargetType , bool bMakeProjectPerTarget )
2022-06-24 17:41:49 -04:00
{
string SolutionConfigName = Configuration . ToString ( ) ;
2023-02-17 12:53:28 -05:00
if ( ! bMakeProjectPerTarget )
2022-06-24 17:41:49 -04:00
{
2023-02-17 12:53:28 -05:00
// Don't bother postfixing "Game" or "Program" -- that will be the default when using "Debug", "Development", etc.
// Also don't postfix "RocketGame" when we're building Rocket game projects. That's the only type of game there is in that case!
if ( TargetType ! = TargetType . Game & & TargetType ! = TargetType . Program )
{
SolutionConfigName + = " " + TargetType . ToString ( ) ;
}
2022-06-24 17:41:49 -04:00
}
return SolutionConfigName ;
}
static IDictionary < PrimaryProjectFolder , Guid > GenerateProjectFolderGuids ( PrimaryProjectFolder RootFolder )
{
IDictionary < PrimaryProjectFolder , Guid > Guids = new Dictionary < PrimaryProjectFolder , Guid > ( ) ;
foreach ( PrimaryProjectFolder Folder in RootFolder . SubFolders )
{
GenerateProjectFolderGuids ( "UE5" , Folder , Guids ) ;
}
return Guids ;
}
static void GenerateProjectFolderGuids ( string ParentPath , PrimaryProjectFolder Folder , IDictionary < PrimaryProjectFolder , Guid > Guids )
{
string Path = String . Format ( "{0}/{1}" , ParentPath , Folder . FolderName ) ;
Guids [ Folder ] = MakeMd5Guid ( Encoding . UTF8 . GetBytes ( Path ) ) ;
foreach ( PrimaryProjectFolder SubFolder in Folder . SubFolders )
{
GenerateProjectFolderGuids ( Path , SubFolder , Guids ) ;
}
}
static Guid MakeMd5Guid ( byte [ ] Input )
{
byte [ ] Hash = MD5 . Create ( ) . ComputeHash ( Input ) ;
Hash [ 6 ] = ( byte ) ( 0x30 | ( Hash [ 6 ] & 0x0f ) ) ; // 0b0011'xxxx Version 3 UUID (MD5)
Hash [ 8 ] = ( byte ) ( 0x80 | ( Hash [ 8 ] & 0x3f ) ) ; // 0b10xx'xxxx RFC 4122 UUID
Array . Reverse ( Hash , 0 , 4 ) ;
Array . Reverse ( Hash , 4 , 2 ) ;
Array . Reverse ( Hash , 6 , 2 ) ;
return new Guid ( Hash ) ;
}
public static Guid MakeMd5Guid ( Guid Namespace , string Text )
{
byte [ ] Input = new byte [ 16 + Encoding . UTF8 . GetByteCount ( Text ) ] ;
Namespace . TryWriteBytes ( Input . AsSpan ( 0 , 16 ) ) ;
Array . Reverse ( Input , 0 , 4 ) ;
Array . Reverse ( Input , 4 , 2 ) ;
Array . Reverse ( Input , 6 , 2 ) ;
Encoding . UTF8 . GetBytes ( Text , 0 , Text . Length , Input , 16 ) ;
return MakeMd5Guid ( Input ) ;
}
public static Guid MakeMd5Guid ( string Text )
{
byte [ ] Input = Encoding . UTF8 . GetBytes ( Text ) ;
return MakeMd5Guid ( Input ) ;
}
2023-06-01 12:58:20 -04:00
private void WriteCommonPropsFile ( ILogger Logger )
{
2023-08-02 20:27:20 -04:00
StringBuilder VCCommonTargetFileContent = new StringBuilder ( ) ;
VCCommonTargetFileContent . AppendLine ( "<?xml version=\"1.0\" encoding=\"utf-8\"?>" ) ;
VCCommonTargetFileContent . AppendLine ( "<Project xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">" ) ;
2023-06-01 12:58:20 -04:00
2023-08-02 20:27:20 -04:00
// Project globals (project GUID, project type, SCC bindings, etc)
{
string ToolVersionString = GetProjectFileToolVersionString ( Settings . ProjectFileFormat ) ;
VCCommonTargetFileContent . AppendLine ( " <PropertyGroup Label=\"Globals\">" ) ;
VCCommonTargetFileContent . AppendLine ( " <Keyword>MakeFileProj</Keyword>" ) ;
AppendPlatformToolsetProperty ( VCCommonTargetFileContent , Settings . ProjectFileFormat ) ;
VCCommonTargetFileContent . AppendLine ( " <MinimumVisualStudioVersion>{0}</MinimumVisualStudioVersion>" , ToolVersionString ) ;
VCCommonTargetFileContent . AppendLine ( " <VCProjectVersion>{0}</VCProjectVersion>" , ToolVersionString ) ;
VCCommonTargetFileContent . AppendLine ( " <NMakeUseOemCodePage>true</NMakeUseOemCodePage>" ) ; // Fixes mojibake with non-Latin character sets (UE-102825)
VCCommonTargetFileContent . AppendLine ( " <TargetRuntime>Native</TargetRuntime>" ) ;
VCCommonTargetFileContent . AppendLine ( " </PropertyGroup>" ) ;
}
// Write the default configuration info
VCCommonTargetFileContent . AppendLine ( " <PropertyGroup Label=\"Configuration\">" ) ;
VCCommonTargetFileContent . AppendLine ( $" <ConfigurationType>{PlatformProjectGenerator.DefaultPlatformConfigurationType}</ConfigurationType>" ) ;
AppendPlatformToolsetProperty ( VCCommonTargetFileContent , Settings . ProjectFileFormat ) ;
VCCommonTargetFileContent . AppendLine ( " </PropertyGroup>" ) ;
VCCommonTargetFileContent . AppendLine ( " <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.Default.props\" />" ) ;
VCCommonTargetFileContent . AppendLine ( " <Import Project=\"$(VCTargetsPath)\\Microsoft.Cpp.props\" />" ) ;
// Write the common and invalid configuration values
{
const string InvalidMessage = "echo The selected platform/configuration is not valid for this target." ;
string ProjectRelativeUnusedDirectory = ProjectFile . NormalizeProjectPath ( DirectoryReference . Combine ( Unreal . EngineDirectory , "Intermediate" , "Build" , "Unused" ) ) ;
VCCommonTargetFileContent . AppendLine ( " <PropertyGroup>" ) ;
DirectoryReference BatchFilesDirectory = DirectoryReference . Combine ( Unreal . EngineDirectory , "Build" , "BatchFiles" ) ;
VCCommonTargetFileContent . AppendLine ( " <BuildBatchScript>{0}</BuildBatchScript>" , ProjectFile . EscapePath ( ProjectFile . NormalizeProjectPath ( FileReference . Combine ( BatchFilesDirectory , "Build.bat" ) ) ) ) ;
VCCommonTargetFileContent . AppendLine ( " <RebuildBatchScript>{0}</RebuildBatchScript>" , ProjectFile . EscapePath ( ProjectFile . NormalizeProjectPath ( FileReference . Combine ( BatchFilesDirectory , "Rebuild.bat" ) ) ) ) ;
VCCommonTargetFileContent . AppendLine ( " <CleanBatchScript>{0}</CleanBatchScript>" , ProjectFile . EscapePath ( ProjectFile . NormalizeProjectPath ( FileReference . Combine ( BatchFilesDirectory , "Clean.bat" ) ) ) ) ;
VCCommonTargetFileContent . AppendLine ( " <NMakeBuildCommandLine>{0}</NMakeBuildCommandLine>" , InvalidMessage ) ;
VCCommonTargetFileContent . AppendLine ( " <NMakeReBuildCommandLine>{0}</NMakeReBuildCommandLine>" , InvalidMessage ) ;
VCCommonTargetFileContent . AppendLine ( " <NMakeCleanCommandLine>{0}</NMakeCleanCommandLine>" , InvalidMessage ) ;
VCCommonTargetFileContent . AppendLine ( " <NMakeOutput>Invalid Output</NMakeOutput>" , InvalidMessage ) ;
VCCommonTargetFileContent . AppendLine ( " <OutDir>{0}{1}</OutDir>" , ProjectRelativeUnusedDirectory , Path . DirectorySeparatorChar ) ;
VCCommonTargetFileContent . AppendLine ( " <IntDir>{0}{1}</IntDir>" , ProjectRelativeUnusedDirectory , Path . DirectorySeparatorChar ) ;
// NOTE: We are intentionally overriding defaults for these paths with empty strings. We never want Visual Studio's
// defaults for these fields to be propagated, since they are version-sensitive paths that may not reflect
// the environment that UBT is building in. We'll set these environment variables ourselves!
// NOTE: We don't touch 'ExecutablePath' because that would result in Visual Studio clobbering the system "Path"
// environment variable
VCCommonTargetFileContent . AppendLine ( " <IncludePath />" ) ;
VCCommonTargetFileContent . AppendLine ( " <ReferencePath />" ) ;
VCCommonTargetFileContent . AppendLine ( " <LibraryPath />" ) ;
VCCommonTargetFileContent . AppendLine ( " <LibraryWPath />" ) ;
VCCommonTargetFileContent . AppendLine ( " <SourcePath />" ) ;
VCCommonTargetFileContent . AppendLine ( " <ExcludePath />" ) ;
// Add all the default system include paths
if ( OperatingSystem . IsWindows ( ) )
2023-06-01 12:58:20 -04:00
{
2023-08-02 20:27:20 -04:00
if ( SupportedPlatforms . Contains ( UnrealTargetPlatform . Win64 ) )
{
VCCommonTargetFileContent . AppendLine ( " <DefaultSystemIncludePaths>{0}</DefaultSystemIncludePaths>" , VCToolChain . GetVCIncludePaths ( UnrealTargetPlatform . Win64 , GetCompilerForIntellisense ( Settings . ProjectFileFormat ) , null , null , Logger ) ) ;
}
}
else
{
Logger . LogInformation ( "Unable to compute VC include paths on non-Windows host" ) ;
VCCommonTargetFileContent . AppendLine ( " <DefaultSystemIncludePaths />" ) ;
2023-06-01 12:58:20 -04:00
}
VCCommonTargetFileContent . AppendLine ( " </PropertyGroup>" ) ;
}
2023-08-02 20:27:20 -04:00
// Write default import group
VCCommonTargetFileContent . AppendLine ( " <ImportGroup Label=\"PropertySheets\">" ) ;
VCCommonTargetFileContent . AppendLine ( " <Import Project=\"$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props\" Condition=\"exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')\" Label=\"LocalAppDataPlatform\" />" ) ;
VCCommonTargetFileContent . AppendLine ( " </ImportGroup>" ) ;
VCCommonTargetFileContent . AppendLine ( "</Project>" ) ;
Utils . WriteFileIfChanged ( FileReference . Combine ( IntermediateProjectFilesPath , "UECommon.props" ) , VCCommonTargetFileContent . ToString ( ) , Logger ) ;
2023-06-01 12:58:20 -04:00
}
2022-06-24 17:41:49 -04:00
/// <summary>
/// Writes the project files to disk
/// </summary>
/// <returns>True if successful</returns>
protected override bool WriteProjectFiles ( PlatformProjectGeneratorCollection PlatformProjectGenerators , ILogger Logger )
{
2023-06-01 12:58:20 -04:00
WriteCommonPropsFile ( Logger ) ;
2022-06-24 17:41:49 -04:00
if ( ! base . WriteProjectFiles ( PlatformProjectGenerators , Logger ) )
{
return false ;
}
// Write AutomationReferences file
// Write in in net core expected format
if ( AutomationProjectFiles . Any ( ) )
{
XNamespace NS = XNamespace . Get ( "http://schemas.microsoft.com/developer/msbuild/2003" ) ;
DirectoryReference AutomationToolDir = DirectoryReference . Combine ( Unreal . EngineSourceDirectory , "Programs" , "AutomationTool" ) ;
2024-01-11 15:47:26 -05:00
DirectoryReference AutomationToolBinariesDir = DirectoryReference . Combine ( Unreal . EngineDirectory , "Binaries" , "DotNET" , "AutomationTool" ) ;
2023-03-24 14:15:34 -04:00
XDocument AutomationToolDocument = new XDocument (
2022-06-24 17:41:49 -04:00
new XElement ( NS + "Project" ,
new XAttribute ( "ToolsVersion" , VCProjectFileGenerator . GetProjectFileToolVersionString ( Settings . ProjectFileFormat ) ) ,
new XAttribute ( "DefaultTargets" , "Build" ) ,
new XElement ( NS + "ItemGroup" ,
from AutomationProject in AutomationProjectFiles
select new XElement ( NS + "ProjectReference" ,
2024-01-11 15:47:26 -05:00
new XAttribute ( "Include" , AutomationProject . ProjectFilePath . MakeRelativeTo ( AutomationToolDir ) ) ,
new XElement ( NS + "Private" , "false" )
)
) ,
// Delete the private copied dlls in case they were ever next to the .exe - that is a bad place for them
new XElement ( NS + "Target" ,
new XAttribute ( "Name" , "CleanUpStaleDlls" ) ,
new XAttribute ( "AfterTargets" , "Build" ) ,
2024-04-03 12:22:43 -04:00
AutomationProjectFiles . SelectMany ( AutomationProject = >
{
string BaseFilename = FileReference . Combine ( AutomationToolBinariesDir , AutomationProject . ProjectFilePath . GetFileNameWithoutExtension ( ) ) . FullName ;
return new List < XElement > ( ) {
new XElement ( NS + "Delete" , new XAttribute ( "Files" , BaseFilename + ".dll" ) ) ,
new XElement ( NS + "Delete" , new XAttribute ( "Files" , BaseFilename + ".dll.config" ) ) ,
new XElement ( NS + "Delete" , new XAttribute ( "Files" , BaseFilename + ".pdb" ) )
2024-01-11 15:47:26 -05:00
} ;
2024-04-03 12:22:43 -04:00
}
2022-06-24 17:41:49 -04:00
)
)
)
2023-03-24 14:15:34 -04:00
) ;
2023-05-30 18:38:07 -04:00
2023-03-24 14:15:34 -04:00
StringBuilder Output = new StringBuilder ( ) ;
Output . Append ( "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" ) ;
XmlWriterSettings XmlSettings = new XmlWriterSettings ( ) ;
XmlSettings . Encoding = new UTF8Encoding ( false ) ;
XmlSettings . Indent = true ;
XmlSettings . OmitXmlDeclaration = true ;
using ( XmlWriter Writer = XmlWriter . Create ( Output , XmlSettings ) )
{
AutomationToolDocument . Save ( Writer ) ;
}
Utils . WriteFileIfChanged ( FileReference . Combine ( IntermediateProjectFilesPath , "AutomationTool.csproj.References" ) , Output . ToString ( ) , Logger ) ;
2022-06-24 17:41:49 -04:00
}
return true ;
}
protected override bool WritePrimaryProjectFile ( ProjectFile ? UBTProject , PlatformProjectGeneratorCollection PlatformProjectGenerators , ILogger Logger )
{
bool bSuccess = true ;
string SolutionFileName = PrimaryProjectName + ".sln" ;
// Setup solution file content
StringBuilder VCSolutionFileContent = new StringBuilder ( ) ;
// Solution file header. Note that a leading newline is required for file type detection to work correclty in the shell.
if ( Settings . ProjectFileFormat = = VCProjectFileFormat . VisualStudio2022 )
{
VCSolutionFileContent . AppendLine ( ) ;
VCSolutionFileContent . AppendLine ( "Microsoft Visual Studio Solution File, Format Version 12.00" ) ;
VCSolutionFileContent . AppendLine ( "# Visual Studio Version 17" ) ;
VCSolutionFileContent . AppendLine ( "VisualStudioVersion = 17.0.31314.256" ) ;
VCSolutionFileContent . AppendLine ( "MinimumVisualStudioVersion = 10.0.40219.1" ) ;
}
else
{
throw new BuildException ( "Unexpected ProjectFileFormat" ) ;
}
IDictionary < PrimaryProjectFolder , Guid > ProjectFolderGuids = GenerateProjectFolderGuids ( RootFolder ) ;
// check if the give folder has any projects that will be put into the solution
System . Func < PrimaryProjectFolder , bool > ? HasProjectFunc = null ;
HasProjectFunc = ( ProjectFolder ) = >
{
foreach ( MSBuildProjectFile ChildProject in ProjectFolder . ChildProjects )
{
if ( AllProjectFiles . Contains ( ChildProject ) )
{
return true ;
}
}
foreach ( PrimaryProjectFolder SubFolder in ProjectFolder . SubFolders )
{
if ( HasProjectFunc ! ( SubFolder ) )
{
return true ;
}
}
return false ;
} ;
// Solution folders, files and project entries
{
// This the GUID that Visual Studio uses to identify a solution folder
string SolutionFolderEntryGUID = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}" ;
// Solution folders
{
IEnumerable < PrimaryProjectFolder > AllSolutionFolders = ProjectFolderGuids . Keys . OrderBy ( Folder = > Folder . FolderName ) . ThenBy ( Folder = > ProjectFolderGuids [ Folder ] ) ;
foreach ( PrimaryProjectFolder CurFolder in AllSolutionFolders )
{
// if this folder has no projects anywhere under it then skip it
if ( ! HasProjectFunc ( CurFolder ) )
{
continue ;
}
string FolderGUIDString = ProjectFolderGuids [ CurFolder ] . ToString ( "B" ) . ToUpperInvariant ( ) ;
VCSolutionFileContent . AppendLine ( "Project(\"" + SolutionFolderEntryGUID + "\") = \"" + CurFolder . FolderName + "\", \"" + CurFolder . FolderName + "\", \"" + FolderGUIDString + "\"" ) ;
// Add any files that are inlined right inside the solution folder
if ( CurFolder . Files . Count > 0 )
{
VCSolutionFileContent . AppendLine ( " ProjectSection(SolutionItems) = preProject" ) ;
foreach ( string CurFile in CurFolder . Files )
{
// Syntax is: <relative file path> = <relative file path>
VCSolutionFileContent . AppendLine ( " " + CurFile + " = " + CurFile ) ;
}
VCSolutionFileContent . AppendLine ( " EndProjectSection" ) ;
}
VCSolutionFileContent . AppendLine ( "EndProject" ) ;
}
}
// Project files
//List<MSBuildProjectFile> AllProjectFilesSorted = AllProjectFiles.OrderBy((ProjFile) => ProjFile.ProjectFilePath.GetFileNameWithoutExtension()).Cast<MSBuildProjectFile>().ToList();
foreach ( MSBuildProjectFile CurProject in AllProjectFiles )
{
// Visual Studio uses different GUID types depending on the project type
string ProjectTypeGUID = CurProject . ProjectTypeGUID ;
// NOTE: The project name in the solution doesn't actually *have* to match the project file name on disk. However,
// we prefer it when it does match so we use the actual file name here.
string ProjectNameInSolution = CurProject . ProjectFilePath . GetFileNameWithoutExtension ( ) ;
// Use the existing project's GUID that's already known to us
string ProjectGUID = CurProject . ProjectGUID . ToString ( "B" ) . ToUpperInvariant ( ) ;
VCSolutionFileContent . AppendLine ( "Project(\"" + ProjectTypeGUID + "\") = \"" + ProjectNameInSolution + "\", \"" + CurProject . ProjectFilePath . MakeRelativeTo ( ProjectFileGenerator . PrimaryProjectPath ) + "\", \"" + ProjectGUID + "\"" ) ;
// Setup dependency on UnrealBuildTool, if we need that. This makes sure that UnrealBuildTool is
// freshly compiled before kicking off any build operations on this target project
if ( ! CurProject . IsStubProject )
{
List < ProjectFile > Dependencies = new List < ProjectFile > ( ) ;
if ( CurProject . IsGeneratedProject & & UBTProject ! = null & & CurProject ! = UBTProject )
{
Dependencies . Add ( UBTProject ) ;
Dependencies . AddRange ( UBTProject . DependsOnProjects ) ;
}
Dependencies . AddRange ( CurProject . DependsOnProjects ) ;
if ( Dependencies . Count > 0 )
{
VCSolutionFileContent . AppendLine ( "\tProjectSection(ProjectDependencies) = postProject" ) ;
// Setup any addition dependencies this project has...
foreach ( ProjectFile DependsOnProject in Dependencies )
{
string DependsOnProjectGUID = ( ( MSBuildProjectFile ) DependsOnProject ) . ProjectGUID . ToString ( "B" ) . ToUpperInvariant ( ) ;
VCSolutionFileContent . AppendLine ( "\t\t" + DependsOnProjectGUID + " = " + DependsOnProjectGUID ) ;
}
VCSolutionFileContent . AppendLine ( "\tEndProjectSection" ) ;
}
}
VCSolutionFileContent . AppendLine ( "EndProject" ) ;
}
// Get the path to the visualizers file. Try to make it relative to the solution directory, but fall back to a full path if it's a foreign project.
FileReference VisualizersFile = FileReference . Combine ( Unreal . EngineDirectory , "Extras" , "VisualStudioDebugging" , "Unreal.natvis" ) ;
2024-08-23 14:51:39 -04:00
FileReference VisualizersStepFile = FileReference . Combine ( Unreal . EngineDirectory , "Extras" , "VisualStudioDebugging" , "Unreal.natstepfilter" ) ;
2022-06-24 17:41:49 -04:00
// Add the visualizers at the solution level. Doesn't seem to be picked up from a makefile project in VS2017 15.8.5.
VCSolutionFileContent . AppendLine ( String . Format ( "Project(\"{0}\") = \"Visualizers\", \"Visualizers\", \"{{1CCEC849-CC72-4C59-8C36-2F7C38706D4C}}\"" , SolutionFolderEntryGUID ) ) ;
VCSolutionFileContent . AppendLine ( "\tProjectSection(SolutionItems) = preProject" ) ;
VCSolutionFileContent . AppendLine ( "\t\t{0} = {0}" , VisualizersFile . MakeRelativeTo ( PrimaryProjectPath ) ) ;
2024-08-23 14:51:39 -04:00
VCSolutionFileContent . AppendLine ( "\t\t{0} = {0}" , VisualizersStepFile . MakeRelativeTo ( PrimaryProjectPath ) ) ;
2022-06-24 17:41:49 -04:00
VCSolutionFileContent . AppendLine ( "\tEndProjectSection" ) ;
VCSolutionFileContent . AppendLine ( "EndProject" ) ;
}
// Solution configuration platforms. This is just a list of all of the platforms and configurations that
// appear in Visual Studio's build configuration selector.
2022-09-16 15:53:01 -04:00
List < VCSolutionConfigCombination > SolutionConfigCombinations ;
2022-06-24 17:41:49 -04:00
// The "Global" section has source control, solution configurations, project configurations,
// preferences, and project hierarchy data
{
VCSolutionFileContent . AppendLine ( "Global" ) ;
{
2022-09-16 15:53:01 -04:00
HashSet < UnrealTargetPlatform > PlatformsValidForProjects ;
2022-06-24 17:41:49 -04:00
{
VCSolutionFileContent . AppendLine ( " GlobalSection(SolutionConfigurationPlatforms) = preSolution" ) ;
2023-02-17 12:53:28 -05:00
CollectSolutionConfigurations ( SupportedConfigurations , SupportedPlatforms , AllProjectFiles , bMakeProjectPerTarget ,
2022-09-16 15:53:01 -04:00
Logger , out PlatformsValidForProjects , out SolutionConfigCombinations ) ;
2022-06-24 17:41:49 -04:00
HashSet < string > AppendedSolutionConfigAndPlatformNames = new HashSet < string > ( StringComparer . InvariantCultureIgnoreCase ) ;
foreach ( VCSolutionConfigCombination SolutionConfigCombination in SolutionConfigCombinations )
{
// We alias "Game" and "Program" to both have the same solution configuration, so we're careful not to add the same combination twice.
if ( ! AppendedSolutionConfigAndPlatformNames . Contains ( SolutionConfigCombination . VCSolutionConfigAndPlatformName ) )
{
VCSolutionFileContent . AppendLine ( " " + SolutionConfigCombination . VCSolutionConfigAndPlatformName + " = " + SolutionConfigCombination . VCSolutionConfigAndPlatformName ) ;
AppendedSolutionConfigAndPlatformNames . Add ( SolutionConfigCombination . VCSolutionConfigAndPlatformName ) ;
}
}
VCSolutionFileContent . AppendLine ( " EndGlobalSection" ) ;
}
// Embed UnrealVS section, which is parsed by that VSPackage (Extension) to know how to handle this solution
{
string UnrealVSGuid = "ddbf523f-7eb6-4887-bd51-85a714ff87eb" ;
VCSolutionFileContent . AppendLine ( "\t# UnrealVS Section" ) ;
VCSolutionFileContent . AppendLine ( "\tGlobalSection({0}) = preSolution" , UnrealVSGuid ) ;
2023-05-30 18:59:32 -04:00
VCSolutionFileContent . AppendLine ( "\t\tAvailablePlatforms={0}" , String . Join ( ";" , PlatformsValidForProjects . Select ( platform = > platform . ToString ( ) ) ) ) ;
2022-06-24 17:41:49 -04:00
VCSolutionFileContent . AppendLine ( "\tEndGlobalSection" ) ;
}
// Assign each project's "project configuration" to our "solution platform + configuration" pairs. This
// also sets up which projects are actually built when building the solution.
{
VCSolutionFileContent . AppendLine ( " GlobalSection(ProjectConfigurationPlatforms) = postSolution" ) ;
foreach ( MSBuildProjectFile CurProject in AllProjectFiles )
{
foreach ( VCSolutionConfigCombination SolutionConfigCombination in SolutionConfigCombinations )
{
// Get the context for the current solution context
2024-02-25 21:51:10 -05:00
MSBuildProjectContext ? ProjectContext = CurProject . GetMatchingProjectContext ( SolutionConfigCombination . TargetConfigurationName , SolutionConfigCombination . Configuration , SolutionConfigCombination . Platform , PlatformProjectGenerators , SolutionConfigCombination . Architecture , Logger ) ;
if ( ProjectContext = = null )
{
continue ;
}
2022-06-24 17:41:49 -04:00
// Override the configuration to build for UBT
if ( Settings . bBuildUBTInDebug & & CurProject = = UBTProject )
{
ProjectContext . ConfigurationName = "Debug" ;
}
// Write the solution mapping (e.g. "{4232C52C-680F-4850-8855-DC39419B5E9B}.Debug|iOS.ActiveCfg = iOS_Debug|Win32")
string CurProjectGUID = CurProject . ProjectGUID . ToString ( "B" ) . ToUpperInvariant ( ) ;
VCSolutionFileContent . AppendLine ( " {0}.{1}.ActiveCfg = {2}" , CurProjectGUID , SolutionConfigCombination . VCSolutionConfigAndPlatformName , ProjectContext . Name ) ;
if ( ProjectContext . bBuildByDefault )
{
VCSolutionFileContent . AppendLine ( " {0}.{1}.Build.0 = {2}" , CurProjectGUID , SolutionConfigCombination . VCSolutionConfigAndPlatformName , ProjectContext . Name ) ;
if ( ProjectContext . bDeployByDefault )
{
VCSolutionFileContent . AppendLine ( " {0}.{1}.Deploy.0 = {2}" , CurProjectGUID , SolutionConfigCombination . VCSolutionConfigAndPlatformName , ProjectContext . Name ) ;
}
}
}
}
VCSolutionFileContent . AppendLine ( " EndGlobalSection" ) ;
}
// Setup other solution properties
{
// HideSolutionNode sets whether or not the top-level solution entry is completely hidden in the UI.
// We don't want that, as we need users to be able to right click on the solution tree item.
VCSolutionFileContent . AppendLine ( " GlobalSection(SolutionProperties) = preSolution" ) ;
VCSolutionFileContent . AppendLine ( " HideSolutionNode = FALSE" ) ;
VCSolutionFileContent . AppendLine ( " EndGlobalSection" ) ;
}
// Solution directory hierarchy
{
VCSolutionFileContent . AppendLine ( " GlobalSection(NestedProjects) = preSolution" ) ;
// Every entry in this section is in the format "Guid1 = Guid2". Guid1 is the child project (or solution
// filter)'s GUID, and Guid2 is the solution filter directory to parent the child project (or solution
// filter) to. This sets up the hierarchical solution explorer tree for all solution folders and projects.
System . Action < StringBuilder /* VCSolutionFileContent */ , List < PrimaryProjectFolder > /* Folders */ > ? FolderProcessorFunction = null ;
FolderProcessorFunction = ( LocalVCSolutionFileContent , LocalPrimaryProjectFolders ) = >
{
foreach ( PrimaryProjectFolder CurFolder in LocalPrimaryProjectFolders )
{
string CurFolderGUIDString = ProjectFolderGuids [ CurFolder ] . ToString ( "B" ) . ToUpperInvariant ( ) ;
foreach ( MSBuildProjectFile ChildProject in CurFolder . ChildProjects )
{
if ( AllProjectFiles . Contains ( ChildProject ) )
{
// e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
LocalVCSolutionFileContent . AppendLine ( " " + ChildProject . ProjectGUID . ToString ( "B" ) . ToUpperInvariant ( ) + " = " + CurFolderGUIDString ) ;
}
}
foreach ( PrimaryProjectFolder SubFolder in CurFolder . SubFolders )
{
// if this folder has no projects anywhere under it then skip it
if ( HasProjectFunc ( SubFolder ) )
{
// e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
LocalVCSolutionFileContent . AppendLine ( " " + ProjectFolderGuids [ SubFolder ] . ToString ( "B" ) . ToUpperInvariant ( ) + " = " + CurFolderGUIDString ) ;
}
}
// Recurse into subfolders
FolderProcessorFunction ! ( LocalVCSolutionFileContent , CurFolder . SubFolders ) ;
}
} ;
FolderProcessorFunction ( VCSolutionFileContent , RootFolder . SubFolders ) ;
VCSolutionFileContent . AppendLine ( " EndGlobalSection" ) ;
}
}
VCSolutionFileContent . AppendLine ( "EndGlobal" ) ;
}
// Save the solution file
if ( bSuccess )
{
string SolutionFilePath = FileReference . Combine ( PrimaryProjectPath , SolutionFileName ) . FullName ;
bSuccess = WriteFileIfChanged ( SolutionFilePath , VCSolutionFileContent . ToString ( ) , Logger ) ;
}
// Save a solution config file which selects the development editor configuration by default.
// .suo file writable only on Windows, requires ole32
if ( bSuccess & & Settings . bWriteSolutionOptionFile & & OperatingSystem . IsWindows ( ) )
{
// Figure out the filename for the SUO file. VS will automatically import the options from earlier versions if necessary.
FileReference SolutionOptionsFileName ;
switch ( Settings . ProjectFileFormat )
{
case VCProjectFileFormat . VisualStudio2022 :
SolutionOptionsFileName = FileReference . Combine ( PrimaryProjectPath , ".vs" , Path . GetFileNameWithoutExtension ( SolutionFileName ) , "v17" , ".suo" ) ;
break ;
default :
throw new BuildException ( "Unsupported Visual Studio version" ) ;
}
// Check it doesn't exist before overwriting it. Since these files store the user's preferences, it'd be bad form to overwrite them.
if ( ! FileReference . Exists ( SolutionOptionsFileName ) )
{
DirectoryReference . CreateDirectory ( SolutionOptionsFileName . Directory ) ;
VCSolutionOptions Options = new VCSolutionOptions ( Settings . ProjectFileFormat ) ;
// Set the default configuration and startup project
2023-05-30 18:38:07 -04:00
VCSolutionConfigCombination ? DefaultConfig = SolutionConfigCombinations . Find ( x = >
2023-02-17 12:53:28 -05:00
x . Configuration = = UnrealTargetConfiguration . Development & &
2023-05-30 18:38:07 -04:00
x . Platform = = UnrealTargetPlatform . Win64 & &
2023-08-11 12:50:25 -04:00
( x . Architecture = = null | | x . Architecture = = UnrealArch . X64 ) & &
2023-02-17 12:53:28 -05:00
( bMakeProjectPerTarget | | x . TargetConfigurationName = = TargetType . Editor ) ) ;
2022-06-24 17:41:49 -04:00
if ( DefaultConfig ! = null )
{
List < VCBinarySetting > Settings = new List < VCBinarySetting > ( ) ;
Settings . Add ( new VCBinarySetting ( "ActiveCfg" , DefaultConfig . VCSolutionConfigAndPlatformName ) ) ;
if ( DefaultProject ! = null )
{
Settings . Add ( new VCBinarySetting ( "StartupProject" , ( ( MSBuildProjectFile ) DefaultProject ) . ProjectGUID . ToString ( "B" ) ) ) ;
}
Options . SetConfiguration ( Settings ) ;
}
// Mark all the projects as closed by default, apart from the startup project
VCSolutionExplorerState ExplorerState = new VCSolutionExplorerState ( ) ;
Options . SetExplorerState ( ExplorerState ) ;
// Write the file
if ( Options . Sections . Count > 0 )
{
Options . Write ( SolutionOptionsFileName . FullName ) ;
}
}
}
2022-08-26 16:33:56 -04:00
if ( bSuccess & & Settings . bVsConfigFile & & OperatingSystem . IsWindows ( ) )
{
StringBuilder VsConfigFileContent = new StringBuilder ( ) ;
VsConfigFileContent . AppendLine ( "{" ) ;
VsConfigFileContent . AppendLine ( " \"version\": \"1.0\"," ) ;
VsConfigFileContent . AppendLine ( " \"components\": [" ) ;
IEnumerable < string > Components = MicrosoftPlatformSDK . GetVisualStudioSuggestedComponents ( Settings . ProjectFileFormat ) ;
2023-05-30 18:59:32 -04:00
string ComponentsString = String . Join ( $",{Environment.NewLine} " , Components . Select ( x = > $"\" { x } \ "" ) ) ;
2022-08-26 16:33:56 -04:00
VsConfigFileContent . AppendLine ( $" {ComponentsString}" ) ;
VsConfigFileContent . AppendLine ( " ]" ) ;
VsConfigFileContent . AppendLine ( "}" ) ;
FileReference VsConfigFileName = FileReference . Combine ( PrimaryProjectPath , ".vsconfig" ) ;
bSuccess = WriteFileIfChanged ( VsConfigFileName . FullName , VsConfigFileContent . ToString ( ) , Logger ) ;
}
2022-06-24 17:41:49 -04:00
return bSuccess ;
}
2022-09-16 15:53:01 -04:00
public static void CollectSolutionConfigurations ( List < UnrealTargetConfiguration > AllConfigurations ,
2023-02-17 12:53:28 -05:00
List < UnrealTargetPlatform > AllPlatforms , List < ProjectFile > AllProjectFiles , bool bMakeProjectPerTarget , ILogger Logger ,
2022-09-16 15:53:01 -04:00
out HashSet < UnrealTargetPlatform > OutValidPlatforms , out List < VCSolutionConfigCombination > OutSolutionConfigs )
{
OutValidPlatforms = new HashSet < UnrealTargetPlatform > ( ) ;
OutSolutionConfigs = new List < VCSolutionConfigCombination > ( ) ;
2023-08-02 20:27:20 -04:00
Dictionary < string , Tuple < UnrealTargetConfiguration , Tuple < ProjectTarget , TargetType > > > SolutionConfigurationsValidForProjects = new ( ) ;
2022-09-16 15:53:01 -04:00
foreach ( UnrealTargetConfiguration CurConfiguration in AllConfigurations )
{
if ( InstalledPlatformInfo . IsValidConfiguration ( CurConfiguration , EProjectType . Code ) )
{
foreach ( UnrealTargetPlatform CurPlatform in AllPlatforms )
{
if ( InstalledPlatformInfo . IsValidPlatform ( CurPlatform , EProjectType . Code ) )
{
foreach ( ProjectFile CurProject in AllProjectFiles )
{
if ( ! CurProject . IsStubProject )
{
if ( CurProject . ProjectTargets . Count = = 0 )
{
throw new BuildException ( "Expecting project '" + CurProject . ProjectFilePath +
2023-05-30 18:38:07 -04:00
"' to have at least one ProjectTarget associated with it!" ) ;
2022-09-16 15:53:01 -04:00
}
// Figure out the set of valid target configuration names
2023-08-02 20:27:20 -04:00
foreach ( ProjectTarget ProjectTarget in CurProject . ProjectTargets . OfType < ProjectTarget > ( ) )
2022-09-16 15:53:01 -04:00
{
if ( VCProjectFile . IsValidProjectPlatformAndConfiguration ( ProjectTarget , CurPlatform ,
2023-05-30 18:38:07 -04:00
CurConfiguration , Logger ) )
2022-09-16 15:53:01 -04:00
{
OutValidPlatforms . Add ( CurPlatform ) ;
// Default to a target configuration name of "Game", since that will collapse down to an empty string
TargetType TargetType = TargetType . Game ;
if ( ProjectTarget . TargetRules ! = null )
{
TargetType = ProjectTarget . TargetRules . Type ;
}
string SolutionConfigName =
2023-02-17 12:53:28 -05:00
MakeSolutionConfigurationName ( CurConfiguration , TargetType , bMakeProjectPerTarget ) ;
2022-09-16 15:53:01 -04:00
SolutionConfigurationsValidForProjects [ SolutionConfigName ] =
2023-08-02 20:27:20 -04:00
new Tuple < UnrealTargetConfiguration , Tuple < ProjectTarget , TargetType > > ( CurConfiguration , new Tuple < ProjectTarget , TargetType > ( ProjectTarget , TargetType ) ) ;
2022-09-16 15:53:01 -04:00
}
}
}
}
}
}
}
}
foreach ( UnrealTargetPlatform CurPlatform in OutValidPlatforms )
{
2023-08-02 20:27:20 -04:00
UEBuildPlatform ? BuildPlatform ;
if ( UEBuildPlatform . TryGetBuildPlatform ( CurPlatform , out BuildPlatform ) )
2022-09-16 15:53:01 -04:00
{
2023-08-02 20:27:20 -04:00
foreach ( KeyValuePair < string , Tuple < UnrealTargetConfiguration , Tuple < ProjectTarget , TargetType > > > SolutionConfigKeyValue in
SolutionConfigurationsValidForProjects )
{
ProjectTarget ProjectTarget = SolutionConfigKeyValue . Value . Item2 . Item1 ;
2022-09-16 15:53:01 -04:00
2024-04-02 20:29:22 -04:00
Action < UnrealArch ? , List < VCSolutionConfigCombination > > AddSolutionConfig = ( UnrealArch ? Arch , List < VCSolutionConfigCombination > OutSolutionConfigs ) = >
2022-09-16 15:53:01 -04:00
{
2023-08-02 20:27:20 -04:00
// e.g. "Development|Win64 = Development|Win64"
string SolutionConfigName = SolutionConfigKeyValue . Key ;
UnrealTargetConfiguration Configuration = SolutionConfigKeyValue . Value . Item1 ;
TargetType TargetType = SolutionConfigKeyValue . Value . Item2 . Item2 ;
string SolutionPlatformName = CurPlatform . ToString ( ) ;
2023-08-11 12:50:25 -04:00
// We use RequiresArchitectureFilenames to determine whether the architecture suffix should be added.
// This is used to tell us what the "default" architecture is.
if ( Arch ! = null & & BuildPlatform . ArchitectureConfig . RequiresArchitectureFilenames ( new UnrealArchitectures ( Arch . Value ) ) )
2023-08-02 20:27:20 -04:00
{
SolutionPlatformName + = $"-{Arch}" ;
}
string SolutionConfigAndPlatformPair = SolutionConfigName + "|" + SolutionPlatformName ;
OutSolutionConfigs . Add (
new VCSolutionConfigCombination ( SolutionConfigAndPlatformPair )
{
Configuration = Configuration ,
Platform = CurPlatform ,
TargetConfigurationName = TargetType ,
Architecture = Arch
}
) ;
2023-08-11 12:50:25 -04:00
} ;
2024-02-25 21:51:10 -05:00
UnrealArchitectures ? Architectures = GetPlatformArchitecturesToGenerate ( BuildPlatform , ProjectTarget ) ;
2023-08-11 12:50:25 -04:00
if ( Architectures = = null )
{
AddSolutionConfig ( null , OutSolutionConfigs ) ;
}
else
{
foreach ( UnrealArch Arch in Architectures . Architectures )
{
AddSolutionConfig ( Arch , OutSolutionConfigs ) ;
}
2022-09-16 15:53:01 -04:00
}
2023-08-02 20:27:20 -04:00
}
2022-09-16 15:53:01 -04:00
}
}
// Sort the list of solution platform strings alphabetically (Visual Studio prefers it)
OutSolutionConfigs . Sort (
new Comparison < VCSolutionConfigCombination > (
( x , y ) = >
{
return String . Compare ( x . VCSolutionConfigAndPlatformName , y . VCSolutionConfigAndPlatformName ,
StringComparison . InvariantCultureIgnoreCase ) ;
}
)
) ;
}
2022-06-24 17:41:49 -04:00
protected override void WriteDebugSolutionFiles ( PlatformProjectGeneratorCollection PlatformProjectGenerators , DirectoryReference IntermediateProjectFilesPath , ILogger Logger )
{
//build and collect UnrealVS configuration
StringBuilder UnrealVSContent = new StringBuilder ( ) ;
foreach ( UnrealTargetPlatform SupportedPlatform in SupportedPlatforms )
{
PlatformProjectGenerator ? ProjGenerator = PlatformProjectGenerators . GetPlatformProjectGenerator ( SupportedPlatform , true ) ;
2024-04-03 17:18:04 -04:00
ProjGenerator ? . GetUnrealVSConfigurationEntries ( UnrealVSContent ) ;
2022-06-24 17:41:49 -04:00
}
if ( UnrealVSContent . Length > 0 )
{
UnrealVSContent . Insert ( 0 , "<UnrealVS>" + ProjectFileGenerator . NewLine ) ;
UnrealVSContent . Append ( "</UnrealVS>" + ProjectFileGenerator . NewLine ) ;
string ConfigFilePath = FileReference . Combine ( IntermediateProjectFilesPath , "UnrealVS.xml" ) . FullName ;
/* bool bSuccess = */
ProjectFileGenerator . WriteFileIfChanged ( ConfigFilePath , UnrealVSContent . ToString ( ) , Logger ) ;
}
}
/// <summary>
/// Takes a string and "cleans it up" to make it parsable by the Visual Studio source control provider's file format
/// </summary>
/// <param name="Str">String to clean up</param>
/// <returns>The cleaned up string</returns>
public string CleanupStringForSCC ( string Str )
{
string Cleaned = Str ;
// SCC is expecting paths to contain only double-backslashes for path separators. It's a bit weird but we need to do it.
Cleaned = Cleaned . Replace ( Path . DirectorySeparatorChar . ToString ( ) , Path . DirectorySeparatorChar . ToString ( ) + Path . DirectorySeparatorChar . ToString ( ) ) ;
Cleaned = Cleaned . Replace ( Path . AltDirectorySeparatorChar . ToString ( ) , Path . DirectorySeparatorChar . ToString ( ) + Path . DirectorySeparatorChar . ToString ( ) ) ;
// SCC is expecting not to see spaces in these strings, so we'll replace spaces with "\u0020"
Cleaned = Cleaned . Replace ( " " , "\\u0020" ) ;
return Cleaned ;
}
}
}