Files
UnrealEngineUWP/Engine/Source/Developer/CrashDebugHelper/Private/CrashDebugHelper.cpp
Simon Tourangeau 19f8663b5a Copying //UE4/Dev-Enterprise to //UE4/Dev-Main (Source: //UE4/Dev-Enterprise @ 3972172)
#lockdown Nick.Penwarden
#rb none

============================
  MAJOR FEATURES & CHANGES
============================

Change 3821754 by Jamie.Dale

	[Python] Class property conversion now goes through NativizeClass/PythonizeClass

	This allows it to coerce from Python wrapped object types

Change 3833107 by Patrick.Boutot

	Added functions to fill an existing DataTable from an existing CSV/JSON file.

Change 3835044 by Aaron.Carlisle

	Exposure for asset_import_data (editor property) and it's functions: extract_filenames and get_first_filename.

Change 3835466 by Patrick.Boutot

	Hide function from Python that need special compile command to be executed by the VM.

Change 3839237 by Jamie.Dale

	Added a way to inspect the full chain of properties that are currently being serialized by an archive

	You used to only have access to the leaf-most property, and while you could use its outer chain to inspect other properties within the same object/struct, you couldn't always get the full chain (eg, if you had an object containing a struct).

Change 3839974 by Jamie.Dale

	Make sure that SerializedProperty is copied correctly, as SetSerializedPropertyChain may set it to something else

Change 3842311 by Jamie.Dale

	Fixing potential null level assert

Change 3842313 by Jamie.Dale

	Updated settings editor to handle external properties

Change 3842316 by Jamie.Dale

	Allowing a console command to be given to GEditor/GEngine even if there's a player

	CL# 1848982 said it was to prevent multiple execution of stat commands, however that no longer seems to be an issue.

Change 3842867 by Jamie.Dale

	Added a way to generate diffs from editor transactions

	The notifications from these diffs are send to UObject::PostTransacted and FCoreUObjectDelegates::OnObjectTransacted.

	These notifications are typically generated when a transaction is "finalized", but can also be generated from "snapshots" (eg, to trap nodes being dragged in the world). They're also generated from normal undo/redo events.

Change 3844428 by Patrick.Boutot

	Move the SetMaterial code from the StaticMeshEditor to StaticMesh to be reusable by script.

Change 3845966 by Jamie.Dale

	Added support for minimal game RPC worlds

	These can be created in the editor and engine and exist to allow RPC communication via Unreal Networking in a way that is sandboxed from any other worlds that may be loaded (like the main game world)

Change 3848844 by Patrick.Boutot

	Expose EComponentMobility to blueprint.

Change 3854616 by Patrick.Boutot

	Add Custom way time step the engine loop. Will be used by the Synchronization of media for enterprise.

Change 3856650 by Jamie.Dale

	Fixed a bug where transaction finalization could miss changes since the last snapshot

Change 3864951 by Patrick.Boutot

	Fix ghost asset in Content Browser when an asset is added and renamed before the RecentlyAddedAssets list had a chance to be processed.

Change 3867158 by JeanMichel.Dignard

	UBT
	- Added the ability for dll programs to export symbols.

	#jira UEENT-541

Change 3872342 by Jamie.Dale

	Merging static analysis fixes from 4.19

Change 3879305 by Jamie.Dale

	Improved the processing of py files from exec commands

	The old logic used to just test if the entire command was a .py file. The new logic extracts out the first token and sees if that's a .py file, and if it is, treats the remaining data as extra arguments.

Change 3879306 by Jamie.Dale

	Added a minimal commandlet for invoking Python scripts

Change 3881631 by Jamie.Dale

	Added basic RTTI to Python meta-data types

Change 3885384 by Jamie.Dale

	[Python] Prevent glue code using reserved names

Change 3888957 by Patrick.Boutot

	In MediaPlayer, only create a PlayerFacade & Playlist when it's not a ClassDefaultObject.
	The MediaPlayerFacade is a MediaTickable. That trigger the tick thread to be awake even if there is no Media playing.

Change 3888961 by Patrick.Boutot

	Fix FInterval::IsValid return type.

Change 3888980 by Patrick.Boutot

	Modification to Media and MediaAsset to support MediaSmith. The TInterval<int64> will be changed into TTinterval<FTimespan> UEENT-947. MediaSampleQueue's critical section will be change into an atomic operation UEENT-948.

Change 3889165 by Patrick.Boutot

	Fix build. Missing include for Timespan. Introduce with CL 3888980.

Change 3889261 by Jamie.Dale

	[Python] Fixing some more name conflicts in generated code

Change 3889504 by Darren.Pegg

	Add option to change PreferredPixelFormat

Change 3891193 by Patrick.Boutot

	Fix build. Missing include for Interval. Introduce with CL 3888980.

Change 3897108 by Patrick.Boutot

	TTinterval use it own traits. Create a Interval traits for Timespan.
	#jira UEENT-947

Change 3899669 by Jamie.Dale

	Fixed Functions sometimes being exposed to Python as if they were Structs

Change 3900692 by Jamie.Dale

	Removed some boilerplate associated with wrapping a basic type to Python

	You can now derive from TPyWrapperBasic to wrap a type that is simply a value copied into Python (see FPyWrapperName and FPyWrapperText for an example)

Change 3901066 by conan.reis

	UE4 editor script bindings (Cobra) and helper functions for version control
	- exposed SourceControl class with common source control methods and associated SourceControlState structure
	- commands have smart file strings that can convert from any of fully qualified path, relative path, long package name, asset path or export text path (often stored on clipboard)
	- commands store any errors in a shared error text object which is optionally printed to the error log
	- renamed some calls across the UE4 codebase to USourceControlHelpers::CheckOutOrAddFile() from USourceControlHelpers::CheckOutFile()
	- included Python test script for source control commands including that auto-creates test files as needed and passes various types of files to test as command line arguments. Any unexpected results displays error messages.

Change 3901388 by Jamie.Dale

	Minimal Slate hooks for Python

Change 3901456 by Jamie.Dale

	Added missing file

Change 3901549 by Jamie.Dale

	Removing some more Windows defines that were causing build issues

Change 3904518 by conan.reis

	Source Control
	- ensured that "check if modified" flag is set whenver getting source control state in USourceControlHelpers::QueryFileState() which was needed when using Perforce source control provider

Change 3905612 by Francis.Hurteau

	Optimize RemoveDuplicates somewhat using a TSet
	#jira UEENT-217

