// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Diagnostics; using System.Threading; using System.Reflection; using System.Linq; using Tools.DotNETCommon; namespace UnrealBuildTool { public class UProjectInfo { public string GameName; public string FileName; public FileReference FilePath; public DirectoryReference Folder; public bool bIsCodeProject; UProjectInfo(FileReference InFilePath, bool bInIsCodeProject) { GameName = InFilePath.GetFileNameWithoutExtension(); FileName = InFilePath.GetFileName(); FilePath = InFilePath; Folder = FilePath.Directory; bIsCodeProject = bInIsCodeProject; } /** Map of relative or complete project file names to the project info */ static Dictionary ProjectInfoDictionary = new Dictionary(); /** Map of short project file names to the relative or complete project file name */ static Dictionary ShortProjectNameDictionary = new Dictionary( StringComparer.InvariantCultureIgnoreCase ); /** Map of target names to the relative or complete project file name */ static Dictionary TargetToProjectDictionary = new Dictionary( StringComparer.InvariantCultureIgnoreCase ); public static bool FindTargetFilesInFolder(DirectoryReference InTargetFolder) { bool bFoundTargetFiles = false; IEnumerable Files; if (!Utils.IsRunningOnMono) { Files = Directory.EnumerateFiles (InTargetFolder.FullName, "*.target.cs", SearchOption.TopDirectoryOnly); } else { Files = Directory.GetFiles (InTargetFolder.FullName, "*.Target.cs", SearchOption.TopDirectoryOnly).AsEnumerable(); } foreach (var TargetFilename in Files) { bFoundTargetFiles = true; foreach (KeyValuePair Entry in ProjectInfoDictionary) { FileInfo ProjectFileInfo = new FileInfo(Entry.Key.FullName); string ProjectDir = ProjectFileInfo.DirectoryName.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; if (TargetFilename.StartsWith(ProjectDir, StringComparison.InvariantCultureIgnoreCase)) { FileInfo TargetInfo = new FileInfo(TargetFilename); // Strip off the target.cs string TargetName = Utils.GetFilenameWithoutAnyExtensions( TargetInfo.Name ); if (TargetToProjectDictionary.ContainsKey(TargetName) == false) { TargetToProjectDictionary.Add(TargetName, Entry.Key); } } } } return bFoundTargetFiles; } public static bool FindTargetFiles(DirectoryReference CurrentTopDirectory, ref bool bOutFoundTargetFiles) { // We will only search as deep as the first target file found List SubFolderList = new List(); // Check the root directory bOutFoundTargetFiles |= FindTargetFilesInFolder(CurrentTopDirectory); if (bOutFoundTargetFiles == false) { foreach (var TargetFolder in Directory.EnumerateDirectories(CurrentTopDirectory.FullName, "*", SearchOption.TopDirectoryOnly).Select(x => new DirectoryReference(x))) { SubFolderList.Add(TargetFolder); bOutFoundTargetFiles |= FindTargetFilesInFolder(TargetFolder); } } if (bOutFoundTargetFiles == false) { // Recurse each folders folders foreach (var SubFolder in SubFolderList) { FindTargetFiles(SubFolder, ref bOutFoundTargetFiles); } } return bOutFoundTargetFiles; } static readonly string RootDirectory = Path.Combine( Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetOriginalLocation()), "..", "..", ".."); static readonly string EngineSourceDirectory = Path.GetFullPath( Path.Combine(RootDirectory, "Engine", "Source") ); /// /// Add a single project to the project info dictionary /// public static void AddProject(FileReference ProjectFile) { if(!ProjectInfoDictionary.ContainsKey(ProjectFile)) { DirectoryReference ProjectDirectory = ProjectFile.Directory; // Check if it's a code project DirectoryReference SourceFolder = DirectoryReference.Combine(ProjectDirectory, "Source"); DirectoryReference IntermediateSourceFolder = DirectoryReference.Combine(ProjectDirectory, "Intermediate", "Source"); bool bIsCodeProject = SourceFolder.Exists() || IntermediateSourceFolder.Exists(); // Create the project, and check the name is unique UProjectInfo NewProjectInfo = new UProjectInfo(ProjectFile, bIsCodeProject); if(ShortProjectNameDictionary.ContainsKey(NewProjectInfo.GameName)) { var FirstProject = ProjectInfoDictionary[ShortProjectNameDictionary[NewProjectInfo.GameName]]; throw new BuildException("There are multiple projects with name {0}\n\t* {1}\n\t* {2}\nThis is not currently supported.", NewProjectInfo.GameName, FirstProject.FilePath.FullName, NewProjectInfo.FilePath.FullName); } // Add it to the name -> project lookups ProjectInfoDictionary.Add(ProjectFile, NewProjectInfo); ShortProjectNameDictionary.Add(NewProjectInfo.GameName, ProjectFile); // Find all Target.cs files if it's a code project if(bIsCodeProject) { bool bFoundTargetFiles = false; if (SourceFolder.Exists() && !FindTargetFiles(SourceFolder, ref bFoundTargetFiles)) { Log.TraceVerbose("No target files found under " + SourceFolder); } if (IntermediateSourceFolder.Exists() && !FindTargetFiles(IntermediateSourceFolder, ref bFoundTargetFiles)) { Log.TraceVerbose("No target files found under " + IntermediateSourceFolder); } } } } /// /// Discover and fill in the project info /// public static void FillProjectInfo() { DateTime StartTime = DateTime.Now; List DirectoriesToSearch = new List(); // Find all the .uprojectdirs files contained in the root folder and add their entries to the search array string RootDirectory = Path.Combine( Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetOriginalLocation()), "..", "..", ".."); string EngineSourceDirectory = Path.GetFullPath( Path.Combine(RootDirectory, "Engine", "Source") ); foreach (var File in Directory.EnumerateFiles(RootDirectory, "*.uprojectdirs", SearchOption.TopDirectoryOnly)) { string FilePath = Path.GetFullPath (File); Log.TraceVerbose("\tFound uprojectdirs file {0}", FilePath); using (StreamReader Reader = new StreamReader(FilePath)) { string LineRead; while ((LineRead = Reader.ReadLine()) != null) { string ProjDirEntry = LineRead.Trim(); if (String.IsNullOrEmpty(ProjDirEntry) == false) { if (ProjDirEntry.StartsWith(";")) { // Commented out line... skip it continue; } else { string DirPath = Path.GetFullPath (Path.Combine (RootDirectory, ProjDirEntry)); DirectoriesToSearch.Add(DirPath); } } } } } Log.TraceVerbose("\tFound {0} directories to search", DirectoriesToSearch.Count); foreach (string DirToSearch in DirectoriesToSearch) { Log.TraceVerbose("\t\tSearching {0}", DirToSearch); if (Directory.Exists(DirToSearch)) { foreach (string SubDir in Directory.EnumerateDirectories(DirToSearch, "*", SearchOption.TopDirectoryOnly)) { Log.TraceVerbose("\t\t\tFound subdir {0}", SubDir); string[] SubDirFiles = Directory.GetFiles(SubDir, "*.uproject", SearchOption.TopDirectoryOnly); foreach (string UProjFile in SubDirFiles) { Log.TraceVerbose("\t\t\t\t{0}", UProjFile); AddProject(new FileReference(UProjFile)); } } } else { Log.TraceVerbose("ProjectInfo: Skipping directory {0} from .uprojectdirs file as it doesn't exist.", DirToSearch); } } DateTime StopTime = DateTime.Now; if( BuildConfiguration.bPrintPerformanceInfo ) { TimeSpan TotalProjectInfoTime = StopTime - StartTime; Log.TraceInformation("FillProjectInfo took {0} milliseconds", TotalProjectInfoTime.Milliseconds); } if (UnrealBuildTool.CommandLineContains("-dumpprojectinfo")) { UProjectInfo.DumpProjectInfo(); } } public static void DumpProjectInfo() { Log.TraceInformation("Dumping project info..."); Log.TraceInformation("\tProjectInfo"); foreach (KeyValuePair InfoEntry in ProjectInfoDictionary) { Log.TraceInformation("\t\t" + InfoEntry.Key); Log.TraceInformation("\t\t\tName : " + InfoEntry.Value.FileName); Log.TraceInformation("\t\t\tFile Folder : " + InfoEntry.Value.Folder); Log.TraceInformation("\t\t\tCode Project : " + (InfoEntry.Value.bIsCodeProject ? "YES" : "NO")); } Log.TraceInformation("\tShortName to Project"); foreach (KeyValuePair ShortEntry in ShortProjectNameDictionary) { Log.TraceInformation("\t\tShort Name : " + ShortEntry.Key); Log.TraceInformation("\t\tProject : " + ShortEntry.Value); } Log.TraceInformation("\tTarget to Project"); foreach (KeyValuePair TargetEntry in TargetToProjectDictionary) { Log.TraceInformation("\t\tTarget : " + TargetEntry.Key); Log.TraceInformation("\t\tProject : " + TargetEntry.Value); } } /// /// Filter the list of game projects /// /// If true, only return code projects /// Game name to filter against. May be null. public static List FilterGameProjects(bool bOnlyCodeProjects, string GameNameFilter) { List Projects = new List(); foreach (KeyValuePair Entry in ProjectInfoDictionary) { if (!bOnlyCodeProjects || Entry.Value.bIsCodeProject) { if (string.IsNullOrEmpty(GameNameFilter) || Entry.Value.GameName == GameNameFilter) { Projects.Add(Entry.Value); } } } return Projects; } /// /// Get the project folder for the given target name /// /// Name of the target of interest /// The project filename /// True if the target was found public static bool TryGetProjectForTarget(string InTargetName, out FileReference OutProjectFileName) { return TargetToProjectDictionary.TryGetValue(InTargetName, out OutProjectFileName); } /// /// Get the project folder for the given project name /// /// Name of the project of interest /// The project filename /// True if the target was found public static bool TryGetProjectFileName(string InProjectName, out FileReference OutProjectFileName) { return ShortProjectNameDictionary.TryGetValue(InProjectName, out OutProjectFileName); } /// /// Determine if a plugin is enabled for a given project /// /// The project to check /// Information about the plugin /// The target platform /// True if the plugin should be enabled for this project public static bool IsPluginEnabledForProject(PluginInfo Plugin, ProjectDescriptor Project, UnrealTargetPlatform Platform) { bool bEnabled = Plugin.Descriptor.bEnabledByDefault || Plugin.LoadedFrom == PluginLoadedFrom.GameProject; if(Project != null && Project.Plugins != null) { foreach(PluginReferenceDescriptor PluginReference in Project.Plugins) { if(String.Compare(PluginReference.Name, Plugin.Name, true) == 0) { bEnabled = PluginReference.IsEnabledForPlatform(Platform); } } } return bEnabled; } } }