Files
UnrealEngineUWP/Engine/Source/Programs/AutomationTool/Scripts/CookCommand.Automation.cs
Dan Oconnor eaf882587b Copying //UE4/Dev-Blueprints to //UE4/Dev-Main (Source: //UE4/Dev-Blueprints @ 2967759)
#lockdown Nick.Penwarden

==========================
MAJOR FEATURES + CHANGES
==========================

Change 2919729 on 2016/03/23 by Ben.Cosh

	Support for macros in the profiler
	#UEBP-177 - Macro instance handling
	#Proj Kismet, BlueprintProfiler

	- Adds support for timings inside macro calls
	- Extends the tunnel graph support to handle multiple entry/exit sites

Change 2938064 on 2016/04/08 by Phillip.Kavan

	[UE-17794] The "Delete Unused Variable" feature now considers the GetClassDefaults node as well.

	change summary:
	- added external linkage to UK2Node_GetClassDefaults::FindClassPin().
	- added an include for the K2Node_GetClassDefaults header file to BlueprintGraphDefinitions.h.
	- added UK2Node_GetClassDefaults::GetInputClass() as a public API w/ external linkage; moved default 'nullptr' param logic into this impl.
	- modified FBlueprintEditorUtils::IsVariableUsed() to add an extra check for a GetClassDefaults node with a visible output pin for the variable that's also connected.
	- modified UK2Node_GetClassDefaults::GetInputClass() to return the generated skeleton class for Blueprint class types.

Change 2938088 on 2016/04/08 by Mike.Beach

	Making bytecode produced by latent action nodes deterministic.

Change 2938101 on 2016/04/08 by Mike.Beach

	Fixing a bug where the compile summary was not being reported because another compiler log was lingering - making it so the MathExpression node compiler log is not initialized with intent to report its own summary (it gets folded into the primary log).

Change 2938121 on 2016/04/08 by Phillip.Kavan

	Remove a few redundant MBASM calls on variable type change.

Change 2940295 on 2016/04/11 by Dan.Oconnor

	We now 'tag subobjects' of a blueprint even if it's part of the rootset, this means we correctly detect references to the BPGC in FPendingDelete::CheckForReferences(). Original rootset check dates to 2012 and I can find no justification for it currently.
	#jira UE-29216

Change 2943227 on 2016/04/13 by Dan.Oconnor

	Fixed improper detection of functions from interfaces that themselves inherit from some other interface
	#jira UE-29440

Change 2944270 on 2016/04/14 by Phillip.Kavan

	[UEBP-176] First pass at BP graph node heat map visualization while profiling.

	change summary:
	- added an "indicator overlay" to graph node widget layouts
	- added a heat mode "mode" selector widget to the BP profiler view panel
	- extended IBlueprintProfilerInterface to include APIs for accessing current heat map mode state
	- added FScriptNodePerfData::GetBlueprintPerfDataForAllTracePaths() (tentative - may need revisiting)
	- added SGraphNode::GetNodeIndicatorOverlayColor() and GetNodeIndicatorOverlayVisibility() delegates
	- added BP-specific delegate overrides to SGraphNodeK2Base; extended to include both compact and variable nodes

Change 2946932 on 2016/04/18 by Mike.Beach

	Guarding against invalid EdGraphPins (ones that have been moved to the transient package) when constructing the widget - prevents a crash that we've been unable to repro or determine the cause of (turns it instead into an ensure, so we can collect more contextual information on the issue).

	#jira UE-26998

Change 2949968 on 2016/04/20 by Dan.Oconnor

	Array access out of bounds by value is a warning again, added ability to elevate individual warnings on a per project basis (or supress them)
	#jira UE-28971

Change 2950113 on 2016/04/20 by Dan.Oconnor

	Removed GBlueprintCompileTime, it was not accurate. Printing BlueprintCompileAndLoadTimerData.GetTime() at start instead

Change 2951086 on 2016/04/21 by Ben.Cosh

	This change addresses the edge case in the blueprint profiler that caused stats to fail when tunnel nodes were linked through to other tunnel nodes.
	#jira UE-28750 - Crash compiling a Blueprint that contains a For Loop with profiler active
	#Proj Kismet, BlueprintProfiler