Change 3912626 by Jamie.Dale

	Fixed ShouldExcludeDerivedClasses option not working

	RecursiveClassesExclusionSet requires a base ClassNames entry to operate on when filtering.

Change 3917739 by Jamie.Dale

	Output Log suggestions list is now clamped to the work area width of the monitor that hosts the widget

Change 3917744 by Jamie.Dale

	Changed generated code to reference the UProperty and UFunction directly, rather than constantly look them up by name

	Names were originally used because UHT couldn't access the objects when it registered the glue code, but now that we generate at runtime via reflection, we already have the relevant objects available, and caching them the glue structs helps performance at both generation time and runtime.

Change 3918832 by Jamie.Dale

	Removed field iteration from Python function calls

	We now cache the input and output parameters for all function calls (methods, get/set, and delegates) and use this rather than iterate the struct fields.

Change 3920648 by Patrick.Boutot

	Remove the bottom right part of the windows border of the grabbed frame when in the editor. Tested in the standalone, windowed and full screen.
	Add option to request a FlushOnDraw on the viewport. Flushing in SDI output flow decreases the performance by ~10ms. SDI output is synchronized and the engine tick follows that synchronization.

Change 3921396 by Jamie.Dale

	Split up the generated type data to correspond to the type being wrapped

	A lot of types can just use the minimal set, but classes and structs have some extra data.

Change 3921619 by conan.reis

	- add delegate to FSourceControlWindows::ChoosePackagesToCheckin() that gives info for result, result description, files added, files checked in and flag indicating whether files were checked out again.
	- also added result info to FSourceControlWindows::PromptForCheckin()

	#jira UE-55255

Change 3921624 by conan.reis

	Removed Source Control common files from pre compiled header
	- main changes are in UnrealEdPrivatePCH.h, UnrealEdSharedPCH.h, SouceControlWindows.h and the added SouceControlWindows.cpp
	- remaining files have includes changed to accomodate

Change 3921958 by conan.reis

	Fix attempt for incude file dependency needed by some build configurations (likely PCH disabled) caused by CL3921619

Change 3922740 by conan.reis

	Included SourceControlOperations.h and SourceControlHelpers.h back in ISourceControlProvider.h though it does not need them since other files that were including ISourceControlProvider.h have come to expect their inclusion.
	They were previously removed in CL3921624

Change 3923375 by Jamie.Dale

	Added optimized FString <-> icu::UnicodeString conversion for platforms using UTF-16 native strings

Change 3926547 by Jamie.Dale

	Added support for struct method "hoisting"

	This allows you to tag a helper function that takes a struct as its first argument with the ScriptMethod meta-data (optionally providing a new name) to "hoist" that helper function to be a method of the struct it operates on when wrapped for Python.

Change 3927050 by conan.reis

	Source control - ensured that  ISourceControlProvider::Execute(FConnect, EConcurrency, FSourceControlOperationComplete) delegate is called on initial connection even if it fails immediately. Modified Perforce, Git and Subversion source control providers

	#JIRA UE-55256

Change 3929268 by conan.reis

	- fixed case in Perforce source control code where the server available flag was set even when the server was not successfully connected
	- removed Perforce error message about file folders outside of the workspace client mappings
	- clarified comments for ISourceControlProvider::IsEnabled() and ISourceControlModule::IsEnabled()

	#JIRA UE-55254

Change 3931024 by Rex.Hill

	Expose FBX and Texture import to python

Change 3931273 by Rex.Hill

	Hide re-import slate notification pop-up during python automated asset import

Change 3931368 by Jamie.Dale

	Stopped bools coercing to numeric types in Python nativization

Change 3931374 by Jamie.Dale

	Added support for struct math operator "hoisting"

	This allows you to tag a helper function that takes a struct as its first argument with the ScriptMathOp meta-data (providing a potentially semi-colon separated list of operators to map to) to "hoist" that helper function to be a math operator of the struct it operates on when wrapped for Python.

Change 3932586 by Rex.Hill

	Removed file read into unused memory buffer during Fbx import

Change 3934308 by Jamie.Dale

	Added a public interface for the Python plugin

	Very basic, just lets you query if Python is compiled in, and lets you execute Python commands like you would via the Output Log.

Change 3935088 by conan.reis

	- Added info/warning and error message storage to all the source control operation structures so additional information can be made available.
	- Added ISourceControlOperation::GetResultInfo() which returns the modified control structures (mentioned above) with appended info/warning messages and error messages and implemented its use in all source control operations in Perforce, Git and Subversion.

	#JIRA UE-55257

Change 3936668 by Rex.Hill

	#jira UE-55985
	Avoid re-allocation of memory buffer holding file bytes during asset import

Change 3940596 by Rex.Hill

	#jira UE-55989 Optimize skeletal mesh import performance scaling

	Overlapping vertex check was O (N^2)
	100k vertex mesh took ~15 seconds to perform overlap step now takes 0.023 seconds

Change 3942629 by Rex.Hill

	#jira UE-55995 Read fbx file only once during import

	Fixes a memory leak of FbxScene and reduces wait time during import.

Change 3942884 by Rex.Hill

	Python asset import can now customize destination asset name

Change 3946278 by Jamie.Dale

	Added stricter conversion for math operator arguments

	PyConversion now returns FPyConversionResult rather than bool, which will tell you not only whether a conversion succeeded or failed, but also whether type coercion was applied during the conversion.

	This allows the operator stack evaluation to run a first pass looking for an exact argument match, before falling back to a coerced match if available. This allows operators to apply correctly to coerced types (eg, int vs float overloads).

Change 3948455 by Jamie.Dale

	Added generic Tick function to FPythonScriptPlugin

	This can also handle init logic for after the engine is fully initialized

Change 3948888 by Jamie.Dale

	Added settings for the Python plugin

	You can now define start-up scripts to execute once the engine is initialized, additional system paths for Python, and whether you want to enable developer mode (which will enable things like deprecation warnings).

Change 3948982 by Jamie.Dale

	Fixed Python 3 build error caused by CObject being removed in Python 3.2

Change 3949614 by Francis.Hurteau

	Create a camera cut track from the camera switcher camera index animation curve when importing a fbx in sequencer
	#jira UEENT-1053

Change 3950829 by Rex.Hill

	Update error message to be more specific when ENGINE_API keyword is found before 'static' keyword for a UFUNCTION

Change 3953452 by Jamie.Dale

	Fixed some dependencies

Change 3953645 by Jamie.Dale

	Fixed Python parameter packing treating bool output paramers as potential return values

	void GetState(bool& OutState) would have previously triggered the code for packing output for a function that returns a bool.

