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