Change 2951336 on 2016/04/21 by Ben.Cosh

	This change enables blueprint breakpoints during instrumented conditions.
	#jira UEBP-178 - Fix breakpoints under profiling conditions
	#Proj CoreUObject, BlueprintProfiler, UnrealEd, KismetCompiler

Change 2951406 on 2016/04/21 by Ben.Cosh

	Fix for blueprint profiler stats for the top level blueprint stat entry not updating correctly.
	#Proj Kismet

Change 2951832 on 2016/04/21 by Ben.Cosh

	Fix for certain blueprint profiler stats not being updated and collected at the blueprint container level due to incorrect tracepaths.
	#Proj Kismet

	#info This should fix the node heatmaps as a side effect.

	#Codereview Phillip.Kavan

Change 2956696 on 2016/04/26 by Dan.Oconnor

	Tweak fix for macros being BS_Dirty after loading. The current fix had the side effect of not recompiling clients of the macro after making a change to the macro and entering PIE
	#jira UE-29495

Change 2957564 on 2016/04/27 by Maciej.Mroz

	Various fixes related to nativized enums.

	#jira UE-27735 Enumerators are not set correctly in packaged games if Nativize Blueprint Assets is set to true

Change 2961626 on 2016/04/29 by Mike.Beach

	Merging //UE4/Dev-Main to Dev-Blueprints (//UE4/Dev-Blueprints)

Change 2962747 on 2016/05/02 by Maciej.Mroz

	#jira UE-30123 Cannot use abilities in nativized Orion build

	GameplayAbility handles BPGC and DynamicClass the same way.
	C++ backend do not assumes that some literal objects are UserDefinesEnum/UserDefinedStruct/BlueprintGeneratedClass.

Change 2965679 on 2016/05/04 by Maciej.Mroz

	Increased stack size (384k) of threads spawned by Task Graph.
	(Temporary?) fix for stack overflow, when amination in Orion are evaluated.

Change 2965758 on 2016/05/04 by Maciej.Mroz

	#jira UE-30300 "ReturnToBase" ability does not work in nativized Orion.
	Fixed CDO creation in async loaded Dynamic Class.
	Fixed too restrict cast assertions.

Change 2966543 on 2016/05/04 by Maciej.Mroz

	#jira UE-30235 Mac QAGame fails to package with nativization
	#jira UE-30282 Match3 nativized android package fails to build

Change 2966839 on 2016/05/04 by Dan.Oconnor

	Typo IMPLEMENT_MODULE creates weird linking error, also may need entry in Target.cs to get BlueprintRuntime to build. Copying that pattern from ___LoadingScreen modules
	#jira UE-30333

Change 2967347 on 2016/05/05 by Maciej.Mroz

	#jira UE-30196 Unable to package a copy of project with Nativize Blueprints enabled

	CommandUtils.GetDirectoryName should not be used with directory path (but only with file path), because it cannot handle paths like "e:\My11Project 4.13" (containing '.'). It seems useless with directory path anyway.

[CL 2968184 by Dan Oconnor in Main branch]
2016-05-05 18:28:40 -04:00

509 lines
21 KiB
C#

// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Reflection;
using System.Linq;
using AutomationTool;
using UnrealBuildTool;
/// <summary>
/// Helper command used for cooking.
/// </summary>
/// <remarks>
/// Command line parameters used by this command:
/// -clean
/// </remarks>
public partial class Project : CommandUtils
{
#region Cook Command
public static void Cook(ProjectParams Params)
{
if ((!Params.Cook && !(Params.CookOnTheFly && !Params.SkipServer)) || Params.SkipCook)
{
return;
}
Params.ValidateAndLog();
Log("********** COOK COMMAND STARTED **********");
string UE4EditorExe = HostPlatform.Current.GetUE4ExePath(Params.UE4Exe);
if (!FileExists(UE4EditorExe))
{
throw new AutomationException("Missing " + UE4EditorExe + " executable. Needs to be built first.");
}
if (Params.CookOnTheFly && !Params.SkipServer)
{
if (Params.HasDLCName)
{
throw new AutomationException("Cook on the fly doesn't support cooking dlc");
}
if (Params.ClientTargetPlatforms.Count > 0)
{
var LogFolderOutsideOfSandbox = GetLogFolderOutsideOfSandbox();
if (!GlobalCommandLine.Installed)
{
// In the installed runs, this is the same folder as CmdEnv.LogFolder so delete only in not-installed
DeleteDirectory(LogFolderOutsideOfSandbox);
CreateDirectory(LogFolderOutsideOfSandbox);
}
String COTFCommandLine = Params.RunCommandline;
if (Params.IterativeCooking)
{
COTFCommandLine += " -iterate";
}
if (Params.UseDebugParamForEditorExe)
{
COTFCommandLine += " -debug";
}
var ServerLogFile = CombinePaths(LogFolderOutsideOfSandbox, "Server.log");
Platform ClientPlatformInst = Params.ClientTargetPlatformInstances[0];
string TargetCook = ClientPlatformInst.GetCookPlatform(false, false, Params.CookFlavor); // cook ont he fly doesn't support server cook platform...
ServerProcess = RunCookOnTheFlyServer(Params.RawProjectPath, Params.NoClient ? "" : ServerLogFile, TargetCook, COTFCommandLine);
if (ServerProcess != null)
{
Log("Waiting a few seconds for the server to start...");
Thread.Sleep(5000);
}
}
else
{
throw new AutomationException("Failed to run, client target platform not specified");
}
}
else
{
var PlatformsToCook = new HashSet<string>();
if (!Params.NoClient)
{
foreach (var ClientPlatform in Params.ClientTargetPlatforms)
{
// Use the data platform, sometimes we will copy another platform's data
var DataPlatform = Params.GetCookedDataPlatformForClientTarget(ClientPlatform);
PlatformsToCook.Add(Params.GetTargetPlatformInstance(DataPlatform).GetCookPlatform(false, Params.Client, Params.CookFlavor));
}
}
if (Params.DedicatedServer)
{
foreach (var ServerPlatform in Params.ServerTargetPlatforms)
{
// Use the data platform, sometimes we will copy another platform's data
var DataPlatform = Params.GetCookedDataPlatformForServerTarget(ServerPlatform);
PlatformsToCook.Add(Params.GetTargetPlatformInstance(DataPlatform).GetCookPlatform(true, false, Params.CookFlavor));
}
}
if (Params.Clean.HasValue && Params.Clean.Value && !Params.IterativeCooking)
{
Log("Cleaning cooked data.");
CleanupCookedData(PlatformsToCook.ToList(), Params);
}
// cook the set of maps, or the run map, or nothing
string[] Maps = null;
if (Params.HasMapsToCook)
{
Maps = Params.MapsToCook.ToArray();
foreach (var M in Maps)
{
Log("HasMapsToCook " + M.ToString());
}
foreach (var M in Params.MapsToCook)
{
Log("Params.HasMapsToCook " + M.ToString());
}
}
string[] Dirs = null;
if (Params.HasDirectoriesToCook)
{
Dirs = Params.DirectoriesToCook.ToArray();
}
string InternationalizationPreset = null;
if (Params.HasInternationalizationPreset)
{
InternationalizationPreset = Params.InternationalizationPreset;
}
string[] CulturesToCook = null;
if (Params.HasCulturesToCook)
{
CulturesToCook = Params.CulturesToCook.ToArray();
}
try
{
var CommandletParams = IsBuildMachine ? "-buildmachine -fileopenlog" : "-fileopenlog";
if (Params.UnversionedCookedContent)
{
CommandletParams += " -unversioned";
}
if (Params.FastCook)
{
CommandletParams += " -FastCook";
}
if (Params.UseDebugParamForEditorExe)
{
CommandletParams += " -debug";
}
if (Params.Manifests)
{
CommandletParams += " -manifests";
}
if (Params.IterativeCooking)
{
CommandletParams += " -iterate";
}
if (Params.CookMapsOnly)
{
CommandletParams += " -mapsonly";
}
if (Params.NewCook)
{
CommandletParams += " -newcook";
}
if (Params.OldCook)
{
CommandletParams += " -oldcook";
}
if (Params.CookAll)
{
CommandletParams += " -cookall";
}
if (Params.HasCreateReleaseVersion)
{
CommandletParams += " -createreleaseversion=" + Params.CreateReleaseVersion;
}
if ( Params.SkipCookingEditorContent)
{
CommandletParams += " -skipeditorcontent";
}
if ( Params.NumCookersToSpawn != 0)
{
CommandletParams += " -numcookerstospawn=" + Params.NumCookersToSpawn;
}
if (Params.HasDLCName)
{
CommandletParams += " -dlcname=" + Params.DLCName;
if ( !Params.DLCIncludeEngineContent )
{
CommandletParams += " -errorOnEngineContentUse";
}
}
// don't include the based on release version unless we are cooking dlc or creating a new release version
// in this case the based on release version is used in packaging
if (Params.HasBasedOnReleaseVersion && (Params.HasDLCName || Params.HasCreateReleaseVersion))
{
CommandletParams += " -basedonreleaseversion=" + Params.BasedOnReleaseVersion;
}
// if we are not going to pak but we specified compressed then compress in the cooker ;)
// otherwise compress the pak files
if (!Params.Pak && !Params.SkipPak && Params.Compressed)
{
CommandletParams += " -compressed";
}
// we provide the option for users to run a conversion on certain (script) assets, translating them
// into native source code... the cooker needs to
if (Params.RunAssetNativization)
{
CommandletParams += " -NativizeAssets";
// Store plugin paths now, it's easiest to do so while PlatformsToCook is still available:
string ProjectDir = Params.RawProjectPath.Directory.ToString();
foreach (var Platform in PlatformsToCook)
{
// If you change this target path you must also update logic in CookOnTheFlyServer.cpp. Passing a single directory around is cumbersome for testing, so I have hard coded it.
// Similarly if you change the .uplugin name you must update DefaultPluginName in BlueprintNativeCodeGenModule.cpp
string GeneratedPluginPath = CombinePaths(ProjectDir, "Intermediate", Platform, "NativizedAssets/NativizedAssets.uplugin");
Params.BlueprintPluginPaths.Add( new FileReference(GeneratedPluginPath) );
}
}
if (Params.HasAdditionalCookerOptions)
{
string FormatedAdditionalCookerParams = Params.AdditionalCookerOptions.TrimStart(new char[] { '\"', ' ' }).TrimEnd(new char[] { '\"', ' ' });
CommandletParams += " ";
CommandletParams += FormatedAdditionalCookerParams;
}
if (!Params.NoClient)
{
var MapsList = Maps == null ? new List<string>() : Maps.ToList();
foreach (var ClientPlatform in Params.ClientTargetPlatforms)
{
var DataPlatform = Params.GetCookedDataPlatformForClientTarget(ClientPlatform);
CommandletParams += (Params.GetTargetPlatformInstance(DataPlatform).GetCookExtraCommandLine(Params));
MapsList.AddRange((Params.GetTargetPlatformInstance(ClientPlatform).GetCookExtraMaps()));
}
Maps = MapsList.ToArray();
}
CookCommandlet(Params.RawProjectPath, Params.UE4Exe, Maps, Dirs, InternationalizationPreset, CulturesToCook, CombineCommandletParams(PlatformsToCook.ToArray()), CommandletParams);
}
catch (Exception Ex)
{
if (Params.IgnoreCookErrors)
{
LogWarning("Ignoring cook failure.");
}
else
{
// Delete cooked data (if any) as it may be incomplete / corrupted.
Log("Cook failed. Deleting cooked data.");
CleanupCookedData(PlatformsToCook.ToList(), Params);
throw new AutomationException(ExitCode.Error_UnknownCookFailure, Ex, "Cook failed.");
}
}
if (Params.HasDiffCookedContentPath)
{
try
{
DiffCookedContent(Params);
}
catch ( Exception Ex )
{
// Delete cooked data (if any) as it may be incomplete / corrupted.
Log("Cook failed. Deleting cooked data.");
CleanupCookedData(PlatformsToCook.ToList(), Params);
throw new AutomationException(ExitCode.Error_UnknownCookFailure, Ex, "Cook failed.");
}
}
}
Log("********** COOK COMMAND COMPLETED **********");
}
private static void DiffCookedContent( ProjectParams Params)
{
List<UnrealTargetPlatform> PlatformsToCook = Params.ClientTargetPlatforms;
string ProjectPath = Params.RawProjectPath.FullName;
var CookedSandboxesPath = CombinePaths(GetDirectoryName(ProjectPath), "Saved", "Cooked");
for (int CookPlatformIndex = 0; CookPlatformIndex < PlatformsToCook.Count; ++CookPlatformIndex)
{
// temporary directory to save the pak file to (pak file is usually not local and on network drive)
var TemporaryPakPath = CombinePaths(GetDirectoryName(ProjectPath), "Saved", "Temp", "LocalPKG");
// extracted files from pak file
var TemporaryFilesPath = CombinePaths(GetDirectoryName(ProjectPath), "Saved", "Temp", "LocalFiles");
try
{
Directory.Delete(TemporaryPakPath, true);
}
catch(Exception Ex)
{
Log("Failed deleting temporary directories "+TemporaryPakPath+" continuing. "+ Ex.GetType().ToString());
}
try
{
Directory.Delete(TemporaryFilesPath, true);
}
catch (Exception Ex)
{
Log("Failed deleting temporary directories " + TemporaryFilesPath + " continuing. " + Ex.GetType().ToString());
}
try
{
Directory.CreateDirectory(TemporaryPakPath);
Directory.CreateDirectory(TemporaryFilesPath);
Platform CurrentPlatform = Params.GetTargetPlatformInstance(PlatformsToCook[CookPlatformIndex]);
string SourceCookedContentPath = Params.DiffCookedContentPath;
List<string> PakFiles = new List<string>();
string CookPlatformString = CurrentPlatform.GetCookPlatform(false, Params.HasDedicatedServerAndClient, Params.CookFlavor);
if (Path.HasExtension(SourceCookedContentPath) && (!SourceCookedContentPath.EndsWith(".pak")))
{
// must be a per platform pkg file try this
CurrentPlatform.ExtractPackage(Params, Params.DiffCookedContentPath, TemporaryPakPath);
// find the pak file
PakFiles.AddRange( Directory.EnumerateFiles(TemporaryPakPath, Params.ShortProjectName+"*.pak", SearchOption.AllDirectories));
PakFiles.AddRange( Directory.EnumerateFiles(TemporaryPakPath, "pakchunk*.pak", SearchOption.AllDirectories));
}
else if (!Path.HasExtension(SourceCookedContentPath))
{
// try find the pak or pkg file
string SourceCookedContentPlatformPath = CombinePaths(SourceCookedContentPath, CookPlatformString);
foreach (var PakName in Directory.EnumerateFiles(SourceCookedContentPlatformPath, Params.ShortProjectName + "*.pak", SearchOption.AllDirectories))
{
string TemporaryPakFilename = CombinePaths(TemporaryPakPath, Path.GetFileName(PakName));
File.Copy(PakName, TemporaryPakFilename);
PakFiles.Add(TemporaryPakFilename);
}
foreach (var PakName in Directory.EnumerateFiles(SourceCookedContentPlatformPath, "pakchunk*.pak", SearchOption.AllDirectories))
{
string TemporaryPakFilename = CombinePaths(TemporaryPakPath, Path.GetFileName(PakName));
File.Copy(PakName, TemporaryPakFilename);
PakFiles.Add(TemporaryPakFilename);
}
if ( PakFiles.Count <= 0 )
{
Log("No Pak files found in " + SourceCookedContentPlatformPath +" :(");
}
}
else if (SourceCookedContentPath.EndsWith(".pak"))
{
string TemporaryPakFilename = CombinePaths(TemporaryPakPath, Path.GetFileName(SourceCookedContentPath));
File.Copy(SourceCookedContentPath, TemporaryPakFilename);
PakFiles.Add(TemporaryPakFilename);
}
string FullCookPath = CombinePaths(CookedSandboxesPath, CookPlatformString);
var UnrealPakExe = CombinePaths(CmdEnv.LocalRoot, "Engine/Binaries/Win64/UnrealPak.exe");
foreach (var Name in PakFiles)
{
Log("Extracting pak " + Name + " for comparision to location " + TemporaryFilesPath);
string UnrealPakParams = Name + " -Extract " + " " + TemporaryFilesPath;
try
{
RunAndLog(CmdEnv, UnrealPakExe, UnrealPakParams, Options: ERunOptions.Default | ERunOptions.UTF8Output | ERunOptions.LoggingOfRunDuration);
}
catch(Exception Ex)
{
Log("Pak failed to extract because of " + Ex.GetType().ToString());
}
}
const string RootFailedContentDirectory = "\\\\epicgames.net\\root\\Developers\\Daniel.Lamb";
string FailedContentDirectory = CombinePaths(RootFailedContentDirectory, CommandUtils.P4Env.BuildRootP4 + CommandUtils.P4Env.ChangelistString, Params.ShortProjectName, CookPlatformString);
Directory.CreateDirectory(FailedContentDirectory);
// diff the content
List<string> AllFiles = Directory.EnumerateFiles(FullCookPath, "*.uasset", System.IO.SearchOption.AllDirectories).ToList();
AllFiles.AddRange(Directory.EnumerateFiles(FullCookPath, "*.umap", System.IO.SearchOption.AllDirectories).ToList());
foreach (string SourceFilename in AllFiles)
{
// Filename.StartsWith( CookedSandboxesPath );
string RelativeFilename = SourceFilename.Remove(0, FullCookPath.Length);
string DestFilename = TemporaryFilesPath + RelativeFilename;
Log("Comparing file "+ RelativeFilename);
byte[] SourceFile = null;
try
{
SourceFile = File.ReadAllBytes(SourceFilename);
}
catch (Exception)
{
Log("Diff cooked content failed to load file " + SourceFilename);
}
byte[] DestFile = null;
try
{
DestFile = File.ReadAllBytes(DestFilename);
}
catch (Exception)
{
Log("Diff cooked content failed to load file " + DestFilename );
}
if (SourceFile == null || DestFile == null)
{
Log("Diff cooked content failed on file " + SourceFilename + " when comparing against " + DestFilename + " " + (SourceFile==null?SourceFilename:DestFilename) + " file is missing");
}
else if (SourceFile.LongLength == DestFile.LongLength)
{
bool bFailedDiff = false;
for (long Index = 0; Index < SourceFile.LongLength; ++Index)
{
if (SourceFile[Index] != DestFile[Index])
{
bFailedDiff = true;
Log("Diff cooked content failed on file " + SourceFilename + " when comparing against " + DestFilename + " at offset " + Index.ToString());
string SavedSourceFilename = CombinePaths(FailedContentDirectory, Path.GetFileName(SourceFilename) + "Source");
string SavedDestFilename = CombinePaths(FailedContentDirectory, Path.GetFileName(DestFilename) + "Dest");
Log("Creating directory " + Path.GetDirectoryName(SavedSourceFilename));
try
{
Directory.CreateDirectory(Path.GetDirectoryName(SavedSourceFilename));
}
catch (Exception E)
{
Log("Failed to create directory " + Path.GetDirectoryName(SavedSourceFilename) + " Exception " + E.ToString());
}
Log("Creating directory " + Path.GetDirectoryName(SavedDestFilename));
try
{
Directory.CreateDirectory(Path.GetDirectoryName(SavedDestFilename));
}
catch(Exception E)
{
Log("Failed to create directory " + Path.GetDirectoryName(SavedDestFilename) + " Exception " + E.ToString());
}
File.Copy(SourceFilename, SavedSourceFilename, true);
File.Copy(DestFilename, SavedDestFilename, true);
Log("Content temporarily saved to " + SavedSourceFilename + " and " + SavedDestFilename + " at offset " + Index.ToString());
break;
}
}
if (!bFailedDiff)
{
Log("Content matches for " + SourceFilename + " and " + DestFilename);
}
}
else
{
Log("Diff cooked content failed on file " + SourceFilename + " when comparing against " + DestFilename + " files are different sizes " + SourceFile.LongLength.ToString() + " " + DestFile.LongLength.ToString());
}
}
}
catch ( Exception Ex )
{
Log("Exception " + Ex.ToString());
continue;
}
}
}
private static void CleanupCookedData(List<string> PlatformsToCook, ProjectParams Params)
{
var ProjectPath = Params.RawProjectPath.FullName;
var CookedSandboxesPath = CombinePaths(GetDirectoryName(ProjectPath), "Saved", "Cooked");
var CleanDirs = new string[PlatformsToCook.Count];
for (int DirIndex = 0; DirIndex < CleanDirs.Length; ++DirIndex)
{
CleanDirs[DirIndex] = CombinePaths(CookedSandboxesPath, PlatformsToCook[DirIndex]);
}
const bool bQuiet = true;
DeleteDirectory(bQuiet, CleanDirs);
}
#endregion
}