Change 3953850 by Jamie.Dale

	Fixed doc string generation for a function with multiple output paramters and no return value

Change 3954279 by Jamie.Dale

	Initial support for exposing deprecated properties and functions to Python

	This handles properties and functions that are directly deprecated. We still need to handle the cases where they're renamed and a redirector is left.

Change 3954922 by Rex.Hill

	Expose UnloadPackages to python

Change 3955209 by Jamie.Dale

	Initial support for exposing deprecated classes to Python

Change 3955248 by Jamie.Dale

	Added a way to load Unreal modules via Python

	unreal.load_module("modulename")

Change 3955561 by Rex.Hill

	Expose asset export to python

Change 3956068 by Rex.Hill

	Linux compile fix.

Change 3960449 by Rex.Hill

	Fix automated test using bCombineMeshes

Change 3960495 by Patrick.Boutot

	Add a temporary menu to show the MetaData of an asset.
	The menu will need to be updated to have a look and feel of the Detail View and support edition at one point.

Change 3961599 by Rex.Hill

	Reduced peak memory during import of meshes related to duplicate vertex tracking

Change 3962104 by Rex.Hill

	Disable import mesh overlapping corners memory optimization to because it can change uv generation

Change 3962507 by Rex.Hill

	Fix uv generation

Change 3965285 by Rex.Hill

	Add support for FBX export as ASCII
	#jira UE-56465

Change 3965287 by Rex.Hill

	Forgotten file, fbx export as ascii

Change 3966772 by Simon.Tourangeau

	Fix MaterialExpressionFunctions for ExternalTexture support

Change 3967014 by Jamie.Dale

	Added a way to get the CDO in Python

	Wrapped objects now have a get_default_object class method

Change 3967151 by Jamie.Dale

	Added stats to track Python generation time

Change 3968006 by Simon.Therriault

	Media Samples

	- Removed Locks and Min/Max SampleTime from queues
	- Added methods to fetch NextSampleTime and SampleCount in queues
	- Added MediaSource base class for players that want to be time synchronized

	#jira UEENT-948

Change 3969119 by Patrick.Boutot

	Add delay functionnality to MediaPlayer to delay the frame by some time. It will allow more than one player to be start at the same time, played at the same frame but offset in relation to each other.

[CL 3972277 by Simon Tourangeau in Main branch]
2018-03-29 13:32:35 -04:00

