Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/AutomationUtils/ProjectUtils.cs
zousar shaker 0832797d28 Ensure that Zen ProjectIds are unique for projects with the same name in differing roots (or in the same root) while attempting to maintain some easily recognizable desriptors for the project.
Staged builds are explicitly assigned a project id via the persistent UECommandline.txt file.  This works on mobile/consoles, but isn't implemented for Windows/Linux/Mac, so staged builds for those platforms don't have a persistent assignment of project id and instead rely on the launch by the editor including the appropriate commandlines.
Unstaged builds on Windows/Mac/Linux now determine project id independently after they have determined the project file path.  This happens after the project file has been determined and has had case corrected.  This ensures it is different/unique for multiple blueprint projects that share the same executable.
Fix a bug where second cook in editor to zen would stall because the queue thinks all additions are finished due to a boolean that isn't reset.

#rb devin.doucette
#rb per.larsson
#preflight 61b39a35a2562c8b1c40f81e

#ROBOMERGE-AUTHOR: zousar.shaker
#ROBOMERGE-SOURCE: CL 18432317 in //UE5/Release-5.0/... via CL 18435404
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v897-18405271)

[CL 18435895 by zousar shaker in ue5-release-engine-test branch]
2021-12-10 18:06:39 -05:00

1058 lines
40 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using UnrealBuildTool;
using System.Diagnostics;
using EpicGames.Core;
using System.Reflection;
using UnrealBuildBase;
namespace AutomationTool
{
public class SingleTargetProperties
{
public string TargetName;
public string TargetClassName;
public TargetRules Rules;
}
/// <summary>
/// Autodetected project properties.
/// </summary>
public class ProjectProperties
{
/// <summary>
/// Full Project path. Must be a .uproject file
/// </summary>
public FileReference RawProjectPath;
/// <summary>
/// True if the uproject contains source code.
/// </summary>
public bool bIsCodeBasedProject;
/// <summary>
/// List of all targets detected for this project.
/// </summary>
public List<SingleTargetProperties> Targets = new List<SingleTargetProperties>();
/// <summary>
/// List of all scripts that were compiled to create the list of Targets
/// </summary>
public List<FileReference> TargetScripts = new List<FileReference>();
/// <summary>
/// List of all Engine ini files for this project
/// </summary>
public Dictionary<UnrealTargetPlatform, ConfigHierarchy> EngineConfigs = new Dictionary<UnrealTargetPlatform,ConfigHierarchy>();
/// <summary>
/// List of all Game ini files for this project
/// </summary>
public Dictionary<UnrealTargetPlatform, ConfigHierarchy> GameConfigs = new Dictionary<UnrealTargetPlatform, ConfigHierarchy>();
/// <summary>
/// List of all programs detected for this project.
/// </summary>
public List<SingleTargetProperties> Programs = new List<SingleTargetProperties>();
/// <summary>
/// Specifies if the target files were generated
/// </summary>
public bool bWasGenerated = false;
internal ProjectProperties()
{
}
}
/// <summary>
/// Project related utility functions.
/// </summary>
public class ProjectUtils
{
private static Dictionary<string, ProjectProperties> PropertiesCache = new Dictionary<string, ProjectProperties>(StringComparer.InvariantCultureIgnoreCase);
/// <summary>
/// Gets a short project name (QAGame, Elemental, etc)
/// </summary>
/// <param name="RawProjectPath">Full project path.</param>
/// <param name="bIsUProjectFile">True if a uproject.</param>
/// <returns>Short project name</returns>
public static string GetShortProjectName(FileReference RawProjectPath)
{
return CommandUtils.GetFilenameWithoutAnyExtensions(RawProjectPath.FullName);
}
/// <summary>
/// Gets a short alphanumeric identifier for the project path.
/// </summary>
/// <param name="RawProjectPath">Full project path.</param>
/// <returns>Project path identifier</returns>
public static string GetProjectPathId(FileReference RawProjectPath)
{
string UniformProjectPath = FileReference.FindCorrectCase(RawProjectPath).ToNormalizedPath();
string ProjectPathHash = ContentHash.MD5(Encoding.UTF8.GetBytes(UniformProjectPath)).ToString();
return String.Format("{0}.{1}", GetShortProjectName(RawProjectPath), ProjectPathHash.Substring(0, 8));
}
/// <summary>
/// Gets project properties.
/// </summary>
/// <param name="RawProjectPath">Full project path.</param>
/// <returns>Properties of the project.</returns>
public static ProjectProperties GetProjectProperties(FileReference RawProjectPath, List<UnrealTargetPlatform> ClientTargetPlatforms = null, List<UnrealTargetConfiguration> ClientTargetConfigurations = null, bool AssetNativizationRequested = false)
{
string ProjectKey = "UE4";
if (RawProjectPath != null)
{
ProjectKey = CommandUtils.ConvertSeparators(PathSeparator.Slash, RawProjectPath.FullName);
}
ProjectProperties Properties;
if (PropertiesCache.TryGetValue(ProjectKey, out Properties) == false)
{
Properties = DetectProjectProperties(RawProjectPath, ClientTargetPlatforms, ClientTargetConfigurations, AssetNativizationRequested);
PropertiesCache.Add(ProjectKey, Properties);
}
return Properties;
}
/// <summary>
/// Checks if the project is a UProject file with source code.
/// </summary>
/// <param name="RawProjectPath">Full project path.</param>
/// <returns>True if the project is a UProject file with source code.</returns>
public static bool IsCodeBasedUProjectFile(FileReference RawProjectPath, List<UnrealTargetConfiguration> ClientTargetConfigurations = null)
{
return GetProjectProperties(RawProjectPath, null, ClientTargetConfigurations).bIsCodeBasedProject;
}
/// <summary>
/// Returns a path to the client binaries folder.
/// </summary>
/// <param name="RawProjectPath">Full project path.</param>
/// <param name="Platform">Platform type.</param>
/// <returns>Path to the binaries folder.</returns>
public static DirectoryReference GetProjectClientBinariesFolder(DirectoryReference ProjectClientBinariesPath, UnrealTargetPlatform Platform)
{
ProjectClientBinariesPath = DirectoryReference.Combine(ProjectClientBinariesPath, Platform.ToString());
return ProjectClientBinariesPath;
}
private static bool ProjectHasCode(FileReference RawProjectPath)
{
// check to see if we already have a Target.cs file
if (File.Exists(Path.Combine(Path.GetDirectoryName(RawProjectPath.FullName), "Source", RawProjectPath.GetFileNameWithoutExtension() + ".Target.cs")))
{
return true;
}
else if (Directory.Exists(Path.Combine(Path.GetDirectoryName(RawProjectPath.FullName), "Source")))
{
// wasn't one in the main Source directory, let's check all sub-directories
//@todo: may want to read each target.cs to see if it has a target corresponding to the project name as a final check
FileInfo[] Files = (new DirectoryInfo(Path.Combine(Path.GetDirectoryName(RawProjectPath.FullName), "Source")).GetFiles("*.Target.cs", SearchOption.AllDirectories));
if (Files.Length > 0)
{
return true;
}
}
return false;
}
private static bool RequiresTempTarget(FileReference RawProjectPath, List<UnrealTargetPlatform> Platforms, List<UnrealTargetConfiguration> Configurations, bool AssetNativizationRequested)
{
bool bHasCode = ProjectHasCode(RawProjectPath);
foreach (UnrealTargetPlatform Platform in Platforms)
{
foreach(UnrealTargetConfiguration Configuration in Configurations)
{
string Reason;
if(RequiresTempTarget(RawProjectPath, bHasCode, Platform, Configuration, TargetType.Game, AssetNativizationRequested, true, out Reason))
{
Log.TraceInformation("{0} requires a temporary target.cs to be generated ({1})", RawProjectPath.GetFileName(), Reason);
return true;
}
}
}
return false;
}
/// <summary>
/// NOTE: This function must mirror the functionality of TargetPlatformBase::RequiresTempTarget
/// </summary>
private static bool RequiresTempTarget(FileReference RawProjectPath, bool bProjectHasCode, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, TargetType TargetType, bool bRequiresAssetNativization, bool bRequiresCookedData, out string OutReason)
{
// check to see if we already have a Target.cs file
if (bProjectHasCode)
{
OutReason = null;
return false;
}
// check if asset nativization is enabled
if (bRequiresAssetNativization)
{
OutReason = "asset nativization is enabled";
return true;
}
// Check if encryption or signing is enabled
EncryptionAndSigning.CryptoSettings Settings = EncryptionAndSigning.ParseCryptoSettings(RawProjectPath.Directory, Platform);
if (Settings.IsAnyEncryptionEnabled() || Settings.IsPakSigningEnabled())
{
OutReason = "encryption/signing is enabled";
return true;
}
// check the target platforms for any differences in build settings or additional plugins
if(!Unreal.IsEngineInstalled() && !PlatformExports.HasDefaultBuildConfig(RawProjectPath, Platform))
{
OutReason = "project has non-default build configuration";
return true;
}
if(PlatformExports.RequiresBuild(RawProjectPath, Platform))
{
OutReason = "overriden by target platform";
return true;
}
// Read the project descriptor, and find all the plugins available to this project
ProjectDescriptor Project = ProjectDescriptor.FromFile(RawProjectPath);
// Enumerate all the available plugins
Dictionary<string, PluginInfo> AllPlugins = Plugins.ReadAvailablePlugins(Unreal.EngineDirectory, DirectoryReference.FromFile(RawProjectPath), new List<DirectoryReference>()).ToDictionary(x => x.Name, x => x, StringComparer.OrdinalIgnoreCase);
// find if there are any plugins enabled or disabled which differ from the default
string Reason;
if (RequiresTempTargetForCodePlugin(Project, Platform, Configuration, TargetType, AllPlugins, out Reason))
{
OutReason = Reason;
return true;
}
OutReason = null;
return false;
}
/// <summary>
/// NOTE: This function must mirror FPluginManager::RequiresTempTargetForCodePlugin
/// </summary>
static bool RequiresTempTargetForCodePlugin(ProjectDescriptor ProjectDescriptor, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, TargetType TargetType, Dictionary<string, PluginInfo> AllPlugins, out string OutReason)
{
PluginReferenceDescriptor MissingPlugin;
HashSet<string> ProjectCodePlugins = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (!GetCodePluginsForTarget(ProjectDescriptor, Platform, Configuration, TargetType, ProjectCodePlugins, AllPlugins, out MissingPlugin))
{
OutReason = String.Format("{0} plugin is referenced by target but not found", MissingPlugin.Name);
return true;
}
HashSet<string> DefaultCodePlugins = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (!GetCodePluginsForTarget(null, Platform, Configuration, TargetType, DefaultCodePlugins, AllPlugins, out MissingPlugin))
{
OutReason = String.Format("{0} plugin is referenced by the default target but not found", MissingPlugin.Name);
return true;
}
foreach (string ProjectCodePlugin in ProjectCodePlugins)
{
if (!DefaultCodePlugins.Contains(ProjectCodePlugin))
{
OutReason = String.Format("{0} plugin is enabled", ProjectCodePlugin);
return true;
}
}
foreach (string DefaultCodePlugin in DefaultCodePlugins)
{
if (!ProjectCodePlugins.Contains(DefaultCodePlugin))
{
OutReason = String.Format("{0} plugin is disabled", DefaultCodePlugin);
return true;
}
}
OutReason = null;
return false;
}
/// <summary>
/// NOTE: This function must mirror FPluginManager::GetCodePluginsForTarget
/// </summary>
static bool GetCodePluginsForTarget(ProjectDescriptor ProjectDescriptor, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, TargetType TargetType, HashSet<string> CodePluginNames, Dictionary<string, PluginInfo> AllPlugins, out PluginReferenceDescriptor OutMissingPlugin)
{
bool bLoadPluginsForTargetPlatforms = (TargetType == TargetType.Editor);
// Map of all enabled plugins
Dictionary<string, PluginInfo> EnabledPlugins = new Dictionary<string, PluginInfo>(StringComparer.OrdinalIgnoreCase);
// Keep a set of all the plugin names that have been configured. We read configuration data from different places, but only configure a plugin from the first place that it's referenced.
HashSet<string> ConfiguredPluginNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
bool bAllowEnginePluginsEnabledByDefault = true;
// Find all the plugin references in the project file
if (ProjectDescriptor != null)
{
bAllowEnginePluginsEnabledByDefault = !ProjectDescriptor.DisableEnginePluginsByDefault;
if (ProjectDescriptor.Plugins != null)
{
// Copy the plugin references, since we may modify the project if any plugins are missing
foreach (PluginReferenceDescriptor PluginReference in ProjectDescriptor.Plugins)
{
if (!ConfiguredPluginNames.Contains(PluginReference.Name))
{
PluginReferenceDescriptor MissingPlugin;
if (!ConfigureEnabledPluginForTarget(PluginReference, ProjectDescriptor, null, Platform, Configuration, TargetType, bLoadPluginsForTargetPlatforms, AllPlugins, EnabledPlugins, out MissingPlugin))
{
OutMissingPlugin = MissingPlugin;
return false;
}
ConfiguredPluginNames.Add(PluginReference.Name);
}
}
}
}
// Add the plugins which are enabled by default
foreach (KeyValuePair<string, PluginInfo> PluginPair in AllPlugins)
{
if (PluginPair.Value.IsEnabledByDefault(bAllowEnginePluginsEnabledByDefault) && !ConfiguredPluginNames.Contains(PluginPair.Key))
{
PluginReferenceDescriptor MissingPlugin;
if (!ConfigureEnabledPluginForTarget(new PluginReferenceDescriptor(PluginPair.Key, null, true), ProjectDescriptor, null, Platform, Configuration, TargetType, bLoadPluginsForTargetPlatforms, AllPlugins, EnabledPlugins, out MissingPlugin))
{
OutMissingPlugin = MissingPlugin;
return false;
}
ConfiguredPluginNames.Add(PluginPair.Key);
}
}
// Figure out which plugins have code
bool bBuildDeveloperTools = (TargetType == TargetType.Editor || TargetType == TargetType.Program || (Configuration != UnrealTargetConfiguration.Test && Configuration != UnrealTargetConfiguration.Shipping));
bool bRequiresCookedData = (TargetType != TargetType.Editor);
foreach (KeyValuePair<string, PluginInfo> Pair in EnabledPlugins)
{
if (Pair.Value.Descriptor.Modules != null)
{
foreach (ModuleDescriptor Module in Pair.Value.Descriptor.Modules)
{
if (Module.IsCompiledInConfiguration(Platform, Configuration, null, TargetType, bBuildDeveloperTools, bRequiresCookedData))
{
CodePluginNames.Add(Pair.Key);
break;
}
}
}
}
OutMissingPlugin = null;
return true;
}
/// <summary>
/// NOTE: This function should mirror FPluginManager::ConfigureEnabledPluginForTarget
/// </summary>
static bool ConfigureEnabledPluginForTarget(PluginReferenceDescriptor FirstReference, ProjectDescriptor ProjectDescriptor, string TargetName, UnrealTargetPlatform Platform, UnrealTargetConfiguration Configuration, TargetType TargetType, bool bLoadPluginsForTargetPlatforms, Dictionary<string, PluginInfo> AllPlugins, Dictionary<string, PluginInfo> EnabledPlugins, out PluginReferenceDescriptor OutMissingPlugin)
{
if (!EnabledPlugins.ContainsKey(FirstReference.Name))
{
// Set of plugin names we've added to the queue for processing
HashSet<string> NewPluginNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
NewPluginNames.Add(FirstReference.Name);
// Queue of plugin references to consider
List<PluginReferenceDescriptor> NewPluginReferences = new List<PluginReferenceDescriptor>();
NewPluginReferences.Add(FirstReference);
// Loop through the queue of plugin references that need to be enabled, queuing more items as we go
for (int Idx = 0; Idx < NewPluginReferences.Count; Idx++)
{
PluginReferenceDescriptor Reference = NewPluginReferences[Idx];
// Check if the plugin is required for this platform
if(!Reference.IsEnabledForPlatform(Platform) || !Reference.IsEnabledForTargetConfiguration(Configuration) || !Reference.IsEnabledForTarget(TargetType))
{
Log.TraceLog("Ignoring plugin '{0}' for platform/configuration", Reference.Name);
continue;
}
// Check if the plugin is required for this platform
if(!bLoadPluginsForTargetPlatforms && !Reference.IsSupportedTargetPlatform(Platform))
{
Log.TraceLog("Ignoring plugin '{0}' due to unsupported platform", Reference.Name);
continue;
}
// Find the plugin being enabled
PluginInfo Plugin;
if (!AllPlugins.TryGetValue(Reference.Name, out Plugin))
{
// Ignore any optional plugins
if (Reference.bOptional)
{
Log.TraceLog("Ignored optional reference to '%s' plugin; plugin was not found.", Reference.Name);
continue;
}
// Add it to the missing list
OutMissingPlugin = Reference;
return false;
}
// Check the plugin supports this platform
if(!bLoadPluginsForTargetPlatforms && !Plugin.Descriptor.SupportsTargetPlatform(Platform))
{
Log.TraceLog("Ignoring plugin '{0}' due to unsupported platform in plugin descriptor", Reference.Name);
continue;
}
// Check that this plugin supports the current program
if (TargetType == TargetType.Program && !Plugin.Descriptor.SupportedPrograms.Contains(TargetName))
{
Log.TraceLog("Ignoring plugin '{0}' due to absence from the supported programs list", Reference.Name);
continue;
}
// Add references to all its dependencies
if (Plugin.Descriptor.Plugins != null)
{
foreach (PluginReferenceDescriptor NextReference in Plugin.Descriptor.Plugins)
{
if (!EnabledPlugins.ContainsKey(NextReference.Name) && !NewPluginNames.Contains(NextReference.Name))
{
NewPluginNames.Add(NextReference.Name);
NewPluginReferences.Add(NextReference);
}
}
}
// Add the plugin
EnabledPlugins.Add(Plugin.Name, Plugin);
}
}
OutMissingPlugin = null;
return true;
}
private static void GenerateTempTarget(FileReference RawProjectPath)
{
DirectoryReference TempDir = DirectoryReference.Combine(RawProjectPath.Directory, "Intermediate", "Source");
DirectoryReference.CreateDirectory(TempDir);
// Get the project name for use in temporary files
string ProjectName = RawProjectPath.GetFileNameWithoutExtension();
// Create a target.cs file
MemoryStream TargetStream = new MemoryStream();
using (StreamWriter Writer = new StreamWriter(TargetStream))
{
Writer.WriteLine("using UnrealBuildTool;");
Writer.WriteLine();
Writer.WriteLine("public class {0}Target : TargetRules", ProjectName);
Writer.WriteLine("{");
Writer.WriteLine("\tpublic {0}Target(TargetInfo Target) : base(Target)", ProjectName);
Writer.WriteLine("\t{");
Writer.WriteLine("\t\tDefaultBuildSettings = BuildSettingsVersion.V2;");
Writer.WriteLine("\t\tType = TargetType.Game;");
Writer.WriteLine("\t\tExtraModuleNames.Add(\"{0}\");", ProjectName);
Writer.WriteLine("\t}");
Writer.WriteLine("}");
}
FileReference TargetLocation = FileReference.Combine(TempDir, ProjectName + ".Target.cs");
FileReference.WriteAllBytesIfDifferent(TargetLocation, TargetStream.ToArray());
// Create a build.cs file
MemoryStream ModuleStream = new MemoryStream();
using (StreamWriter Writer = new StreamWriter(ModuleStream))
{
Writer.WriteLine("using UnrealBuildTool;");
Writer.WriteLine();
Writer.WriteLine("public class {0} : ModuleRules", ProjectName);
Writer.WriteLine("{");
Writer.WriteLine("\tpublic {0}(ReadOnlyTargetRules Target) : base(Target)", ProjectName);
Writer.WriteLine("\t{");
Writer.WriteLine("\t\tPCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;");
Writer.WriteLine();
Writer.WriteLine("\t\tPrivateDependencyModuleNames.Add(\"Core\");");
Writer.WriteLine("\t\tPrivateDependencyModuleNames.Add(\"Core\");");
Writer.WriteLine("\t}");
Writer.WriteLine("}");
}
FileReference ModuleLocation = FileReference.Combine(TempDir, ProjectName + ".Build.cs");
FileReference.WriteAllBytesIfDifferent(ModuleLocation, ModuleStream.ToArray());
// Create a main module cpp file
MemoryStream SourceFileStream = new MemoryStream();
using (StreamWriter Writer = new StreamWriter(SourceFileStream))
{
Writer.WriteLine("#include \"CoreTypes.h\"");
Writer.WriteLine("#include \"Modules/ModuleManager.h\"");
Writer.WriteLine();
Writer.WriteLine("IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultModuleImpl, {0}, \"{0}\");", ProjectName);
}
FileReference SourceFileLocation = FileReference.Combine(TempDir, ProjectName + ".cpp");
FileReference.WriteAllBytesIfDifferent(SourceFileLocation, SourceFileStream.ToArray());
}
/// <summary>
/// Attempts to autodetect project properties.
/// </summary>
/// <param name="RawProjectPath">Full project path.</param>
/// <returns>Project properties.</returns>
private static ProjectProperties DetectProjectProperties(FileReference RawProjectPath, List<UnrealTargetPlatform> ClientTargetPlatforms, List<UnrealTargetConfiguration> ClientTargetConfigurations, bool AssetNativizationRequested)
{
ProjectProperties Properties = new ProjectProperties();
Properties.RawProjectPath = RawProjectPath;
// detect if the project is content only, but has non-default build settings
List<string> ExtraSearchPaths = null;
if (RawProjectPath != null)
{
// no Target file, now check to see if build settings have changed
List<UnrealTargetPlatform> TargetPlatforms = ClientTargetPlatforms;
if (ClientTargetPlatforms == null || ClientTargetPlatforms.Count < 1)
{
// No client target platforms, add all in
TargetPlatforms = new List<UnrealTargetPlatform>();
foreach (UnrealTargetPlatform TargetPlatformType in UnrealTargetPlatform.GetValidPlatforms())
{
TargetPlatforms.Add(TargetPlatformType);
}
}
List<UnrealTargetConfiguration> TargetConfigurations = ClientTargetConfigurations;
if (TargetConfigurations == null || TargetConfigurations.Count < 1)
{
// No client target configurations, add all in
TargetConfigurations = new List<UnrealTargetConfiguration>();
foreach (UnrealTargetConfiguration TargetConfigurationType in Enum.GetValues(typeof(UnrealTargetConfiguration)))
{
if (TargetConfigurationType != UnrealTargetConfiguration.Unknown)
{
TargetConfigurations.Add(TargetConfigurationType);
}
}
}
string TempTargetDir = CommandUtils.CombinePaths(Path.GetDirectoryName(RawProjectPath.FullName), "Intermediate", "Source");
if (RequiresTempTarget(RawProjectPath, TargetPlatforms, TargetConfigurations, AssetNativizationRequested))
{
GenerateTempTarget(RawProjectPath);
Properties.bWasGenerated = true;
ExtraSearchPaths = new List<string>();
ExtraSearchPaths.Add(TempTargetDir);
}
else if (File.Exists(Path.Combine(Path.GetDirectoryName(RawProjectPath.FullName), "Intermediate", "Source", Path.GetFileNameWithoutExtension(RawProjectPath.FullName) + ".Target.cs")))
{
File.Delete(Path.Combine(Path.GetDirectoryName(RawProjectPath.FullName), "Intermediate", "Source", Path.GetFileNameWithoutExtension(RawProjectPath.FullName) + ".Target.cs"));
}
// in case the RulesCompiler (what we use to find all the
// Target.cs files) has already cached the contents of this
// directory, then we need to invalidate that cache (so
// it'll find/use the new Target.cs file)
Rules.InvalidateRulesFileCache(TempTargetDir);
}
if (CommandUtils.CmdEnv.HasCapabilityToCompile)
{
DetectTargetsForProject(Properties, ExtraSearchPaths);
Properties.bIsCodeBasedProject = !CommandUtils.IsNullOrEmpty(Properties.Targets) || !CommandUtils.IsNullOrEmpty(Properties.Programs);
}
else
{
// should never ask for engine targets if we can't compile
if (RawProjectPath == null)
{
throw new AutomationException("Cannot determine engine targets if we can't compile.");
}
Properties.bIsCodeBasedProject = Properties.bWasGenerated;
// if there's a Source directory with source code in it, then mark us as having source code
string SourceDir = CommandUtils.CombinePaths(Path.GetDirectoryName(RawProjectPath.FullName), "Source");
if (Directory.Exists(SourceDir))
{
string[] CppFiles = Directory.GetFiles(SourceDir, "*.cpp", SearchOption.AllDirectories);
string[] HFiles = Directory.GetFiles(SourceDir, "*.h", SearchOption.AllDirectories);
Properties.bIsCodeBasedProject |= (CppFiles.Length > 0 || HFiles.Length > 0);
}
}
// check to see if the uproject loads modules, only if we haven't already determined it is a code based project
if (!Properties.bIsCodeBasedProject && RawProjectPath != null)
{
string uprojectStr = File.ReadAllText(RawProjectPath.FullName);
Properties.bIsCodeBasedProject = uprojectStr.Contains("\"Modules\"");
}
// Get all ini files
if (RawProjectPath != null)
{
CommandUtils.LogVerbose("Loading ini files for {0}", RawProjectPath);
foreach (UnrealTargetPlatform TargetPlatformType in UnrealTargetPlatform.GetValidPlatforms())
{
ConfigHierarchy EngineConfig = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, RawProjectPath.Directory, TargetPlatformType);
Properties.EngineConfigs.Add(TargetPlatformType, EngineConfig);
ConfigHierarchy GameConfig = ConfigCache.ReadHierarchy(ConfigHierarchyType.Game, RawProjectPath.Directory, TargetPlatformType);
Properties.GameConfigs.Add(TargetPlatformType, GameConfig);
}
}
return Properties;
}
/// <summary>
/// Gets the project's root binaries folder.
/// </summary>
/// <param name="RawProjectPath">Full project path.</param>
/// <param name="TargetType">Target type.</param>
/// <param name="bIsUProjectFile">True if uproject file.</param>
/// <returns>Binaries path.</returns>
public static DirectoryReference GetClientProjectBinariesRootPath(FileReference RawProjectPath, TargetType TargetType, bool bIsCodeBasedProject)
{
DirectoryReference BinPath = null;
switch (TargetType)
{
case TargetType.Program:
BinPath = DirectoryReference.Combine(Unreal.RootDirectory, "Engine", "Binaries");
break;
case TargetType.Client:
case TargetType.Game:
if (!bIsCodeBasedProject)
{
BinPath = DirectoryReference.Combine(Unreal.RootDirectory, "Engine", "Binaries");
}
else
{
BinPath = DirectoryReference.Combine(RawProjectPath.Directory, "Binaries");
}
break;
}
return BinPath;
}
/// <summary>
/// Gets the location where all rules assemblies should go
/// </summary>
private static string GetRulesAssemblyFolder()
{
string RulesFolder;
if (Unreal.IsEngineInstalled())
{
RulesFolder = CommandUtils.CombinePaths(Path.GetTempPath(), "UAT", CommandUtils.EscapePath(CommandUtils.CmdEnv.LocalRoot), "Rules");
}
else
{
RulesFolder = CommandUtils.CombinePaths(CommandUtils.CmdEnv.EngineSavedFolder, "Rules");
}
return RulesFolder;
}
/// <summary>
/// Finds all targets for the project.
/// </summary>
/// <param name="Properties">Project properties.</param>
/// <param name="ExtraSearchPaths">Additional search paths.</param>
private static void DetectTargetsForProject(ProjectProperties Properties, List<string> ExtraSearchPaths = null)
{
Properties.Targets = new List<SingleTargetProperties>();
FileReference TargetsDllFilename;
string FullProjectPath = null;
List<DirectoryReference> GameFolders = new List<DirectoryReference>();
DirectoryReference RulesFolder = new DirectoryReference(GetRulesAssemblyFolder());
if (Properties.RawProjectPath != null)
{
CommandUtils.LogVerbose("Looking for targets for project {0}", Properties.RawProjectPath);
TargetsDllFilename = FileReference.Combine(RulesFolder, String.Format("UATRules-{0}.dll", ContentHash.MD5(Properties.RawProjectPath.FullName.ToUpperInvariant()).ToString()));
FullProjectPath = CommandUtils.GetDirectoryName(Properties.RawProjectPath.FullName);
GameFolders.Add(new DirectoryReference(FullProjectPath));
CommandUtils.LogVerbose("Searching for target rule files in {0}", FullProjectPath);
}
else
{
TargetsDllFilename = FileReference.Combine(RulesFolder, String.Format("UATRules{0}.dll", "_BaseEngine_"));
}
// the UBT code assumes a certain CWD, but artists don't have this CWD.
string SourceDir = CommandUtils.CombinePaths(CommandUtils.CmdEnv.LocalRoot, "Engine", "Source");
bool DirPushed = false;
if (CommandUtils.DirectoryExists_NoExceptions(SourceDir))
{
CommandUtils.PushDir(SourceDir);
DirPushed = true;
}
List<DirectoryReference> ExtraSearchDirectories = (ExtraSearchPaths == null)? null : ExtraSearchPaths.Select(x => new DirectoryReference(x)).ToList();
List<FileReference> TargetScripts = Rules.FindAllRulesSourceFiles(Rules.RulesFileType.Target, GameFolders: GameFolders, ForeignPlugins: null, AdditionalSearchPaths: ExtraSearchDirectories);
if (DirPushed)
{
CommandUtils.PopDir();
}
if (!CommandUtils.IsNullOrEmpty(TargetScripts))
{
// We only care about project target script so filter out any scripts not in the project folder, or take them all if we are just doing engine stuff
List<FileReference> ProjectTargetScripts = new List<FileReference>();
foreach (FileReference TargetScript in TargetScripts)
{
if (FullProjectPath == null || TargetScript.IsUnderDirectory(new DirectoryReference(FullProjectPath)))
{
// skip target rules that are platform extension or platform group specializations
string[] TargetPathSplit = TargetScript.GetFileNameWithoutAnyExtensions().Split(new char[]{'_'}, StringSplitOptions.RemoveEmptyEntries );
if (TargetPathSplit.Length > 1 && (UnrealTargetPlatform.IsValidName(TargetPathSplit.Last()) || UnrealPlatformGroup.IsValidName(TargetPathSplit.Last()) ) )
{
continue;
}
ProjectTargetScripts.Add(TargetScript);
}
}
TargetScripts = ProjectTargetScripts;
}
if (!CommandUtils.IsNullOrEmpty(TargetScripts))
{
CommandUtils.LogVerbose("Found {0} target rule files:", TargetScripts.Count);
foreach (FileReference Filename in TargetScripts)
{
CommandUtils.LogVerbose(" {0}", Filename);
}
// Check if the scripts require compilation
bool DoNotCompile = false;
if (!CommandUtils.IsBuildMachine && !CheckIfScriptAssemblyIsOutOfDate(TargetsDllFilename, TargetScripts))
{
Log.TraceVerbose("Targets DLL {0} is up to date.", TargetsDllFilename);
DoNotCompile = true;
}
if (!DoNotCompile && CommandUtils.FileExists_NoExceptions(TargetsDllFilename.FullName))
{
if (!CommandUtils.DeleteFile_NoExceptions(TargetsDllFilename.FullName, true))
{
DoNotCompile = true;
CommandUtils.LogVerbose("Could not delete {0} assuming it is up to date and reusable for a recursive UAT call.", TargetsDllFilename);
}
}
CompileAndLoadTargetsAssembly(Properties, TargetsDllFilename, DoNotCompile, TargetScripts);
}
}
/// <summary>
/// Optionally compiles and loads target rules assembly.
/// </summary>
/// <param name="Properties"></param>
/// <param name="TargetsDllFilename"></param>
/// <param name="DoNotCompile"></param>
/// <param name="TargetScripts"></param>
private static void CompileAndLoadTargetsAssembly(ProjectProperties Properties, FileReference TargetsDllFilename, bool DoNotCompile, List<FileReference> TargetScripts)
{
Properties.TargetScripts = new List<FileReference>(TargetScripts);
CommandUtils.LogVerbose("Compiling targets DLL: {0}", TargetsDllFilename);
List<string> ReferencedAssemblies = new List<string>()
{
typeof(UnrealBuildTool.PlatformExports).Assembly.Location
};
List<string> PreprocessorDefinitions = RulesAssembly.GetPreprocessorDefinitions();
Assembly TargetsDLL = DynamicCompilation.CompileAndLoadAssembly(TargetsDllFilename, new HashSet<FileReference>(TargetScripts), ReferencedAssemblies, PreprocessorDefinitions, DoNotCompile);
Type[] AllCompiledTypes = TargetsDLL.GetTypes();
foreach (Type TargetType in AllCompiledTypes)
{
// Find TargetRules but skip all "UnrealEditor", "UnrealGame" targets.
if (typeof(TargetRules).IsAssignableFrom(TargetType) && !TargetType.IsAbstract)
{
string TargetName = GetTargetName(TargetType);
TargetInfo DummyTargetInfo = new TargetInfo(TargetName, BuildHostPlatform.Current.Platform, UnrealTargetConfiguration.Development, "", Properties.RawProjectPath, null);
// Create an instance of this type
CommandUtils.LogVerbose("Creating target rules object: {0}", TargetType.Name);
TargetRules Rules = Activator.CreateInstance(TargetType, DummyTargetInfo) as TargetRules;
CommandUtils.LogVerbose("Adding target: {0} ({1})", TargetType.Name, Rules.Type);
SingleTargetProperties TargetData = new SingleTargetProperties();
TargetData.TargetName = GetTargetName(TargetType);
TargetData.TargetClassName = TargetType.FullName;
TargetData.Rules = Rules;
if (Rules.Type == global::UnrealBuildTool.TargetType.Program)
{
Properties.Programs.Add(TargetData);
}
else
{
Properties.Targets.Add(TargetData);
}
}
}
}
/// <summary>
/// Checks if any of the script files in newer than the generated assembly.
/// </summary>
/// <param name="TargetsDllFilename"></param>
/// <param name="TargetScripts"></param>
/// <returns>True if the generated assembly is out of date.</returns>
private static bool CheckIfScriptAssemblyIsOutOfDate(FileReference TargetsDllFilename, List<FileReference> TargetScripts)
{
bool bOutOfDate = false;
FileInfo AssemblyInfo = new FileInfo(TargetsDllFilename.FullName);
if (AssemblyInfo.Exists)
{
foreach (FileReference ScriptFilename in TargetScripts)
{
FileInfo ScriptInfo = new FileInfo(ScriptFilename.FullName);
if (ScriptInfo.Exists && ScriptInfo.LastWriteTimeUtc > AssemblyInfo.LastWriteTimeUtc)
{
bOutOfDate = true;
break;
}
}
}
else
{
bOutOfDate = true;
}
return bOutOfDate;
}
/// <summary>
/// Converts class type name (usually ends with Target) to a target name (without the postfix).
/// </summary>
/// <param name="TargetRulesType">Tagert class.</param>
/// <returns>Target name</returns>
private static string GetTargetName(Type TargetRulesType)
{
const string TargetPostfix = "Target";
string Name = TargetRulesType.Name;
if (Name.EndsWith(TargetPostfix, StringComparison.InvariantCultureIgnoreCase))
{
Name = Name.Substring(0, Name.Length - TargetPostfix.Length);
}
return Name;
}
/// <summary>
/// Performs initial cleanup of target rules folder
/// </summary>
public static void CleanupFolders()
{
CommandUtils.LogVerbose("Cleaning up project rules folder");
string RulesFolder = GetRulesAssemblyFolder();
if (CommandUtils.DirectoryExists(RulesFolder))
{
CommandUtils.DeleteDirectoryContents(RulesFolder);
}
}
/// <summary>
/// Takes a game name (e.g "ShooterGame") and tries to find the path to the project file
/// </summary>
/// <param name="GameName"></param>
/// <returns></returns>
public static FileReference FindProjectFileFromName(string GameName)
{
// if they passed in a path then easy.
if (File.Exists(GameName))
{
return new FileReference(GameName);
}
// Start with the gamename regardless of what they passed in
GameName = Path.GetFileNameWithoutExtension(GameName);
// Turn Foo into Foo.uproject
string ProjectFile = GameName;
if (string.IsNullOrEmpty(Path.GetExtension(ProjectFile)))
{
// if project was specified but had no extension then just add it.
ProjectFile = Path.ChangeExtension(GameName, ".uproject");
}
// Turn Foo.uproject into Foo/Foo.uproject
ProjectFile = Path.Combine(GameName, ProjectFile);
GameName = Path.GetFileNameWithoutExtension(GameName);
// check for sibling to engine
if (File.Exists(ProjectFile))
{
return new FileReference(ProjectFile);
}
// Search NativeProjects (sibling folders).
IEnumerable<FileReference> Projects = NativeProjects.EnumerateProjectFiles();
FileReference ProjectPath = Projects.Where(R => string.Equals(R.GetFileName(), ProjectFile, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (ProjectPath == null)
{
// read .uprojectdirs
List<string> SearchPaths = new List<string>();
SearchPaths.Add("");
string ProjectDirsFile = Directory.EnumerateFiles(Environment.CurrentDirectory, "*.uprojectdirs").FirstOrDefault();
if (ProjectDirsFile != null)
{
foreach (string FilePath in File.ReadAllLines(ProjectDirsFile))
{
string Trimmed = FilePath.Trim();
if (!Trimmed.StartsWith("./", StringComparison.OrdinalIgnoreCase) &&
!Trimmed.StartsWith(";", StringComparison.OrdinalIgnoreCase) &&
Trimmed.IndexOfAny(Path.GetInvalidPathChars()) < 0)
{
SearchPaths.Add(Trimmed);
}
}
string ResolvedFile = SearchPaths.Select(P => Path.Combine(P, ProjectFile))
.Where(P => File.Exists(P))
.FirstOrDefault();
if (ResolvedFile != null)
{
ProjectPath = new FileReference(ResolvedFile);
}
}
}
// either valid or we're out of ideas...
return ProjectPath;
}
/// <summary>
/// Full path to the Project executable for the current platform.
/// </summary>
/// <param name="ProjectFile">Path to Project file</param>
/// <param name="TargetType">Target type</param>
/// <param name="TargetPlatform">Target platform</param>
/// <param name="TargetConfiguration">Target build configuration</param>
/// <param name="Cmd">Do you want the console subsystem/commandlet executable?</param>
/// <returns></returns>
public static FileSystemReference GetProjectTarget(FileReference ProjectFile, UnrealBuildTool.TargetType TargetType, UnrealBuildTool.UnrealTargetPlatform TargetPlatform, UnrealBuildTool.UnrealTargetConfiguration TargetConfiguration = UnrealBuildTool.UnrealTargetConfiguration.Development, bool Cmd = false)
{
ProjectProperties Properties = ProjectUtils.GetProjectProperties(ProjectFile);
List<SingleTargetProperties> Targets = Properties.Targets.Where(x => x.Rules.Type == TargetType).ToList();
string TargetName = null;
switch (Targets.Count)
{
case 0:
return null;
case 1:
TargetName = Targets.First().TargetName;
break;
default:
Properties.EngineConfigs[TargetPlatform].GetString("/Script/BuildSettings.BuildSettings", "DefaultEditorTarget", out TargetName);
break;
}
FileReference TargetReceiptFileName = UnrealBuildTool.TargetReceipt.GetDefaultPath(ProjectFile.Directory, TargetName, TargetPlatform, TargetConfiguration, "");
UnrealBuildTool.TargetReceipt TargetReceipt = UnrealBuildTool.TargetReceipt.Read(TargetReceiptFileName);
if (Cmd)
{
return TargetReceipt.LaunchCmd;
}
if (TargetPlatform == UnrealTargetPlatform.Mac)
{
// Remove trailing "/Contents/MacOS/UnrealEngine" to get back to .app directory
return TargetReceipt.Launch.Directory.ParentDirectory.ParentDirectory;
}
return TargetReceipt.Launch;
}
}
public class BranchInfo
{
[DebuggerDisplay("{GameName}")]
public class BranchUProject
{
public string GameName;
public FileReference FilePath;
private ProjectProperties CachedProperties;
public ProjectProperties Properties
{
get
{
if(CachedProperties == null)
{
CachedProperties = ProjectUtils.GetProjectProperties(FilePath);
}
return CachedProperties;
}
}
public BranchUProject(FileReference ProjectFile)
{
GameName = ProjectFile.GetFileNameWithoutExtension();
//not sure what the heck this path is relative to
FilePath = ProjectFile;
if (!CommandUtils.FileExists_NoExceptions(FilePath.FullName))
{
throw new AutomationException("Could not resolve relative path corrctly {0} -> {1} which doesn't exist.", ProjectFile, FilePath);
}
}
}
public List<BranchUProject> AllProjects = new List<BranchUProject>();
public BranchInfo()
{
IEnumerable<FileReference> ProjectFiles = UnrealBuildTool.NativeProjects.EnumerateProjectFiles();
foreach (FileReference InfoEntry in ProjectFiles)
{
AllProjects.Add(new BranchUProject(InfoEntry));
}
CommandUtils.LogVerbose(" {0} projects:", AllProjects.Count);
foreach (BranchUProject Proj in AllProjects)
{
CommandUtils.LogLog(" {0}: {1}", Proj.GameName, Proj.FilePath);
}
}
public BranchUProject FindGame(string GameName)
{
foreach (BranchUProject Proj in AllProjects)
{
if (Proj.GameName.Equals(GameName, StringComparison.InvariantCultureIgnoreCase))
{
return Proj;
}
}
return null;
}
public BranchUProject FindGameChecked(string GameName)
{
BranchUProject Project = FindGame(GameName);
if(Project == null)
{
throw new AutomationException("Cannot find project '{0}' in branch", GameName);
}
return Project;
}
}
}