Ejemplo n.º 1
0
        public static VCSolutionOptions Read(string InputFileName)
        {
            IOleStorage Storage = null;

            StgOpenStorage(InputFileName, null, STGM.Direct | STGM.Read | STGM.ShareExclusive, IntPtr.Zero, 0, out Storage);
            try
            {
                IOleEnumSTATSTG Enumerator = null;
                Storage.EnumElements(0, IntPtr.Zero, 0, out Enumerator);
                try
                {
                    uint      Fetched;
                    STATSTG[] Stats = new STATSTG[200];
                    Enumerator.Next((uint)Stats.Length, Stats, out Fetched);

                    VCSolutionOptions Options = new VCSolutionOptions();
                    foreach (STATSTG Stat in Stats)
                    {
                        if (Stat.pwcsName == "SolutionConfiguration")
                        {
                            IOleStream OleStream;
                            Storage.OpenStream(Stat.pwcsName, IntPtr.Zero, STGM.Read | STGM.ShareExclusive, 0, out OleStream);
                            try
                            {
                                uint   SizeRead;
                                byte[] Buffer = new byte[Stat.cbSize];
                                OleStream.Read(Buffer, (uint)Stat.cbSize, out SizeRead);

                                using (MemoryStream InputStream = new MemoryStream(Buffer, false))
                                {
                                    BinaryReader Reader = new BinaryReader(InputStream, Encoding.Unicode);
                                    while (InputStream.Position < InputStream.Length)
                                    {
                                        Options.SolutionConfiguration.Add(VCBinarySetting.Read(Reader));
                                    }
                                }
                            }
                            finally
                            {
                                Marshal.ReleaseComObject(OleStream);
                            }
                        }
                    }
                    return(Options);
                }
                finally
                {
                    Marshal.ReleaseComObject(Enumerator);
                }
            }
            finally
            {
                Marshal.ReleaseComObject(Storage);
            }
        }
		protected override bool WriteMasterProjectFile(ProjectFile UBTProject)
		{
			bool bSuccess = true;

			string SolutionFileName = MasterProjectName + ".sln";

			// Setup solution file content
			StringBuilder VCSolutionFileContent = new StringBuilder();

			const string VersionTag = "# UnrealEngineGeneratedSolutionVersion=1.0";

			// Solution file header
			if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2017)
			{
				VCSolutionFileContent.Append(
					ProjectFileGenerator.NewLine +
					"Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
					"# Visual Studio 15" + ProjectFileGenerator.NewLine +
					"VisualStudioVersion = 15.0.25618.0" + ProjectFileGenerator.NewLine +
					"MinimumVisualStudioVersion = 10.0.40219.1" + ProjectFileGenerator.NewLine);
			}
			else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2015)
			{
				VCSolutionFileContent.Append(
					ProjectFileGenerator.NewLine +
					"Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
					"# Visual Studio 14" + ProjectFileGenerator.NewLine +
					"VisualStudioVersion = 14.0.22310.1" + ProjectFileGenerator.NewLine +
					"MinimumVisualStudioVersion = 10.0.40219.1" + ProjectFileGenerator.NewLine);
			}
			else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2013)
			{
				VCSolutionFileContent.Append(
					ProjectFileGenerator.NewLine +
					"Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
					"# Visual Studio 2013" + ProjectFileGenerator.NewLine +
					VersionTag + ProjectFileGenerator.NewLine);
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2012)
            {
                VCSolutionFileContent.Append(
                    ProjectFileGenerator.NewLine +
                    "Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
                    "# Visual Studio 2012" + ProjectFileGenerator.NewLine +
                    VersionTag + ProjectFileGenerator.NewLine);
            }
			else
			{
				throw new BuildException("Unexpected ProjectFileFormat");
			}

			// Find the projects for ShaderCompileWorker and UnrealLightmass
			ProjectFile ShaderCompileWorkerProject = null;
			ProjectFile UnrealLightmassProject = null;
			foreach (ProjectFile Project in AllProjectFiles)
			{
				if (Project.ProjectTargets.Count == 1)
				{
					FileReference TargetFilePath = Project.ProjectTargets[0].TargetFilePath;
					if (TargetFilePath != null)
					{
						string TargetFileName = TargetFilePath.GetFileNameWithoutAnyExtensions();
						if (TargetFileName.Equals("ShaderCompileWorker", StringComparison.InvariantCultureIgnoreCase))
						{
							ShaderCompileWorkerProject = Project;
						}
						else if (TargetFileName.Equals("UnrealLightmass", StringComparison.InvariantCultureIgnoreCase))
						{
							UnrealLightmassProject = Project;
						}
					}
					if (ShaderCompileWorkerProject != null
						&& UnrealLightmassProject != null)
					{
						break;
					}
				}
			}

			// 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
				{
					List<MasterProjectFolder> AllSolutionFolders = new List<MasterProjectFolder>();
					System.Action<List<MasterProjectFolder> /* Folders */ > GatherFoldersFunction = null;
					GatherFoldersFunction = FolderList =>
						{
							AllSolutionFolders.AddRange(FolderList);
							foreach (MasterProjectFolder CurSubFolder in FolderList)
							{
								GatherFoldersFunction(CurSubFolder.SubFolders);
							}
						};
					GatherFoldersFunction(RootFolder.SubFolders);

					foreach (VisualStudioSolutionFolder CurFolder in AllSolutionFolders)
					{
						string FolderGUIDString = CurFolder.FolderGUID.ToString("B").ToUpperInvariant();
						VCSolutionFileContent.Append(
								"Project(\"" + SolutionFolderEntryGUID + "\") = \"" + CurFolder.FolderName + "\", \"" + CurFolder.FolderName + "\", \"" + FolderGUIDString + "\"" + ProjectFileGenerator.NewLine);

						// Add any files that are inlined right inside the solution folder
						if (CurFolder.Files.Count > 0)
						{
							VCSolutionFileContent.Append(
									"	ProjectSection(SolutionItems) = preProject" + ProjectFileGenerator.NewLine);
							foreach (string CurFile in CurFolder.Files)
							{
								// Syntax is:  <relative file path> = <relative file path>
								VCSolutionFileContent.Append(
									"		" + CurFile + " = " + CurFile + ProjectFileGenerator.NewLine);
							}
							VCSolutionFileContent.Append(
									"	EndProjectSection" + ProjectFileGenerator.NewLine);
						}

						VCSolutionFileContent.Append(
								"EndProject" + ProjectFileGenerator.NewLine
							);
					}
				}


				// Project files
				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.Append(
							"Project(\"" + ProjectTypeGUID + "\") = \"" + ProjectNameInSolution + "\", \"" + CurProject.ProjectFilePath.MakeRelativeTo(ProjectFileGenerator.MasterProjectPath) + "\", \"" + ProjectGUID + "\"" + ProjectFileGenerator.NewLine);

					// 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);
						}
						if (UEBuildConfiguration.bEditorDependsOnShaderCompileWorker && CurProject.IsGeneratedProject && ShaderCompileWorkerProject != null && CurProject.ProjectTargets.Any(x => x.TargetRules != null && x.TargetRules.Type == TargetRules.TargetType.Editor))
						{
							Dependencies.Add(ShaderCompileWorkerProject);
						}
						Dependencies.AddRange(CurProject.DependsOnProjects);

						if (Dependencies.Count > 0)
						{
							VCSolutionFileContent.Append("\tProjectSection(ProjectDependencies) = postProject" + ProjectFileGenerator.NewLine);

							// Setup any addition dependencies this project has...
							foreach (ProjectFile DependsOnProject in Dependencies)
							{
								string DependsOnProjectGUID = ((MSBuildProjectFile)DependsOnProject).ProjectGUID.ToString("B").ToUpperInvariant();
								VCSolutionFileContent.Append("\t\t" + DependsOnProjectGUID + " = " + DependsOnProjectGUID + ProjectFileGenerator.NewLine);
							}

							VCSolutionFileContent.Append("\tEndProjectSection" + ProjectFileGenerator.NewLine);
						}
					}

					VCSolutionFileContent.Append(
							"EndProject" + ProjectFileGenerator.NewLine
						);
				}
			}

			// 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.Append(
					"Global" + ProjectFileGenerator.NewLine);
				{
					{
						VCSolutionFileContent.Append(
							"	GlobalSection(SolutionConfigurationPlatforms) = preSolution" + ProjectFileGenerator.NewLine);

						Dictionary<string, Tuple<UnrealTargetConfiguration, string>> SolutionConfigurationsValidForProjects = new Dictionary<string, Tuple<UnrealTargetConfiguration, string>>();
						HashSet<UnrealTargetPlatform> PlatformsValidForProjects = new HashSet<UnrealTargetPlatform>();

						foreach (UnrealTargetConfiguration CurConfiguration in SupportedConfigurations)
						{
							if (UnrealBuildTool.IsValidConfiguration(CurConfiguration))
							{
								foreach (UnrealTargetPlatform CurPlatform in SupportedPlatforms)
								{
									if (UnrealBuildTool.IsValidPlatform(CurPlatform))
									{
										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))
													{
														PlatformsValidForProjects.Add(CurPlatform);

														// Default to a target configuration name of "Game", since that will collapse down to an empty string
														string TargetConfigurationName = TargetRules.TargetType.Game.ToString();
														if (ProjectTarget.TargetRules != null)
														{
															TargetConfigurationName = ProjectTarget.TargetRules.ConfigurationName;
														}

														string SolutionConfigName = MakeSolutionConfigurationName(CurConfiguration, TargetConfigurationName);
														SolutionConfigurationsValidForProjects[SolutionConfigName] = new Tuple<UnrealTargetConfiguration, string>(CurConfiguration, TargetConfigurationName);
													}
												}
											}
										}
									}
								}
							}
						}

						foreach (UnrealTargetPlatform CurPlatform in PlatformsValidForProjects)
						{
							foreach (KeyValuePair<string, Tuple<UnrealTargetConfiguration, string>> SolutionConfigKeyValue in SolutionConfigurationsValidForProjects)
							{
								// e.g.  "Development|Win64 = Development|Win64"
								string SolutionConfigName = SolutionConfigKeyValue.Key;
								UnrealTargetConfiguration Configuration = SolutionConfigKeyValue.Value.Item1;
								string TargetConfigurationName = SolutionConfigKeyValue.Value.Item2;

								string SolutionPlatformName = CurPlatform.ToString();

								string SolutionConfigAndPlatformPair = SolutionConfigName + "|" + SolutionPlatformName;
								SolutionConfigCombinations.Add(
										new VCSolutionConfigCombination
										{
											VCSolutionConfigAndPlatformName = SolutionConfigAndPlatformPair,
											Configuration = Configuration,
											Platform = CurPlatform,
											TargetConfigurationName = TargetConfigurationName
										}
									);
							}
						}

						// 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.Append(
									"		" + SolutionConfigCombination.VCSolutionConfigAndPlatformName + " = " + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ProjectFileGenerator.NewLine);
								AppendedSolutionConfigAndPlatformNames.Add(SolutionConfigCombination.VCSolutionConfigAndPlatformName);
							}
						}

						VCSolutionFileContent.Append(
							"	EndGlobalSection" + ProjectFileGenerator.NewLine);
					}


					// 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.Append(
							"	GlobalSection(ProjectConfigurationPlatforms) = postSolution" + ProjectFileGenerator.NewLine);

						List<VCSolutionConfigCombination> CombinationsThatWereMatchedToProjects = new List<VCSolutionConfigCombination>();

						foreach (MSBuildProjectFile CurProject in AllProjectFiles)
						{
							// NOTE: We don't emit solution configuration entries for "stub" projects.  Those projects are only
							// built using UnrealBuildTool and don't require a presence in the solution project list

							// NOTE: We also process projects that were "imported" here, hoping to match those to our solution
							//       configurations.  In some cases this may not be successful though.  Imported projects
							//       should always be carefully setup to match our project generator's solution configs.
							if (!CurProject.IsStubProject)
							{
								if (CurProject.ProjectTargets.Count == 0)
								{
									throw new BuildException("Expecting project '" + CurProject.ProjectFilePath + "' to have at least one ProjectTarget associated with it!");
								}
								bool IsProgramProject = CurProject.ProjectTargets[0].TargetRules != null && CurProject.ProjectTargets[0].TargetRules.Type == TargetRules.TargetType.Program;

								HashSet<string> GameOrProgramConfigsAlreadyMapped = new HashSet<string>();
								foreach (VCSolutionConfigCombination SolutionConfigCombination in SolutionConfigCombinations)
								{
									// Handle aliasing of Program and Game target configuration names
									if ((IsProgramProject && GameOrProgramConfigsAlreadyMapped.Add(SolutionConfigCombination.VCSolutionConfigAndPlatformName)) ||
										IsProgramProject && SolutionConfigCombination.TargetConfigurationName != TargetRules.TargetType.Game.ToString() ||
										!IsProgramProject && SolutionConfigCombination.TargetConfigurationName != TargetRules.TargetType.Program.ToString())
									{
										string TargetConfigurationName = SolutionConfigCombination.TargetConfigurationName;
										if (IsProgramProject)
										{
											TargetConfigurationName = TargetRules.TargetType.Program.ToString();
										}

										// Now, we want to find a target in this project that maps to the current solution config combination.  Only up to one target should
										// and every solution config combination should map to at least one target in one project (otherwise we shouldn't have added it!).
										ProjectTarget MatchingProjectTarget = null;
										foreach (ProjectTarget ProjectTarget in CurProject.ProjectTargets)
										{
											bool IsMatchingCombination = VCProjectFile.IsValidProjectPlatformAndConfiguration(ProjectTarget, SolutionConfigCombination.Platform, SolutionConfigCombination.Configuration);
											if (ProjectTarget.TargetRules != null)
											{
												if (TargetConfigurationName != ProjectTarget.TargetRules.ConfigurationName)
												{
													// Solution configuration name for this combination doesn't match this target's configuration name.  It's not buildable.
													IsMatchingCombination = false;
												}
											}
											else
											{
												// UBT gets a pass because it is a dependency of every single configuration combination
												if (CurProject != UBTProject &&
													!CurProject.ShouldBuildForAllSolutionTargets &&
													TargetConfigurationName != TargetRules.TargetType.Game.ToString())
												{
													// Can't build non-generated project in configurations except for the default (Game)
													IsMatchingCombination = false;
												}
											}

											if (IsMatchingCombination)
											{
												if (MatchingProjectTarget != null)
												{
													// Not expecting more than one target to match a single solution configuration per project!
													throw new BuildException("Not expecting more than one target for project " + CurProject.ProjectFilePath + " to match solution configuration " + SolutionConfigCombination.VCSolutionConfigAndPlatformName);
												}

												MatchingProjectTarget = ProjectTarget;

												// NOTE: For faster perf, we could "break" here and bail out early, but that would circumvent the error checking
												//		 for multiple targets within a project that may map to a single solution configuration.
											}
										}

										UnrealTargetConfiguration SolutionConfiguration = SolutionConfigCombination.Configuration;
										UnrealTargetPlatform SolutionPlatform = SolutionConfigCombination.Platform;


										if (MatchingProjectTarget == null)
										{
											// The current configuration/platform and target configuration name doesn't map to anything our project actually supports.
											// We'll map it to a default config.
											SolutionConfiguration = UnrealTargetConfiguration.Development;

											// Prefer using Win64 as the default, but fall back to a platform the project file actually supports if needed.  This is for
											// projects that can never be compiled in Windows, such as UnrealLaunchDaemon which is an iOS-only program
											SolutionPlatform = UnrealTargetPlatform.Win64;
											if (CurProject.ProjectTargets[0].TargetRules != null)
											{
												List<UnrealTargetPlatform> ProjectSupportedPlatforms = new List<UnrealTargetPlatform>();
												CurProject.ProjectTargets[0].TargetRules.GetSupportedPlatforms(ref ProjectSupportedPlatforms);
												if (!ProjectSupportedPlatforms.Contains(SolutionPlatform))
												{
													SolutionPlatform = ProjectSupportedPlatforms[0];
												}
											}


											if (IsProgramProject)
											{
												TargetConfigurationName = TargetRules.TargetType.Program.ToString();
											}
											else
											{
												TargetConfigurationName = TargetRules.TargetType.Game.ToString();
											}
										}


										// If the project wants to always build in "Development", regardless of what the solution
										// configuration is set to, then we'll do that here.  This is used for projects like
										// UnrealBuildTool and ShaderCompileWorker
										if (MatchingProjectTarget != null)
										{
											if (MatchingProjectTarget.ForceDevelopmentConfiguration)
											{
												SolutionConfiguration = UnrealTargetConfiguration.Development;
											}
										}

										// Always allow SCW and UnrealLighmass to build in editor configurations
										if (MatchingProjectTarget == null && SolutionConfigCombination.TargetConfigurationName == TargetRules.TargetType.Editor.ToString() && SolutionConfigCombination.Platform == UnrealTargetPlatform.Win64)
										{
											if (CurProject == ShaderCompileWorkerProject)
											{
												MatchingProjectTarget = ShaderCompileWorkerProject.ProjectTargets[0];
											}
											else if (CurProject == UnrealLightmassProject)
											{
												MatchingProjectTarget = UnrealLightmassProject.ProjectTargets[0];
											}
										}

										string ProjectConfigName;
										string ProjectPlatformName;
										CurProject.MakeProjectPlatformAndConfigurationNames(SolutionPlatform, SolutionConfiguration, TargetConfigurationName, out ProjectPlatformName, out ProjectConfigName);

										string ProjectConfigAndPlatformPair = ProjectConfigName.ToString() + "|" + ProjectPlatformName.ToString();

										// e.g.  "{4232C52C-680F-4850-8855-DC39419B5E9B}.Debug|iOS.ActiveCfg = iOS_Debug|Win32"
										string CurProjectGUID = CurProject.ProjectGUID.ToString("B").ToUpperInvariant();
										VCSolutionFileContent.Append(
											"		" + CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".ActiveCfg = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine);


										// Set whether this project configuration should be built when the user initiates "build solution"
										if (MatchingProjectTarget != null && CurProject.ShouldBuildByDefaultForSolutionTargets)
										{
											// Some targets are "dummy targets"; they only exist to show user friendly errors in VS. Weed them out here, and don't set them to build by default.
											List<UnrealTargetPlatform> SupportedPlatforms = null;
											if (MatchingProjectTarget.TargetRules != null)
											{
												SupportedPlatforms = new List<UnrealTargetPlatform>();
												MatchingProjectTarget.TargetRules.GetSupportedPlatforms(ref SupportedPlatforms);
											}
											if (SupportedPlatforms == null || SupportedPlatforms.Contains(SolutionConfigCombination.Platform))
											{
												VCSolutionFileContent.Append(
														"		" + CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".Build.0 = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine);

												UEPlatformProjectGenerator ProjGen = UEPlatformProjectGenerator.GetPlatformProjectGenerator(SolutionConfigCombination.Platform, true);
												if (MatchingProjectTarget.ProjectDeploys ||
													((ProjGen != null) && (ProjGen.GetVisualStudioDeploymentEnabled(SolutionPlatform, SolutionConfiguration) == true)))
												{
													VCSolutionFileContent.Append(
															"		" + CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".Deploy.0 = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine);
												}
											}
										}

										CombinationsThatWereMatchedToProjects.Add(SolutionConfigCombination);
									}
								}
							}
						}

						// Check for problems
						foreach (VCSolutionConfigCombination SolutionConfigCombination in SolutionConfigCombinations)
						{
							if (!CombinationsThatWereMatchedToProjects.Contains(SolutionConfigCombination))
							{
								throw new BuildException("Unable to find a ProjectTarget that matches the solution configuration/platform mapping: " + SolutionConfigCombination.Configuration.ToString() + ", " + SolutionConfigCombination.Platform.ToString() + ", " + SolutionConfigCombination.TargetConfigurationName);
							}
						}
						VCSolutionFileContent.Append(
							"	EndGlobalSection" + ProjectFileGenerator.NewLine);
					}


					// Setup other solution properties
					{
						VCSolutionFileContent.Append(
							"	GlobalSection(SolutionProperties) = preSolution" + ProjectFileGenerator.NewLine);

						// 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.Append(
							"		HideSolutionNode = FALSE" + ProjectFileGenerator.NewLine);

						VCSolutionFileContent.Append(
							"	EndGlobalSection" + ProjectFileGenerator.NewLine);
					}



					// Solution directory hierarchy
					{
						VCSolutionFileContent.Append(
							"	GlobalSection(NestedProjects) = preSolution" + ProjectFileGenerator.NewLine);

						// 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 (VisualStudioSolutionFolder CurFolder in LocalMasterProjectFolders)
								{
									string CurFolderGUIDString = CurFolder.FolderGUID.ToString("B").ToUpperInvariant();

									foreach (MSBuildProjectFile ChildProject in CurFolder.ChildProjects)
									{
										//	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
										LocalVCSolutionFileContent.Append(
											"		" + ChildProject.ProjectGUID.ToString("B").ToUpperInvariant() + " = " + CurFolderGUIDString + ProjectFileGenerator.NewLine);
									}

									foreach (VisualStudioSolutionFolder SubFolder in CurFolder.SubFolders)
									{
										//	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
										LocalVCSolutionFileContent.Append(
											"		" + SubFolder.FolderGUID.ToString("B").ToUpperInvariant() + " = " + CurFolderGUIDString + ProjectFileGenerator.NewLine);
									}

									// Recurse into subfolders
									FolderProcessorFunction(LocalVCSolutionFileContent, CurFolder.SubFolders);
								}
							};
						FolderProcessorFunction(VCSolutionFileContent, RootFolder.SubFolders);

						VCSolutionFileContent.Append(
							"	EndGlobalSection" + ProjectFileGenerator.NewLine);
					}
				}

				VCSolutionFileContent.Append(
					"EndGlobal" + ProjectFileGenerator.NewLine);
			}


			// 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)
			{
				// Figure out the filename for the SUO file. VS will automatically import the options from earlier versions if necessary.
				FileReference SolutionOptionsFileName;
				switch (ProjectFileFormat)
                {
                    case VCProjectFileFormat.VisualStudio2012:
						SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, Path.ChangeExtension(SolutionFileName, "v11.suo"));
                        break;
					case VCProjectFileFormat.VisualStudio2013:
						SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, Path.ChangeExtension(SolutionFileName, "v12.suo"));
						break;
					case VCProjectFileFormat.VisualStudio2015:
						SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".vs", Path.GetFileNameWithoutExtension(SolutionFileName), "v14", ".suo");
						break;
					case VCProjectFileFormat.VisualStudio2017:
						SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".vs", Path.GetFileNameWithoutExtension(SolutionFileName), "v15", ".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 (!SolutionOptionsFileName.Exists())
				{
					SolutionOptionsFileName.Directory.CreateDirectory();

					VCSolutionOptions Options = new VCSolutionOptions();

					// Set the default configuration and startup project
					VCSolutionConfigCombination DefaultConfig = SolutionConfigCombinations.Find(x => x.Configuration == UnrealTargetConfiguration.Development && x.Platform == UnrealTargetPlatform.Win64 && x.TargetConfigurationName == "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();
					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]));
					}
					Options.SetExplorerState(ExplorerState);

					// Write the file
					if (Options.Sections.Count > 0)
					{
						Options.Write(SolutionOptionsFileName.FullName);
					}
				}
			}

			return bSuccess;
		}
        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 (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 (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 if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2015)
            {
                VCSolutionFileContent.AppendLine();
                VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
                VCSolutionFileContent.AppendLine("# Visual Studio 14");
                VCSolutionFileContent.AppendLine("VisualStudioVersion = 14.0.22310.1");
                VCSolutionFileContent.AppendLine("MinimumVisualStudioVersion = 10.0.40219.1");
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2013)
            {
                VCSolutionFileContent.AppendLine();
                VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
                VCSolutionFileContent.AppendLine("# Visual Studio 2013");
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2012)
            {
                VCSolutionFileContent.AppendLine();
                VCSolutionFileContent.AppendLine("Microsoft Visual Studio Solution File, Format Version 12.00");
                VCSolutionFileContent.AppendLine("# Visual Studio 2012");
            }
            else
            {
                throw new BuildException("Unexpected ProjectFileFormat");
            }

            // 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
                {
                    List <MasterProjectFolder> AllSolutionFolders = new List <MasterProjectFolder>();
                    System.Action <List <MasterProjectFolder> /* Folders */> GatherFoldersFunction = null;
                    GatherFoldersFunction = FolderList =>
                    {
                        AllSolutionFolders.AddRange(FolderList);
                        foreach (MasterProjectFolder CurSubFolder in FolderList)
                        {
                            GatherFoldersFunction(CurSubFolder.SubFolders);
                        }
                    };
                    GatherFoldersFunction(RootFolder.SubFolders);

                    AllSolutionFolders.Sort((Lhs, Rhs) => Lhs.FolderName.CompareTo(Rhs.FolderName));

                    foreach (VisualStudioSolutionFolder CurFolder in AllSolutionFolders)
                    {
                        string FolderGUIDString = CurFolder.FolderGUID.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(UnrealBuildTool.EngineDirectory, "Extras", "VisualStudioDebugging", "UE4.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(\"{{2150E333-8FDC-42A3-9474-1A3956D46DE8}}\") = \"Visualizers\", \"Visualizers\", \"{0}\"", Guid.NewGuid().ToString("B").ToUpperInvariant()));
                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");
                {
                    {
                        VCSolutionFileContent.AppendLine("	GlobalSection(SolutionConfigurationPlatforms) = preSolution");

                        Dictionary <string, Tuple <UnrealTargetConfiguration, TargetType> > SolutionConfigurationsValidForProjects = new Dictionary <string, Tuple <UnrealTargetConfiguration, TargetType> >();
                        HashSet <UnrealTargetPlatform> PlatformsValidForProjects = new HashSet <UnrealTargetPlatform>();

                        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");
                    }


                    // 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);

                                // 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 (VisualStudioSolutionFolder CurFolder in LocalMasterProjectFolders)
                            {
                                string CurFolderGUIDString = CurFolder.FolderGUID.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 (VisualStudioSolutionFolder SubFolder in CurFolder.SubFolders)
                                {
                                    //	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
                                    LocalVCSolutionFileContent.AppendLine("		"+ SubFolder.FolderGUID.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 && bWriteSolutionOptionFile)
            {
                // Figure out the filename for the SUO file. VS will automatically import the options from earlier versions if necessary.
                FileReference SolutionOptionsFileName;
                switch (ProjectFileFormat)
                {
                case VCProjectFileFormat.VisualStudio2012:
                    SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, Path.ChangeExtension(SolutionFileName, "v11.suo"));
                    break;

                case VCProjectFileFormat.VisualStudio2013:
                    SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, Path.ChangeExtension(SolutionFileName, "v12.suo"));
                    break;

                case VCProjectFileFormat.VisualStudio2015:
                    SolutionOptionsFileName = FileReference.Combine(MasterProjectPath, ".vs", Path.GetFileNameWithoutExtension(SolutionFileName), "v14", ".suo");
                    break;

                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), "v15", ".suo");                             // Still uses v15
                    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(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 (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);
        }
Ejemplo n.º 4
0
        protected override bool WriteMasterProjectFile(ProjectFile UBTProject)
        {
            bool bSuccess = true;

            var SolutionFileName = MasterProjectName + ".sln";

            // Setup solution file content
            var VCSolutionFileContent = new StringBuilder();

            const string VersionTag = "# UnrealEngineGeneratedSolutionVersion=1.0";

            // Solution file header
            if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2015)
            {
                VCSolutionFileContent.Append(
                    ProjectFileGenerator.NewLine +
                    "Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
                    "# Visual Studio 14" + ProjectFileGenerator.NewLine +
                    "VisualStudioVersion = 14.0.22310.1" + ProjectFileGenerator.NewLine +
                    "MinimumVisualStudioVersion = 10.0.40219.1" + ProjectFileGenerator.NewLine);
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2013)
            {
                VCSolutionFileContent.Append(
                    ProjectFileGenerator.NewLine +
                    "Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
                    "# Visual Studio 2013" + ProjectFileGenerator.NewLine +
                    VersionTag + ProjectFileGenerator.NewLine);
            }
            else if (ProjectFileFormat == VCProjectFileFormat.VisualStudio2012)
            {
                VCSolutionFileContent.Append(
                    ProjectFileGenerator.NewLine +
                    "Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
                    "# Visual Studio 2012" + ProjectFileGenerator.NewLine +
                    VersionTag + ProjectFileGenerator.NewLine);
            }
            else
            {
                throw new BuildException("Unexpected ProjectFileFormat");
            }


            // Solution folders, files and project entries
            {
                // This the GUID that Visual Studio uses to identify a solution folder
                var SolutionFolderEntryGUID = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";

                // Solution folders
                {
                    var AllSolutionFolders = new List <MasterProjectFolder>();
                    System.Action <List <MasterProjectFolder> /* Folders */> GatherFoldersFunction = null;
                    GatherFoldersFunction = FolderList =>
                    {
                        AllSolutionFolders.AddRange(FolderList);
                        foreach (var CurSubFolder in FolderList)
                        {
                            GatherFoldersFunction(CurSubFolder.SubFolders);
                        }
                    };
                    GatherFoldersFunction(RootFolder.SubFolders);

                    foreach (VisualStudioSolutionFolder CurFolder in AllSolutionFolders)
                    {
                        var FolderGUIDString = CurFolder.FolderGUID.ToString("B").ToUpperInvariant();
                        VCSolutionFileContent.Append(
                            "Project(\"" + SolutionFolderEntryGUID + "\") = \"" + CurFolder.FolderName + "\", \"" + CurFolder.FolderName + "\", \"" + FolderGUIDString + "\"" + ProjectFileGenerator.NewLine);

                        // Add any files that are inlined right inside the solution folder
                        if (CurFolder.Files.Count > 0)
                        {
                            VCSolutionFileContent.Append(
                                "	ProjectSection(SolutionItems) = preProject"+ ProjectFileGenerator.NewLine);
                            foreach (var CurFile in CurFolder.Files)
                            {
                                // Syntax is:  <relative file path> = <relative file path>
                                VCSolutionFileContent.Append(
                                    "		"+ CurFile + " = " + CurFile + ProjectFileGenerator.NewLine);
                            }
                            VCSolutionFileContent.Append(
                                "	EndProjectSection"+ ProjectFileGenerator.NewLine);
                        }

                        VCSolutionFileContent.Append(
                            "EndProject" + ProjectFileGenerator.NewLine
                            );
                    }
                }


                // Project files
                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.
                    var ProjectNameInSolution = Path.GetFileNameWithoutExtension(CurProject.ProjectFilePath);

                    // Use the existing project's GUID that's already known to us
                    var ProjectGUID = CurProject.ProjectGUID.ToString("B").ToUpperInvariant();

                    VCSolutionFileContent.Append(
                        "Project(\"" + ProjectTypeGUID + "\") = \"" + ProjectNameInSolution + "\", \"" + CurProject.RelativeProjectFilePath + "\", \"" + ProjectGUID + "\"" + ProjectFileGenerator.NewLine);

                    // 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)
                    {
                        var 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.Append("\tProjectSection(ProjectDependencies) = postProject" + ProjectFileGenerator.NewLine);

                            // Setup any addition dependencies this project has...
                            foreach (var DependsOnProject in Dependencies)
                            {
                                var DependsOnProjectGUID = ((MSBuildProjectFile)DependsOnProject).ProjectGUID.ToString("B").ToUpperInvariant();
                                VCSolutionFileContent.Append("\t\t" + DependsOnProjectGUID + " = " + DependsOnProjectGUID + ProjectFileGenerator.NewLine);
                            }

                            VCSolutionFileContent.Append("\tEndProjectSection" + ProjectFileGenerator.NewLine);
                        }
                    }

                    VCSolutionFileContent.Append(
                        "EndProject" + ProjectFileGenerator.NewLine
                        );
                }
            }

            // Solution configuration platforms.  This is just a list of all of the platforms and configurations that
            // appear in Visual Studio's build configuration selector.
            var SolutionConfigCombinations = new List <VCSolutionConfigCombination>();

            // The "Global" section has source control, solution configurations, project configurations,
            // preferences, and project hierarchy data
            {
                VCSolutionFileContent.Append(
                    "Global" + ProjectFileGenerator.NewLine);
                {
                    {
                        VCSolutionFileContent.Append(
                            "	GlobalSection(SolutionConfigurationPlatforms) = preSolution"+ ProjectFileGenerator.NewLine);

                        var SolutionConfigurationsValidForProjects = new Dictionary <string, Tuple <UnrealTargetConfiguration, string> >();
                        var PlatformsValidForProjects = new HashSet <UnrealTargetPlatform>();

                        foreach (var CurConfiguration in SupportedConfigurations)
                        {
                            if (UnrealBuildTool.IsValidConfiguration(CurConfiguration))
                            {
                                foreach (var CurPlatform in SupportedPlatforms)
                                {
                                    if (UnrealBuildTool.IsValidPlatform(CurPlatform))
                                    {
                                        foreach (var 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 (var ProjectTarget in CurProject.ProjectTargets)
                                                {
                                                    if (VCProjectFile.IsValidProjectPlatformAndConfiguration(ProjectTarget, CurPlatform, CurConfiguration))
                                                    {
                                                        PlatformsValidForProjects.Add(CurPlatform);

                                                        // Default to a target configuration name of "Game", since that will collapse down to an empty string
                                                        var TargetConfigurationName = TargetRules.TargetType.Game.ToString();
                                                        if (ProjectTarget.TargetRules != null)
                                                        {
                                                            TargetConfigurationName = ProjectTarget.TargetRules.ConfigurationName;
                                                        }

                                                        var SolutionConfigName = MakeSolutionConfigurationName(CurConfiguration, TargetConfigurationName);
                                                        SolutionConfigurationsValidForProjects[SolutionConfigName] = new Tuple <UnrealTargetConfiguration, string>(CurConfiguration, TargetConfigurationName);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        foreach (var CurPlatform in PlatformsValidForProjects)
                        {
                            foreach (var SolutionConfigKeyValue in SolutionConfigurationsValidForProjects)
                            {
                                // e.g.  "Development|Win64 = Development|Win64"
                                var SolutionConfigName      = SolutionConfigKeyValue.Key;
                                var Configuration           = SolutionConfigKeyValue.Value.Item1;
                                var TargetConfigurationName = SolutionConfigKeyValue.Value.Item2;

                                var SolutionPlatformName = CurPlatform.ToString();

                                var SolutionConfigAndPlatformPair = SolutionConfigName + "|" + SolutionPlatformName;
                                SolutionConfigCombinations.Add(
                                    new VCSolutionConfigCombination
                                {
                                    VCSolutionConfigAndPlatformName = SolutionConfigAndPlatformPair,
                                    Configuration           = Configuration,
                                    Platform                = CurPlatform,
                                    TargetConfigurationName = TargetConfigurationName
                                }
                                    );
                            }
                        }

                        // 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)); }
                                )
                            );

                        var AppendedSolutionConfigAndPlatformNames = new HashSet <string>(StringComparer.InvariantCultureIgnoreCase);
                        foreach (var 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.Append(
                                    "		"+ SolutionConfigCombination.VCSolutionConfigAndPlatformName + " = " + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ProjectFileGenerator.NewLine);
                                AppendedSolutionConfigAndPlatformNames.Add(SolutionConfigCombination.VCSolutionConfigAndPlatformName);
                            }
                        }

                        VCSolutionFileContent.Append(
                            "	EndGlobalSection"+ ProjectFileGenerator.NewLine);
                    }


                    // 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.Append(
                            "	GlobalSection(ProjectConfigurationPlatforms) = postSolution"+ ProjectFileGenerator.NewLine);

                        var CombinationsThatWereMatchedToProjects = new List <VCSolutionConfigCombination>();

                        foreach (MSBuildProjectFile CurProject in AllProjectFiles)
                        {
                            // NOTE: We don't emit solution configuration entries for "stub" projects.  Those projects are only
                            // built using UnrealBuildTool and don't require a presence in the solution project list

                            // NOTE: We also process projects that were "imported" here, hoping to match those to our solution
                            //       configurations.  In some cases this may not be successful though.  Imported projects
                            //       should always be carefully setup to match our project generator's solution configs.
                            if (!CurProject.IsStubProject)
                            {
                                if (CurProject.ProjectTargets.Count == 0)
                                {
                                    throw new BuildException("Expecting project '" + CurProject.ProjectFilePath + "' to have at least one ProjectTarget associated with it!");
                                }
                                var IsProgramProject = CurProject.ProjectTargets[0].TargetRules != null && CurProject.ProjectTargets[0].TargetRules.Type == TargetRules.TargetType.Program;

                                var GameOrProgramConfigsAlreadyMapped = new HashSet <string>();
                                foreach (var SolutionConfigCombination in SolutionConfigCombinations)
                                {
                                    // Handle aliasing of Program and Game target configuration names
                                    if ((IsProgramProject && GameOrProgramConfigsAlreadyMapped.Add(SolutionConfigCombination.VCSolutionConfigAndPlatformName)) ||
                                        IsProgramProject && SolutionConfigCombination.TargetConfigurationName != TargetRules.TargetType.Game.ToString() ||
                                        !IsProgramProject && SolutionConfigCombination.TargetConfigurationName != TargetRules.TargetType.Program.ToString())
                                    {
                                        var TargetConfigurationName = SolutionConfigCombination.TargetConfigurationName;
                                        if (IsProgramProject && TargetConfigurationName == TargetRules.TargetType.Game.ToString())
                                        {
                                            TargetConfigurationName = TargetRules.TargetType.Program.ToString();
                                        }

                                        // Now, we want to find a target in this project that maps to the current solution config combination.  Only up to one target should
                                        // and every solution config combination should map to at least one target in one project (otherwise we shouldn't have added it!).
                                        ProjectTarget MatchingProjectTarget = null;
                                        foreach (var ProjectTarget in CurProject.ProjectTargets)
                                        {
                                            bool IsMatchingCombination = VCProjectFile.IsValidProjectPlatformAndConfiguration(ProjectTarget, SolutionConfigCombination.Platform, SolutionConfigCombination.Configuration);
                                            if (ProjectTarget.TargetRules != null)
                                            {
                                                if (TargetConfigurationName != ProjectTarget.TargetRules.ConfigurationName)
                                                {
                                                    // Solution configuration name for this combination doesn't match this target's configuration name.  It's not buildable.
                                                    IsMatchingCombination = false;
                                                }
                                            }
                                            else
                                            {
                                                // UBT gets a pass because it is a dependency of every single configuration combination
                                                if (CurProject != UBTProject &&
                                                    !CurProject.ShouldBuildForAllSolutionTargets &&
                                                    TargetConfigurationName != TargetRules.TargetType.Game.ToString())
                                                {
                                                    // Can't build non-generated project in configurations except for the default (Game)
                                                    IsMatchingCombination = false;
                                                }
                                            }

                                            if (IsMatchingCombination)
                                            {
                                                if (MatchingProjectTarget != null)
                                                {
                                                    // Not expecting more than one target to match a single solution configuration per project!
                                                    throw new BuildException("Not expecting more than one target for project " + CurProject.ProjectFilePath + " to match solution configuration " + SolutionConfigCombination.VCSolutionConfigAndPlatformName);
                                                }

                                                MatchingProjectTarget = ProjectTarget;

                                                // NOTE: For faster perf, we could "break" here and bail out early, but that would circumvent the error checking
                                                //		 for multiple targets within a project that may map to a single solution configuration.
                                            }
                                        }

                                        var SolutionConfiguration = SolutionConfigCombination.Configuration;
                                        var SolutionPlatform      = SolutionConfigCombination.Platform;


                                        if (MatchingProjectTarget == null)
                                        {
                                            // The current configuration/platform and target configuration name doesn't map to anything our project actually supports.
                                            // We'll map it to a default config.
                                            SolutionConfiguration = UnrealTargetConfiguration.Development;

                                            // Prefer using Win64 as the default, but fall back to a platform the project file actually supports if needed.  This is for
                                            // projects that can never be compiled in Windows, such as UnrealLaunchDaemon which is an iOS-only program
                                            SolutionPlatform = UnrealTargetPlatform.Win64;
                                            if (CurProject.ProjectTargets[0].TargetRules != null)
                                            {
                                                var ProjectSupportedPlatforms = new List <UnrealTargetPlatform>();
                                                CurProject.ProjectTargets[0].TargetRules.GetSupportedPlatforms(ref ProjectSupportedPlatforms);
                                                if (!ProjectSupportedPlatforms.Contains(SolutionPlatform))
                                                {
                                                    SolutionPlatform = ProjectSupportedPlatforms[0];
                                                }
                                            }


                                            if (IsProgramProject)
                                            {
                                                TargetConfigurationName = TargetRules.TargetType.Program.ToString();
                                            }
                                            else
                                            {
                                                TargetConfigurationName = TargetRules.TargetType.Game.ToString();
                                            }
                                        }


                                        // If the project wants to always build in "Development", regardless of what the solution
                                        // configuration is set to, then we'll do that here.  This is used for projects like
                                        // UnrealBuildTool and ShaderCompileWorker
                                        if (MatchingProjectTarget != null)
                                        {
                                            if (MatchingProjectTarget.ForceDevelopmentConfiguration)
                                            {
                                                SolutionConfiguration = UnrealTargetConfiguration.Development;
                                            }
                                        }


                                        string ProjectConfigName;
                                        string ProjectPlatformName;
                                        CurProject.MakeProjectPlatformAndConfigurationNames(SolutionPlatform, SolutionConfiguration, TargetConfigurationName, out ProjectPlatformName, out ProjectConfigName);

                                        var ProjectConfigAndPlatformPair = ProjectConfigName.ToString() + "|" + ProjectPlatformName.ToString();

                                        // e.g.  "{4232C52C-680F-4850-8855-DC39419B5E9B}.Debug|iOS.ActiveCfg = iOS_Debug|Win32"
                                        var CurProjectGUID = CurProject.ProjectGUID.ToString("B").ToUpperInvariant();
                                        VCSolutionFileContent.Append(
                                            "		"+ CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".ActiveCfg = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine);


                                        // Set whether this project configuration should be built when the user initiates "build solution"
                                        if (MatchingProjectTarget != null && CurProject.ShouldBuildByDefaultForSolutionTargets)
                                        {
                                            VCSolutionFileContent.Append(
                                                "		"+ CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".Build.0 = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine);

                                            var ProjGen = UEPlatformProjectGenerator.GetPlatformProjectGenerator(SolutionConfigCombination.Platform, true);
                                            if (MatchingProjectTarget.ProjectDeploys ||
                                                ((ProjGen != null) && (ProjGen.GetVisualStudioDeploymentEnabled(SolutionPlatform, SolutionConfiguration) == true)))
                                            {
                                                VCSolutionFileContent.Append(
                                                    "		"+ CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".Deploy.0 = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine);
                                            }
                                        }

                                        CombinationsThatWereMatchedToProjects.Add(SolutionConfigCombination);
                                    }
                                }
                            }
                        }

                        // Check for problems
                        foreach (var SolutionConfigCombination in SolutionConfigCombinations)
                        {
                            if (!CombinationsThatWereMatchedToProjects.Contains(SolutionConfigCombination))
                            {
                                throw new BuildException("Unable to find a ProjectTarget that matches the solution configuration/platform mapping: " + SolutionConfigCombination.Configuration.ToString() + ", " + SolutionConfigCombination.Platform.ToString() + ", " + SolutionConfigCombination.TargetConfigurationName);
                            }
                        }
                        VCSolutionFileContent.Append(
                            "	EndGlobalSection"+ ProjectFileGenerator.NewLine);
                    }


                    // Setup other solution properties
                    {
                        VCSolutionFileContent.Append(
                            "	GlobalSection(SolutionProperties) = preSolution"+ ProjectFileGenerator.NewLine);

                        // 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.Append(
                            "		HideSolutionNode = FALSE"+ ProjectFileGenerator.NewLine);

                        VCSolutionFileContent.Append(
                            "	EndGlobalSection"+ ProjectFileGenerator.NewLine);
                    }



                    // Solution directory hierarchy
                    {
                        VCSolutionFileContent.Append(
                            "	GlobalSection(NestedProjects) = preSolution"+ ProjectFileGenerator.NewLine);

                        // 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 (VisualStudioSolutionFolder CurFolder in LocalMasterProjectFolders)
                            {
                                var CurFolderGUIDString = CurFolder.FolderGUID.ToString("B").ToUpperInvariant();

                                foreach (MSBuildProjectFile ChildProject in CurFolder.ChildProjects)
                                {
                                    //	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
                                    LocalVCSolutionFileContent.Append(
                                        "		"+ ChildProject.ProjectGUID.ToString("B").ToUpperInvariant() + " = " + CurFolderGUIDString + ProjectFileGenerator.NewLine);
                                }

                                foreach (VisualStudioSolutionFolder SubFolder in CurFolder.SubFolders)
                                {
                                    //	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
                                    LocalVCSolutionFileContent.Append(
                                        "		"+ SubFolder.FolderGUID.ToString("B").ToUpperInvariant() + " = " + CurFolderGUIDString + ProjectFileGenerator.NewLine);
                                }

                                // Recurse into subfolders
                                FolderProcessorFunction(LocalVCSolutionFileContent, CurFolder.SubFolders);
                            }
                        };
                        FolderProcessorFunction(VCSolutionFileContent, RootFolder.SubFolders);

                        VCSolutionFileContent.Append(
                            "	EndGlobalSection"+ ProjectFileGenerator.NewLine);
                    }
                }

                VCSolutionFileContent.Append(
                    "EndGlobal" + ProjectFileGenerator.NewLine);
            }


            // Save the solution file
            if (bSuccess)
            {
                var SolutionFilePath = Path.Combine(MasterProjectRelativePath, SolutionFileName);
                bSuccess = WriteFileIfChanged(SolutionFilePath, VCSolutionFileContent.ToString());
            }


            // Save a solution config file which selects the development editor configuration by default.
            if (bSuccess)
            {
                // Figure out the filename for the SUO file. VS will automatically import the options from earlier versions if necessary.
                string SolutionOptionsExtension = "vUnknown.suo";
                switch (ProjectFileFormat)
                {
                case VCProjectFileFormat.VisualStudio2012:
                    SolutionOptionsExtension = "v11.suo";
                    break;

                case VCProjectFileFormat.VisualStudio2013:
                    SolutionOptionsExtension = "v12.suo";
                    break;

                case VCProjectFileFormat.VisualStudio2015:
                    SolutionOptionsExtension = "v14.suo";
                    break;
                }

                // Check it doesn't exist before overwriting it. Since these files store the user's preferences, it'd be bad form to overwrite them.
                string SolutionOptionsFileName = Path.Combine(MasterProjectRelativePath, Path.ChangeExtension(SolutionFileName, SolutionOptionsExtension));
                if (!File.Exists(SolutionOptionsFileName))
                {
                    VCSolutionOptions Options = new VCSolutionOptions();

                    // Set the default configuration and startup project
                    VCSolutionConfigCombination DefaultConfig = SolutionConfigCombinations.Find(x => x.Configuration == UnrealTargetConfiguration.Development && x.Platform == UnrealTargetPlatform.Win64 && x.TargetConfigurationName == "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();
                    foreach (ProjectFile ProjectFile in AllProjectFiles)
                    {
                        string ProjectName = Path.GetFileNameWithoutExtension(ProjectFile.ProjectFilePath);
                        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]));
                    }
                    Options.SetExplorerState(ExplorerState);

                    // Write the file
                    if (Options.Sections.Count > 0)
                    {
                        Options.Write(SolutionOptionsFileName);
                    }
                }
            }

            return(bSuccess);
        }
        protected override bool WriteMasterProjectFile( ProjectFile UBTProject )
        {
            bool bSuccess = true;

            var SolutionFileName = MasterProjectName + ".sln";

            // Setup solution file content
            var VCSolutionFileContent = new StringBuilder();

            // Solution file header
            if( ProjectFileFormat == VCProjectFileFormat.VisualStudio2013 )
            {
                VCSolutionFileContent.Append(
                    ProjectFileGenerator.NewLine +
                    "Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
                    "# Visual Studio 2013" + ProjectFileGenerator.NewLine );

                /* This is not required by VS 2013 to load the projects
                VCSolutionFileContent.Append(
                    "VisualStudioVersion = 12.0.20617.1 PREVIEW" + ProjectFileGenerator.NewLine +
                    "MinimumVisualStudioVersion = 10.0.40219.1" + ProjectFileGenerator.NewLine );*/
            }
            else if( ProjectFileFormat == VCProjectFileFormat.VisualStudio2012 )
            {
                VCSolutionFileContent.Append(
                    ProjectFileGenerator.NewLine +
                    "Microsoft Visual Studio Solution File, Format Version 12.00" + ProjectFileGenerator.NewLine +
                    "# Visual Studio 2012" + ProjectFileGenerator.NewLine );
            }
            else
            {
                throw new BuildException( "Unexpected ProjectFileFormat" );
            }

            // Solution folders, files and project entries
            {
                // This the GUID that Visual Studio uses to identify a solution folder
                var SolutionFolderEntryGUID = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}";

                // Solution folders
                {
                    var AllSolutionFolders = new List<MasterProjectFolder>();
                    System.Action< List<MasterProjectFolder> /* Folders */ > GatherFoldersFunction = null;
                    GatherFoldersFunction = FolderList =>
                        {
                            AllSolutionFolders.AddRange( FolderList );
                            foreach( var CurSubFolder in FolderList )
                            {
                                GatherFoldersFunction( CurSubFolder.SubFolders );
                            }
                        };
                    GatherFoldersFunction( RootFolder.SubFolders );

                    foreach( VisualStudioSolutionFolder CurFolder in AllSolutionFolders )
                    {
                        var FolderGUIDString = CurFolder.FolderGUID.ToString( "B" ).ToUpperInvariant();
                        VCSolutionFileContent.Append(
                                "Project(\"" + SolutionFolderEntryGUID + "\") = \"" + CurFolder.FolderName + "\", \"" + CurFolder.FolderName + "\", \"" + FolderGUIDString + "\"" + ProjectFileGenerator.NewLine );

                        // Add any files that are inlined right inside the solution folder
                        if( CurFolder.Files.Count > 0 )
                        {
                            VCSolutionFileContent.Append(
                                    "	ProjectSection(SolutionItems) = preProject" + ProjectFileGenerator.NewLine );
                            foreach( var CurFile in CurFolder.Files )
                            {
                                // Syntax is:  <relative file path> = <relative file path>
                                VCSolutionFileContent.Append(
                                    "		" + CurFile + " = " + CurFile + ProjectFileGenerator.NewLine );
                            }
                            VCSolutionFileContent.Append(
                                    "	EndProjectSection" + ProjectFileGenerator.NewLine );
                        }

                        VCSolutionFileContent.Append(
                                "EndProject" + ProjectFileGenerator.NewLine
                            );
                    }
                }

                // Project files
                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.
                    var ProjectNameInSolution = Path.GetFileNameWithoutExtension(CurProject.ProjectFilePath);

                    // Use the existing project's GUID that's already known to us
                    var ProjectGUID = CurProject.ProjectGUID.ToString( "B" ).ToUpperInvariant();

                    VCSolutionFileContent.Append(
                            "Project(\"" + ProjectTypeGUID + "\") = \"" + ProjectNameInSolution + "\", \"" + CurProject.RelativeProjectFilePath + "\", \"" + ProjectGUID + "\"" + ProjectFileGenerator.NewLine );

                    // 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 )
                    {
                        // Don't add self as a project dependency!
                        if ((CurProject != UBTProject) && (CurProject.IsGeneratedProject || (CurProject.DependsOnProjects.Count > 0)))
                        {
                            VCSolutionFileContent.Append(
                                    "	ProjectSection(ProjectDependencies) = postProject" + ProjectFileGenerator.NewLine);

                            if (CurProject.IsGeneratedProject && UBTProject != null)
                            {
                                var UBTProjectGUID = ((MSBuildProjectFile)UBTProject).ProjectGUID.ToString("B").ToUpperInvariant();
                                VCSolutionFileContent.Append(
                                        "		" + UBTProjectGUID + " = " + UBTProjectGUID + ProjectFileGenerator.NewLine);
                            }

                            // Setup any addition dependencies this project has...
                            foreach (var DependsOnProject in CurProject.DependsOnProjects)
                            {
                                var DependsOnProjectGUID = ((MSBuildProjectFile)DependsOnProject).ProjectGUID.ToString("B").ToUpperInvariant();
                                VCSolutionFileContent.Append(
                                    "		" + DependsOnProjectGUID + " = " + DependsOnProjectGUID + ProjectFileGenerator.NewLine);
                            }

                            VCSolutionFileContent.Append(
                                    "	EndProjectSection" + ProjectFileGenerator.NewLine);
                        }
                    }

                    VCSolutionFileContent.Append(
                            "EndProject" + ProjectFileGenerator.NewLine
                        );
                }
            }

            // Solution configuration platforms.  This is just a list of all of the platforms and configurations that
            // appear in Visual Studio's build configuration selector.
            var SolutionConfigCombinations = new List<VCSolutionConfigCombination>();

            // The "Global" section has source control, solution configurations, project configurations,
            // preferences, and project hierarchy data
            {
                VCSolutionFileContent.Append(
                    "Global" + ProjectFileGenerator.NewLine );
                {
                    {
                        VCSolutionFileContent.Append(
                            "	GlobalSection(SolutionConfigurationPlatforms) = preSolution" + ProjectFileGenerator.NewLine );

                        var SolutionConfigurationsValidForProjects = new Dictionary<string, Tuple< UnrealTargetConfiguration, string>>();
                        var PlatformsValidForProjects = new HashSet<UnrealTargetPlatform>();

                        foreach( var CurConfiguration in SupportedConfigurations )
                        {
                            if (UnrealBuildTool.IsValidConfiguration(CurConfiguration))
                            {
                                foreach (var CurPlatform in SupportedPlatforms)
                                {
                                    if (UnrealBuildTool.IsValidPlatform(CurPlatform))
                                    {
                                        foreach( var 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( var ProjectTarget in CurProject.ProjectTargets )
                                                {
                                                    if( VCProjectFile.IsValidProjectPlatformAndConfiguration( ProjectTarget, CurPlatform, CurConfiguration ) )
                                                    {
                                                        PlatformsValidForProjects.Add( CurPlatform );

                                                        // Default to a target configuration name of "Game", since that will collapse down to an empty string
                                                        var TargetConfigurationName = TargetRules.TargetType.Game.ToString();
                                                        if( ProjectTarget.TargetRules != null )
                                                        {
                                                            TargetConfigurationName = ProjectTarget.TargetRules.ConfigurationName;
                                                        }

                                                        var SolutionConfigName = MakeSolutionConfigurationName( CurConfiguration, TargetConfigurationName );
                                                        SolutionConfigurationsValidForProjects[ SolutionConfigName ] = new Tuple<UnrealTargetConfiguration, string>( CurConfiguration, TargetConfigurationName );
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        foreach( var CurPlatform in PlatformsValidForProjects )
                        {
                            foreach( var SolutionConfigKeyValue in SolutionConfigurationsValidForProjects )
                            {
                                // e.g.  "Development|Win64 = Development|Win64"
                                var SolutionConfigName = SolutionConfigKeyValue.Key;
                                var Configuration = SolutionConfigKeyValue.Value.Item1;
                                var TargetConfigurationName = SolutionConfigKeyValue.Value.Item2;

                                var SolutionPlatformName = CurPlatform.ToString();

                                // For Rocket, there are currently no targets that are valid to build both for Win32 and Win64.  So we simply things by
                                // only displaying a "Windows" platform and building for the appropriate Windows platform automatically based on whichever
                                // configuration they have selected.
                                if( UnrealBuildTool.RunningRocket() && ( CurPlatform == UnrealTargetPlatform.Win32 || CurPlatform == UnrealTargetPlatform.Win64 ) )
                                {
                                    SolutionPlatformName = "Windows";
                                    if( Configuration == UnrealTargetConfiguration.Shipping )
                                    {
                                        if(CurPlatform != UnrealTargetPlatform.Win32)
                                        {
                                            continue;
                                        }
                                    }
                                    else
                                    {
                                        if(CurPlatform != UnrealTargetPlatform.Win64)
                                        {
                                            continue;
                                        }
                                    }
                                }

                                var SolutionConfigAndPlatformPair = SolutionConfigName + "|" + SolutionPlatformName;
                                SolutionConfigCombinations.Add(
                                        new VCSolutionConfigCombination
                                        {
                                            VCSolutionConfigAndPlatformName = SolutionConfigAndPlatformPair,
                                            Configuration = Configuration,
                                            Platform = CurPlatform,
                                            TargetConfigurationName = TargetConfigurationName
                                        }
                                    );
                            }
                        }

                        // 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 ); }
                                )
                            );

                        var AppendedSolutionConfigAndPlatformNames = new HashSet<string>( StringComparer.InvariantCultureIgnoreCase );
                        foreach( var 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.Append(
                                    "		" + SolutionConfigCombination.VCSolutionConfigAndPlatformName + " = " + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ProjectFileGenerator.NewLine );
                                AppendedSolutionConfigAndPlatformNames.Add( SolutionConfigCombination.VCSolutionConfigAndPlatformName );
                            }
                        }

                        VCSolutionFileContent.Append(
                            "	EndGlobalSection" + ProjectFileGenerator.NewLine );
                    }

                    // 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.Append(
                            "	GlobalSection(ProjectConfigurationPlatforms) = postSolution" + ProjectFileGenerator.NewLine );

                        var CombinationsThatWereMatchedToProjects = new List<VCSolutionConfigCombination>();

                        foreach( MSBuildProjectFile CurProject in AllProjectFiles )
                        {
                            // NOTE: We don't emit solution configuration entries for "stub" projects.  Those projects are only
                            // built using UnrealBuildTool and don't require a presence in the solution project list

                            // NOTE: We also process projects that were "imported" here, hoping to match those to our solution
                            //       configurations.  In some cases this may not be successful though.  Imported projects
                            //       should always be carefully setup to match our project generator's solution configs.
                            if( !CurProject.IsStubProject )
                            {
                                if( CurProject.ProjectTargets.Count == 0 )
                                {
                                    throw new BuildException( "Expecting project '" + CurProject.ProjectFilePath + "' to have at least one ProjectTarget associated with it!" );
                                }
                                var IsProgramProject = CurProject.ProjectTargets[ 0 ].TargetRules != null && CurProject.ProjectTargets[ 0 ].TargetRules.Type == TargetRules.TargetType.Program;

                                var GameOrProgramConfigsAlreadyMapped = new HashSet<string>();
                                foreach( var SolutionConfigCombination in SolutionConfigCombinations )
                                {
                                    // Handle aliasing of Program and Game target configuration names
                                    if( ( IsProgramProject && GameOrProgramConfigsAlreadyMapped.Add( SolutionConfigCombination.VCSolutionConfigAndPlatformName ) ) ||
                                        IsProgramProject && SolutionConfigCombination.TargetConfigurationName != TargetRules.TargetType.Game.ToString() ||
                                        !IsProgramProject && SolutionConfigCombination.TargetConfigurationName != TargetRules.TargetType.Program.ToString() )
                                    {
                                        var TargetConfigurationName = SolutionConfigCombination.TargetConfigurationName;
                                        if( IsProgramProject && TargetConfigurationName == TargetRules.TargetType.Game.ToString() )
                                        {
                                            TargetConfigurationName = TargetRules.TargetType.Program.ToString();
                                        }

                                        // Now, we want to find a target in this project that maps to the current solution config combination.  Only up to one target should
                                        // and every solution config combination should map to at least one target in one project (otherwise we shouldn't have added it!).
                                        ProjectTarget MatchingProjectTarget = null;
                                        foreach( var ProjectTarget in CurProject.ProjectTargets )
                                        {
                                            bool IsMatchingCombination = VCProjectFile.IsValidProjectPlatformAndConfiguration( ProjectTarget, SolutionConfigCombination.Platform, SolutionConfigCombination.Configuration );
                                            if( ProjectTarget.TargetRules != null )
                                            {
                                                if( TargetConfigurationName != ProjectTarget.TargetRules.ConfigurationName )
                                                {
                                                    // Solution configuration name for this combination doesn't match this target's configuration name.  It's not buildable.
                                                    IsMatchingCombination = false;
                                                }
                                            }
                                            else
                                            {
                                                // UBT gets a pass because it is a dependency of every single configuration combination
                                                if( CurProject != UBTProject &&
                                                    TargetConfigurationName != TargetRules.TargetType.Game.ToString() )
                                                {
                                                    // Can't build non-generated project in configurations except for the default (Game)
                                                    IsMatchingCombination = false;
                                                }
                                            }

                                            if( IsMatchingCombination )
                                            {
                                                if( MatchingProjectTarget != null )
                                                {
                                                    // Not expecting more than one target to match a single solution configuration per project!
                                                    throw new BuildException( "Not expecting more than one target for project " + CurProject.ProjectFilePath + " to match solution configuration " + SolutionConfigCombination.VCSolutionConfigAndPlatformName );
                                                }

                                                MatchingProjectTarget = ProjectTarget;

                                                // NOTE: For faster perf, we could "break" here and bail out early, but that would circumvent the error checking
                                                //		 for multiple targets within a project that may map to a single solution configuration.
                                            }
                                        }

                                        var SolutionConfiguration = SolutionConfigCombination.Configuration;
                                        var SolutionPlatform = SolutionConfigCombination.Platform;

                                        if( MatchingProjectTarget == null )
                                        {
                                            // The current configuration/platform and target configuration name doesn't map to anything our project actually supports.
                                            // We'll map it to a default config.
                                            SolutionConfiguration = UnrealTargetConfiguration.Development;

                                            // Prefer using Win64 as the default, but fall back to a platform the project file actually supports if needed.  This is for
                                            // projects that can never be compiled in Windows, such as UnrealLaunchDaemon which is an iOS-only program
                                            SolutionPlatform = UnrealTargetPlatform.Win64;
                                            if( CurProject.ProjectTargets[0].TargetRules != null )
                                            {
                                                var ProjectSupportedPlatforms = new List<UnrealTargetPlatform>();
                                                CurProject.ProjectTargets[0].TargetRules.GetSupportedPlatforms( ref ProjectSupportedPlatforms );
                                                if( !ProjectSupportedPlatforms.Contains( SolutionPlatform ) )
                                                {
                                                    SolutionPlatform = ProjectSupportedPlatforms[ 0 ];
                                                }
                                            }

                                            if( IsProgramProject )
                                            {
                                                TargetConfigurationName = TargetRules.TargetType.Program.ToString();
                                            }
                                            else
                                            {
                                                TargetConfigurationName = TargetRules.TargetType.Game.ToString();
                                            }
                                        }

                                        // If the project wants to always build in "Development", regardless of what the solution
                                        // configuration is set to, then we'll do that here.  This is used for projects like
                                        // UnrealBuildTool and ShaderCompileWorker
                                        if( MatchingProjectTarget != null )
                                        {
                                            if( MatchingProjectTarget.ForceDevelopmentConfiguration )
                                            {
                                                SolutionConfiguration = UnrealTargetConfiguration.Development;
                                            }
                                        }

                                        string ProjectConfigName;
                                        string ProjectPlatformName;
                                        CurProject.MakeProjectPlatformAndConfigurationNames( SolutionPlatform, SolutionConfiguration, TargetConfigurationName, out ProjectPlatformName, out ProjectConfigName );

                                        var ProjectConfigAndPlatformPair = ProjectConfigName.ToString() + "|" + ProjectPlatformName.ToString();

                                        // e.g.  "{4232C52C-680F-4850-8855-DC39419B5E9B}.Debug|iOS.ActiveCfg = iOS_Debug|Win32"
                                        var CurProjectGUID = CurProject.ProjectGUID.ToString( "B" ).ToUpperInvariant();
                                        VCSolutionFileContent.Append(
                                            "		" + CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".ActiveCfg = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine );

                                        // Set whether this project configuration should be built when the user initiates "build solution"
                                        if( MatchingProjectTarget != null )
                                        {
                                            VCSolutionFileContent.Append(
                                                    "		" + CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".Build.0 = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine );

                                            var ProjGen = UEPlatformProjectGenerator.GetPlatformProjectGenerator(SolutionConfigCombination.Platform, true);
                                            if (MatchingProjectTarget.ProjectDeploys ||
                                                ((ProjGen != null) && (ProjGen.GetVisualStudioDeploymentEnabled(SolutionPlatform, SolutionConfiguration) == true)))
                                            {
                                                VCSolutionFileContent.Append(
                                                        "		" + CurProjectGUID + "." + SolutionConfigCombination.VCSolutionConfigAndPlatformName + ".Deploy.0 = " + ProjectConfigAndPlatformPair + ProjectFileGenerator.NewLine );
                                            }
                                        }

                                        CombinationsThatWereMatchedToProjects.Add( SolutionConfigCombination );
                                    }
                                }
                            }
                        }

                        // Check for problems
                        foreach( var SolutionConfigCombination in SolutionConfigCombinations )
                        {
                            if( !CombinationsThatWereMatchedToProjects.Contains( SolutionConfigCombination ) )
                            {
                                throw new BuildException( "Unable to find a ProjectTarget that matches the solution configuration/platform mapping: " + SolutionConfigCombination.Configuration.ToString() + ", " + SolutionConfigCombination.Platform.ToString() + ", " + SolutionConfigCombination.TargetConfigurationName );
                            }
                        }
                        VCSolutionFileContent.Append(
                            "	EndGlobalSection" + ProjectFileGenerator.NewLine );
                    }

                    // Setup other solution properties
                    {
                        VCSolutionFileContent.Append(
                            "	GlobalSection(SolutionProperties) = preSolution" + ProjectFileGenerator.NewLine );

                        // 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.Append(
                            "		HideSolutionNode = FALSE" + ProjectFileGenerator.NewLine );

                        VCSolutionFileContent.Append(
                            "	EndGlobalSection" + ProjectFileGenerator.NewLine );
                    }

                    // Solution directory hierarchy
                    {
                        VCSolutionFileContent.Append(
                            "	GlobalSection(NestedProjects) = preSolution" + ProjectFileGenerator.NewLine );

                        // 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( VisualStudioSolutionFolder CurFolder in LocalMasterProjectFolders )
                                {
                                    var CurFolderGUIDString = CurFolder.FolderGUID.ToString( "B" ).ToUpperInvariant();

                                    foreach( MSBuildProjectFile ChildProject in CurFolder.ChildProjects )
                                    {
                                        //	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
                                        LocalVCSolutionFileContent.Append(
                                            "		" + ChildProject.ProjectGUID.ToString( "B" ).ToUpperInvariant() + " = " + CurFolderGUIDString + ProjectFileGenerator.NewLine );
                                    }

                                    foreach( VisualStudioSolutionFolder SubFolder in CurFolder.SubFolders )
                                    {
                                        //	e.g. "{BF6FB09F-A2A6-468F-BE6F-DEBE07EAD3EA} = {C43B6BB5-3EF0-4784-B896-4099753BCDA9}"
                                        LocalVCSolutionFileContent.Append(
                                            "		" + SubFolder.FolderGUID.ToString( "B" ).ToUpperInvariant() + " = " + CurFolderGUIDString + ProjectFileGenerator.NewLine );
                                    }

                                    // Recurse into subfolders
                                    FolderProcessorFunction( LocalVCSolutionFileContent, CurFolder.SubFolders );
                                }
                            };
                        FolderProcessorFunction( VCSolutionFileContent, RootFolder.SubFolders );

                        VCSolutionFileContent.Append(
                            "	EndGlobalSection" + ProjectFileGenerator.NewLine );
                    }
                }

                VCSolutionFileContent.Append(
                    "EndGlobal" + ProjectFileGenerator.NewLine );
            }

            // Save the solution file
            if( bSuccess )
            {
                var SolutionFilePath = Path.Combine( MasterProjectRelativePath, SolutionFileName );
                bSuccess = WriteFileIfChanged( SolutionFilePath, VCSolutionFileContent.ToString() );
            }

            // Save a solution config file which selects the development editor configuration by default.
            if (bSuccess && ProjectFileFormat == VCProjectFileFormat.VisualStudio2013)
            {
                VCSolutionConfigCombination DefaultConfig = SolutionConfigCombinations.Find(x => x.Configuration == UnrealTargetConfiguration.Development && x.Platform == UnrealTargetPlatform.Win64 && x.TargetConfigurationName == "Editor");
                if (DefaultConfig != null)
                {
                    string SolutionOptionsFileName = Path.Combine(MasterProjectRelativePath, Path.ChangeExtension(SolutionFileName, "v12.suo"));
                    if(!File.Exists(SolutionOptionsFileName))
                    {
                        VCSolutionOptions Options = new VCSolutionOptions();
                        Options.SolutionConfiguration.Add(new VCBinarySetting("ActiveCfg", DefaultConfig.VCSolutionConfigAndPlatformName));
                        Options.Write(SolutionOptionsFileName);
                    }
                }
            }

            return bSuccess;
        }
        public static VCSolutionOptions Read(string InputFileName)
        {
            IOleStorage Storage = null;
            StgOpenStorage(InputFileName, null, STGM.Direct | STGM.Read | STGM.ShareExclusive, IntPtr.Zero, 0, out Storage);
            try
            {
                IOleEnumSTATSTG Enumerator = null;
                Storage.EnumElements(0, IntPtr.Zero, 0, out Enumerator);
                try
                {
                    uint Fetched;
                    STATSTG[] Stats = new STATSTG[200];
                    Enumerator.Next((uint)Stats.Length, Stats, out Fetched);

                    VCSolutionOptions Options = new VCSolutionOptions();
                    foreach (STATSTG Stat in Stats)
                    {
                        if (Stat.pwcsName == "SolutionConfiguration")
                        {
                            IOleStream OleStream;
                            Storage.OpenStream(Stat.pwcsName, IntPtr.Zero, STGM.Read | STGM.ShareExclusive, 0, out OleStream);
                            try
                            {
                                uint SizeRead;
                                byte[] Buffer = new byte[Stat.cbSize];
                                OleStream.Read(Buffer, (uint)Stat.cbSize, out SizeRead);

                                using (MemoryStream InputStream = new MemoryStream(Buffer, false))
                                {
                                    BinaryReader Reader = new BinaryReader(InputStream, Encoding.Unicode);
                                    while (InputStream.Position < InputStream.Length)
                                    {
                                        Options.SolutionConfiguration.Add(VCBinarySetting.Read(Reader));
                                    }
                                }
                            }
                            finally
                            {
                                Marshal.ReleaseComObject(OleStream);
                            }
                        }
                    }
                    return Options;
                }
                finally
                {
                    Marshal.ReleaseComObject(Enumerator);
                }
            }
            finally
            {
                Marshal.ReleaseComObject(Storage);
            }
        }