951 lines
31 KiB
C++

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.
#include "CrashDebugHelper.h"
#include "HAL/FileManager.h"
#include "Misc/Parse.h"
#include "Misc/CommandLine.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Stats/StatsMisc.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/App.h"
#include "CrashDebugHelperPrivate.h"
#include "CrashDebugPDBCache.h"
#include "Misc/EngineVersion.h"
#if UE_EDITOR || IS_PROGRAM
#include "ISourceControlOperation.h"
#include "SourceControlOperations.h"
#include "ISourceControlRevision.h"
#include "ISourceControlProvider.h"
#include "ISourceControlModule.h"
#include "ISourceControlLabel.h"
#include "SourceControlHelpers.h"
#endif
#ifndef MINIDUMPDIAGNOSTICS
#define MINIDUMPDIAGNOSTICS 0
#endif
/*-----------------------------------------------------------------------------
FCrashDebugHelperConfig
-----------------------------------------------------------------------------*/
/**
* Holds FullCrashDump properties from the config.
*
* PDBCache_0_Branch=UE4-Branch
* PDBCache_0_ExecutablePathPattern=ue4.net\Builds\UE4-Branch\%ENGINE_VERSION%
* PDBCache_0_SymbolPathPattern=ue4.net\Builds\UE4-Branch\%ENGINE_VERSION%
*
* If PDBCache_0_SymbolPathPattern is missing, the value from PDBCache_0_ExecutablePathPattern will be used
*/
struct FPDBCacheConfigEntry
{
/** Initialization constructor. */
FPDBCacheConfigEntry( const FString& InBranch, const FString& InExecutablePathPattern, const FString& InSymbolPathPattern )
: Branch( InBranch )
, ExecutablePathPattern( InExecutablePathPattern )
, SymbolPathPattern( InSymbolPathPattern )
{}
/** Branch name. */
const FString Branch;
/** Location of the executables */
const FString ExecutablePathPattern;
/** Location of the symbols, usually the same as the executables. */
const FString SymbolPathPattern;
};
/** Helper struct for reading PDB cache configuration. */
struct FCrashDebugHelperConfig
{
static FCrashDebugHelperConfig& Get()
{
static FCrashDebugHelperConfig Instance;
return Instance;
}
bool IsValid() const
{
// We need a least one entry to proceed.
return PDBCacheConfigEntries.Num() > 0;
}
/** Reads configuration. */
void ReadFullCrashDumpConfigurations();
/** Gets the config for branch. */
FPDBCacheConfigEntry GetCacheConfigEntryForBranch( const FString& Branch ) const
{
const FString BranchFixed = Branch.Replace( TEXT( "/" ), TEXT( "+" ) );
for (const auto& It : PDBCacheConfigEntries)
{
if (BranchFixed.Contains( It.Branch ))
{
return It;
}
}
// Invalid entry.
return FPDBCacheConfigEntry( Branch, FString(), FString() );
}
protected:
/** Returns empty string if couldn't read. */
FString GetKey( const FString& KeyName );
/** Configuration for PDB Cache. */
TArray<FPDBCacheConfigEntry> PDBCacheConfigEntries;
};
void FCrashDebugHelperConfig::ReadFullCrashDumpConfigurations()
{
for (int32 NumEntries = 0;; ++NumEntries)
{
const FString Branch = GetKey( FString::Printf( TEXT( "PDBCache_%i_Branch" ), NumEntries ) );
if (Branch.IsEmpty())
{
break;
}
const FString ExecutablePathPattern = GetKey( FString::Printf( TEXT( "PDBCache_%i_ExecutablePathPattern" ), NumEntries ) );
if (ExecutablePathPattern.IsEmpty())
{
break;
}
FString SymbolPathPattern = GetKey( FString::Printf( TEXT( "PDBCache_%i_SymbolPathPattern" ), NumEntries ) );
if (SymbolPathPattern.IsEmpty())
{
SymbolPathPattern = ExecutablePathPattern;
}
PDBCacheConfigEntries.Add( FPDBCacheConfigEntry( Branch, ExecutablePathPattern, SymbolPathPattern ) );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "PDBCacheConfigEntry: Branch:%s ExecutablePathPattern:%s SymbolPathPattern:%s" ), *Branch, *ExecutablePathPattern, *SymbolPathPattern );
}
}
FString FCrashDebugHelperConfig::GetKey( const FString& KeyName )
{
const FString SectionName = TEXT( "Engine.CrashDebugHelper" );
FString Result;
if (!GConfig->GetString( *SectionName, *KeyName, Result, GEngineIni ))
{
return TEXT( "" );
}
return Result;
}
/*-----------------------------------------------------------------------------
ICrashDebugHelper
-----------------------------------------------------------------------------*/
void ICrashDebugHelper::SetDepotIndex( FString& PathToChange )
{
FString CmdDepotIndex;
FParse::Value( FCommandLine::Get(), TEXT( "DepotIndex=" ), CmdDepotIndex );
// %DEPOT_INDEX% - Index of the depot, when multiple processor are used.
PathToChange.ReplaceInline( TEXT( "%DEPOT_INDEX%" ), *CmdDepotIndex );
}
bool ICrashDebugHelper::Init()
{
bInitialized = true;
CrashInfo.bMutexPDBCache = FParse::Param(FCommandLine::Get(), TEXT("MutexPDBCache"));
FParse::Value(FCommandLine::Get(), TEXT("PDBCacheLock="), CrashInfo.PDBCacheLockName);
// Check if we have a valid EngineVersion, if so use it.
FString CmdEngineVersion;
const bool bHasEngineVersion = FParse::Value( FCommandLine::Get(), TEXT( "EngineVersion=" ), CmdEngineVersion );
if( bHasEngineVersion )
{
FEngineVersion EngineVersion;
FEngineVersion::Parse( CmdEngineVersion, EngineVersion );
// Clean branch name.
CrashInfo.DepotName = EngineVersion.GetBranch();
CrashInfo.BuiltFromCL = (int32)EngineVersion.GetChangelist();
CrashInfo.EngineVersion = CmdEngineVersion;
}
else
{
// Use the current values.
const FEngineVersion& EngineVersion = FEngineVersion::Current();
CrashInfo.DepotName = EngineVersion.GetBranch();
CrashInfo.BuiltFromCL = (int32)EngineVersion.GetChangelist();
CrashInfo.EngineVersion = EngineVersion.ToString();
}
// Check if we have a valid BuildVersion, if so use it.
FString CmdBuildVersion;
const bool bHasBuildVersion = FParse::Value(FCommandLine::Get(), TEXT("BuildVersion="), CmdBuildVersion);
if (bHasBuildVersion)
{
CrashInfo.BuildVersion = CmdBuildVersion;
}
else
{
CrashInfo.BuildVersion = FApp::GetBuildVersion();
}
FString PlatformName;
const bool bHasPlatformName = FParse::Value(FCommandLine::Get(), TEXT("PlatformName="), PlatformName);
if (bHasPlatformName)
{
CrashInfo.PlatformName = PlatformName;
}
else
{
// Use the current values.
CrashInfo.PlatformName = FPlatformProperties::PlatformName();
}
FString PlatformVariantName;
const bool bHasPlatformVariantName = FParse::Value(FCommandLine::Get(), TEXT("PlatformVariantName="), PlatformVariantName);
if (bHasPlatformVariantName)
{
CrashInfo.PlatformVariantName = PlatformVariantName;
}
else
{
// Use the basic platform name.
CrashInfo.PlatformVariantName = CrashInfo.PlatformName;
}
UE_LOG( LogCrashDebugHelper, Log, TEXT( "DepotName: %s" ), *CrashInfo.DepotName );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "BuiltFromCL: %i" ), CrashInfo.BuiltFromCL );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "EngineVersion: %s" ), *CrashInfo.EngineVersion );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "BuildVersion: %s" ), *CrashInfo.BuildVersion );
GConfig->GetString( TEXT( "Engine.CrashDebugHelper" ), TEXT( "SourceControlBuildLabelPattern" ), SourceControlBuildLabelPattern, GEngineIni );
FCrashDebugHelperConfig::Get().ReadFullCrashDumpConfigurations();
const bool bUsePDBCache = FCrashDebugHelperConfig::Get().IsValid();
UE_CLOG( !bUsePDBCache, LogCrashDebugHelper, Warning, TEXT( "CrashDebugHelperConfig invalid" ) );
if (bUsePDBCache)
{
if (CrashInfo.bMutexPDBCache && !CrashInfo.PDBCacheLockName.IsEmpty())
{
// Scoped lock
FSystemWideCriticalSection PDBCacheLock(CrashInfo.PDBCacheLockName, FTimespan::FromMinutes(10.0));
if (PDBCacheLock.IsValid())
{
FPDBCache::Get().Init();
}
}
else
{
FPDBCache::Get().Init();
}
if (!FPDBCache::Get().UsePDBCache())
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("PDB Cache failed to initialize"));
}
}
else
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "PDB Cache disabled" ) );
}
return bInitialized;
}
/**
* Initialise the source control interface, and ensure we have a valid connection
*/
bool ICrashDebugHelper::InitSourceControl(bool bShowLogin)
{
// Ensure we are in a valid state to sync
if (bInitialized == false)
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("InitSourceControl: CrashDebugHelper is not initialized properly."));
return false;
}
#if UE_EDITOR || IS_PROGRAM
// Initialize the source control if it hasn't already been
if( !ISourceControlModule::Get().IsEnabled() || !ISourceControlModule::Get().GetProvider().IsAvailable() )
{
// make sure our provider is set to Perforce
ISourceControlModule::Get().SetProvider("Perforce");
// Attempt to load in a source control module
ISourceControlModule::Get().GetProvider().Init();
#if !MINIDUMPDIAGNOSTICS
if ((ISourceControlModule::Get().GetProvider().IsAvailable() == false) || bShowLogin)
{
// Unable to connect? Prompt the user for login information
ISourceControlModule::Get().ShowLoginDialog(FSourceControlLoginClosed(), ELoginWindowMode::Modeless, EOnLoginWindowStartup::PreserveProvider);
}
#endif
// If it's still disabled, none was found, so exit
if( !ISourceControlModule::Get().IsEnabled() || !ISourceControlModule::Get().GetProvider().IsAvailable() )
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("InitSourceControl: Source control unavailable or disabled."));
return false;
}
}
#endif
return true;
}
/**
* Shutdown the connection to source control
*/
void ICrashDebugHelper::ShutdownSourceControl()
{
#if UE_EDITOR || IS_PROGRAM
ISourceControlModule::Get().GetProvider().Close();
#endif
}
bool ICrashDebugHelper::SyncModules(bool& bOutPDBCacheEntryValid)
{
bOutPDBCacheEntryValid = false;
if( !FPDBCache::Get().UsePDBCache() )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "The PDB Cache is disabled, cannot proceed, %s" ), *CrashInfo.EngineVersion );
return false;
}
// #CrashReport: 2015-02-23 Obsolete, remove after 4.8
const TCHAR* UESymbols = TEXT( "Rocket/Symbols/" );
const bool bHasExecutable = !CrashInfo.ExecutablesPath.IsEmpty();
const bool bHasSymbols = !CrashInfo.SymbolsPath.IsEmpty();
if( bHasExecutable && bHasSymbols )
{
if(FPDBCache::Get().ContainsPDBCacheEntry(CrashInfo.BuildVersion))
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Using cached storage: %s" ), *CrashInfo.BuildVersion);
CrashInfo.PDBCacheEntry = FPDBCache::Get().FindAndTouchPDBCacheEntry( CrashInfo.BuildVersion);
}
else
{
SCOPE_LOG_TIME_IN_SECONDS( TEXT( "SyncExecutableAndSymbolsFromNetwork" ), nullptr );
// Find all executables.
TArray<FString> NetworkExecutables;
// Don't duplicate work.
if (CrashInfo.ExecutablesPath != CrashInfo.SymbolsPath)
{
IFileManager::Get().FindFilesRecursive( NetworkExecutables, *CrashInfo.ExecutablesPath, TEXT( "*.dll" ), true, false, false );
IFileManager::Get().FindFilesRecursive( NetworkExecutables, *CrashInfo.ExecutablesPath, TEXT( "*.exe" ), true, false, false );
}
// Find all symbols.
TArray<FString> NetworkSymbols;
IFileManager::Get().FindFilesRecursive( NetworkSymbols, *CrashInfo.SymbolsPath, TEXT( "*.pdb" ), true, false, false );
IFileManager::Get().FindFilesRecursive( NetworkSymbols, *CrashInfo.SymbolsPath, TEXT( "*.dll" ), true, false, false );
IFileManager::Get().FindFilesRecursive( NetworkSymbols, *CrashInfo.SymbolsPath, TEXT( "*.exe" ), true, false, false );
// From=Full pathname
// To=Relative pathname
TMap<FString, FString> FilesToBeCached;
for( const auto& ExecutablePath : NetworkExecutables )
{
const FString NetworkRelativePath = ExecutablePath.Replace( *CrashInfo.ExecutablesPath, TEXT( "" ) );
FilesToBeCached.Add( ExecutablePath, NetworkRelativePath );
}
for( const auto& SymbolPath : NetworkSymbols )
{
const FString SymbolRelativePath = SymbolPath.Replace( *CrashInfo.SymbolsPath, TEXT( "" ) );
FilesToBeCached.Add( SymbolPath, SymbolRelativePath );
}
// Initialize and add a new PDB Cache entry to the database.
CrashInfo.PDBCacheEntry = FPDBCache::Get().CreateAndAddPDBCacheEntryMixed( CrashInfo.BuildVersion, FilesToBeCached );
}
}
// OBSOLETE PATH
else
{
// Command line for blocking obsolete path
#if UE_EDITOR || IS_PROGRAM
const bool bNoP4Symbols = FParse::Param(FCommandLine::Get(), TEXT("NoP4Symbols"));
// Check source control
if (bNoP4Symbols || !ISourceControlModule::Get().IsEnabled())
{
return false;
}
// Get all labels associated with the crash info's label.
TArray< TSharedRef<ISourceControlLabel> > Labels = ISourceControlModule::Get().GetProvider().GetLabels(CrashInfo.LabelName);
if (Labels.Num() >= 1)
{
TSharedRef<ISourceControlLabel> Label = Labels[0];
TSet<FString> FilesToSync;
if (FPDBCache::Get().ContainsPDBCacheEntry(CrashInfo.EngineVersion))
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("Using cached storage: %s"), *CrashInfo.EngineVersion);
CrashInfo.PDBCacheEntry = FPDBCache::Get().FindAndTouchPDBCacheEntry(CrashInfo.EngineVersion);
}
// Use product version instead of label name to make a distinguish between chosen methods.
else if (FPDBCache::Get().ContainsPDBCacheEntry(CrashInfo.LabelName))
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("Using cached storage: %s"), *CrashInfo.LabelName);
CrashInfo.PDBCacheEntry = FPDBCache::Get().FindAndTouchPDBCacheEntry(CrashInfo.LabelName);
}
else if (bHasExecutable)
{
SCOPE_LOG_TIME_IN_SECONDS(TEXT("SyncModulesAndNetwork"), nullptr);
// Grab information about symbols.
TArray< TSharedRef<class ISourceControlRevision, ESPMode::ThreadSafe> > PDBSourceControlRevisions;
const FString PDBsPath = FString::Printf(TEXT("%s/%s....pdb"), *CrashInfo.DepotName, UESymbols);
Label->GetFileRevisions(PDBsPath, PDBSourceControlRevisions);
TSet<FString> PDBPaths;
for (const auto& PDBSrc : PDBSourceControlRevisions)
{
PDBPaths.Add(PDBSrc->GetFilename());
}
// Now, sync symbols.
for (const auto& PDBPath : PDBPaths)
{
if (Label->Sync(PDBPath))
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("Synced PDB: %s"), *PDBPath);
}
}
// Find all the executables in the product network path.
TArray<FString> NetworkExecutables;
IFileManager::Get().FindFilesRecursive(NetworkExecutables, *CrashInfo.ExecutablesPath, TEXT("*.dll"), true, false, false);
IFileManager::Get().FindFilesRecursive(NetworkExecutables, *CrashInfo.ExecutablesPath, TEXT("*.exe"), true, false, false);
// From=Full pathname
// To=Relative pathname
TMap<FString, FString> FilesToBeCached;
// If a symbol matches an executable, add the pair to the list of files that should be cached.
for (const auto& NetworkExecutableFullpath : NetworkExecutables)
{
for (const auto& PDBPath : PDBPaths)
{
const FString PDBRelativePath = PDBPath.Replace(*CrashInfo.DepotName, TEXT("")).Replace(UESymbols, TEXT(""));
const FString PDBFullpath = FPDBCache::Get().GetDepotRoot() / PDBPath;
const FString PDBMatch = PDBRelativePath.Replace(TEXT("pdb"), TEXT(""));
const FString NetworkRelativePath = NetworkExecutableFullpath.Replace(*CrashInfo.ExecutablesPath, TEXT(""));
const bool bMatch = NetworkExecutableFullpath.Contains(PDBMatch);
if (bMatch)
{
// From -> Where
FilesToBeCached.Add(NetworkExecutableFullpath, NetworkRelativePath);
FilesToBeCached.Add(PDBFullpath, PDBRelativePath);
break;
}
}
}
// Initialize and add a new PDB Cache entry to the database.
CrashInfo.PDBCacheEntry = FPDBCache::Get().CreateAndAddPDBCacheEntryMixed(CrashInfo.EngineVersion, FilesToBeCached);
}
else
{
TArray<FString> FilesToBeCached;
//@TODO: MAC: Excluding labels for Mac since we are only syncing windows binaries here...
if (Label->GetName().Contains(TEXT("Mac")))
{
UE_LOG(LogCrashDebugHelper, Log, TEXT("Skipping Mac label: %s"), *Label->GetName());
}
else
{
// Sync all the dll, exes, and related symbol files
UE_LOG(LogCrashDebugHelper, Log, TEXT("Syncing modules with label: %s"), *Label->GetName());
SCOPE_LOG_TIME_IN_SECONDS(TEXT("SyncModules"), nullptr);
// Grab all dll and pdb files for the specified label.
TArray< TSharedRef<class ISourceControlRevision, ESPMode::ThreadSafe> > DLLSourceControlRevisions;
const FString DLLsPath = FString::Printf(TEXT("%s/....dll"), *CrashInfo.DepotName);
Label->GetFileRevisions(DLLsPath, DLLSourceControlRevisions);
TArray< TSharedRef<class ISourceControlRevision, ESPMode::ThreadSafe> > EXESourceControlRevisions;
const FString EXEsPath = FString::Printf(TEXT("%s/....exe"), *CrashInfo.DepotName);
Label->GetFileRevisions(EXEsPath, EXESourceControlRevisions);
TArray< TSharedRef<class ISourceControlRevision, ESPMode::ThreadSafe> > PDBSourceControlRevisions;
const FString PDBsPath = FString::Printf(TEXT("%s/....pdb"), *CrashInfo.DepotName);
Label->GetFileRevisions(PDBsPath, PDBSourceControlRevisions);
TSet<FString> ModulesPaths;
for (const auto& DLLSrc : DLLSourceControlRevisions)
{
ModulesPaths.Add(DLLSrc->GetFilename().Replace(*CrashInfo.DepotName, TEXT("")));
}
for (const auto& EXESrc : EXESourceControlRevisions)
{
ModulesPaths.Add(EXESrc->GetFilename().Replace(*CrashInfo.DepotName, TEXT("")));
}
TSet<FString> PDBPaths;
for (const auto& PDBSrc : PDBSourceControlRevisions)
{
PDBPaths.Add(PDBSrc->GetFilename().Replace(*CrashInfo.DepotName, TEXT("")));
}
// Iterate through all module and see if we have dll and pdb associated with the module, if so add it to the files to sync.
for (const auto& ModuleName : CrashInfo.ModuleNames)
{
const FString ModuleNamePDB = ModuleName.Replace(TEXT(".dll"), TEXT(".pdb")).Replace(TEXT(".exe"), TEXT(".pdb"));
for (const auto& ModulePath : ModulesPaths)
{
const bool bContainsModule = ModulePath.Contains(ModuleName);
if (bContainsModule)
{
FilesToSync.Add(ModulePath);
}
}
for (const auto& PDBPath : PDBPaths)
{
const bool bContainsPDB = PDBPath.Contains(ModuleNamePDB);
if (bContainsPDB)
{
FilesToSync.Add(PDBPath);
}
}
}
// Now, sync all files.
for (const auto& Filename : FilesToSync)
{
const FString DepotPath = CrashInfo.DepotName + Filename;
if (Label->Sync(DepotPath))
{
UE_LOG(LogCrashDebugHelper, Warning, TEXT("Synced binary: %s"), *DepotPath);
}
FilesToBeCached.Add(DepotPath);
}
}
// Initialize and add a new PDB Cache entry to the database.
CrashInfo.PDBCacheEntry = FPDBCache::Get().CreateAndAddPDBCacheEntry(CrashInfo.LabelName, CrashInfo.DepotName, FilesToBeCached);
}
}
else
{
UE_LOG(LogCrashDebugHelper, Error, TEXT("Could not find label: %s"), *CrashInfo.LabelName);
return false;
}
#endif
}
bOutPDBCacheEntryValid = CrashInfo.PDBCacheEntry.IsValid() && CrashInfo.PDBCacheEntry->Files.Num() > 0;
return true;
}
bool ICrashDebugHelper::SyncSourceFile()
{
#if UE_EDITOR || IS_PROGRAM
// Check source control
if( !ISourceControlModule::Get().IsEnabled() )
{
return false;
}
// Sync a single source file to requested CL.
FString DepotPath = CrashInfo.DepotName / CrashInfo.SourceFile + TEXT( "@" ) + TTypeToString<int32>::ToString( CrashInfo.BuiltFromCL );
ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create<FSync>(), DepotPath);
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Syncing a single source file: %s"), *DepotPath );
#endif
return true;
}
bool ICrashDebugHelper::ReadSourceFile( TArray<FString>& OutStrings )
{
const bool bUsePDBCache = FPDBCache::Get().UsePDBCache();
FString FilePath;
if (bUsePDBCache)
{
// We assume a special folder for syncing all streams and //depot/ is not used in the view mapping.
/*
//depot/... //machine/...
//UE4/... //machine/Stream/UE4/...
*/
const TCHAR* DepotDelimiter = TEXT( "//depot/" );
const bool bIsDepot = CrashInfo.DepotName.Contains( DepotDelimiter );
if (bIsDepot)
{
FilePath = FPDBCache::Get().GetDepotRoot() / CrashInfo.DepotName.Replace( DepotDelimiter, TEXT( "" ) ) / CrashInfo.SourceFile;
}
else
{
FilePath = FPDBCache::Get().GetDepotRoot() / TEXT( "Stream" ) / CrashInfo.DepotName / CrashInfo.SourceFile;
FilePath.ReplaceInline( TEXT( "//" ), TEXT( "/" ) );
}
}
else
{
FilePath = FPaths::RootDir() / CrashInfo.SourceFile;
}
FString Line;
if (FFileHelper::LoadFileToString( Line, *FilePath ))
{
Line = Line.Replace( TEXT( "\r" ), TEXT( "" ) );
Line.ParseIntoArray( OutStrings, TEXT( "\n" ), false );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Reading a single source file: %s" ), *FilePath );
return true;
}
else
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Failed to open source file: %s" ), *FilePath );
return false;
}
}
void ICrashDebugHelper::AddSourceToReport()
{
if( CrashInfo.SourceFile.Len() > 0 && CrashInfo.SourceLineNumber != 0 )
{
TArray<FString> Lines;
ReadSourceFile( Lines );
const uint32 MinLine = FMath::Clamp( CrashInfo.SourceLineNumber - 15, (uint32)1, (uint32)Lines.Num() );
const uint32 MaxLine = FMath::Clamp( CrashInfo.SourceLineNumber + 15, (uint32)1, (uint32)Lines.Num() );
for( uint32 Line = MinLine; Line < MaxLine; Line++ )
{
if( Line == CrashInfo.SourceLineNumber - 1 )
{
CrashInfo.SourceContext.Add( FString::Printf( TEXT( "%5u ***** %s" ), Line, *Lines[Line] ) );
}
else
{
CrashInfo.SourceContext.Add( FString::Printf( TEXT( "%5u %s" ), Line, *Lines[Line] ) );
}
}
}
}
bool ICrashDebugHelper::AddAnnotatedSourceToReport()
{
// Make sure we have a source file to interrogate
if( CrashInfo.SourceFile.Len() > 0 && CrashInfo.SourceLineNumber != 0 && !CrashInfo.LabelName.IsEmpty() )
{
#if UE_EDITOR || IS_PROGRAM
// Check source control
if( !ISourceControlModule::Get().IsEnabled() )
{
return false;
}
// Ask source control to annotate the file for us
FString DepotPath = CrashInfo.DepotName / CrashInfo.SourceFile;
TArray<FAnnotationLine> Lines;
SourceControlHelpers::AnnotateFile( ISourceControlModule::Get().GetProvider(), CrashInfo.BuiltFromCL, DepotPath, Lines );
uint32 MinLine = FMath::Clamp( CrashInfo.SourceLineNumber - 15, (uint32)1, (uint32)Lines.Num() );
uint32 MaxLine = FMath::Clamp( CrashInfo.SourceLineNumber + 15, (uint32)1, (uint32)Lines.Num() );
// Display a source context in the report, and decorate each line with the last editor of the line
for( uint32 Line = MinLine; Line < MaxLine; Line++ )
{
if( Line == CrashInfo.SourceLineNumber )
{
CrashInfo.SourceContext.Add( FString::Printf( TEXT( "%5u ***** %20s: %s" ), Line, *Lines[Line].UserName, *Lines[Line].Line ) );
}
else
{
CrashInfo.SourceContext.Add( FString::Printf( TEXT( "%5u %20s: %s" ), Line, *Lines[Line].UserName, *Lines[Line].Line ) );
}
}
#endif
return true;
}
return false;
}
void FCrashInfo::Log( FString Line )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT("%s"), *Line );
Report += Line + LINE_TERMINATOR;
}
const TCHAR* FCrashInfo::GetProcessorArchitecture( EProcessorArchitecture PA )
{
switch( PA )
{
case PA_X86:
return TEXT( "x86" );
case PA_X64:
return TEXT( "x64" );
case PA_ARM:
return TEXT( "ARM" );
}
return TEXT( "Unknown" );
}
int64 FCrashInfo::StringSize( const ANSICHAR* Line )
{
int64 Size = 0;
if( Line != nullptr )
{
while( *Line++ != 0 )
{
Size++;
}
}
return Size;
}
void FCrashInfo::WriteLine( FArchive* ReportFile, const ANSICHAR* Line )
{
if( Line != NULL )
{
int64 StringBytes = StringSize( Line );
ReportFile->Serialize( ( void* )Line, StringBytes );
}
ReportFile->Serialize( TCHAR_TO_UTF8( LINE_TERMINATOR ), FCStringWide::Strlen(LINE_TERMINATOR) );
}
void FCrashInfo::GenerateReport( const FString& DiagnosticsPath )
{
FArchive* ReportFile = IFileManager::Get().CreateFileWriter( *DiagnosticsPath );
if( ReportFile != NULL )
{
FString Line;
WriteLine( ReportFile, TCHAR_TO_UTF8( TEXT( "Generating report for minidump" ) ) );
WriteLine( ReportFile );
if ( EngineVersion.Len() > 0 )
{
Line = FString::Printf( TEXT( "Application version %s" ), *EngineVersion );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
else if( Modules.Num() > 0 )
{
Line = FString::Printf( TEXT( "Application version %d.%d.%d" ), Modules[0].Major, Modules[0].Minor, Modules[0].Patch );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
Line = FString::Printf( TEXT( " ... built from changelist %d" ), BuiltFromCL );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
if( LabelName.Len() > 0 )
{
Line = FString::Printf( TEXT( " ... based on label %s" ), *LabelName );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "OS version %d.%d.%d.%d" ), SystemInfo.OSMajor, SystemInfo.OSMinor, SystemInfo.OSBuild, SystemInfo.OSRevision );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
Line = FString::Printf( TEXT( "Running %d %s processors" ), SystemInfo.ProcessorCount, GetProcessorArchitecture( SystemInfo.ProcessorArchitecture ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
Line = FString::Printf( TEXT( "Exception was \"%s\"" ), *Exception.ExceptionString );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "Source context from \"%s\"" ), *SourceFile );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "<SOURCE START>" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
for( int32 LineIndex = 0; LineIndex < SourceContext.Num(); LineIndex++ )
{
Line = FString::Printf( TEXT( "%s" ), *SourceContext[LineIndex] );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
Line = FString::Printf( TEXT( "<SOURCE END>" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "<CALLSTACK START>" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
for( int32 StackIndex = 0; StackIndex < Exception.CallStackString.Num(); StackIndex++ )
{
Line = FString::Printf( TEXT( "%s" ), *Exception.CallStackString[StackIndex] );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
}
Line = FString::Printf( TEXT( "<CALLSTACK END>" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
WriteLine( ReportFile );
Line = FString::Printf( TEXT( "%d loaded modules" ), Modules.Num() );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
for( int32 ModuleIndex = 0; ModuleIndex < Modules.Num(); ModuleIndex++ )
{
FCrashModuleInfo& Module = Modules[ModuleIndex];
FString ModuleDirectory = FPaths::GetPath(Module.Name);
FString ModuleName = FPaths::GetBaseFilename( Module.Name, true ) + FPaths::GetExtension( Module.Name, true );
FString ModuleDetail = FString::Printf( TEXT( "%40s" ), *ModuleName );
FString Version = FString::Printf( TEXT( " (%d.%d.%d.%d)" ), Module.Major, Module.Minor, Module.Patch, Module.Revision );
ModuleDetail += FString::Printf( TEXT( " %22s" ), *Version );
ModuleDetail += FString::Printf( TEXT( " 0x%016x 0x%08x" ), Module.BaseOfImage, Module.SizeOfImage );
ModuleDetail += FString::Printf( TEXT( " %s" ), *ModuleDirectory );
WriteLine( ReportFile, TCHAR_TO_UTF8( *ModuleDetail ) );
}
WriteLine( ReportFile );
// Write out the processor debugging log
WriteLine( ReportFile, TCHAR_TO_UTF8( *Report ) );
Line = FString::Printf( TEXT( "Report end!" ) );
WriteLine( ReportFile, TCHAR_TO_UTF8( *Line ) );
ReportFile->Close();
delete ReportFile;
}
}
void ICrashDebugHelper::FindSymbolsAndBinariesStorage()
{
CrashInfo.ExecutablesPath.Empty();
CrashInfo.SymbolsPath.Empty();
CrashInfo.LabelName.Empty();
if( CrashInfo.BuiltFromCL == FCrashInfo::INVALID_CHANGELIST )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "Invalid parameters" ) );
return;
}
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Engine version: %s" ), *CrashInfo.EngineVersion );
const FPDBCacheConfigEntry ConfigEntry = FCrashDebugHelperConfig::Get().GetCacheConfigEntryForBranch( CrashInfo.DepotName );
FString ExecutablePathPattern = ConfigEntry.ExecutablePathPattern;
FString SymbolPathPattern = ConfigEntry.SymbolPathPattern;
if (!ExecutablePathPattern.IsEmpty() || !SymbolPathPattern.IsEmpty())
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Using branch: %s" ), *CrashInfo.DepotName );
}
else
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Branch not found: %s" ), *CrashInfo.DepotName );
return;
}
const FString StrENGINE_VERSION = CrashInfo.EngineVersion;
const FString StrPLATFORM_NAME = CrashInfo.PlatformName;
const FString StrPLATFORM_VARIANT = CrashInfo.PlatformVariantName;
const FString StrOLD_ENGINE_VERSION = FString::Printf( TEXT( "%s-CL-%i" ), *CrashInfo.DepotName.Replace( TEXT( "+" ), TEXT( "/" ) ), CrashInfo.BuiltFromCL )
.Replace( TEXT("/"), TEXT("+") );
const FString StrBUILD_VERSION = CrashInfo.BuildVersion;
const FString TestExecutablesPath = ExecutablePathPattern
.Replace( TEXT( "%ENGINE_VERSION%" ), *StrENGINE_VERSION )
.Replace( TEXT( "%PLATFORM_NAME%" ), *StrPLATFORM_NAME )
.Replace( TEXT( "%PLATFORM_VARIANT%" ), *StrPLATFORM_VARIANT )
.Replace( TEXT( "%OLD_ENGINE_VERSION%" ), *StrOLD_ENGINE_VERSION )
.Replace( TEXT( "%BUILD_VERSION%" ), *StrBUILD_VERSION );
const FString TestSymbolsPath = SymbolPathPattern
.Replace( TEXT( "%ENGINE_VERSION%" ), *StrENGINE_VERSION )
.Replace( TEXT( "%PLATFORM_NAME%" ), *StrPLATFORM_NAME )
.Replace( TEXT( "%PLATFORM_VARIANT%" ), *StrPLATFORM_VARIANT )
.Replace( TEXT( "%OLD_ENGINE_VERSION%" ), *StrOLD_ENGINE_VERSION )
.Replace( TEXT( "%BUILD_VERSION%" ), *StrBUILD_VERSION );
// Try to find the network path by using the pattern supplied via ini.
// If this step successes, we will grab the executable from the network path instead of P4.
bool bFoundDirectory = false;
bool bHasExecutables = false;
if (!TestExecutablesPath.IsEmpty())
{
bHasExecutables = IFileManager::Get().DirectoryExists(*TestExecutablesPath);
}
bool bHasSymbols = false;
if (!TestSymbolsPath.IsEmpty())
{
bHasSymbols = IFileManager::Get().DirectoryExists(*TestSymbolsPath);
}
if( bHasExecutables && bHasSymbols )
{
CrashInfo.ExecutablesPath = TestExecutablesPath;
CrashInfo.SymbolsPath = TestSymbolsPath;
bFoundDirectory = true;
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Using path for executables and symbols: %s" ), *CrashInfo.ExecutablesPath );
}
else if( bHasExecutables )
{
CrashInfo.ExecutablesPath = TestExecutablesPath;
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Using path for executables: %s" ), *CrashInfo.ExecutablesPath );
}
else
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Path for executables not found: %s" ), *TestExecutablesPath );
}
// Try to find the label directly in source control by using the pattern supplied via ini.
if( !bFoundDirectory && !SourceControlBuildLabelPattern.IsEmpty() )
{
const FString ChangelistString = FString::Printf( TEXT( "%d" ), CrashInfo.BuiltFromCL );
const FString LabelWithCL = SourceControlBuildLabelPattern.Replace( TEXT( "%CHANGELISTNUMBER%" ), *ChangelistString, ESearchCase::CaseSensitive );
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Label matching pattern: %s" ), *LabelWithCL );
#if UE_EDITOR || IS_PROGRAM
TArray< TSharedRef<ISourceControlLabel> > Labels = ISourceControlModule::Get().GetProvider().GetLabels( LabelWithCL );
if( Labels.Num() > 0 )
{
const int32 LabelIndex = 0;
CrashInfo.LabelName = Labels[LabelIndex]->GetName();;
// If we found more than one label, warn about it and just use the first one
if( Labels.Num() > 1 )
{
UE_LOG( LogCrashDebugHelper, Warning, TEXT( "More than one build label found, using label: %s" ), *LabelWithCL, *CrashInfo.LabelName );
}
else
{
UE_LOG( LogCrashDebugHelper, Log, TEXT( "Using label: %s" ), *CrashInfo.LabelName );
}
}
#endif
}
}