// Copyright 1998-2017 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 { /// /// Stores information about a project /// public class UProjectInfo { /// /// Name of the project /// public string GameName; /// /// Name of the .uproject file /// public string FileName; /// /// Full path to the project on disk /// public FileReference FilePath; /// /// Folder containing the project /// public DirectoryReference Folder; /// /// Whether the project has source code /// 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); /// /// Find all the target files under the given folder and add them to the TargetToProjectDictionary map /// /// Folder to search /// True if any target files were found 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 (string 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 (DirectoryReference 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 (DirectoryReference 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 = DirectoryReference.Exists(SourceFolder) || DirectoryReference.Exists(IntermediateSourceFolder); // Create the project, and check the name is unique UProjectInfo NewProjectInfo = new UProjectInfo(ProjectFile, bIsCodeProject); if (ShortProjectNameDictionary.ContainsKey(NewProjectInfo.GameName)) { UProjectInfo 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 (DirectoryReference.Exists(SourceFolder) && !FindTargetFiles(SourceFolder, ref bFoundTargetFiles)) { Log.TraceVerbose("No target files found under " + SourceFolder); } if (DirectoryReference.Exists(IntermediateSourceFolder) && !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 EngineSourceDirectory = Path.GetFullPath(Path.Combine(RootDirectory, "Engine", "Source")); foreach (FileReference ProjectDirsFile in DirectoryReference.EnumerateFiles(UnrealBuildTool.RootDirectory, "*.uprojectdirs", SearchOption.TopDirectoryOnly)) { Log.TraceVerbose("\tFound uprojectdirs file {0}", ProjectDirsFile.FullName); foreach(string Line in File.ReadAllLines(ProjectDirsFile.FullName)) { string TrimLine = Line.Trim(); if(!TrimLine.StartsWith(";")) { DirectoryReference BaseProjectDir = DirectoryReference.Combine(UnrealBuildTool.RootDirectory, TrimLine); if(BaseProjectDir.IsUnderDirectory(UnrealBuildTool.RootDirectory)) { DirectoriesToSearch.Add(BaseProjectDir); } else { Log.TraceWarning("Project search path '{0}' is not under root directory, ignoring.", TrimLine); } } } } Log.TraceVerbose("\tFound {0} directories to search", DirectoriesToSearch.Count); foreach (DirectoryReference DirToSearch in DirectoriesToSearch) { Log.TraceVerbose("\t\tSearching {0}", DirToSearch.FullName); if (DirectoryReference.Exists(DirToSearch)) { foreach (DirectoryReference SubDir in DirectoryReference.EnumerateDirectories(DirToSearch, "*", SearchOption.TopDirectoryOnly)) { Log.TraceVerbose("\t\t\tFound subdir {0}", SubDir.FullName); foreach(FileReference UProjFile in DirectoryReference.EnumerateFiles(SubDir, "*.uproject", SearchOption.TopDirectoryOnly)) { Log.TraceVerbose("\t\t\t\t{0}", UProjFile.FullName); AddProject(UProjFile); } } } else { Log.TraceVerbose("ProjectInfo: Skipping directory {0} from .uprojectdirs file as it doesn't exist.", DirToSearch); } } DateTime StopTime = DateTime.Now; if (UnrealBuildTool.bPrintPerformanceInfo) { TimeSpan TotalProjectInfoTime = StopTime - StartTime; Log.TraceInformation("FillProjectInfo took {0} milliseconds", TotalProjectInfoTime.TotalMilliseconds); } } /// /// Print out all the info for known projects /// 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; } /// /// Check to see if a project name is a Game /// /// /// true if it is a game public static bool IsGameProject(string GameNameFilter) { List Projects = new List(); foreach (KeyValuePair Entry in ProjectInfoDictionary) { if(Entry.Value.GameName == GameNameFilter) { return true; } } return false; } /// /// 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, TargetType Target) { bool bEnabled = Plugin.Descriptor.bEnabledByDefault; 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) && PluginReference.IsEnabledForTarget(Target); } } } return bEnabled; } /// /// 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 IsPluginDescriptorRequiredForProject(PluginInfo Plugin, ProjectDescriptor Project, UnrealTargetPlatform Platform, TargetType TargetType, bool bBuildDeveloperTools, bool bBuildEditor, bool bBuildRequiresCookedData) { // Check if it's referenced by name from the project descriptor. If it is, we'll need the plugin to be included with the project regardless of whether it has // any platform-specific modules or content, just so the runtime can make the call. if (Project != null && Project.Plugins != null) { foreach (PluginReferenceDescriptor PluginReference in Project.Plugins) { if (String.Compare(PluginReference.Name, Plugin.Name, true) == 0) { return PluginReference.IsEnabledForPlatform(Platform) && PluginReference.IsEnabledForTarget(TargetType); } } } // If the plugin contains content, it should be included for all platforms if(Plugin.Descriptor.bCanContainContent) { return true; } // Check if the plugin has any modules for the given target foreach (ModuleDescriptor Module in Plugin.Descriptor.Modules) { if(Module.IsCompiledInConfiguration(Platform, TargetType, bBuildDeveloperTools, bBuildEditor, bBuildRequiresCookedData)) { return true; } } return false; } } }