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