Files
2025-05-18 13:04:45 +08:00

201 lines
5.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Commands/RehydrateCommand.h"
#include "ISourceControlProvider.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/PackageName.h"
#include "Misc/Parse.h"
#include "Misc/ScopeExit.h"
#include "ProjectFiles.h"
#include "UnrealVirtualizationTool.h"
#include "Virtualization/VirtualizationSystem.h"
namespace UE::Virtualization
{
FRehydrateCommand::FRehydrateCommand(FStringView CommandName)
: FCommand(CommandName)
{
}
void FRehydrateCommand::PrintCmdLineHelp()
{
UE_LOG(LogVirtualizationTool, Display, TEXT("<ProjectFilePath> -Mode=Rehydrate -Package=<string>"));
UE_LOG(LogVirtualizationTool, Display, TEXT("<ProjectFilePath> -Mode=Rehydrate -PackageDir=<string>"));
UE_LOG(LogVirtualizationTool, Display, TEXT("<ProjectFilePath> -Mode=Rehydrate -Changelist=<number>"));
UE_LOG(LogVirtualizationTool, Display, TEXT(""));
}
bool FRehydrateCommand::Initialize(const TCHAR* CmdLine)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FRehydrateCommand::Initialize);
// Note that we haven't loaded any projects config files and so don't really have
// any valid project mount points so we cannot use FPackagePath or FPackageName
// and expect to find anything!
TArray<FString> Tokens;
TArray<FString> Switches;
ParseCommandLine(CmdLine, Tokens, Switches);
FString SourceChangelistNumber;
FString SwitchValue;
for (const FString& Switch : Switches)
{
EPathResult Result = ParseSwitchForPaths(Switch, Packages);
if (Result == EPathResult::Error)
{
return false;
}
else if (Result == EPathResult::Success)
{
continue; // If we already matched the switch we don't need to check against any others
}
if (FParse::Value(*Switch, TEXT("ClientSpecName="), SwitchValue))
{
ClientSpecName = SwitchValue;
UE_LOG(LogVirtualizationTool, Display, TEXT("\tWorkspace name provided '%s'"), *ClientSpecName);
}
else if (FParse::Value(*Switch, TEXT("Changelist="), SwitchValue))
{
SourceChangelistNumber = SwitchValue;
}
else if (Switch == TEXT("Checkout"))
{
bShouldCheckout = true;
}
}
// Process the provided changelist if one was found
if (!SourceChangelistNumber.IsEmpty())
{
// If no client spec was provided we need to find it for the changelist
// In theory this duplicates a lot of the work found in ::TryParseChangelist
// but at the moment the FGetChangelistDetails operation is not compatible
// with the FSourceControlChangelistStateRef/FSourceControlStateRef API
// so we are stuck with duplication of work.
if (ClientSpecName.IsEmpty())
{
ClientSpecName = FindClientSpecForChangelist(SourceChangelistNumber);
if (!ClientSpecName.IsEmpty())
{
FSourceControlResultInfo Info;
if (SCCProvider->SwitchWorkspace(ClientSpecName, Info, nullptr) != ECommandResult::Succeeded)
{
UE_LOG(LogVirtualization, Error, TEXT("Failed to switch to workspace '%s'"), *ClientSpecName);
return false;
}
}
else
{
UE_LOG(LogVirtualization, Error, TEXT("Count not find a valid workspace for the changelist '%s'"), *SourceChangelistNumber);
return false;
}
}
if (!TryParseChangelist(ClientSpecName, SourceChangelistNumber, Packages, nullptr))
{
UE_LOG(LogVirtualization, Error, TEXT("Failed to find the files in the changelist '%s'"), *SourceChangelistNumber);
return false;
}
}
return true;
}
void FRehydrateCommand::Serialize(FJsonSerializerBase& Serializer)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FRehydrateCommand::Serialize);
Serializer.Serialize(TEXT("ShouldCheckout"), bShouldCheckout);
}
bool FRehydrateCommand::ProcessProject(const FProject& Project, TUniquePtr<FCommandOutput>& Output)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FRehydrateCommand::ProcessProject);
TStringBuilder<128> ProjectName;
ProjectName << Project.GetProjectName();
UE_LOG(LogVirtualizationTool, Display, TEXT("\tProcessing package(s) for the project '%s'..."), ProjectName.ToString());
FConfigFile EngineConfigWithProject;
if (!Project.TryLoadConfig(EngineConfigWithProject))
{
return false;
}
Project.RegisterMountPoints();
ON_SCOPE_EXIT
{
Project.UnRegisterMountPoints();
};
UE::Virtualization::FInitParams InitParams(ProjectName, EngineConfigWithProject);
UE::Virtualization::Initialize(InitParams, UE::Virtualization::EInitializationFlags::ForceInitialize);
ON_SCOPE_EXIT
{
UE::Virtualization::Shutdown();
};
UE_LOG(LogVirtualizationTool, Display, TEXT("\t\tAttempting to rehydrate packages..."), ProjectName.ToString());
const TArray<FString> ProjectPackages = Project.GetAllPackages();
ERehydrationOptions Options = ERehydrationOptions::None;
if (bShouldCheckout)
{
Options |= ERehydrationOptions::Checkout;
// Make sure that we have a valid source control connection if we might try to checkout packages
TryConnectToSourceControl();
}
FRehydrationResult Result = UE::Virtualization::IVirtualizationSystem::Get().TryRehydratePackages(ProjectPackages, Options);
if (!Result.WasSuccessful())
{
UE_LOG(LogVirtualizationTool, Error, TEXT("The rehydration process failed with the following errors:"));
for (const FText& Error : Result.Errors)
{
UE_LOG(LogVirtualizationTool, Error, TEXT("\t%s"), *Error.ToString());
}
return false;
}
if (bShouldCheckout)
{
UE_LOG(LogVirtualizationTool, Display, TEXT("\t\t%d packages were checked out of revision control"), Result.CheckedOutPackages.Num());
}
UE_LOG(LogVirtualizationTool, Display, TEXT("\t\tTime taken %.2f(s)"), Result.TimeTaken);
UE_LOG(LogVirtualizationTool, Display, TEXT("\t\tRehyration of project packages complete!"), ProjectName.ToString());
return true;
}
bool FRehydrateCommand::ProcessOutput(const TArray<TUniquePtr<FCommandOutput>>& OutputArray)
{
// Command has no additional work to do
return true;
}
TUniquePtr<FCommandOutput> FRehydrateCommand::CreateOutputObject() const
{
// This command does not create any output
return TUniquePtr<FCommandOutput>();
}
const TArray<FString>& FRehydrateCommand::GetPackages() const
{
return Packages;
}
} // namespace UE::Virtualization