Files
UnrealEngineUWP/Engine/Source/Developer/DesktopPlatform/Private/DesktopPlatformBase.cpp

1476 lines
46 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DesktopPlatformBase.h"
#include "HAL/FileManager.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Misc/Guid.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/FeedbackContext.h"
#include "Misc/App.h"
#include "Misc/EngineVersion.h"
#include "Serialization/JsonTypes.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "Modules/ModuleManager.h"
#include "DesktopPlatformPrivate.h"
#include "Misc/OutputDeviceRedirector.h"
#define LOCTEXT_NAMESPACE "DesktopPlatform"
FDesktopPlatformBase::FDesktopPlatformBase()
{
LauncherInstallationTimestamp = FDateTime::MinValue();
}
FString FDesktopPlatformBase::GetEngineDescription(const FString& Identifier)
{
// Official release versions just have a version number
if(IsStockEngineRelease(Identifier))
{
return Identifier;
}
// Otherwise get the path
FString RootDir;
if(!GetEngineRootDirFromIdentifier(Identifier, RootDir))
{
return FString();
}
// Convert it to a platform directory
FString PlatformRootDir = RootDir;
FPaths::MakePlatformFilename(PlatformRootDir);
// Perforce build
if (IsSourceDistribution(RootDir))
{
return FString::Printf(TEXT("Source build at %s"), *PlatformRootDir);
}
else
{
return FString::Printf(TEXT("Binary build at %s"), *PlatformRootDir);
}
}
FString FDesktopPlatformBase::GetCurrentEngineIdentifier()
{
if(CurrentEngineIdentifier.Len() == 0 && !GetEngineIdentifierFromRootDir(FPlatformMisc::RootDir(), CurrentEngineIdentifier))
{
CurrentEngineIdentifier.Empty();
}
return CurrentEngineIdentifier;
}
void FDesktopPlatformBase::EnumerateLauncherEngineInstallations(TMap<FString, FString> &OutInstallations)
{
// Cache the launcher install list if necessary
ReadLauncherInstallationList();
// We've got a list of launcher installations. Filter it by the engine installations.
for(TMap<FString, FString>::TConstIterator Iter(LauncherInstallationList); Iter; ++Iter)
{
FString AppName = Iter.Key();
if(AppName.RemoveFromStart(TEXT("UE_"), ESearchCase::CaseSensitive))
{
OutInstallations.Add(AppName, Iter.Value());
}
}
}
void FDesktopPlatformBase::EnumerateLauncherSampleInstallations(TArray<FString> &OutInstallations)
{
// Cache the launcher install list if necessary
ReadLauncherInstallationList();
// We've got a list of launcher installations. Filter it by the engine installations.
for(TMap<FString, FString>::TConstIterator Iter(LauncherInstallationList); Iter; ++Iter)
{
FString AppName = Iter.Key();
if(!AppName.StartsWith(TEXT("UE_"), ESearchCase::CaseSensitive))
{
OutInstallations.Add(Iter.Value());
}
}
}
void FDesktopPlatformBase::EnumerateLauncherSampleProjects(TArray<FString> &OutFileNames)
{
// Enumerate all the sample installation directories
TArray<FString> LauncherSampleDirectories;
EnumerateLauncherSampleInstallations(LauncherSampleDirectories);
// Find all the project files within them
for(int32 Idx = 0; Idx < LauncherSampleDirectories.Num(); Idx++)
{
TArray<FString> FileNames;
IFileManager::Get().FindFiles(FileNames, *(LauncherSampleDirectories[Idx] / TEXT("*.uproject")), true, false);
OutFileNames.Append(FileNames);
}
}
bool FDesktopPlatformBase::GetEngineRootDirFromIdentifier(const FString &Identifier, FString &OutRootDir)
{
// Get all the installations
TMap<FString, FString> Installations;
EnumerateEngineInstallations(Installations);
// Find the one with the right identifier
for (TMap<FString, FString>::TConstIterator Iter(Installations); Iter; ++Iter)
{
if (Iter->Key == Identifier)
{
OutRootDir = Iter->Value;
return true;
}
}
return false;
}
bool FDesktopPlatformBase::GetEngineIdentifierFromRootDir(const FString &RootDir, FString &OutIdentifier)
{
// Get all the installations
TMap<FString, FString> Installations;
EnumerateEngineInstallations(Installations);
// Normalize the root directory
FString NormalizedRootDir = RootDir;
FPaths::CollapseRelativeDirectories(NormalizedRootDir);
FPaths::NormalizeDirectoryName(NormalizedRootDir);
// Find the label for the given directory
for (TMap<FString, FString>::TConstIterator Iter(Installations); Iter; ++Iter)
{
if (Iter->Value == NormalizedRootDir)
{
OutIdentifier = Iter->Key;
return true;
}
}
// Otherwise just try to add it
return RegisterEngineInstallation(RootDir, OutIdentifier);
}
bool FDesktopPlatformBase::GetDefaultEngineIdentifier(FString &OutId)
{
TMap<FString, FString> Installations;
EnumerateEngineInstallations(Installations);
bool bRes = false;
if (Installations.Num() > 0)
{
// Default to the first install
TMap<FString, FString>::TConstIterator Iter(Installations);
OutId = Iter.Key();
++Iter;
// Try to find the most preferred install
for(; Iter; ++Iter)
{
if(IsPreferredEngineIdentifier(Iter.Key(), OutId))
{
OutId = Iter.Key();
}
}
}
return bRes;
}
bool FDesktopPlatformBase::GetDefaultEngineRootDir(FString &OutDirName)
{
FString Identifier;
return GetDefaultEngineIdentifier(Identifier) && GetEngineRootDirFromIdentifier(Identifier, OutDirName);
}
bool FDesktopPlatformBase::IsPreferredEngineIdentifier(const FString &Identifier, const FString &OtherIdentifier)
{
int32 Version = ParseReleaseVersion(Identifier);
int32 OtherVersion = ParseReleaseVersion(OtherIdentifier);
if(Version != OtherVersion)
{
return Version > OtherVersion;
}
else
{
return Identifier > OtherIdentifier;
}
}
bool FDesktopPlatformBase::TryGetEngineVersion(const FString& RootDir, FEngineVersion& OutVersion)
{
// Read the file to a string
FString VersionText;
if(FFileHelper::LoadFileToString(VersionText, *(RootDir / TEXT("Engine/Build/Build.version"))))
{
// Deserialize a JSON object from the string
TSharedPtr< FJsonObject > Object;
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(VersionText);
if(FJsonSerializer::Deserialize(Reader, Object) && Object.IsValid())
{
int32 MajorVersion = 0;
int32 MinorVersion = 0;
int32 PatchVersion = 0;
if(Object->TryGetNumberField(TEXT("MajorVersion"), MajorVersion) && Object->TryGetNumberField(TEXT("MinorVersion"), MinorVersion) && Object->TryGetNumberField(TEXT("PatchVersion"), PatchVersion))
{
int32 Changelist = 0;
if(!Object->TryGetNumberField(TEXT("Changelist"), Changelist))
{
Changelist = 0;
}
int32 IsLicenseeVersion = 0;
if(!Object->TryGetNumberField(TEXT("IsLicenseeVersion"), IsLicenseeVersion))
{
IsLicenseeVersion = 0;
}
FString BranchName;
if(!Object->TryGetStringField(TEXT("BranchName"), BranchName))
{
BranchName = FString();
}
int EncodedChangelist = (IsLicenseeVersion == 0)? Changelist : FEngineVersionBase::EncodeLicenseeChangelist(Changelist);
OutVersion = FEngineVersion(MajorVersion, MinorVersion, PatchVersion, EncodedChangelist, BranchName);
return true;
}
}
}
// Try to read the version file
FString VersionHeader;
if(FFileHelper::LoadFileToString(VersionHeader, *(RootDir / TEXT("Engine/Source/Runtime/Launch/Resources/Version.h"))))
{
int32 MajorVersion = -1;
int32 MinorVersion = -1;
int32 PatchVersion = -1;
int32 Changelist = 0;
int32 IsLicenseeVersion = 0;
FString BranchName;
// Scan the file for version defines
const TCHAR* TextPos = *VersionHeader;
while(*TextPos)
{
// Skip over any newlines
while(FChar::IsWhitespace(*TextPos))
{
TextPos++;
}
// Buffer up a line of tokens
TArray<FString> Tokens;
while(*TextPos != '\n' && *TextPos != 0)
{
if(*TextPos == ' ' || *TextPos == '\t' || *TextPos == '\r')
{
// Skip over whitespace
TextPos++;
}
else if(FChar::IsIdentifier(*TextPos))
{
// Parse an identifier. Exact C rules for an identifier don't really matter; we just need alphanumeric sequences.
const TCHAR* TokenStart = TextPos++;
while(FChar::IsIdentifier(*TextPos)) TextPos++;
Tokens.Add(FString(TextPos - TokenStart, TokenStart));
}
else if(*TextPos == '\"')
{
// Parse a string
const TCHAR* TokenStart = TextPos++;
while(*TextPos != 0 && (TextPos == TokenStart + 1 || *(TextPos - 1) != '\"')) TextPos++;
Tokens.Add(FString(TextPos - TokenStart, TokenStart));
}
else if(*TextPos == '/' && *(TextPos + 1) == '/')
{
// Skip a C++ style comment
TextPos += 2;
while(*TextPos != '\n' && *TextPos != 0) TextPos++;
}
else if(*TextPos == '/' && *(TextPos + 1) == '*' && *(TextPos + 2) != 0 && *(TextPos + 3) != 0)
{
// Skip a C-style comment
TextPos += 4;
while(*TextPos != 0 && (*(TextPos - 2) != '*' || *(TextPos - 1) != '/')) TextPos++;
}
else
{
// Take a single symbol character
Tokens.Add(FString(1, TextPos));
TextPos++;
}
}
// Check if it matches any version defines
if(Tokens.Num() >= 4 && Tokens[0] == "#" && Tokens[1] == "define")
{
if(FChar::IsDigit(Tokens[3][0]))
{
if(Tokens[2] == "ENGINE_MAJOR_VERSION")
{
MajorVersion = FCString::Atoi(*Tokens[3]);
}
else if(Tokens[2] == "ENGINE_MINOR_VERSION")
{
MinorVersion = FCString::Atoi(*Tokens[3]);
}
else if(Tokens[2] == "ENGINE_PATCH_VERSION")
{
PatchVersion = FCString::Atoi(*Tokens[3]);
}
else if(Tokens[2] == "BUILT_FROM_CHANGELIST")
{
Changelist = FCString::Atoi(*Tokens[3]);
}
else if(Tokens[2] == "ENGINE_IS_LICENSEE_VERSION")
{
IsLicenseeVersion = FCString::Atoi(*Tokens[3]);
}
}
else if(Tokens[3].StartsWith("\"") && Tokens[3].EndsWith("\""))
{
if(Tokens[2] == "BRANCH_NAME")
{
BranchName = Tokens[3].TrimQuotes();
}
}
}
}
// If we have everything we need, fill in the version struct
if(MajorVersion != -1 && MinorVersion != -1 && PatchVersion != -1)
{
int EncodedChangelist = (IsLicenseeVersion == 0)? Changelist : FEngineVersionBase::EncodeLicenseeChangelist(Changelist);
OutVersion = FEngineVersion(MajorVersion, MinorVersion, PatchVersion, EncodedChangelist, BranchName);
return true;
}
}
return false;
}
bool FDesktopPlatformBase::IsStockEngineRelease(const FString &Identifier)
{
FGuid Guid;
return !FGuid::Parse(Identifier, Guid);
}
bool FDesktopPlatformBase::TryParseStockEngineVersion(const FString& Identifier, FEngineVersion& OutVersion)
{
TCHAR* End;
uint64 Major = FCString::Strtoui64(*Identifier, &End, 10);
if (Major > MAX_uint16 || *(End++) != '.')
{
return false;
}
uint64 Minor = FCString::Strtoui64(End, &End, 10);
if (Minor > MAX_uint16 || *End != 0)
{
return false;
}
OutVersion = FEngineVersion(Major, Minor, 0, 0, TEXT(""));
return true;
}
bool FDesktopPlatformBase::IsSourceDistribution(const FString &EngineRootDir)
{
// Check for the existence of a SourceBuild.txt file
FString SourceBuildPath = EngineRootDir / TEXT("Engine/Build/SourceDistribution.txt");
return (IFileManager::Get().FileSize(*SourceBuildPath) >= 0);
}
bool FDesktopPlatformBase::IsPerforceBuild(const FString &EngineRootDir)
{
// Check for the existence of a SourceBuild.txt file
FString PerforceBuildPath = EngineRootDir / TEXT("Engine/Build/PerforceBuild.txt");
return (IFileManager::Get().FileSize(*PerforceBuildPath) >= 0);
}
bool FDesktopPlatformBase::IsValidRootDirectory(const FString &RootDir)
{
// Check that there's an Engine\Binaries directory underneath the root
FString EngineBinariesDirName = RootDir / TEXT("Engine/Binaries");
FPaths::NormalizeDirectoryName(EngineBinariesDirName);
if(!IFileManager::Get().DirectoryExists(*EngineBinariesDirName))
{
return false;
}
// Also check there's an Engine\Build directory. This will filter out anything that has an engine-like directory structure but doesn't allow building code projects - like the launcher.
FString EngineBuildDirName = RootDir / TEXT("Engine/Build");
FPaths::NormalizeDirectoryName(EngineBuildDirName);
if(!IFileManager::Get().DirectoryExists(*EngineBuildDirName))
{
return false;
}
// Otherwise it's valid
return true;
}
bool FDesktopPlatformBase::SetEngineIdentifierForProject(const FString &ProjectFileName, const FString &InIdentifier)
{
// Load the project file
TSharedPtr<FJsonObject> ProjectFile = LoadProjectFile(ProjectFileName);
if (!ProjectFile.IsValid())
{
return false;
}
// Check if the project is a non-foreign project of the given engine installation. If so, blank the identifier
// string to allow portability between source control databases. GetEngineIdentifierForProject will translate
// the association back into a local identifier on other machines or syncs.
FString Identifier = InIdentifier;
if(Identifier.Len() > 0)
{
FString RootDir;
if(GetEngineRootDirFromIdentifier(Identifier, RootDir))
{
const FUProjectDictionary &Dictionary = GetCachedProjectDictionary(RootDir);
if(!Dictionary.IsForeignProject(ProjectFileName))
{
Identifier.Empty();
}
}
}
// Set the association on the project and save it
ProjectFile->SetStringField(TEXT("EngineAssociation"), Identifier);
return SaveProjectFile(ProjectFileName, ProjectFile);
}
bool FDesktopPlatformBase::GetEngineIdentifierForProject(const FString& ProjectFileName, FString& OutIdentifier)
{
OutIdentifier.Empty();
// Load the project file
TSharedPtr<FJsonObject> ProjectFile = LoadProjectFile(ProjectFileName);
if(!ProjectFile.IsValid())
{
return false;
}
// Try to read the identifier from it
TSharedPtr<FJsonValue> Value = ProjectFile->TryGetField(TEXT("EngineAssociation"));
if(Value.IsValid() && Value->Type == EJson::String)
{
OutIdentifier = Value->AsString();
if(OutIdentifier.Len() > 0)
{
// If it's a path, convert it into an engine identifier
if(OutIdentifier.Contains(TEXT("/")) || OutIdentifier.Contains("\\"))
{
FString EngineRootDir = FPaths::ConvertRelativePathToFull(FPaths::GetPath(ProjectFileName), OutIdentifier);
if(!GetEngineIdentifierFromRootDir(EngineRootDir, OutIdentifier))
{
return false;
}
}
return true;
}
}
// Otherwise scan up through the directory hierarchy to find an installation
FString ParentDir = FPaths::GetPath(ProjectFileName);
FPaths::NormalizeDirectoryName(ParentDir);
// Keep going until we reach the root
int32 SeparatorIdx;
while(ParentDir.FindLastChar(TEXT('/'), SeparatorIdx))
{
ParentDir.RemoveAt(SeparatorIdx, ParentDir.Len() - SeparatorIdx);
if(IsValidRootDirectory(ParentDir) && GetEngineIdentifierFromRootDir(ParentDir, OutIdentifier))
{
return true;
}
}
// Otherwise check the engine version string for 4.0, in case this project existed before the engine association stuff went in
FString EngineVersionString;
if(ProjectFile->TryGetStringField(TEXT("EngineVersion"), EngineVersionString) && EngineVersionString.Len() > 0)
{
FEngineVersion EngineVersion;
if(FEngineVersion::Parse(EngineVersionString, EngineVersion) && EngineVersion.HasChangelist() && EngineVersion.ToString(EVersionComponent::Minor) == TEXT("4.0"))
{
OutIdentifier = TEXT("4.0");
return true;
}
}
return false;
}
bool FDesktopPlatformBase::OpenProject(const FString& ProjectFileName)
{
FPlatformProcess::LaunchFileInDefaultExternalApplication(*ProjectFileName);
return true;
}
bool FDesktopPlatformBase::CleanGameProject(const FString& ProjectDir, FString& OutFailPath, FFeedbackContext* Warn)
{
// Begin a task
Warn->BeginSlowTask(LOCTEXT("CleaningProject", "Removing stale build products..."), true);
// Enumerate all the files
TArray<FString> FileNames;
TArray<FString> DirectoryNames;
GetProjectBuildProducts(ProjectDir, FileNames, DirectoryNames);
// Remove all the files
for(int32 Idx = 0; Idx < FileNames.Num(); Idx++)
{
// Remove the file
if(!IFileManager::Get().Delete(*FileNames[Idx]))
{
OutFailPath = FileNames[Idx];
Warn->EndSlowTask();
return false;
}
// Update the progress
Warn->UpdateProgress(Idx, FileNames.Num() + DirectoryNames.Num());
}
// Remove all the directories
for(int32 Idx = 0; Idx < DirectoryNames.Num(); Idx++)
{
// Remove the directory
if(!IFileManager::Get().DeleteDirectory(*DirectoryNames[Idx], false, true))
{
OutFailPath = DirectoryNames[Idx];
Warn->EndSlowTask();
return false;
}
// Update the progress
Warn->UpdateProgress(Idx + FileNames.Num(), FileNames.Num() + DirectoryNames.Num());
}
// End the task
Warn->EndSlowTask();
return true;
}
bool FDesktopPlatformBase::CompileGameProject(const FString& RootDir, const FString& ProjectFileName, FFeedbackContext* Warn, ECompilationResult::Type* OutResult)
{
FModuleManager& ModuleManager = FModuleManager::Get();
// Build the argument list
FString Arguments = FString::Printf(TEXT("%s %s"), ModuleManager.GetUBTConfiguration(), FPlatformMisc::GetUBTPlatform());
// Append the project name if it's a foreign project. Otherwise compile UnrealEditor.
if ( ProjectFileName.IsEmpty() )
{
Arguments = TEXT("UnrealEditor ") + Arguments;
}
else
{
Arguments += FString::Printf(TEXT(" -Project=\"%s\" -TargetType=Editor"), *IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ProjectFileName));
}
// Append any other options
Arguments += " -Progress -NoEngineChanges -NoHotReloadFromIDE";
// Run UBT
int32 ExitCode;
bool bResult = static_cast<IDesktopPlatform*>(this)->RunUnrealBuildTool(LOCTEXT("CompilingProject", "Compiling project..."), RootDir, Arguments, Warn, ExitCode);
if (OutResult != nullptr)
{
*OutResult = (ECompilationResult::Type)ExitCode;
}
// Reset module paths in case they have changed during compilation
ModuleManager.ResetModulePathsCache();
return bResult;
}
bool FDesktopPlatformBase::GenerateProjectFiles(const FString& RootDir, const FString& ProjectFileName, FFeedbackContext* Warn, FString LogFilePath)
{
FString Arguments = TEXT(" -projectfiles");
// Build the arguments to pass to UBT. If it's a non-foreign project, just build full project files.
if ( !ProjectFileName.IsEmpty() && GetCachedProjectDictionary(RootDir).IsForeignProject(ProjectFileName) )
{
// Figure out whether it's a foreign project
const FUProjectDictionary &ProjectDictionary = GetCachedProjectDictionary(RootDir);
if(ProjectDictionary.IsForeignProject(ProjectFileName))
{
Arguments += FString::Printf(TEXT(" -project=\"%s\""), *IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ProjectFileName));
// Always include game source
Arguments += TEXT(" -game");
// Determine whether or not to include engine source
if(IsSourceDistribution(RootDir))
{
Arguments += TEXT(" -engine");
}
else
{
// If this is used within UnrealVersionSelector then we still need to pass
// -rocket to deal with old versions that don't use Rocket.txt file
Arguments += TEXT(" -rocket");
}
}
}
Arguments += TEXT(" -progress");
if (!LogFilePath.IsEmpty())
{
Arguments += FString::Printf(TEXT(" -log=\"%s\""), *LogFilePath);
}
// Compile UnrealBuildTool if it doesn't exist. This can happen if we're just copying source from somewhere.
bool bRes = true;
Warn->BeginSlowTask(LOCTEXT("GeneratingProjectFiles", "Generating project files..."), true, true);
if(!FPaths::FileExists(GetUnrealBuildToolExecutableFilename(RootDir)))
{
Warn->StatusUpdate(0, 1, LOCTEXT("BuildingUBT", "Building UnrealBuildTool..."));
bRes = BuildUnrealBuildTool(RootDir, *Warn);
}
if(bRes)
{
Warn->StatusUpdate(0, 1, LOCTEXT("GeneratingProjectFiles", "Generating project files..."));
bRes = RunUnrealBuildTool(LOCTEXT("GeneratingProjectFiles", "Generating project files..."), RootDir, Arguments, Warn);
}
Warn->EndSlowTask();
return bRes;
}
bool FDesktopPlatformBase::IsUnrealBuildToolAvailable()
{
// If using installed build and the unreal build tool executable exists, then UBT is available. Otherwise check it can be built.
if (FApp::IsEngineInstalled())
{
return FPaths::FileExists(GetUnrealBuildToolExecutableFilename(FPaths::RootDir()));
}
else
{
return FPaths::FileExists(GetUnrealBuildToolProjectFileName(FPaths::RootDir()));
}
}
bool FDesktopPlatformBase::InvokeUnrealBuildToolSync(const FString& InCmdLineParams, FOutputDevice &Ar, bool bSkipBuildUBT, int32& OutReturnCode, FString& OutProcOutput)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FDesktopPlatformBase::InvokeUnrealBuildToolSync);
void* PipeRead = nullptr;
void* PipeWrite = nullptr;
verify(FPlatformProcess::CreatePipe(PipeRead, PipeWrite));
bool bInvoked = false;
FProcHandle ProcHandle = InvokeUnrealBuildToolAsync(InCmdLineParams, Ar, PipeRead, PipeWrite, bSkipBuildUBT);
if (ProcHandle.IsValid())
{
// rather than waiting, we must flush the read pipe or UBT will stall if it writes out a ton of text to the console.
while (FPlatformProcess::IsProcRunning(ProcHandle))
{
OutProcOutput += FPlatformProcess::ReadPipe(PipeRead);
FPlatformProcess::Sleep(0.1f);
}
bInvoked = true;
bool bGotReturnCode = FPlatformProcess::GetProcReturnCode(ProcHandle, &OutReturnCode);
check(bGotReturnCode);
}
else
{
bInvoked = false;
OutReturnCode = -1;
OutProcOutput = TEXT("");
}
FPlatformProcess::ClosePipe(PipeRead, PipeWrite);
return bInvoked;
}
FProcHandle FDesktopPlatformBase::InvokeUnrealBuildToolAsync(const FString& InCmdLineParams, FOutputDevice &Ar, void*& OutReadPipe, void*& OutWritePipe, bool bSkipBuildUBT)
{
FString CmdLineParams = InCmdLineParams;
// UnrealBuildTool is currently always located in the Binaries/DotNET folder
FString ExecutableFileName = GetUnrealBuildToolExecutableFilename(FPaths::RootDir());
// Installed builds never build UBT, UnrealBuildTool should already exist
bool bSkipBuild = FApp::IsEngineInstalled() || bSkipBuildUBT;
if (!bSkipBuild)
{
// When not using an installed build, we should attempt to build UBT to make sure it is up to date
// Only do this if we have not already successfully done it once during this session.
static bool bSuccessfullyBuiltUBTOnce = false;
if (!bSuccessfullyBuiltUBTOnce)
{
Ar.Log(TEXT("Building UnrealBuildTool..."));
if (BuildUnrealBuildTool(FPaths::RootDir(), Ar))
{
bSuccessfullyBuiltUBTOnce = true;
}
else
{
// Failed to build UBT
Ar.Log(TEXT("Failed to build UnrealBuildTool."));
return FProcHandle();
}
}
}
#if PLATFORM_LINUX
CmdLineParams += (" -progress");
#endif // PLATFORM_LINUX
Ar.Logf(TEXT("Launching UnrealBuildTool... [%s %s]"), *ExecutableFileName, *CmdLineParams);
// Run UnrealBuildTool
const bool bLaunchDetached = false;
const bool bLaunchHidden = true;
const bool bLaunchReallyHidden = bLaunchHidden;
FProcHandle ProcHandle = FPlatformProcess::CreateProc(*ExecutableFileName, *CmdLineParams, bLaunchDetached, bLaunchHidden, bLaunchReallyHidden, NULL, 0, NULL, OutWritePipe);
if (!ProcHandle.IsValid())
{
Ar.Logf(TEXT("Failed to launch Unreal Build Tool. (%s)"), *ExecutableFileName);
}
return ProcHandle;
}
bool FDesktopPlatformBase::RunUnrealBuildTool(const FText& Description, const FString& RootDir, const FString& Arguments, FFeedbackContext* Warn)
{
int32 ExitCode;
return static_cast<IDesktopPlatform*>(this)->RunUnrealBuildTool(Description, RootDir, Arguments, Warn, ExitCode);
}
struct FTargetFileVisitor : IPlatformFile::FDirectoryStatVisitor
{
TSet<FString>& RemainingTargetNames;
FDateTime MaxDateTime;
TArray<FString> SubDirectories;
bool bSearchSubDirectories;
FTargetFileVisitor(TSet<FString>& InRemainingTargetNames, FDateTime InMaxDateTime)
: RemainingTargetNames(InRemainingTargetNames)
, MaxDateTime(InMaxDateTime)
, bSearchSubDirectories(false)
{
}
virtual bool Visit(const TCHAR* FileNameOrDirectory, const FFileStatData& StatData) override
{
if (StatData.bIsDirectory)
{
SubDirectories.Add(FileNameOrDirectory);
return true;
}
int32 Length = FCString::Strlen(FileNameOrDirectory);
static const TCHAR TargetExt[] = TEXT(".Target.cs");
static const int32 TargetExtLen = UE_ARRAY_COUNT(TargetExt) - 1;
if (Length > TargetExtLen && FCString::Stricmp(FileNameOrDirectory + Length - TargetExtLen, TargetExt) == 0)
{
FString TargetName = FPaths::GetCleanFilename(FString(Length - TargetExtLen, FileNameOrDirectory));
return (StatData.ModificationTime < MaxDateTime && RemainingTargetNames.Remove(TargetName) == 1);
}
static const TCHAR ModuleExt[] = TEXT(".Build.cs");
static const int32 ModuleExtLen = UE_ARRAY_COUNT(ModuleExt) - 1;
if (Length > ModuleExtLen && FCString::Stricmp(FileNameOrDirectory + Length - ModuleExtLen, ModuleExt) == 0)
{
bSearchSubDirectories = false;
return true;
}
return true;
}
};
bool IsTargetInfoValid(const TArray<FTargetInfo>& Targets, const FString& SourceDir, const FDateTime& LastModifiedTime)
{
if (FApp::GetEngineIsPromotedBuild())
{
// Promoted builds may not have source code, so we will assume all supplied targets are valid since they will not appear on disk
return true;
}
// Create the state
TSet<FString> RemainingTargetNames;
for (const FTargetInfo& Target : Targets)
{
RemainingTargetNames.Add(Target.Name);
}
// Loop through all the directories
TArray<FString> DirectoryNames = { SourceDir };
for(int Idx = 0; Idx < DirectoryNames.Num(); Idx++)
{
FTargetFileVisitor Visitor(RemainingTargetNames, LastModifiedTime);
if(!IFileManager::Get().IterateDirectoryStat(*DirectoryNames[Idx], Visitor))
{
return false;
}
if(Visitor.bSearchSubDirectories)
{
DirectoryNames += Visitor.SubDirectories;
}
}
// If we found all the previous target files
return RemainingTargetNames.Num() == 0;
}
const TArray<FTargetInfo>& FDesktopPlatformBase::GetTargetsForProject(const FString& ProjectFile) const
{
// Normalize the project filename
FString NormalizedProjectFile = ProjectFile;
FPaths::NormalizeFilename(NormalizedProjectFile);
// Check if there's already a cached entry for this project
const TArray<FTargetInfo>* Targets = ProjectFileToTargets.Find(NormalizedProjectFile);
if (Targets != nullptr)
{
return *Targets;
}
// Get the base directory for the project (or the engine directory)
FString ProjectDir;
if(ProjectFile.Len() == 0)
{
ProjectDir = FPaths::EngineDir();
}
else
{
ProjectDir = FPaths::GetPath(ProjectFile);
}
FString ProjectSourceDir = ProjectDir / TEXT("Source");
// Get the path to the info filename
FString InfoFileName = ProjectDir / TEXT("Intermediate/TargetInfo.json");
// Check if the file already exists
FFileStatData StatData = IFileManager::Get().GetStatData(*InfoFileName);
if(StatData.bIsValid)
{
// Read it in and check it's still valid
TArray<FTargetInfo> NewTargets;
if(ReadTargetInfo(InfoFileName, NewTargets) && IsTargetInfoValid(NewTargets, ProjectSourceDir, StatData.ModificationTime))
{
return ProjectFileToTargets.Emplace(MoveTemp(NormalizedProjectFile), MoveTemp(NewTargets));
}
}
// Get the project source directory. If it doesn't exist, there are no targets for this project.
if (!IFileManager::Get().DirectoryExists(*ProjectSourceDir))
{
return ProjectFileToTargets.Add(NormalizedProjectFile, TArray<FTargetInfo>());
}
// Otherwise, we'll have to run UBT to update it
FString Arguments = TEXT("-Mode=QueryTargets");
if(ProjectFile.Len() > 0)
{
Arguments += FString::Printf(TEXT(" -Project=\"%s\""), *IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ProjectFile));
}
Arguments += FString::Printf(TEXT(" -Output=\"%s\""), *IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*InfoFileName));
// Run UBT to update the list of targets. Try to run it without building first.
FString Output;
int32 ReturnCode = 0;
if (!FPaths::FileExists(*GetUnrealBuildToolExecutableFilename(FPaths::RootDir())) || !const_cast<FDesktopPlatformBase*>(this)->InvokeUnrealBuildToolSync(Arguments, *GLog, true, ReturnCode, Output) || ReturnCode != 0)
{
// If that failed, try to build and run again. Build machines should always have an up-to-date copy, and shouldn't build anything without being told to do so.
if (!GIsBuildMachine)
{
const_cast<FDesktopPlatformBase*>(this)->InvokeUnrealBuildToolSync(Arguments, *GLog, false, ReturnCode, Output);
}
}
// Try to read the new targets
TArray<FTargetInfo> NewTargets;
if(!ReadTargetInfo(InfoFileName, NewTargets))
{
UE_LOG(LogDesktopPlatform, Warning, TEXT("Unable to read target info for %s"), (ProjectFile.Len() == 0)? TEXT("engine") : *ProjectFile);
}
// Add it to the cache
return ProjectFileToTargets.Emplace(MoveTemp(NormalizedProjectFile), MoveTemp(NewTargets));
}
const TArray<FTargetInfo>& FDesktopPlatformBase::GetTargetsForCurrentProject() const
{
return GetTargetsForProject(FPaths::GetProjectFilePath());
}
bool FDesktopPlatformBase::GetSolutionPath(FString& OutSolutionPath)
{
// Get the platform-specific suffix for solution files
#if PLATFORM_MAC
const TCHAR* Suffix = TEXT(".xcworkspace/contents.xcworkspacedata");
#elif PLATFORM_LINUX
const TCHAR* Suffix = TEXT(".workspace"); // FIXME: Should depend on PreferredAccessor setting
#else
const TCHAR* Suffix = TEXT(".sln");
#endif
// When using game specific uproject files, the solution is named after the game and in the uproject folder
if(FPaths::IsProjectFilePathSet())
{
FString SolutionPath = FPaths::ProjectDir() / FPaths::GetBaseFilename(FPaths::GetProjectFilePath()) + Suffix;
if(FPaths::FileExists(SolutionPath))
{
OutSolutionPath = SolutionPath;
return true;
}
}
// Otherwise, it is simply titled UE4.sln
FString DefaultSolutionPath = FPaths::RootDir() / FString(TEXT("UE4")) + Suffix;
if(FPaths::FileExists(DefaultSolutionPath))
{
OutSolutionPath = DefaultSolutionPath;
return true;
}
return false;
}
FString FDesktopPlatformBase::GetDefaultProjectCreationPath()
{
// My Documents
const FString DefaultProjectSubFolder = TEXT("Unreal Projects");
return FString(FPlatformProcess::UserDir()) + DefaultProjectSubFolder;
}
void FDesktopPlatformBase::ReadLauncherInstallationList()
{
FString InstalledListFile = FString(FPlatformProcess::ApplicationSettingsDir()) / TEXT("UnrealEngineLauncher/LauncherInstalled.dat");
// If the file does not exist, manually check for the 4.0 or 4.1 manifest
FDateTime NewListTimestamp = IFileManager::Get().GetTimeStamp(*InstalledListFile);
if(NewListTimestamp == FDateTime::MinValue())
{
if(LauncherInstallationList.Num() == 0)
{
CheckForLauncherEngineInstallation(TEXT("40003"), TEXT("UE_4.0"), LauncherInstallationList);
CheckForLauncherEngineInstallation(TEXT("1040003"), TEXT("UE_4.1"), LauncherInstallationList);
}
}
else if(NewListTimestamp != LauncherInstallationTimestamp)
{
// Read the installation manifest
FString InstalledText;
if (FFileHelper::LoadFileToString(InstalledText, *InstalledListFile))
{
// Deserialize the object
TSharedPtr< FJsonObject > RootObject;
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(InstalledText);
if (FJsonSerializer::Deserialize(Reader, RootObject) && RootObject.IsValid())
{
// Parse the list of installations
TArray< TSharedPtr<FJsonValue> > InstallationList = RootObject->GetArrayField(TEXT("InstallationList"));
for(int32 Idx = 0; Idx < InstallationList.Num(); Idx++)
{
TSharedPtr<FJsonObject> InstallationItem = InstallationList[Idx]->AsObject();
FString AppName = InstallationItem->GetStringField(TEXT("AppName"));
FString InstallLocation = InstallationItem->GetStringField(TEXT("InstallLocation"));
if(AppName.Len() > 0 && InstallLocation.Len() > 0)
{
FPaths::NormalizeDirectoryName(InstallLocation);
LauncherInstallationList.Add(AppName, InstallLocation);
}
}
}
LauncherInstallationTimestamp = NewListTimestamp;
}
}
}
void FDesktopPlatformBase::CheckForLauncherEngineInstallation(const FString &AppId, const FString &Identifier, TMap<FString, FString> &OutInstallations)
{
FString ManifestText;
FString ManifestFileName = FString(FPlatformProcess::ApplicationSettingsDir()) / FString::Printf(TEXT("UnrealEngineLauncher/Data/Manifests/%s.manifest"), *AppId);
if (FFileHelper::LoadFileToString(ManifestText, *ManifestFileName))
{
TSharedPtr< FJsonObject > RootObject;
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(ManifestText);
if (FJsonSerializer::Deserialize(Reader, RootObject) && RootObject.IsValid())
{
TSharedPtr<FJsonObject> CustomFieldsObject = RootObject->GetObjectField(TEXT("CustomFields"));
if (CustomFieldsObject.IsValid())
{
FString InstallLocation = CustomFieldsObject->GetStringField("InstallLocation");
if (InstallLocation.Len() > 0)
{
OutInstallations.Add(Identifier, InstallLocation);
}
}
}
}
}
int32 FDesktopPlatformBase::ParseReleaseVersion(const FString &Version)
{
TCHAR *End;
uint64 Major = FCString::Strtoui64(*Version, &End, 10);
if (Major >= MAX_int16 || *(End++) != '.')
{
return INDEX_NONE;
}
uint64 Minor = FCString::Strtoui64(End, &End, 10);
if (Minor >= MAX_int16 || *End != 0)
{
return INDEX_NONE;
}
return (Major << 16) + Minor;
}
TSharedPtr<FJsonObject> FDesktopPlatformBase::LoadProjectFile(const FString &FileName)
{
FString FileContents;
if (!FFileHelper::LoadFileToString(FileContents, *FileName))
{
return TSharedPtr<FJsonObject>(NULL);
}
TSharedPtr< FJsonObject > JsonObject;
TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(FileContents);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
return TSharedPtr<FJsonObject>(NULL);
}
return JsonObject;
}
bool FDesktopPlatformBase::SaveProjectFile(const FString &FileName, TSharedPtr<FJsonObject> Object)
{
FString FileContents;
TSharedRef< TJsonWriter<> > Writer = TJsonWriterFactory<>::Create(&FileContents);
if (!FJsonSerializer::Serialize(Object.ToSharedRef(), Writer))
{
return false;
}
if (!FFileHelper::SaveStringToFile(FileContents, *FileName))
{
return false;
}
return true;
}
const FUProjectDictionary &FDesktopPlatformBase::GetCachedProjectDictionary(const FString& RootDir)
{
FString NormalizedRootDir = RootDir;
FPaths::NormalizeDirectoryName(NormalizedRootDir);
FUProjectDictionary *Dictionary = CachedProjectDictionaries.Find(NormalizedRootDir);
if(Dictionary == NULL)
{
Dictionary = &CachedProjectDictionaries.Add(RootDir, FUProjectDictionary(RootDir));
}
return *Dictionary;
}
void FDesktopPlatformBase::GetProjectBuildProducts(const FString& ProjectDir, TArray<FString> &OutFileNames, TArray<FString> &OutDirectoryNames)
{
FString NormalizedProjectDir = ProjectDir;
FPaths::NormalizeDirectoryName(NormalizedProjectDir);
// Find all the build roots
TArray<FString> BuildRootDirectories;
BuildRootDirectories.Add(NormalizedProjectDir);
// Add all the plugin directories
TArray<FString> PluginFileNames;
IFileManager::Get().FindFilesRecursive(PluginFileNames, *(NormalizedProjectDir / TEXT("Plugins")), TEXT("*.uplugin"), true, false);
for(int32 Idx = 0; Idx < PluginFileNames.Num(); Idx++)
{
BuildRootDirectories.Add(FPaths::GetPath(PluginFileNames[Idx]));
}
// Add all the intermediate directories
for(int32 Idx = 0; Idx < BuildRootDirectories.Num(); Idx++)
{
OutDirectoryNames.Add(BuildRootDirectories[Idx] / TEXT("Intermediate"));
}
// Add the files in the cleaned directories to the output list
for(int32 Idx = 0; Idx < OutDirectoryNames.Num(); Idx++)
{
IFileManager::Get().FindFilesRecursive(OutFileNames, *OutDirectoryNames[Idx], TEXT("*"), true, false, false);
}
}
FString FDesktopPlatformBase::GetEngineSavedConfigDirectory(const FString& Identifier)
{
// Get the engine root directory
FString RootDir;
if (!GetEngineRootDirFromIdentifier(Identifier, RootDir))
{
return FString();
}
// Get the path to the game agnostic settings
FString UserDir;
if (IsStockEngineRelease(Identifier))
{
UserDir = FPaths::Combine(FPlatformProcess::UserSettingsDir(), *FApp::GetEpicProductIdentifier(), *Identifier);
}
else
{
UserDir = FPaths::Combine(*RootDir, TEXT("Engine"));
}
// Get the game agnostic config dir
return UserDir / TEXT("Saved/Config") / ANSI_TO_TCHAR(FPlatformProperties::PlatformName());
}
bool FDesktopPlatformBase::EnumerateProjectsKnownByEngine(const FString &Identifier, bool bIncludeNativeProjects, TArray<FString> &OutProjectFileNames)
{
// Get the engine root directory
FString RootDir;
if (!GetEngineRootDirFromIdentifier(Identifier, RootDir))
{
return false;
}
FString GameAgnosticConfigDir = GetEngineSavedConfigDirectory(Identifier);
if (GameAgnosticConfigDir.Len() == 0)
{
return false;
}
// Find all the created project directories. Start with the default project creation path.
TArray<FString> SearchDirectories;
SearchDirectories.AddUnique(GetDefaultProjectCreationPath());
// Load the config file
FConfigFile GameAgnosticConfig;
FConfigCacheIni::LoadExternalIniFile(GameAgnosticConfig, TEXT("EditorSettings"), NULL, *GameAgnosticConfigDir, false);
// Find the editor game-agnostic settings
FConfigSection* Section = GameAgnosticConfig.Find(TEXT("/Script/UnrealEd.EditorSettings"));
if (Section == NULL)
{
FConfigCacheIni::LoadExternalIniFile(GameAgnosticConfig, TEXT("EditorGameAgnostic"), NULL, *GameAgnosticConfigDir, false);
Section = GameAgnosticConfig.Find(TEXT("/Script/UnrealEd.EditorGameAgnosticSettings"));
}
if(Section != NULL)
{
// Add in every path that the user has ever created a project file. This is to catch new projects showing up in the user's project folders
TArray<FString> AdditionalDirectories;
Section->MultiFind(TEXT("CreatedProjectPaths"), AdditionalDirectories);
for(int Idx = 0; Idx < AdditionalDirectories.Num(); Idx++)
{
FPaths::NormalizeDirectoryName(AdditionalDirectories[Idx]);
SearchDirectories.AddUnique(AdditionalDirectories[Idx]);
}
// Also add in all the recently opened projects
TArray<FString> RecentlyOpenedFiles;
Section->MultiFind(TEXT("RecentlyOpenedProjectFiles"), RecentlyOpenedFiles);
for(int Idx = 0; Idx < RecentlyOpenedFiles.Num(); Idx++)
{
FPaths::NormalizeFilename(RecentlyOpenedFiles[Idx]);
OutProjectFileNames.AddUnique(RecentlyOpenedFiles[Idx]);
}
}
// Find all the other projects that are in the search directories
for(int Idx = 0; Idx < SearchDirectories.Num(); Idx++)
{
TArray<FString> ProjectFolders;
IFileManager::Get().FindFiles(ProjectFolders, *(SearchDirectories[Idx] / TEXT("*")), false, true);
for(int32 FolderIdx = 0; FolderIdx < ProjectFolders.Num(); FolderIdx++)
{
TArray<FString> ProjectFiles;
IFileManager::Get().FindFiles(ProjectFiles, *(SearchDirectories[Idx] / ProjectFolders[FolderIdx] / TEXT("*.uproject")), true, false);
for(int32 FileIdx = 0; FileIdx < ProjectFiles.Num(); FileIdx++)
{
OutProjectFileNames.AddUnique(SearchDirectories[Idx] / ProjectFolders[FolderIdx] / ProjectFiles[FileIdx]);
}
}
}
// Find all the native projects, and either add or remove them from the list depending on whether we want native projects
const FUProjectDictionary &Dictionary = GetCachedProjectDictionary(RootDir);
if(bIncludeNativeProjects)
{
TArray<FString> NativeProjectPaths = Dictionary.GetProjectPaths();
for(int Idx = 0; Idx < NativeProjectPaths.Num(); Idx++)
{
if(!NativeProjectPaths[Idx].Contains(TEXT("/Templates/")))
{
OutProjectFileNames.AddUnique(NativeProjectPaths[Idx]);
}
}
}
else
{
TArray<FString> NativeProjectPaths = Dictionary.GetProjectPaths();
for(int Idx = 0; Idx < NativeProjectPaths.Num(); Idx++)
{
OutProjectFileNames.Remove(NativeProjectPaths[Idx]);
}
}
return true;
}
#if PLATFORM_WINDOWS
#include "Windows/WindowsHWrapper.h"
#include "Windows/AllowWindowsPlatformTypes.h"
#include <ShlObj.h>
#include "Windows/HideWindowsPlatformTypes.h"
static bool TryReadMsBuildInstallPath(HKEY RootKey, const TCHAR* KeyName, const TCHAR* ValueName, const TCHAR* MsBuildRelativePath, FString& OutMsBuildPath)
{
FString Value;
if (!FWindowsPlatformMisc::QueryRegKey(RootKey, KeyName, ValueName, Value))
{
return false;
}
FString Result = Value / MsBuildRelativePath;
if (!FPaths::FileExists(Result))
{
return false;
}
OutMsBuildPath = Result;
return true;
}
static bool TryReadMsBuildInstallPath(const TCHAR* KeyRelativeName, const TCHAR* ValueName, const TCHAR* MsBuildRelativePath, FString& OutMsBuildPath)
{
if (TryReadMsBuildInstallPath(HKEY_CURRENT_USER, *(FString("SOFTWARE\\") + KeyRelativeName), ValueName, MsBuildRelativePath, OutMsBuildPath))
{
return true;
}
if (TryReadMsBuildInstallPath(HKEY_LOCAL_MACHINE, *(FString("SOFTWARE\\") + KeyRelativeName), ValueName, MsBuildRelativePath, OutMsBuildPath))
{
return true;
}
if (TryReadMsBuildInstallPath(HKEY_CURRENT_USER, *(FString("SOFTWARE\\Wow6432Node\\") + KeyRelativeName), ValueName, MsBuildRelativePath, OutMsBuildPath))
{
return true;
}
if (TryReadMsBuildInstallPath(HKEY_LOCAL_MACHINE, *(FString("SOFTWARE\\Wow6432Node\\") + KeyRelativeName), ValueName, MsBuildRelativePath, OutMsBuildPath))
{
return true;
}
return false;
}
static bool TryReadMsBuildInstallPath(FString& OutPath)
{
// Try to get the MSBuild 14.0 path directly (see https://msdn.microsoft.com/en-us/library/hh162058(v=vs.120).aspx)
TCHAR ProgramFilesX86[MAX_PATH];
if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES | CSIDL_FLAG_CREATE, NULL, SHGFP_TYPE_CURRENT, ProgramFilesX86)))
{
FString ToolPath = FString(ProgramFilesX86) / TEXT("MSBuild/14.0/bin/MSBuild.exe");
if (FPaths::FileExists(ToolPath))
{
OutPath = ToolPath;
return true;
}
}
// Try to get the MSBuild 14.0 path from the registry
if (TryReadMsBuildInstallPath(TEXT("Microsoft\\MSBuild\\ToolsVersions\\14.0"), TEXT("MSBuildToolsPath"), TEXT("MSBuild.exe"), OutPath))
{
return true;
}
// Check for MSBuild 15. This is installed alongside Visual Studio 2017, so we get the path relative to that.
if (TryReadMsBuildInstallPath(TEXT("Microsoft\\VisualStudio\\SxS\\VS7"), TEXT("15.0"), TEXT("MSBuild\\15.0\\bin\\MSBuild.exe"), OutPath))
{
return true;
}
// Check for older versions of MSBuild. These are registered as separate versions in the registry.
if (TryReadMsBuildInstallPath(TEXT("Microsoft\\MSBuild\\ToolsVersions\\12.0"), TEXT("MSBuildToolsPath"), TEXT("MSBuild.exe"), OutPath))
{
return true;
}
if (TryReadMsBuildInstallPath(TEXT("Microsoft\\MSBuild\\ToolsVersions\\4.0"), TEXT("MSBuildToolsPath"), TEXT("MSBuild.exe"), OutPath))
{
return true;
}
return false;
}
#endif
bool FDesktopPlatformBase::BuildUnrealBuildTool(const FString& RootDir, FOutputDevice& Ar)
{
Ar.Logf(TEXT("Building UnrealBuildTool in %s..."), *RootDir);
// Check the project file exists
FString CsProjLocation = GetUnrealBuildToolProjectFileName(RootDir);
if (!FPaths::FileExists(CsProjLocation))
{
Ar.Logf(TEXT("Project file not found at %s"), *CsProjLocation);
return false;
}
FString CompilerExecutableFilename;
FString CmdLineParams;
if (PLATFORM_WINDOWS)
{
#if PLATFORM_WINDOWS
if (!TryReadMsBuildInstallPath(CompilerExecutableFilename))
{
Ar.Logf(TEXT("Couldn't find MSBuild installation; skipping."));
return false;
}
#endif
CmdLineParams = FString::Printf(TEXT("/nologo /verbosity:quiet \"%s\" /property:Configuration=Development /property:Platform=AnyCPU"), *CsProjLocation);
}
else if (PLATFORM_MAC)
{
FString ScriptPath = FPaths::ConvertRelativePathToFull(RootDir / TEXT("Engine/Build/BatchFiles/Mac/RunXBuild.sh"));
CompilerExecutableFilename = TEXT("/bin/sh");
CmdLineParams = FString::Printf(TEXT("\"%s\" /property:Configuration=Development %s"), *ScriptPath, *CsProjLocation);
}
else if (PLATFORM_LINUX)
{
FString ScriptPath = FPaths::ConvertRelativePathToFull(RootDir / TEXT("Engine/Build/BatchFiles/Linux/RunXBuild.sh"));
CompilerExecutableFilename = TEXT("/bin/bash");
CmdLineParams = FString::Printf(TEXT("\"%s\" /property:Configuration=Development %s"), *ScriptPath, *CsProjLocation);
}
else
{
Ar.Log(TEXT("Unknown platform, unable to build UnrealBuildTool."));
return false;
}
// Spawn the compiler
Ar.Logf(TEXT("Running: %s %s"), *CompilerExecutableFilename, *CmdLineParams);
const bool bLaunchDetached = false;
const bool bLaunchHidden = true;
const bool bLaunchReallyHidden = bLaunchHidden;
FProcHandle ProcHandle = FPlatformProcess::CreateProc(*CompilerExecutableFilename, *CmdLineParams, bLaunchDetached, bLaunchHidden, bLaunchReallyHidden, NULL, 0, NULL, NULL);
if (!ProcHandle.IsValid())
{
Ar.Log(TEXT("Failed to start process."));
return false;
}
FPlatformProcess::WaitForProc(ProcHandle);
FPlatformProcess::CloseProc(ProcHandle);
// If the executable appeared where we expect it, then we were successful
FString UnrealBuildToolExePath = GetUnrealBuildToolExecutableFilename(RootDir);
if (!FPaths::FileExists(UnrealBuildToolExePath))
{
Ar.Logf(TEXT("Missing %s after build"), *UnrealBuildToolExePath);
return false;
}
return true;
}
bool FDesktopPlatformBase::ReadTargetInfo(const FString& FileName, TArray<FTargetInfo>& Targets)
{
// Read the file to a string
FString Contents;
if (!FFileHelper::LoadFileToString(Contents, *FileName))
{
return false;
}
// Deserialize a JSON object from the string
TSharedPtr<FJsonObject> ObjectPtr;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Contents);
if (!FJsonSerializer::Deserialize(Reader, ObjectPtr) || !ObjectPtr.IsValid())
{
return false;
}
// Get the targets array
const TArray<TSharedPtr<FJsonValue>>* TargetArray;
if (!ObjectPtr->TryGetArrayField(TEXT("Targets"), TargetArray))
{
return false;
}
// Presize the output array
Targets.SetNum(TargetArray->Num());
// Parse the entries
for (int Idx = 0; Idx < TargetArray->Num(); Idx++)
{
const FJsonValue& TargetValue = *(*TargetArray)[Idx].Get();
if (TargetValue.Type != EJson::Object)
{
return false;
}
const FJsonObject& TargetObject = *TargetValue.AsObject();
if(!TargetObject.TryGetStringField(TEXT("Name"), Targets[Idx].Name) || !TargetObject.TryGetStringField(TEXT("Path"), Targets[Idx].Path))
{
return false;
}
FString Type;
if (!TargetObject.TryGetStringField(TEXT("Type"), Type))
{
return false;
}
if(!LexTryParseString(Targets[Idx].Type, *Type) || Targets[Idx].Type == EBuildTargetType::Unknown)
{
return false;
}
}
return true;
}
FString FDesktopPlatformBase::GetUnrealBuildToolProjectFileName(const FString& RootDir) const
{
return FPaths::ConvertRelativePathToFull(RootDir / TEXT("Engine/Source/Programs/UnrealBuildTool/UnrealBuildTool.csproj"));
}
FString FDesktopPlatformBase::GetUnrealBuildToolExecutableFilename(const FString& RootDir) const
{
FConfigFile Config;
if (FConfigCacheIni::LoadExternalIniFile(Config, TEXT("Engine"), *FPaths::Combine(RootDir, TEXT("Engine/Config/")), *FPaths::Combine(RootDir, TEXT("Engine/Config/")), true, NULL, false, /*bWriteDestIni*/ false))
{
FString Entry;
if( Config.GetString( TEXT("PlatformPaths"), TEXT("UnrealBuildTool"), Entry ))
{
FString NewPath = FPaths::ConvertRelativePathToFull(RootDir / Entry);
return NewPath;
}
}
return FPaths::ConvertRelativePathToFull(RootDir / TEXT("Engine/Binaries/DotNET/UnrealBuildTool.exe"));
}
#undef LOCTEXT_NAMESPACE