/// <summary> /// Recursively collapses all sub-folders that are redundant. Should only be called after we're done adding /// files and projects to the master project. /// </summary> /// <param name="Folder">The folder whose sub-folders we should potentially collapse into</param> void EliminateRedundantMasterProjectSubFolders(MasterProjectFolder Folder, string ParentMasterProjectFolderPath) { // NOTE: This is for diagnostics output only var MasterProjectFolderPath = String.IsNullOrEmpty(ParentMasterProjectFolderPath) ? Folder.FolderName : (ParentMasterProjectFolderPath + "/" + Folder.FolderName); // We can eliminate folders that meet all of these requirements: // 1) Have only a single project file in them // 2) Have no files in the folder except project files, and no sub-folders // 3) The project file matches the folder name // // Additionally, if KeepSourceSubDirectories==false, we can eliminate directories called "Source". // // Also, we can kill folders that are completely empty. foreach (var SubFolder in Folder.SubFolders) { // Recurse EliminateRedundantMasterProjectSubFolders(SubFolder, MasterProjectFolderPath); } var SubFoldersToAdd = new List<MasterProjectFolder>(); var SubFoldersToRemove = new List<MasterProjectFolder>(); foreach (var SubFolder in Folder.SubFolders) { bool CanCollapseFolder = false; // 1) if (SubFolder.ChildProjects.Count == 1) { // 2) if (SubFolder.Files.Count == 0 && SubFolder.SubFolders.Count == 0) { // 3) if (SubFolder.FolderName.Equals(Utils.GetFilenameWithoutAnyExtensions(SubFolder.ChildProjects[0].ProjectFilePath), StringComparison.InvariantCultureIgnoreCase)) { CanCollapseFolder = true; } } } if (!KeepSourceSubDirectories) { if (SubFolder.FolderName.Equals("Source", StringComparison.InvariantCultureIgnoreCase)) { // Avoid collapsing the Engine's Source directory, since there are so many other solution folders in // the parent directory. if (!Folder.FolderName.Equals("Engine", StringComparison.InvariantCultureIgnoreCase)) { CanCollapseFolder = true; } } } if (SubFolder.ChildProjects.Count == 0 && SubFolder.Files.Count == 0 & SubFolder.SubFolders.Count == 0) { // Folder is totally empty CanCollapseFolder = true; } if (CanCollapseFolder) { // OK, this folder is redundant and can be collapsed away. SubFoldersToAdd.AddRange(SubFolder.SubFolders); SubFolder.SubFolders.Clear(); Folder.ChildProjects.AddRange(SubFolder.ChildProjects); SubFolder.ChildProjects.Clear(); Folder.Files.AddRange(SubFolder.Files); SubFolder.Files.Clear(); SubFoldersToRemove.Add(SubFolder); } } foreach (var SubFolderToRemove in SubFoldersToRemove) { Folder.SubFolders.Remove(SubFolderToRemove); } Folder.SubFolders.AddRange(SubFoldersToAdd); // After everything has been collapsed, do a bit of data validation Validate(Folder, ParentMasterProjectFolderPath); }
/// <summary> /// Generates a Visual Studio solution file and Visual C++ project files for all known engine and game targets. /// Does not actually build anything. /// </summary> /// <param name="Arguments">Command-line arguments</param> /// <param name="bSuccess">True if everything went OK</param> public virtual void GenerateProjectFiles(String[] Arguments, out bool bSuccess) { bSuccess = true; // Parse project generator options bool IncludeAllPlatforms = true; ConfigureProjectFileGeneration(Arguments, ref IncludeAllPlatforms); if (bGeneratingGameProjectFiles) { Log.TraceInformation("Discovering modules, targets and source code for game..."); MasterProjectRelativePath = STBuildTool.GetUProjectPath(); // Set the project file name MasterProjectName = Path.GetFileNameWithoutExtension(STBuildTool.GetUProjectFile()); if (!Directory.Exists(MasterProjectRelativePath + "/Source")) { if (BuildHostPlatform.Current.Platform == STTargetPlatform.Mac) { MasterProjectRelativePath = Path.GetFullPath(Path.Combine(Utils.GetExecutingAssemblyDirectory(), "..", "..", "..", "Engine")); GameProjectName = "UE4Game"; } if (!Directory.Exists(MasterProjectRelativePath + "/Source")) { throw new BuildException("Directory '{0}' is missing 'Source' folder.", MasterProjectRelativePath); } } IntermediateProjectFilesPath = Path.Combine(MasterProjectRelativePath, "Intermediate", "ProjectFiles"); } else if (bGeneratingRocketProjectFiles) { Log.TraceInformation("Discovering modules, targets and source code for project..."); // NOTE: Realistically, the distro that the Rocket user is generating projects FROM won't have NoRedist files in it. But when // testing from a developer branch, this is useful to get authentic projects. This only really matters when // bIncludeEngineModulesInRocketProjects=true (defaults to false.) bExcludeNoRedistFiles = true; MasterProjectRelativePath = STBuildTool.GetUProjectPath(); IntermediateProjectFilesPath = Path.Combine(MasterProjectRelativePath, "Intermediate", "ProjectFiles"); // Set the project file name MasterProjectName = Path.GetFileNameWithoutExtension(STBuildTool.GetUProjectFile()); if (!Directory.Exists(MasterProjectRelativePath + "/Source")) { throw new BuildException("Directory '{0}' is missing 'Source' folder.", MasterProjectRelativePath); } } // Modify the name if specific platforms were given if (ProjectPlatforms.Count > 0) { // Sort the platforms names so we get consistent names List<string> SortedPlatformNames = new List<string>(); foreach (STTargetPlatform SpecificPlatform in ProjectPlatforms) { SortedPlatformNames.Add(SpecificPlatform.ToString()); } SortedPlatformNames.Sort(); MasterProjectName += "_"; foreach (string SortedPlatform in SortedPlatformNames) { MasterProjectName += SortedPlatform; IntermediateProjectFilesPath += SortedPlatform; } } bool bCleanProjectFiles = STBuildTool.CommandLineContains("-CleanProjects"); if (bCleanProjectFiles) { CleanProjectFiles(MasterProjectRelativePath, MasterProjectName, IntermediateProjectFilesPath); } // Figure out which platforms we should generate project files for. string SupportedPlatformNames; SetupSupportedPlatformsAndConfigurations(IncludeAllPlatforms: IncludeAllPlatforms, SupportedPlatformNames: out SupportedPlatformNames); Log.TraceVerbose("Detected supported platforms: " + SupportedPlatformNames); RootFolder = AllocateMasterProjectFolder(this, "<Root>"); // Build the list of games to generate projects for var AllGameProjects = UProjectInfo.FilterGameProjects(true, bGeneratingGameProjectFiles ? GameProjectName : null); var AssemblyName = "ProjectFileGenerator"; if (bGeneratingGameProjectFiles) { AssemblyName = GameProjectName + "ProjectFileGenerator"; } else if (bGeneratingRocketProjectFiles) { AssemblyName = "RocketProjectFileGenerator"; } List<string> AssemblyGameFolders = new List<string>(); foreach (UProjectInfo Project in AllGameProjects) { AssemblyGameFolders.Add(Project.Folder); } RulesCompiler.SetAssemblyNameAndGameFolders(AssemblyName, AssemblyGameFolders); ProjectFile EngineProject = null; Dictionary<string, ProjectFile> GameProjects = null; Dictionary<string, ProjectFile> ProgramProjects = null; HashSet<ProjectFile> TemplateGameProjects = null; { // Setup buildable projects for all targets AddProjectsForAllTargets(AllGameProjects, out EngineProject, out GameProjects, out ProgramProjects, out TemplateGameProjects); // Add all game projects and game config files AddAllGameProjects(GameProjects, SupportedPlatformNames, RootFolder); // Set the game to be the default project if (bGeneratingGameProjectFiles && GameProjects.Count > 0) { DefaultProject = GameProjects.Values.First(); } // Place projects into root level solution folders if (IncludeEngineSource) { // If we're still missing an engine project because we don't have any targets for it, make one up. if (EngineProject == null) { string ProjectFilePath = Path.Combine(IntermediateProjectFilesPath, "UE4" + ProjectFileExtension); bool bAlreadyExisted; EngineProject = FindOrAddProject(Utils.MakePathRelativeTo(ProjectFilePath, MasterProjectRelativePath), true, out bAlreadyExisted); EngineProject.IsForeignProject = false; EngineProject.IsGeneratedProject = true; EngineProject.IsStubProject = true; } if (EngineProject != null) { RootFolder.AddSubFolder("Engine").ChildProjects.Add(EngineProject); // Engine config files if (IncludeConfigFiles) { AddEngineConfigFiles(EngineProject); if (IncludeEnginePrograms) { AddUnrealHeaderToolConfigFiles(EngineProject); AddUBTConfigFilesToEngineProject(EngineProject); } } // Engine localization files if (IncludeLocalizationFiles) { AddEngineLocalizationFiles(EngineProject); } // Engine template files if (IncludeTemplateFiles) { AddEngineTemplateFiles(EngineProject); } if (IncludeShaderSource) { Log.TraceVerbose("Adding shader source code..."); // Find shader source files and generate stub project AddEngineShaderSource(EngineProject); } if (IncludeBuildSystemFiles) { Log.TraceVerbose("Adding build system files..."); AddEngineBuildFiles(EngineProject); } if (IncludeDocumentation) { AddEngineDocumentation(EngineProject); } } foreach (var CurGameProject in GameProjects.Values) { // Templates go under a different solution folder than games if (TemplateGameProjects.Contains(CurGameProject)) { RootFolder.AddSubFolder("Templates").ChildProjects.Add(CurGameProject); } else { RootFolder.AddSubFolder("Games").ChildProjects.Add(CurGameProject); } } foreach (var CurProgramProject in ProgramProjects.Values) { RootFolder.AddSubFolder("Programs").ChildProjects.Add(CurProgramProject); } // Add all of the config files for generated program targets AddEngineProgramConfigFiles(ProgramProjects); } } // Find all of the module files. This will filter out any modules or targets that don't belong to platforms // we're generating project files for. var AllModuleFiles = DiscoverModules(); // Setup "stub" projects for all modules AddProjectsForAllModules(AllGameProjects, ProgramProjects, AllModuleFiles, bGatherThirdPartySource); { if (IncludeEnginePrograms) { MasterProjectFolder ProgramsFolder = RootFolder.AddSubFolder("Programs"); // Add STBuildTool to the master project AddSTBuildToolProject(ProgramsFolder); // Add AutomationTool to the master project ProgramsFolder.ChildProjects.Add(AddSimpleCSharpProject("AutomationTool", bShouldBuildForAllSolutionTargets: true, bForceDevelopmentConfiguration: true)); // Add UnrealAutomationTool (launcher) to the master project ProgramsFolder.ChildProjects.Add(AddSimpleCSharpProject("AutomationToolLauncher", bShouldBuildForAllSolutionTargets: true, bForceDevelopmentConfiguration: true)); // Add automation.csproj files to the master project AddAutomationModules(ProgramsFolder); // Add Distill to the master project ProgramsFolder.ChildProjects.Add(AddSimpleCSharpProject("Distill")); // Add DotNETUtilities to the master project ProgramsFolder.ChildProjects.Add(AddSimpleCSharpProject("DotNETCommon/DotNETUtilities", bShouldBuildForAllSolutionTargets: true, bForceDevelopmentConfiguration: true)); // Add the Git dependencies project ProgramsFolder.ChildProjects.Add(AddSimpleCSharpProject("GitDependencies", bShouldBuildForAllSolutionTargets: true, bForceDevelopmentConfiguration: true)); // Add all of the IOS C# projects AddIOSProjects(ProgramsFolder); // Add all of the Android C# projects AddAndroidProjects(ProgramsFolder); // Add all of the PS4 C# projects AddPS4Projects(ProgramsFolder); } // Eliminate all redundant master project folders. E.g., folders which contain only one project and that project // has the same name as the folder itself. To the user, projects "feel like" folders already in the IDE, so we // want to collapse them down where possible. EliminateRedundantMasterProjectSubFolders(RootFolder, ""); bool bWriteFileManifest = STBuildTool.CommandLineContains("-filemanifest"); if (bWriteFileManifest == false) { // Figure out which targets we need about IntelliSense for. We only need to worry about targets for projects // that we're actually generating in this session. var IntelliSenseTargetFiles = new List<Tuple<ProjectFile, string>>(); { // Engine targets if (EngineProject != null && !bGeneratingRocketProjectFiles) { foreach (var ProjectTarget in EngineProject.ProjectTargets) { if (!String.IsNullOrEmpty(ProjectTarget.TargetFilePath)) { // Only bother with the editor target. We want to make sure that definitions are setup to be as inclusive as possible // for good quality IntelliSense. For example, we want WITH_EDITORONLY_DATA=1, so using the editor targets works well. if (ProjectTarget.TargetRules.Type == TargetRules.TargetType.Editor) { IntelliSenseTargetFiles.Add(Tuple.Create(EngineProject, ProjectTarget.TargetFilePath)); } } } } // Program targets foreach (var ProgramProject in ProgramProjects.Values) { foreach (var ProjectTarget in ProgramProject.ProjectTargets) { if (!String.IsNullOrEmpty(ProjectTarget.TargetFilePath)) { IntelliSenseTargetFiles.Add(Tuple.Create(ProgramProject, ProjectTarget.TargetFilePath)); } } } // Game/template targets foreach (var GameProject in GameProjects.Values) { foreach (var ProjectTarget in GameProject.ProjectTargets) { if (!String.IsNullOrEmpty(ProjectTarget.TargetFilePath)) { // Only bother with the editor target. We want to make sure that definitions are setup to be as inclusive as possible // for good quality IntelliSense. For example, we want WITH_EDITORONLY_DATA=1, so using the editor targets works well. if (ProjectTarget.TargetRules.Type == TargetRules.TargetType.Editor) { IntelliSenseTargetFiles.Add(Tuple.Create(GameProject, ProjectTarget.TargetFilePath)); } } } } } // Generate IntelliSense data if we need to. This involves having UBT simulate the action compilation of // the targets so that we can extra the compiler defines, include paths, etc. bSuccess = GenerateIntelliSenseData(Arguments, IntelliSenseTargetFiles); } // If everything went OK, we'll now save out all of the new project files if (bSuccess) { if (bWriteFileManifest == false) { // Save new project files WriteProjectFiles(); Log.TraceVerbose("Project generation complete ({0} generated, {1} imported)", GeneratedProjectFiles.Count, OtherProjectFiles.Count); } else { WriteProjectFileManifest(); } } } }
/// <summary> /// Adds all of the PS4 C# projects to the master project /// </summary> private void AddPS4Projects(MasterProjectFolder Folder) { string ProjectFolderName = Path.Combine(EngineRelativePath, "Source", "Programs", "PS4"); DirectoryInfo ProjectFolderInfo = new DirectoryInfo(ProjectFolderName); if (ProjectFolderInfo.Exists) { Folder.ChildProjects.Add(AddSimpleCSharpProject("PS4/PS4DevKitUtil")); } }
/// <summary> /// Adds STBuildTool to the master project /// </summary> private void AddSTBuildToolProject(MasterProjectFolder ProgramsFolder) { var ProjectFileName = Utils.MakePathRelativeTo(Path.Combine(Path.Combine(EngineRelativePath, "Source"), "Programs", "STBuildTool", "STBuildTool.csproj"), MasterProjectRelativePath); var STBuildToolProject = new VCSharpProjectFile(ProjectFileName); STBuildToolProject.ShouldBuildForAllSolutionTargets = true; // Store it off as we need it when generating target projects. UBTProject = STBuildToolProject; // Add the project AddExistingProjectFile(STBuildToolProject, bNeedsAllPlatformAndConfigurations: true, bForceDevelopmentConfiguration: true); // Put this in a solution folder ProgramsFolder.ChildProjects.Add(STBuildToolProject); }
/// <summary> /// Adds all of the IOS C# projects to the master project /// </summary> private void AddIOSProjects(MasterProjectFolder Folder) { string ProjectFolderName = Path.Combine(EngineRelativePath, "Source", "Programs", "IOS"); DirectoryInfo ProjectFolderInfo = new DirectoryInfo(ProjectFolderName); if (ProjectFolderInfo.Exists) { Folder.ChildProjects.Add(AddSimpleCSharpProject("IOS/iPhonePackager")); Folder.ChildProjects.Add(AddSimpleCSharpProject("IOS/DeploymentInterface", true)); // Build by default; needed for UAT. Folder.ChildProjects.Add(AddSimpleCSharpProject("IOS/DeploymentServer")); Folder.ChildProjects.Add(AddSimpleCSharpProject("IOS/MobileDeviceInterface")); } }
/// <summary> /// Adds all .automation.csproj files to the solution. /// </summary> void AddAutomationModules(MasterProjectFolder ProgramsFolder) { var Folder = ProgramsFolder.AddSubFolder("Automation"); var AllGameFolders = STBuildTarget.DiscoverAllGameFolders(); var BuildFolders = new List<string>(AllGameFolders.Count); foreach (var GameFolder in AllGameFolders) { var GameBuildFolder = Path.Combine(GameFolder, "Build"); if (Directory.Exists(GameBuildFolder)) { BuildFolders.Add(GameBuildFolder); } } // Find all the automation modules .csproj files to add var ModuleFiles = RulesCompiler.FindAllRulesSourceFiles(RulesCompiler.RulesFileType.AutomationModule, BuildFolders); foreach (var ProjectFile in ModuleFiles) { FileInfo Info = new FileInfo(Path.Combine(ProjectFile)); if (Info.Exists) { var RelativeFileName = Utils.MakePathRelativeTo(ProjectFile, MasterProjectRelativePath); var Project = new VCSharpProjectFile(RelativeFileName); Project.ShouldBuildForAllSolutionTargets = true; AddExistingProjectFile(Project, bForceDevelopmentConfiguration: true); Folder.ChildProjects.Add(Project); } } }
/// <summary> /// Adds all of the Android C# projects to the master project /// </summary> private void AddAndroidProjects(MasterProjectFolder Folder) { }
/// <summary> /// Adds all game project files, including target projects and config files /// </summary> private void AddAllGameProjects(Dictionary<string, ProjectFile> GameProjects, string SupportedPlatformNames, MasterProjectFolder ProjectsFolder) { foreach (var GameFolderAndProjectFile in GameProjects) { var GameFolderName = GameFolderAndProjectFile.Key; // @todo projectfiles: We have engine localization files, but should we also add GAME localization files? string GameProjectDirectory = GameFolderName; GameProjectDirectory = GameProjectDirectory.Replace("/", "\\"); // Game config files if (IncludeConfigFiles) { var GameConfigDirectory = Path.Combine(GameProjectDirectory, "Config"); if (Directory.Exists(GameConfigDirectory)) { var GameProjectFile = GameFolderAndProjectFile.Value; var DirectoriesToSearch = new List<string>(); DirectoriesToSearch.Add(GameConfigDirectory); GameProjectFile.AddFilesToProject(SourceFileSearch.FindFiles(DirectoriesToSearch, ExcludeNoRedistFiles: bExcludeNoRedistFiles), GameFolderName); } } } }
/// <summary> /// Validate the specified Folder. Default implementation requires /// for project file names to be unique! /// </summary> /// <param name="Folder">Folder.</param> /// <param name="MasterProjectFolderPath">Parent master project folder path.</param> protected virtual void Validate(MasterProjectFolder Folder, string MasterProjectFolderPath) { foreach (var CurChildProject in Folder.ChildProjects) { foreach (var OtherChildProject in Folder.ChildProjects) { if (CurChildProject != OtherChildProject) { if (Utils.GetFilenameWithoutAnyExtensions(CurChildProject.ProjectFilePath).Equals(Utils.GetFilenameWithoutAnyExtensions(OtherChildProject.ProjectFilePath), StringComparison.InvariantCultureIgnoreCase)) { throw new BuildException("Detected collision between two project files with the same path in the same master project folder, " + OtherChildProject.ProjectFilePath + " and " + CurChildProject.ProjectFilePath + " (master project folder: " + MasterProjectFolderPath + ")"); } } } } foreach (var SubFolder in Folder.SubFolders) { // If the parent folder already has a child project or file item with the same name as this sub-folder, then // that's considered an error (it should never have been allowed to have a folder name that collided // with project file names or file items, as that's not supported in Visual Studio.) foreach (var CurChildProject in Folder.ChildProjects) { if (Utils.GetFilenameWithoutAnyExtensions(CurChildProject.ProjectFilePath).Equals(SubFolder.FolderName, StringComparison.InvariantCultureIgnoreCase)) { throw new BuildException("Detected collision between a master project sub-folder " + SubFolder.FolderName + " and a project within the outer folder " + CurChildProject.ProjectFilePath + " (master project folder: " + MasterProjectFolderPath + ")"); } } foreach (var CurFile in Folder.Files) { if (Path.GetFileName(CurFile).Equals(SubFolder.FolderName, StringComparison.InvariantCultureIgnoreCase)) { throw new BuildException("Detected collision between a master project sub-folder " + SubFolder.FolderName + " and a file within the outer folder " + CurFile + " (master project folder: " + MasterProjectFolderPath + ")"); } } foreach (var CurFolder in Folder.SubFolders) { if (CurFolder != SubFolder) { if (CurFolder.FolderName.Equals(SubFolder.FolderName, StringComparison.InvariantCultureIgnoreCase)) { throw new BuildException("Detected collision between a master project sub-folder " + SubFolder.FolderName + " and a sibling folder " + CurFolder.FolderName + " (master project folder: " + MasterProjectFolderPath + ")"); } } } } }