Files
UnrealEngineUWP/Engine/Source/Developer/Virtualization/Private/VirtualizationSourceControlBackend.cpp
paul chipchase 8f28e32d6b Allow Mirage to connect to the perforce source control provider even if it was not specified in the config file or command line.
#rb PJ.Kack
#rnx
#preflight 60fff7d57f21c90001f9399d

- When we check the source control provider name, we will now attempt to set it to "Perforce" if it is current the default "None" provider.
- If the user has specified a different source control provider than "Perforce" we will give a fatal error that the content virtualization graph containing the source control backend will not work with that setup.
- This part of the workflow might need to change in the future or at least the Mirage documentation must make very clear that when using source control as the persistent storage backend that a connection to perforce will be required, at least until we support our source control solutions but even then the connect to that provider will be required.

#ROBOMERGE-SOURCE: CL 16968160 in //UE5/Main/...
#ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v838-16927207)

[CL 16968164 by paul chipchase in ue5-release-engine-test branch]
2021-07-27 09:46:51 -04:00

174 lines
6.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Virtualization/IVirtualizationBackend.h"
#include "ISourceControlModule.h"
#include "ISourceControlProvider.h"
#include "Misc/Parse.h"
#include "SourceControlOperations.h"
#include "Virtualization/PayloadId.h"
#include "VirtualizationSourceControlUtilities.h"
#include "VirtualizationUtilities.h"
// When the SourceControl module (or at least the perforce source control module) is thread safe we
// can enable this and stop using the hacky work around 'TryToDownloadFileFromBackgroundThread'
#define IS_SOURCE_CONTROL_THREAD_SAFE 0
namespace UE::Virtualization
{
/**
* This backend can be used to access payloads stored in source control.
* The backend doesn't 'check out' a payload file but instead will just download the payload as
* a binary blob.
* It is assumed that the files are stored with the same path convention as the file system
* backend, found in Utils::PayloadIdToPath.
*
* Ini file setup:
* 'Name'=(Type=SourceControl, DepotRoot="//XXX/")
* Where 'Name' is the backend name in the hierarchy and 'XXX' is the path in the source control
* depot where the payload files are being stored.
*/
class FSourceControlBackend : public IVirtualizationBackend
{
public:
FSourceControlBackend(FStringView ConfigName)
: IVirtualizationBackend(EOperations::Pull)
{
}
virtual bool Initialize(const FString& ConfigEntry) override
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSourceControlBackend::Initialize);
// We require that a valid depot root has been provided
if (!FParse::Value(*ConfigEntry, TEXT("DepotRoot="), DepotRoot))
{
UE_LOG(LogVirtualization, Error, TEXT("'DepotRoot=' not found in the config file"));
return false;
}
ISourceControlModule& SSCModule = ISourceControlModule::Get();
// We require perforce as the source control provider as it is currently the only one that has the virtualization functionality implemented
const FName SourceControlName = SSCModule.GetProvider().GetName();
if (SourceControlName.IsNone())
{
// No source control provider is set so we can try to set it to "Perforce"
// Note this call will fatal error if "Perforce" is not a valid option
SSCModule.SetProvider(FName("Perforce"));
}
else if (SourceControlName != TEXT("Perforce"))
{
UE_LOG(LogVirtualization, Error, TEXT("Attempting to initialize FSourceControlBackend but source control is '%s' and only Perforce is currently supported!"), *SourceControlName.ToString());
return false;
}
ISourceControlProvider& SCCProvider = SSCModule.GetProvider();
if (!SCCProvider.IsAvailable())
{
SCCProvider.Init();
}
// When a source control depot is set up a file named 'payload_metainfo.txt' should be submitted to it's root.
// This allows us to check for the existence of the file to confirm that the depot root is indeed valid.
const FString PayloadMetaInfoPath = FString::Printf(TEXT("%spayload_metainfo.txt"), *DepotRoot);
#if IS_SOURCE_CONTROL_THREAD_SAFE
TSharedRef<FDownloadFile, ESPMode::ThreadSafe> DownloadCommand = ISourceControlOperation::Create<FDownloadFile>();
if (SCCProvider.Execute(DownloadCommand, PayloadMetaInfoPath, EConcurrency::Synchronous) != ECommandResult::Succeeded)
{
UE_LOG(LogVirtualization, Error, TEXT("Failed to find 'payload_metainfo.txt' in the depot '%s', is your config set up correctly?"), *DepotRoot);
return false;
}
#else
TSharedRef<FDownloadFile, ESPMode::ThreadSafe> DownloadCommand = ISourceControlOperation::Create<FDownloadFile>();
if (!SCCProvider.TryToDownloadFileFromBackgroundThread(DownloadCommand, PayloadMetaInfoPath))
{
UE_LOG(LogVirtualization, Error, TEXT("Failed to find 'payload_metainfo.txt' in the depot '%s', is your config set up correctly?"), *DepotRoot);
return false;
}
#endif //IS_SOURCE_CONTROL_THREAD_SAFE
FSharedBuffer MetaInfoBuffer = DownloadCommand->GetFileData(PayloadMetaInfoPath);
if (MetaInfoBuffer.IsNull())
{
UE_LOG(LogVirtualization, Error, TEXT("Failed to find 'payload_metainfo.txt' in the depot '%s', is your config set up correctly?"), *DepotRoot);
return false;
}
// Currently we do not do anything with the payload meta info, in the future we could structure
// it's format to include more information that might be worth logging or something.
// But for now being able to pull the payload meta info path at least shows that we can use the
// depot.
return true;
}
virtual EPushResult PushData(const FPayloadId& Id, const FCompressedBuffer& Payload) override
{
// This backend will not actually push data to source control, that will be done by
// a separate submission tool. Since files submitted to source control are there forever
// we don't want the user to be uploaded new entries each time that they save the asset
// but instead upload once when the package is committed to source control.
// TODO: As we put the pieces together it is likely that we will change the backend API
// so that we don't need dummy implementations of PushData for backends that don't need it.
// Especially considering that it is most likely that no backend will push.
checkNoEntry();
return EPushResult::Failed;
}
virtual FCompressedBuffer PullData(const FPayloadId& Id) override
{
TRACE_CPUPROFILER_EVENT_SCOPE(FSourceControlBackend::PullData);
TStringBuilder<512> DepotPath;
CreateDepotPath(Id, DepotPath);
ISourceControlProvider& SCCProvider = ISourceControlModule::Get().GetProvider();
#if IS_SOURCE_CONTROL_THREAD_SAFE
TSharedRef<FDownloadFile, ESPMode::ThreadSafe> DownloadCommand = ISourceControlOperation::Create<FDownloadFile>(FDownloadFile::EVerbosity::None);
if (SCCProvider.Execute(DownloadCommand, DepotPath.ToString(), EConcurrency::Synchronous) != ECommandResult::Succeeded)
{
return FCompressedBuffer();
}
#else
TSharedRef<FDownloadFile> DownloadCommand = ISourceControlOperation::Create<FDownloadFile>(FDownloadFile::EVerbosity::None);
if (!SCCProvider.TryToDownloadFileFromBackgroundThread(DownloadCommand, DepotPath.ToString()))
{
return FCompressedBuffer();
}
#endif
// The payload was created by FCompressedBuffer::Compress so we can return it
// as a FCompressedBuffer.
FSharedBuffer Buffer = DownloadCommand->GetFileData(DepotPath);
return FCompressedBuffer::FromCompressed(Buffer);
}
virtual FString GetDebugString() const override
{
return FString(TEXT("SourceControl"));
}
void CreateDepotPath(const FPayloadId& PayloadId, FStringBuilderBase& OutPath)
{
TStringBuilder<52> PayloadPath;
Utils::PayloadIdToPath(PayloadId, PayloadPath);
OutPath << DepotRoot << PayloadPath;
}
private:
/** The root where the virtualized payloads are stored in source control */
FString DepotRoot;
};
UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY(FSourceControlBackend, SourceControl);
} // namespace UE::Virtualization