Files
UnrealEngineUWP/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudio/VCProjectFileGenerator.cs
jonathan adamczewski d58996b916 UnrealBuildTool: move some path constants into BuildUtilities
RootDirectory, EngineDirectory, UnrealBuildToolPath are now found in BuildUtilities' UnrealBuild namesapce.

The way these are computed has changed. Previously, it was assumed that the application is UnrealBuildTool, and paths were constructed relative to that assembly.

Now, the assumption is that the process is located under a "Engine/Build/DotNET" sub-path and paths are constructed relative to that.

#jira none

#ROBOMERGE-SOURCE: CL 16607440 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v828-16531559)

[CL 16607455 by jonathan adamczewski in ue5-release-engine-test branch]
2021-06-09 12:55:13 -04:00

984 lines
40 KiB
C#

// 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;
using System.Xml.Linq;
using EpicGames.Core;
using UnrealBuildBase;
#nullable disable
namespace UnrealBuildTool
{
/// <summary>
/// Represents a folder within the master project (e.g. Visual Studio solution)
/// </summary>
class VisualStudioSolutionFolder : MasterProjectFolder
{
/// <summary>
/// Constructor
/// </summary>
public VisualStudioSolutionFolder(ProjectFileGenerator InitOwnerProjectFileGenerator, string InitFolderName)
: base(InitOwnerProjectFileGenerator, InitFolderName)
{
}
}
enum VCProjectFileFormat
{
Default, // Default to the best installed version, but allow SDKs to override
VisualStudio2017,
VisualStudio2019,
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
/// of the include path list in KB.
/// </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>
/// Whether to write a solution option (suo) file for the sln.
/// </summary>
[XmlConfigFile(Category = "BuildConfiguration")]
public bool bWriteSolutionOptionFile = true;
/// <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;
}
/// <summary>
/// Visual C++ project file generator implementation
/// </summary>
class VCProjectFileGenerator : ProjectFileGenerator
{
/// <summary>
/// The settings object
/// </summary>
protected VCProjectFileSettings Settings = new VCProjectFileSettings();
/// <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;
}
if (InArguments.HasOption("-2017"))
{
BuildToolOverride = "-2017";
}
else if (InArguments.HasOption("-2019"))
{
BuildToolOverride = "-2019";
}
else if (InArguments.HasOption("-2022"))
{
BuildToolOverride = "-2022";
}
}
public override string[] GetTargetArguments(string[] Arguments)
{
return Arguments.Where(s => string.Equals(s, BuildToolOverride, StringComparison.InvariantCultureIgnoreCase)).ToArray();
}
/// File extension for project files we'll be generating (e.g. ".vcxproj")
override public string ProjectFileExtension
{
get
{
return ".vcxproj";
}
}
/// <summary>
/// </summary>
public override void CleanProjectFiles(DirectoryReference InMasterProjectDirectory, string InMasterProjectName, DirectoryReference InIntermediateProjectFilesDirectory)
{
FileReference MasterProjectFile = FileReference.Combine(InMasterProjectDirectory, InMasterProjectName);
FileReference MasterProjDeleteFilename = MasterProjectFile + ".sln";
if (FileReference.Exists(MasterProjDeleteFilename))
{
FileReference.Delete(MasterProjDeleteFilename);
}
MasterProjDeleteFilename = MasterProjectFile + ".sdf";
if (FileReference.Exists(MasterProjDeleteFilename))
{
FileReference.Delete(MasterProjDeleteFilename);
}
MasterProjDeleteFilename = MasterProjectFile + ".suo";
if (FileReference.Exists(MasterProjDeleteFilename))
{
FileReference.Delete(MasterProjDeleteFilename);
}
MasterProjDeleteFilename = MasterProjectFile + ".v11.suo";
if (FileReference.Exists(MasterProjDeleteFilename))
{
FileReference.Delete(MasterProjDeleteFilename);
}
MasterProjDeleteFilename = MasterProjectFile + ".v12.suo";
if (FileReference.Exists(MasterProjDeleteFilename))
{
FileReference.Delete(MasterProjDeleteFilename);
}
// Delete the project files folder
if (DirectoryReference.Exists(InIntermediateProjectFilesDirectory))
{
try
{
DirectoryReference.Delete(InIntermediateProjectFilesDirectory, true);
}
catch (Exception Ex)
{
Log.TraceInformation("Error while trying to clean project files path {0}. Ignored.", InIntermediateProjectFilesDirectory);
Log.TraceInformation("\t" + Ex.Message);
}
}
}
/// <summary>
/// Allocates a generator-specific project file object
/// </summary>
/// <param name="InitFilePath">Path to the project file</param>
/// <returns>The newly allocated project file object</returns>
protected override ProjectFile AllocateProjectFile(FileReference InitFilePath)
{
return new VCProjectFile(InitFilePath, Settings.ProjectFileFormat, bUsePrecompiled, BuildToolOverride, Settings);
}
/// ProjectFileGenerator interface
public override MasterProjectFolder AllocateMasterProjectFolder(ProjectFileGenerator InitOwnerProjectFileGenerator, string InitFolderName)
{
return new VisualStudioSolutionFolder(InitOwnerProjectFileGenerator, InitFolderName);
}
/// "4.0", "12.0", or "14.0", etc...
static public string GetProjectFileToolVersionString(VCProjectFileFormat ProjectFileFormat)
{
switch (ProjectFileFormat)
{
case VCProjectFileFormat.VisualStudio2017:
return "15.0";
case VCProjectFileFormat.VisualStudio2019:
return "16.0";
case VCProjectFileFormat.VisualStudio2022:
return "17.0";
}
return string.Empty;
}
/// for instance: <PlatformToolset>v110</PlatformToolset>
static public string GetProjectFilePlatformToolsetVersionString(VCProjectFileFormat ProjectFileFormat)
{
switch (ProjectFileFormat)
{
case VCProjectFileFormat.VisualStudio2017:
return "v141";
case VCProjectFileFormat.VisualStudio2019:
case VCProjectFileFormat.VisualStudio2022:
return "v142";
}
return string.Empty;
}
static public void AppendPlatformToolsetProperty(StringBuilder VCProjectFileContent, VCProjectFileFormat ProjectFileFormat)
{
string ToolVersionString = GetProjectFileToolVersionString(ProjectFileFormat);
string PlatformToolsetVersionString = GetProjectFilePlatformToolsetVersionString(ProjectFileFormat);
VCProjectFileContent.AppendLine(" <PlatformToolset>{0}</PlatformToolset>", PlatformToolsetVersionString);
}
/// <summary>
/// Configures project generator based on command-line options
/// </summary>
/// <param name="Arguments">Arguments passed into the program</param>
/// <param name="IncludeAllPlatforms">True if all platforms should be included</param>
protected override void ConfigureProjectFileGeneration(String[] Arguments, ref bool IncludeAllPlatforms)
{
// Call parent implementation first
base.ConfigureProjectFileGeneration(Arguments, ref IncludeAllPlatforms);
}
/// <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="SupportedPlatformNames">Output string for supported platforms, returned as comma-separated values.</param>
protected override void SetupSupportedPlatformsAndConfigurations(bool IncludeAllPlatforms, out string SupportedPlatformNames)
{
// Call parent implementation to figure out the actual platforms
base.SetupSupportedPlatformsAndConfigurations(IncludeAllPlatforms, out SupportedPlatformNames);
// If we have a non-default setting for visual studio, check the compiler exists. If not, revert to the default.
if (Settings.ProjectFileFormat == VCProjectFileFormat.VisualStudio2017)
{
if (!WindowsPlatform.HasCompiler(WindowsCompiler.VisualStudio2017))
{
Log.TraceWarning("Visual Studio C++ 2017 installation not found - ignoring preferred project file format.");
Settings.ProjectFileFormat = VCProjectFileFormat.Default;
}
}
else if (Settings.ProjectFileFormat == VCProjectFileFormat.VisualStudio2019)
{
if (!WindowsPlatform.HasCompiler(WindowsCompiler.VisualStudio2019))
{
Log.TraceWarning("Visual Studio C++ 2019 installation not found - ignoring preferred project file format.");
Settings.ProjectFileFormat = VCProjectFileFormat.Default;
}
}
else if (Settings.ProjectFileFormat == VCProjectFileFormat.VisualStudio2022)
{
if (!WindowsPlatform.HasCompiler(WindowsCompiler.VisualStudio2022))
{
Log.TraceWarning("Visual Studio C++ 2022 installation not found - ignoring preferred project file format.");
Settings.ProjectFileFormat = VCProjectFileFormat.Default;
}
}
// 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)
{
// Pick the best platform installed by default
if (WindowsPlatform.HasCompiler(WindowsCompiler.VisualStudio2019) && WindowsPlatform.HasIDE(WindowsCompiler.VisualStudio2019))
{
Settings.ProjectFileFormat = VCProjectFileFormat.VisualStudio2019;
}
else if (WindowsPlatform.HasCompiler(WindowsCompiler.VisualStudio2017) && WindowsPlatform.HasIDE(WindowsCompiler.VisualStudio2017))
{
Settings.ProjectFileFormat = VCProjectFileFormat.VisualStudio2017;
}
else if (WindowsPlatform.HasCompiler(WindowsCompiler.VisualStudio2022) && WindowsPlatform.HasIDE(WindowsCompiler.VisualStudio2022))
{
Settings.ProjectFileFormat = VCProjectFileFormat.VisualStudio2022;
}
// 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)
{
Settings.ProjectFileFormat = ProposedFormat;
}
}
}
}
}
}
}
/// <summary>
/// Used to sort VC solution config names along with the config and platform values
/// </summary>
class VCSolutionConfigCombination
{
/// <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;
public override string ToString()
{
return String.Format("{0}={1} {2} {3}", VCSolutionConfigAndPlatformName, Configuration, Platform, TargetConfigurationName);
}
}
/// <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>
/// <returns>The generated solution configuration name</returns>
string MakeSolutionConfigurationName(UnrealTargetConfiguration Configuration, TargetType TargetType)
{
string SolutionConfigName = Configuration.ToString();
// 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();
}
return SolutionConfigName;
}
static IDictionary<MasterProjectFolder, Guid> GenerateProjectFolderGuids(MasterProjectFolder RootFolder)
{
IDictionary<MasterProjectFolder, Guid> Guids = new Dictionary<MasterProjectFolder, Guid>();
foreach (MasterProjectFolder Folder in RootFolder.SubFolders)
{
GenerateProjectFolderGuids("UE5", Folder, Guids);
}
return Guids;
}
static void GenerateProjectFolderGuids(string ParentPath, MasterProjectFolder Folder, IDictionary<MasterProjectFolder, Guid> Guids)
{
string Path = String.Format("{0}/{1}", ParentPath, Folder.FolderName);
Guids[Folder] = MakeMd5Guid(Encoding.UTF8.GetBytes(Path));
foreach (MasterProjectFolder 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);
}
/// <summary>
/// Writes the project files to disk
/// </summary>
/// <returns>True if successful</returns>
protected override bool WriteProjectFiles(PlatformProjectGeneratorCollection PlatformProjectGenerators)
{
if (!base.WriteProjectFiles(PlatformProjectGenerators))
{
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(UnrealBuildTool.EngineSourceDirectory, "Programs", "AutomationTool");
new XDocument(
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",
new XAttribute("Include", AutomationProject.ProjectFilePath.MakeRelativeTo(AutomationToolDir))
)
)
)
).Save(FileReference.Combine(AutomationToolDir, "AutomationTool.csproj.References").FullName);
}
return true;
}
protected override bool WriteMasterProjectFile(ProjectFile UBTProject, PlatformProjectGeneratorCollection PlatformProjectGenerators)
{
bool bSuccess = true;
string SolutionFileName = MasterProjectName + ".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 if (Settings.ProjectFileFormat == VCProjectFileFormat.VisualStudio2019)
{
VCSolutionFileContent.AppendLine();
VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
VCSolutionFileContent.AppendLine("# Visual Studio Version 16");
VCSolutionFileContent.AppendLine("VisualStudioVersion = 16.0.28315.86");
VCSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1");
}
else if (Settings.ProjectFileFormat == VCProjectFileFormat.VisualStudio2017)
{
VCSolutionFileContent.AppendLine();
VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
VCSolutionFileContent.AppendLine("# Visual Studio 15");
VCSolutionFileContent.AppendLine("VisualStudioVersion = 15.0.25807.0");
VCSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1");
}
else
{
throw new BuildException("Unexpected ProjectFileFormat");
}
IDictionary<MasterProjectFolder, Guid> ProjectFolderGuids = GenerateProjectFolderGuids(RootFolder);
// 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<MasterProjectFolder> AllSolutionFolders = ProjectFolderGuids.Keys.OrderBy(Folder => Folder.FolderName).ThenBy(Folder => ProjectFolderGuids[Folder]);
foreach (MasterProjectFolder CurFolder in AllSolutionFolders)
{
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.MasterProjectPath) + "\", \"" + 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(UnrealBuild.EngineDirectory, "Extras", "VisualStudioDebugging", "Unreal.natvis");
// 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(MasterProjectPath));
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.
List<VCSolutionConfigCombination> SolutionConfigCombinations = new List<VCSolutionConfigCombination>();
// The "Global" section has source control, solution configurations, project configurations,
// preferences, and project hierarchy data
{
VCSolutionFileContent.AppendLine("Global");
{
HashSet<UnrealTargetPlatform> PlatformsValidForProjects = new HashSet<UnrealTargetPlatform>();
{
VCSolutionFileContent.AppendLine(" GlobalSection(SolutionConfigurationPlatforms) = preSolution");
Dictionary<string, Tuple<UnrealTargetConfiguration, TargetType>> SolutionConfigurationsValidForProjects = new Dictionary<string, Tuple<UnrealTargetConfiguration, TargetType>>();
foreach (UnrealTargetConfiguration CurConfiguration in SupportedConfigurations)
{
if (InstalledPlatformInfo.IsValidConfiguration(CurConfiguration, EProjectType.Code))
{
foreach (UnrealTargetPlatform CurPlatform in SupportedPlatforms)
{
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 + "' to have at least one ProjectTarget associated with it!");
}
// Figure out the set of valid target configuration names
foreach (ProjectTarget ProjectTarget in CurProject.ProjectTargets)
{
if (VCProjectFile.IsValidProjectPlatformAndConfiguration(ProjectTarget, CurPlatform, CurConfiguration, PlatformProjectGenerators))
{
PlatformsValidForProjects.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 = MakeSolutionConfigurationName(CurConfiguration, TargetType);
SolutionConfigurationsValidForProjects[SolutionConfigName] = new Tuple<UnrealTargetConfiguration, TargetType>(CurConfiguration, TargetType);
}
}
}
}
}
}
}
}
foreach (UnrealTargetPlatform CurPlatform in PlatformsValidForProjects)
{
foreach (KeyValuePair<string, Tuple<UnrealTargetConfiguration, TargetType>> SolutionConfigKeyValue in SolutionConfigurationsValidForProjects)
{
// e.g. "Development|Win64 = Development|Win64"
string SolutionConfigName = SolutionConfigKeyValue.Key;
UnrealTargetConfiguration Configuration = SolutionConfigKeyValue.Value.Item1;
TargetType TargetType = SolutionConfigKeyValue.Value.Item2;
string SolutionPlatformName = CurPlatform.ToString();
string SolutionConfigAndPlatformPair = SolutionConfigName + "|" + SolutionPlatformName;
SolutionConfigCombinations.Add(
new VCSolutionConfigCombination
{
VCSolutionConfigAndPlatformName = SolutionConfigAndPlatformPair,
Configuration = Configuration,
Platform = CurPlatform,
TargetConfigurationName = TargetType
}
);
}
}
// Sort the list of solution platform strings alphabetically (Visual Studio prefers it)
SolutionConfigCombinations.Sort(
new Comparison<VCSolutionConfigCombination>(
(x, y) => { return String.Compare(x.VCSolutionConfigAndPlatformName, y.VCSolutionConfigAndPlatformName, StringComparison.InvariantCultureIgnoreCase); }
)
);
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);
VCSolutionFileContent.AppendLine("\t\tAvailablePlatforms={0}", string.Join(";", PlatformsValidForProjects.Select(platform => platform.ToString())));
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
MSBuildProjectContext ProjectContext = CurProject.GetMatchingProjectContext(SolutionConfigCombination.TargetConfigurationName, SolutionConfigCombination.Configuration, SolutionConfigCombination.Platform, PlatformProjectGenerators);
// 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<MasterProjectFolder> /* Folders */ > FolderProcessorFunction = null;
FolderProcessorFunction = (LocalVCSolutionFileContent, LocalMasterProjectFolders) =>
{
foreach (MasterProjectFolder CurFolder in LocalMasterProjectFolders)
{
string CurFolderGUIDString = ProjectFolderGuids[CurFolder].ToString("B").ToUpperInvariant();
foreach (MSBuildProjectFile ChildProject in CurFolder.ChildProjects)
{
// e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
LocalVCSolutionFileContent.AppendLine(" " + ChildProject.ProjectGUID.ToString("B").ToUpperInvariant() + " = " + CurFolderGUIDString);
}
foreach (MasterProjectFolder SubFolder in CurFolder.SubFolders)
{
// 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(MasterProjectPath, SolutionFileName).FullName;
bSuccess = WriteFileIfChanged(SolutionFilePath, VCSolutionFileContent.ToString());
}
// Save a solution config file which selects the development editor configuration by default.
if (bSuccess && Settings.bWriteSolutionOptionFile)
{
// 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.VisualStudio2017:
SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".vs", Path.GetFileNameWithoutExtension(SolutionFileName), "v15", ".suo");
break;
case VCProjectFileFormat.VisualStudio2019:
SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".vs", Path.GetFileNameWithoutExtension(SolutionFileName), "v16", ".suo");
break;
case VCProjectFileFormat.VisualStudio2022:
SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".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
VCSolutionConfigCombination DefaultConfig = SolutionConfigCombinations.Find(x => x.Configuration == UnrealTargetConfiguration.Development && x.Platform == UnrealTargetPlatform.Win64 && x.TargetConfigurationName == TargetType.Editor);
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();
if (Settings.ProjectFileFormat >= VCProjectFileFormat.VisualStudio2017)
{
BuildSolutionExplorerState_VS2017(RootFolder, "", ExplorerState, DefaultProject);
}
else
{
BuildSolutionExplorerState_VS2015(AllProjectFiles, ExplorerState, DefaultProject, IncludeEnginePrograms);
}
Options.SetExplorerState(ExplorerState);
// Write the file
if (Options.Sections.Count > 0)
{
Options.Write(SolutionOptionsFileName.FullName);
}
}
}
return bSuccess;
}
protected override void WriteDebugSolutionFiles(PlatformProjectGeneratorCollection PlatformProjectGenerators, DirectoryReference IntermediateProjectFilesPath)
{
//build and collect UnrealVS configuration
StringBuilder UnrealVSContent = new StringBuilder();
foreach (UnrealTargetPlatform SupportedPlatform in SupportedPlatforms)
{
PlatformProjectGenerator ProjGenerator = PlatformProjectGenerators.GetPlatformProjectGenerator(SupportedPlatform, true);
if (ProjGenerator != null)
{
ProjGenerator.GetUnrealVSConfigurationEntries(UnrealVSContent);
}
}
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());
}
}
static void BuildSolutionExplorerState_VS2017(MasterProjectFolder Folder, string Suffix, VCSolutionExplorerState ExplorerState, ProjectFile DefaultProject)
{
foreach (ProjectFile Project in Folder.ChildProjects)
{
string ProjectIdentifier = String.Format("{0}{1}", Project.ProjectFilePath.GetFileNameWithoutExtension(), Suffix);
if (Project == DefaultProject)
{
ExplorerState.OpenProjects.Add(new Tuple<string, string[]>(ProjectIdentifier, new string[] { ProjectIdentifier }));
}
else
{
ExplorerState.OpenProjects.Add(new Tuple<string, string[]>(ProjectIdentifier, new string[] { }));
}
}
foreach (MasterProjectFolder SubFolder in Folder.SubFolders)
{
string SubFolderName = SubFolder.FolderName + Suffix;
if (SubFolderName == "Automation;Programs")
{
ExplorerState.OpenProjects.Add(new Tuple<string, string[]>(SubFolderName, new string[] { }));
}
else
{
ExplorerState.OpenProjects.Add(new Tuple<string, string[]>(SubFolderName, new string[] { SubFolderName }));
}
BuildSolutionExplorerState_VS2017(SubFolder, ";" + SubFolderName, ExplorerState, DefaultProject);
}
}
static void BuildSolutionExplorerState_VS2015(List<ProjectFile> AllProjectFiles, VCSolutionExplorerState ExplorerState, ProjectFile DefaultProject, bool IncludeEnginePrograms)
{
foreach (ProjectFile ProjectFile in AllProjectFiles)
{
string ProjectName = ProjectFile.ProjectFilePath.GetFileNameWithoutExtension();
if (ProjectFile == DefaultProject)
{
ExplorerState.OpenProjects.Add(new Tuple<string, string[]>(ProjectName, new string[] { ProjectName }));
}
else
{
ExplorerState.OpenProjects.Add(new Tuple<string, string[]>(ProjectName, new string[] { }));
}
}
if (IncludeEnginePrograms)
{
ExplorerState.OpenProjects.Add(new Tuple<string, string[]>("Automation", new string[0]));
}
}
/// <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;
}
}
}