You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden
============================
MAJOR FEATURES & CHANGES
============================
Change 4287066 by Ben.Marsh
BuildGraph: Prevent errors being shown when running with -listonly.
Change 4288274 by Ben.Marsh
If unable to get dbghelp module info, fallback to use available module info (fixes PC calculations for portable stack walk)
(Originally CL 4206596 by Josh.Engerbretson)
Change 4303760 by Ben.Marsh
Automatically apply XML config to all platform rules objects.
Change 4304461 by Mike.Erwin
UAT: fix breakage when cooking Blueprint projects
Yesterday I broke this in CL 4301305. Reworked logic around the -server option was causing
"ERROR: Server target not found. Server target is required with -server and -cook or -cookonthefly"
This CL fixes projects that don't have a Server.Target.cs but want to -cook.
#jira UE-60512
Change 4304652 by Ben.Marsh
Improve error message when unable to parse the current CL from a stream.
Change 4304795 by Ben.Marsh
Fix occasional incremental build error in AutomationScripts project. Explicit dependency on Localization output file was invalidating project dependency.
Change 4307117 by Ben.Marsh
UAT: Fix problems making installed builds with Lumin enabled, when passing the -Clean argument to UAT.
* UE4Build was deleting all files in the manifest before running, even if they were output by UBT.
* Lumin was specifying AllowParallelExecutor="false" in InstalledEngineBuild.xml, which was causing each compile to be executed in isolation. Since modules are now compiled into object files separately to being linked into binaries, this was causing object files and .precompiled files to be deleted during the clean operation.
* Individual compile tasks can now override the global "clean' option.
Change 4307187 by Ben.Marsh
UAT: Remove code for deleting build products and retrying XGE compiles.
Change 4307411 by Chad.Garyet
Merging 4279381 to DevBuild
Fix for UE4Build.cs for Swarm
#jira none
Change 4307794 by Ben.Marsh
UBT: Add a $(TargetOutputDir) variable that can be used in UBT paths and expands to the directory containing the executable. Can be used to ensure DLLs are copied to the appropriate output folder during a build.
Also rename the current $(OutputDir) variable (indicating the binary output directory) to $(BinaryOutputDir) to distinguish between them.
Change 4307912 by Ben.Marsh
Remove the Embree and FBX DLLs that are checked in to Engine/Binaries/Win64, and copy them from the ThirdParty directory as part of the build process instead. This fixes issues where the editor executable is under the project directory rather than the engine directory because it's been built with custom settings (eg. as RoboRecall's editor was).
Change 4307950 by Ben.Marsh
UBT: Add a field to the target receipt which specifies the executable to launch. Allows procedurally determining the location of the editor for a particular target.
Change 4308057 by Ben.Marsh
UGS: Try to read the editor executable path from the target receipt, rather than guessing the executable path directly.
Change 4308651 by Ben.Marsh
UBT: Fix project files not containing include search paths for dependent modules.
#jira UE-62042
Change 4310103 by Ben.Marsh
UGS: Fix exception entering custom server settings when UGS registry key does not exist.
Change 4310109 by Ben.Marsh
UGS: Update launcher version to 1.15.
Change 4310232 by Ben.Marsh
UBT: Catch the case where manifests are recycled if the -NoManifestChanges argument is specified.
Change 4310901 by Ben.Marsh
UBT: Prevent continuing past manifest errors.
Change 4311049 by Ben.Marsh
UBT: Suppress message about change to action history if the previous build product did not exist.
Change 4315494 by Mike.Erwin
Fix backup log timestamps.
When engine starts up, it makes a backup of previous run's log with timestamp appended. Previous code used the current engine startup time; basically recording the time it was backed up, not the time it was generated. This change honors the file's original timestamp. This way files sort properly and we get accurate information about when each log was generated.
#jira none
Change 4318354 by Ben.Marsh
UGS: Add proper support for DPI scaling.
Change 4318356 by Ben.Marsh
UGS: Increase version to 1.153.
Change 4318597 by Ben.Marsh
UGS: Make UGS launcher DPI-aware.
Change 4319205 by Ben.Marsh
UAT: Always use the manifest for determining output files. UBT will initialize it to an empty list of files if the arguments deem it necessary.
Change 4319565 by Ben.Marsh
UAT: Generate the manifest for each compiled target at the same time as exporting its build steps. Prevents module manifests being clobbered due to being overwritten twice.
Change 4320058 by Ben.Marsh
UGS: Prevent "path too long" errors when enumerating files to delete. If the path is invalid, we probably never synced it in the first place, and P4 failed silently.
Change 4320559 by Ben.Marsh
UAT: Fix ParallelExecutor not being used when XGE is not installed, and UBT being run twice for each target.
Change 4322499 by Chad.Garyet
Adding regex to rip the first two p4 path fragments out of a project name to look for wildcard matches in the db
Also actually now allows ability to get ids for specific projects, versus before where all ids were returned for projects that would have matched via wildcard
Pushed to Live 8/28/18
#jira none
Change 4323023 by Ben.Marsh
UBT: Fix cook problems caused by copied runtime dependencies not being added to the receipt or manifest.
#jira UE-63416
Change 4323094 by Ben.Marsh
Add missing dependency on UnrealPak being compiled before building feature packs.
#jira UE-63430
Change 4323330 by Ben.Marsh
Prevent runtime dependencies being copied into installed locations.
#jira UE-63434
Change 4325603 by Ben.Marsh
UBT: Output a message when there is nothing to build for a target (eg. because it's an installed build)
Change 4325607 by Ben.Marsh
UnrealPak: Prevent non-deterministic padding data when compression is disabled.
Change 4328149 by Ben.Marsh
UBT: Fixed issue where MaxProcessorCount and ProcessorCountMultiplier settings would only affect the first class found. These values can now be configured for each executor separately, via the LocalExecutor, ParallelExecutor and SNDBS sections in BuildConfiguration.xml.
Change 4330605 by Ben.Marsh
UGS: When browsing for a project file, always default to the directory containing the current selection.
#jira UE-63474
Change 4333349 by Ben.Marsh
PR #5052: Remove OutputLines = new List<string>(); (Contributed by LizardThief)
Change 4334312 by Ben.Marsh
Update UGS version to 1.154, launcher version to 1.16.
Change 4335790 by Ben.Marsh
UAT: Add a script that checks that the following macro pairs are correctly balanced within every source file in the engine:
* PRAGMA_DISABLE_OPTIMIZATION and PRAGMA_ENABLE_OPTIMIZATION
* PRAGMA_DISABLE_DEPRECATION_WARNINGS and PRAGMA_ENABLE_DEPRECATION_WARNINGS
* THIRD_PARTY_INCLUDES_START and THIRD_PARTY_INCLUDES_END
* PRAGMA_DISABLE_SHADOW_VARIABLE_WARNINGS and PRAGMA_ENABLE_SHADOW_VARIABLE_WARNINGS
* PRAGMA_DISABLE_UNDEFINED_IDENTIFIER_WARNINGS and PRAGMA_ENABLE_UNDEFINED_IDENTIFIER_WARNINGS
* PRAGMA_DISABLE_MISSING_VIRTUAL_DESTRUCTOR_WARNINGS and PRAGMA_ENABLE_MISSING_VIRTUAL_DESTRUCTOR_WARNINGS"
* BEGIN_FUNCTION_BUILD_OPTIMIZATION and END_FUNCTION_BUILD_OPTIMIZATION
* BEGIN_FUNCTION_BUILD_OPTIMIZATION and END_FUNCTION_BUILD_OPTIMIZATION
* BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION and END_SLATE_FUNCTION_BUILD_OPTIMIZATION
Additional projects to scan may be specified with the -Project=<FileName> argument.
Change 4336441 by Ben.Marsh
UGS: Halve P4 update rate (now once every 2m)
Change 4336597 by Ben.Marsh
UAT: Add a command for stashing and unstashing a target. Useful for A/B testing. Example syntax:
Stashing:
RunUAT StashTarget -Name=UE4Editor -To=D:\TempEditor
RunUAT StashTarget -Name=UE4Editor -Platform=Win64 -Configuration=Debug -To=D:\TempEditor
RunUAT StashTarget -Name=ShooterGameEditor -Platform=Win64 -Configuration=Debug -Project=D:\P4\Samples\ShooterGame\ShooterGame.uproject -To=D:\TempEditor
Unstashing:
RunUAT UnstashTarget -From=D:\TempEditor
Change 4301305 by Mike.Erwin
UAT: support multiple Client and Server targets per project.
AutomationTool now accepts values for its -client and -server parameters.
So if you have ClientA.Target.cs and ClientB.Target.cs, run UAT with -client=ClientB to make a build of that variant.
If there is only one target of a particular type (true for all projects up to this point) UAT will detect and use it. If more than target is found, you must specify which one to use. Existing build commands should continue working as before.
Project.Properties.Targets is now a List instead of a Dictionary. RocketSamples.cs is updated here, scripts in other branches might need similar changes. Tested ok in Dev-Core, 4.20, and Fortnite-Main.
#jira UE-60512
Change 4304662 by Ben.Marsh
Always pass an explicit file list into BuildPatchTool when chunking a build. Less prone to errors than filtering files out from a build.
Change 4304874 by Ben.Marsh
UBT: Add an option to produce an error whenever a module manifest is changing, to make it easier to track down issues with files being modified.
Change 4319181 by Ben.Marsh
UAT: Use enum for platform and configuration settings in UE4Build.cs.
#rb none
#ROBOMERGE-OWNER: jason.bestimt
#ROBOMERGE-SOURCE: CL 4336671 in //UE4/Main/...
#ROBOMERGE-BOT: DEVVR (Main -> Dev-VR)
[CL 4336684 by ben marsh in Dev-VR branch]
830 lines
32 KiB
C#
830 lines
32 KiB
C#
// Copyright 1998-2018 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 Tools.DotNETCommon;
|
|
using System.Reflection;
|
|
|
|
namespace AutomationTool
|
|
{
|
|
public struct SingleTargetProperties
|
|
{
|
|
public string TargetName;
|
|
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 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 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 = UnrealTargetPlatform.Unknown)
|
|
{
|
|
if (Platform != UnrealTargetPlatform.Unknown)
|
|
{
|
|
ProjectClientBinariesPath = DirectoryReference.Combine(ProjectClientBinariesPath, Platform.ToString());
|
|
}
|
|
return ProjectClientBinariesPath;
|
|
}
|
|
|
|
private static bool RequiresTempTarget(FileReference RawProjectPath, List<UnrealTargetPlatform> ClientTargetPlatforms, List<UnrealTargetConfiguration> ClientTargetConfigurations, bool AssetNativizationRequested)
|
|
{
|
|
// 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 false;
|
|
}
|
|
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 false;
|
|
}
|
|
}
|
|
|
|
//
|
|
// once we reach this point, we can surmise that this is an asset-
|
|
// only (code free) project
|
|
|
|
if (AssetNativizationRequested)
|
|
{
|
|
// we're going to be converting some of the project's assets
|
|
// into native code, so we require a distinct target (executable)
|
|
// be generated for this project
|
|
return true;
|
|
}
|
|
|
|
if (ClientTargetPlatforms != null)
|
|
{
|
|
foreach (UnrealTargetPlatform ClientPlatform in ClientTargetPlatforms)
|
|
{
|
|
EncryptionAndSigning.CryptoSettings Settings = EncryptionAndSigning.ParseCryptoSettings(RawProjectPath.Directory, ClientPlatform);
|
|
if (Settings.IsAnyEncryptionEnabled() || Settings.bEnablePakSigning)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 Enum.GetValues(typeof(UnrealTargetPlatform)))
|
|
{
|
|
if (TargetPlatformType != UnrealTargetPlatform.Unknown)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Change the working directory to be the Engine/Source folder. We are running from Engine/Binaries/DotNET
|
|
DirectoryReference oldCWD = DirectoryReference.GetCurrentDirectory();
|
|
try
|
|
{
|
|
DirectoryReference EngineSourceDirectory = DirectoryReference.Combine(CommandUtils.EngineDirectory, "Source");
|
|
if (!DirectoryReference.Exists(EngineSourceDirectory)) // only set the directory if it exists, this should only happen if we are launching the editor from an artist sync
|
|
{
|
|
EngineSourceDirectory = DirectoryReference.Combine(CommandUtils.EngineDirectory, "Binaries");
|
|
}
|
|
Directory.SetCurrentDirectory(EngineSourceDirectory.FullName);
|
|
|
|
// Read the project descriptor, and find all the plugins available to this project
|
|
ProjectDescriptor Project = ProjectDescriptor.FromFile(RawProjectPath);
|
|
List<PluginInfo> AvailablePlugins = Plugins.ReadAvailablePlugins(CommandUtils.EngineDirectory, RawProjectPath, Project.AdditionalPluginDirectories);
|
|
|
|
// check the target platforms for any differences in build settings or additional plugins
|
|
bool RetVal = false;
|
|
foreach (UnrealTargetPlatform TargetPlatformType in TargetPlatforms)
|
|
{
|
|
if((!Automation.IsEngineInstalled() && !PlatformExports.HasDefaultBuildConfig(RawProjectPath, TargetPlatformType)) || PlatformExports.RequiresBuild(RawProjectPath, TargetPlatformType))
|
|
{
|
|
RetVal = true;
|
|
break;
|
|
}
|
|
|
|
// find if there are any plugins enabled or disabled which differ from the default
|
|
foreach(PluginInfo Plugin in AvailablePlugins)
|
|
{
|
|
bool bPluginEnabledForProject = false;
|
|
foreach (UnrealTargetConfiguration TargetConfigType in TargetConfigurations)
|
|
{
|
|
bPluginEnabledForProject |= Plugins.IsPluginEnabledForProject(Plugin, Project, TargetPlatformType, TargetConfigType, TargetRules.TargetType.Game);
|
|
}
|
|
if ((bPluginEnabledForProject != Plugin.EnabledByDefault) || (bPluginEnabledForProject && Plugin.Descriptor.bInstalled))
|
|
{
|
|
// NOTE: this code was only marking plugins that compiled for the platform to upgrade to code project, however
|
|
// this doesn't work in practice, because the runtime code will look for the plugin, without a .uplugin file,
|
|
// and will fail. This is the safest way to make sure all platforms are acting the same. However, if you
|
|
// whitelist the plugin in the .uproject file, the above UProjectInfo.IsPluginEnabledForProject check won't pass
|
|
// so you won't get in here. Leaving this commented out code in there, because someone is bound to come looking
|
|
// for why a non-whitelisted platform module is causing a project to convert to code-based.
|
|
// As an aside, if you run the project with UE4Game (not your Project's binary) from the debugger, it will work
|
|
// _in this case_ because the .uplugin file will have been staged, and there is no needed library
|
|
// if(Plugin.Descriptor.Modules.Any(Module => Module.IsCompiledInConfiguration(TargetPlatformType, TargetType.Game, bBuildDeveloperTools: false, bBuildEditor: false)))
|
|
{
|
|
RetVal = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return RetVal;
|
|
}
|
|
finally
|
|
{
|
|
// Change back to the original directory
|
|
Directory.SetCurrentDirectory(oldCWD.FullName);
|
|
}
|
|
}
|
|
|
|
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
|
|
FileReference TargetLocation = FileReference.Combine(TempDir, ProjectName + ".Target.cs");
|
|
using (StreamWriter Writer = new StreamWriter(TargetLocation.FullName))
|
|
{
|
|
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\tType = TargetType.Game;");
|
|
Writer.WriteLine("\t\tExtraModuleNames.Add(\"{0}\");", ProjectName);
|
|
Writer.WriteLine("\t}");
|
|
Writer.WriteLine("}");
|
|
}
|
|
|
|
// Create a build.cs file
|
|
FileReference ModuleLocation = FileReference.Combine(TempDir, ProjectName + ".Build.cs");
|
|
using (StreamWriter Writer = new StreamWriter(ModuleLocation.FullName))
|
|
{
|
|
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("}");
|
|
}
|
|
|
|
// Create a main module cpp file
|
|
FileReference SourceFileLocation = FileReference.Combine(TempDir, ProjectName + ".cpp");
|
|
using (StreamWriter Writer = new StreamWriter(SourceFileLocation.FullName))
|
|
{
|
|
Writer.WriteLine("#include \"CoreTypes.h\"");
|
|
Writer.WriteLine("#include \"Modules/ModuleManager.h\"");
|
|
Writer.WriteLine();
|
|
Writer.WriteLine("IMPLEMENT_PRIMARY_GAME_MODULE(FDefaultModuleImpl, {0}, \"{0}\");", ProjectName);
|
|
}
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
var 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)
|
|
{
|
|
string TempTargetDir = CommandUtils.CombinePaths(Path.GetDirectoryName(RawProjectPath.FullName), "Intermediate", "Source");
|
|
if (RequiresTempTarget(RawProjectPath, ClientTargetPlatforms, ClientTargetConfigurations, 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)
|
|
RulesCompiler.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 Enum.GetValues(typeof(UnrealTargetPlatform)))
|
|
{
|
|
if (TargetPlatformType != UnrealTargetPlatform.Unknown)
|
|
{
|
|
var Config = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, RawProjectPath.Directory, TargetPlatformType);
|
|
Properties.EngineConfigs.Add(TargetPlatformType, Config);
|
|
}
|
|
}
|
|
|
|
foreach (UnrealTargetPlatform TargetPlatformType in Enum.GetValues(typeof(UnrealTargetPlatform)))
|
|
{
|
|
if (TargetPlatformType != UnrealTargetPlatform.Unknown)
|
|
{
|
|
var Config = ConfigCache.ReadHierarchy(ConfigHierarchyType.Game, RawProjectPath.Directory, TargetPlatformType);
|
|
Properties.GameConfigs.Add(TargetPlatformType, Config);
|
|
}
|
|
}
|
|
}
|
|
|
|
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(CommandUtils.RootDirectory, "Engine", "Binaries");
|
|
break;
|
|
case TargetType.Client:
|
|
case TargetType.Game:
|
|
if (!bIsCodeBasedProject)
|
|
{
|
|
BinPath = DirectoryReference.Combine(CommandUtils.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 (GlobalCommandLine.Installed.IsSet)
|
|
{
|
|
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;
|
|
|
|
var GameFolders = new List<DirectoryReference>();
|
|
var 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", Properties.RawProjectPath.GetHashCode()));
|
|
|
|
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.
|
|
var SourceDir = CommandUtils.CombinePaths(CommandUtils.CmdEnv.LocalRoot, "Engine", "Source");
|
|
bool DirPushed = false;
|
|
if (CommandUtils.DirectoryExists_NoExceptions(SourceDir))
|
|
{
|
|
CommandUtils.PushDir(SourceDir);
|
|
DirPushed = true;
|
|
}
|
|
var ExtraSearchDirectories = (ExtraSearchPaths == null)? null : ExtraSearchPaths.Select(x => new DirectoryReference(x)).ToList();
|
|
var TargetScripts = RulesCompiler.FindAllRulesSourceFiles(RulesCompiler.RulesFileType.Target, GameFolders: GameFolders, ForeignPlugins: null, AdditionalSearchPaths: ExtraSearchDirectories, bIncludeEnterprise: false);
|
|
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
|
|
var ProjectTargetScripts = new List<FileReference>();
|
|
foreach (var TargetScript in TargetScripts)
|
|
{
|
|
if (FullProjectPath == null || TargetScript.IsUnderDirectory(new DirectoryReference(FullProjectPath)))
|
|
{
|
|
ProjectTargetScripts.Add(TargetScript);
|
|
}
|
|
}
|
|
TargetScripts = ProjectTargetScripts;
|
|
}
|
|
|
|
if (!CommandUtils.IsNullOrEmpty(TargetScripts))
|
|
{
|
|
CommandUtils.LogVerbose("Found {0} target rule files:", TargetScripts.Count);
|
|
foreach (var 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)
|
|
{
|
|
CommandUtils.LogVerbose("Compiling targets DLL: {0}", TargetsDllFilename);
|
|
|
|
var ReferencedAssemblies = new List<string>()
|
|
{
|
|
"System.dll",
|
|
"System.Core.dll",
|
|
"System.Xml.dll",
|
|
typeof(UnrealBuildTool.PlatformExports).Assembly.Location
|
|
};
|
|
List<string> PreprocessorDefinitions = RulesAssembly.GetPreprocessorDefinitions();
|
|
var TargetsDLL = DynamicCompilation.CompileAndLoadAssembly(TargetsDllFilename, TargetScripts, ReferencedAssemblies, PreprocessorDefinitions, DoNotCompile);
|
|
var AllCompiledTypes = TargetsDLL.GetTypes();
|
|
foreach (Type TargetType in AllCompiledTypes)
|
|
{
|
|
// Find TargetRules but skip all "UE4Editor", "UE4Game" targets.
|
|
if (typeof(TargetRules).IsAssignableFrom(TargetType) && !TargetType.IsAbstract)
|
|
{
|
|
string TargetName = GetTargetName(TargetType);
|
|
|
|
ReadOnlyBuildVersion Version = new ReadOnlyBuildVersion(BuildVersion.ReadDefault());
|
|
TargetInfo DummyTargetInfo = new TargetInfo(TargetName, BuildHostPlatform.Current.Platform, UnrealTargetConfiguration.Development, "", Properties.RawProjectPath, Version);
|
|
|
|
// 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;
|
|
TargetData.TargetName = GetTargetName(TargetType);
|
|
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)
|
|
{
|
|
var bOutOfDate = false;
|
|
var AssemblyInfo = new FileInfo(TargetsDllFilename.FullName);
|
|
if (AssemblyInfo.Exists)
|
|
{
|
|
foreach (var ScriptFilename in TargetScripts)
|
|
{
|
|
var 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";
|
|
var 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");
|
|
var RulesFolder = GetRulesAssemblyFolder();
|
|
if (CommandUtils.DirectoryExists(RulesFolder))
|
|
{
|
|
CommandUtils.DeleteDirectoryContents(RulesFolder);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class BranchInfo
|
|
{
|
|
|
|
public static List<TargetType> MonolithicKinds = new List<TargetType>
|
|
{
|
|
TargetType.Game,
|
|
TargetType.Client,
|
|
TargetType.Server,
|
|
};
|
|
|
|
[DebuggerDisplay("{GameName}")]
|
|
public class BranchUProject
|
|
{
|
|
public string GameName;
|
|
public FileReference FilePath;
|
|
public ProjectProperties Properties;
|
|
|
|
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);
|
|
}
|
|
|
|
Properties = ProjectUtils.GetProjectProperties(FilePath);
|
|
|
|
|
|
|
|
}
|
|
public BranchUProject()
|
|
{
|
|
GameName = "UE4";
|
|
Properties = ProjectUtils.GetProjectProperties(null);
|
|
if (!Properties.Targets.Exists(Target => Target.Rules.Type == TargetType.Editor))
|
|
{
|
|
throw new AutomationException("Base UE4 project did not contain an editor target.");
|
|
}
|
|
}
|
|
public void Dump(List<UnrealTargetPlatform> InHostPlatforms)
|
|
{
|
|
CommandUtils.LogVerbose(" ShortName: " + GameName);
|
|
CommandUtils.LogVerbose(" FilePath : " + FilePath);
|
|
CommandUtils.LogVerbose(" bIsCodeBasedProject : " + (Properties.bIsCodeBasedProject ? "YES" : "NO"));
|
|
foreach (var HostPlatform in InHostPlatforms)
|
|
{
|
|
CommandUtils.LogVerbose(" For Host : " + HostPlatform.ToString());
|
|
CommandUtils.LogVerbose(" Targets {0}:", Properties.Targets.Count);
|
|
foreach (var ThisTarget in Properties.Targets)
|
|
{
|
|
CommandUtils.LogVerbose(" TargetName : " + ThisTarget.TargetName);
|
|
CommandUtils.LogVerbose(" Type : " + ThisTarget.Rules.Type);
|
|
CommandUtils.LogVerbose(" bUsesSteam : " + (ThisTarget.Rules.bUsesSteam ? "YES" : "NO"));
|
|
CommandUtils.LogVerbose(" bUsesCEF3 : " + (ThisTarget.Rules.bUsesCEF3 ? "YES" : "NO"));
|
|
CommandUtils.LogVerbose(" bUsesSlate : " + (ThisTarget.Rules.bUsesSlate ? "YES" : "NO"));
|
|
}
|
|
CommandUtils.LogVerbose(" Programs {0}:", Properties.Programs.Count);
|
|
foreach (var ThisTarget in Properties.Programs)
|
|
{
|
|
CommandUtils.LogVerbose(" TargetName : " + ThisTarget.TargetName);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
public BranchUProject BaseEngineProject = null;
|
|
public List<BranchUProject> CodeProjects = new List<BranchUProject>();
|
|
public List<BranchUProject> NonCodeProjects = new List<BranchUProject>();
|
|
|
|
public IEnumerable<BranchUProject> AllProjects
|
|
{
|
|
get { return CodeProjects.Union(NonCodeProjects); }
|
|
}
|
|
|
|
public BranchInfo(List<UnrealTargetPlatform> InHostPlatforms)
|
|
{
|
|
BaseEngineProject = new BranchUProject();
|
|
|
|
var AllProjects = UnrealBuildTool.UProjectInfo.AllProjectFiles;
|
|
using(TelemetryStopwatch SortProjectsStopwatch = new TelemetryStopwatch("SortProjects"))
|
|
{
|
|
foreach (var InfoEntry in AllProjects)
|
|
{
|
|
var UProject = new BranchUProject(InfoEntry);
|
|
if (UProject.Properties.bIsCodeBasedProject)
|
|
{
|
|
CodeProjects.Add(UProject);
|
|
}
|
|
else
|
|
{
|
|
NonCodeProjects.Add(UProject);
|
|
// the base project uses BlankProject if it really needs a .uproject file
|
|
if (BaseEngineProject.FilePath == null && UProject.GameName == "BlankProject")
|
|
{
|
|
BaseEngineProject.FilePath = UProject.FilePath;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/* if (String.IsNullOrEmpty(BaseEngineProject.FilePath))
|
|
{
|
|
throw new AutomationException("All branches must have the blank project /Samples/Sandbox/BlankProject");
|
|
}*/
|
|
|
|
using(TelemetryStopwatch ProjectDumpStopwatch = new TelemetryStopwatch("Project Dump"))
|
|
{
|
|
CommandUtils.LogVerbose(" Base Engine:");
|
|
BaseEngineProject.Dump(InHostPlatforms);
|
|
|
|
CommandUtils.LogVerbose(" {0} Code projects:", CodeProjects.Count);
|
|
foreach (var Proj in CodeProjects)
|
|
{
|
|
Proj.Dump(InHostPlatforms);
|
|
}
|
|
CommandUtils.LogVerbose(" {0} Non-Code projects:", NonCodeProjects.Count);
|
|
foreach (var Proj in NonCodeProjects)
|
|
{
|
|
Proj.Dump(InHostPlatforms);
|
|
}
|
|
}
|
|
}
|
|
|
|
public BranchUProject FindGame(string GameName)
|
|
{
|
|
foreach (var Proj in CodeProjects)
|
|
{
|
|
if (Proj.GameName.Equals(GameName, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
return Proj;
|
|
}
|
|
}
|
|
foreach (var Proj in NonCodeProjects)
|
|
{
|
|
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;
|
|
}
|
|
public SingleTargetProperties FindProgram(string ProgramName)
|
|
{
|
|
foreach (var Proj in BaseEngineProject.Properties.Programs)
|
|
{
|
|
if (Proj.TargetName.Equals(ProgramName, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
return Proj;
|
|
}
|
|
}
|
|
|
|
foreach (var CodeProj in CodeProjects)
|
|
{
|
|
foreach (var Proj in CodeProj.Properties.Programs)
|
|
{
|
|
if (Proj.TargetName.Equals(ProgramName, StringComparison.InvariantCultureIgnoreCase))
|
|
{
|
|
return Proj;
|
|
}
|
|
}
|
|
}
|
|
|
|
SingleTargetProperties Result;
|
|
Result.TargetName = ProgramName;
|
|
Result.Rules = null;
|
|
return Result;
|
|
}
|
|
};
|
|
|
|
}
|