// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "DesktopPlatformPrivatePCH.h" #include "DesktopPlatformBase.h" #include "UProjectInfo.h" #include "EngineVersion.h" #include "ModuleManager.h" #define LOCTEXT_NAMESPACE "DesktopPlatform" FDesktopPlatformBase::FDesktopPlatformBase() { LauncherInstallationTimestamp = FDateTime::MinValue(); } FString FDesktopPlatformBase::GetCurrentEngineIdentifier() { if(CurrentEngineIdentifier.Len() == 0 && !GetEngineIdentifierFromRootDir(FPlatformMisc::RootDir(), CurrentEngineIdentifier)) { CurrentEngineIdentifier.Empty(); } return CurrentEngineIdentifier; } void FDesktopPlatformBase::EnumerateLauncherEngineInstallations(TMap &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::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 &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::TConstIterator Iter(LauncherInstallationList); Iter; ++Iter) { FString AppName = Iter.Key(); if(!AppName.StartsWith(TEXT("UE_"), ESearchCase::CaseSensitive)) { OutInstallations.Add(Iter.Value()); } } } bool FDesktopPlatformBase::GetEngineRootDirFromIdentifier(const FString &Identifier, FString &OutRootDir) { // Get all the installations TMap Installations; EnumerateEngineInstallations(Installations); // Find the one with the right identifier for (TMap::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 Installations; EnumerateEngineInstallations(Installations); // Normalize the root directory FString NormalizedRootDir = RootDir; FPaths::NormalizeDirectoryName(NormalizedRootDir); // Find the label for the given directory for (TMap::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 Installations; EnumerateEngineInstallations(Installations); bool bRes = false; if (Installations.Num() > 0) { // Default to the first install TMap::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::IsStockEngineRelease(const FString &Identifier) { return Identifier.Len() > 0 && FChar::IsDigit(Identifier[0]); } 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) { FString EngineBinariesDirName = RootDir / TEXT("Engine/Binaries"); FPaths::NormalizeDirectoryName(EngineBinariesDirName); return IFileManager::Get().DirectoryExists(*EngineBinariesDirName); } bool FDesktopPlatformBase::SetEngineIdentifierForProject(const FString &ProjectFileName, const FString &InIdentifier) { // Load the project file TSharedPtr 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) { // Load the project file TSharedPtr ProjectFile = LoadProjectFile(ProjectFileName); if(!ProjectFile.IsValid()) { return false; } // Read the identifier from it OutIdentifier = ProjectFile->GetStringField(TEXT("EngineAssociation")); if(OutIdentifier.Len() > 0) { 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 = ProjectFile->GetStringField(TEXT("EngineVersion")); if(EngineVersionString.Len() > 0) { FEngineVersion EngineVersion; if(FEngineVersion::Parse(EngineVersionString, EngineVersion) && EngineVersion.IsPromotedBuild() && EngineVersion.ToString(EVersionComponent::Minor) == TEXT("4.0")) { OutIdentifier = TEXT("4.0"); return true; } } return false; } bool FDesktopPlatformBase::CleanGameProject(const FString &ProjectDir, FFeedbackContext* Warn) { // Begin a task Warn->BeginSlowTask(LOCTEXT("CleaningProject", "Removing stale build products..."), true); // Enumerate all the files TArray FileNames; GetProjectBuildProducts(ProjectDir, FileNames); // Remove all the files for(int32 Idx = 0; Idx < FileNames.Num(); Idx++) { // Remove the file if(!IFileManager::Get().Delete(*FileNames[Idx])) { Warn->Logf(ELogVerbosity::Error, TEXT("ERROR: Couldn't delete file '%s'"), *FileNames[Idx]); return false; } // Try to remove an empty directory hierarchy FString DirName = FPaths::GetPath(FileNames[Idx]); for(;;) { TArray Contents; IFileManager::Get().FindFiles(Contents, *(DirName / TEXT("*")), true, true); if(Contents.Num() > 0) break; if(!IFileManager::Get().DeleteDirectory(*DirName)) break; int32 ParentIdx; if(!DirName.FindLastChar(TEXT('/'), ParentIdx)) break; DirName = DirName.Left(ParentIdx); } // Update the progress Warn->UpdateProgress(Idx, FileNames.Num()); } // End the task Warn->EndSlowTask(); return true; } bool FDesktopPlatformBase::CompileGameProject(const FString& RootDir, const FString& ProjectFileName, FFeedbackContext* Warn) { // Get the target name FString Arguments = FString::Printf(TEXT("%sEditor %s %s"), *FPaths::GetBaseFilename(ProjectFileName), FModuleManager::Get().GetUBTConfiguration(), FPlatformMisc::GetUBTPlatform()); // Append the project name if it's a foreign project if ( !ProjectFileName.IsEmpty() ) { FUProjectDictionary ProjectDictionary(RootDir); if(ProjectDictionary.IsForeignProject(ProjectFileName)) { Arguments += FString::Printf(TEXT(" -project=\"%s\""), *IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ProjectFileName)); } } // Append the Rocket flag if(!IsSourceDistribution(RootDir)) { Arguments += TEXT(" -rocket"); } // Append any other options Arguments += " -editorrecompile -progress"; // Run UBT return RunUnrealBuildTool(LOCTEXT("CompilingProject", "Compiling project..."), RootDir, Arguments, Warn); } bool FDesktopPlatformBase::GenerateProjectFiles(const FString& RootDir, const FString& ProjectFileName, FFeedbackContext* Warn) { #if PLATFORM_MAC FString Arguments = TEXT("-xcodeprojectfile"); #else FString Arguments = TEXT("-projectfiles"); #endif // Build the arguments to pass to UBT if ( !ProjectFileName.IsEmpty() ) { // 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 += " -game"; // Determine whether or not to include engine source if(IsSourceDistribution(RootDir)) { Arguments += " -engine"; } else { Arguments += " -rocket"; } } } Arguments += " -progress"; // Run UnrealBuildTool Warn->BeginSlowTask(LOCTEXT("GeneratingProjectFiles", "Generating project files..."), true, true); bool bRes = RunUnrealBuildTool(LOCTEXT("GeneratingProjectFiles", "Generating project files..."), RootDir, Arguments, Warn); Warn->EndSlowTask(); return bRes; } 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 > InstallationList = RootObject->GetArrayField(TEXT("InstallationList")); for(int32 Idx = 0; Idx < InstallationList.Num(); Idx++) { TSharedPtr 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 &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 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 FDesktopPlatformBase::LoadProjectFile(const FString &FileName) { FString FileContents; if (!FFileHelper::LoadFileToString(FileContents, *FileName)) { return TSharedPtr(NULL); } TSharedPtr< FJsonObject > JsonObject; TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(FileContents); if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid()) { return TSharedPtr(NULL); } return JsonObject; } bool FDesktopPlatformBase::SaveProjectFile(const FString &FileName, TSharedPtr 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 &OutFileNames) { FString NormalizedProjectDir = ProjectDir; FPaths::NormalizeDirectoryName(NormalizedProjectDir); // Find all the build roots TArray BuildRootDirectories; BuildRootDirectories.Add(NormalizedProjectDir); // Add all the plugin directories TArray 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])); } // Find all the target filenames TArray TargetNames; IFileManager::Get().FindFilesRecursive(TargetNames, *(NormalizedProjectDir / TEXT("Source")), TEXT("*.Target.cs"), true, false, false); for(int32 Idx = 0; Idx < TargetNames.Num(); Idx++) { TargetNames[Idx] = FPaths::GetCleanFilename(TargetNames[Idx]); TargetNames[Idx].RemoveFromEnd(TEXT(".Target.cs")); } TargetNames.Add(TEXT("UE4Editor")); // Add the binaries for all of the build roots for(int32 RootIdx = 0; RootIdx < BuildRootDirectories.Num(); RootIdx++) { const FString &BuildRootDirectory = BuildRootDirectories[RootIdx]; // Find all the binaries under this build root TArray Binaries; IFileManager::Get().FindFilesRecursive(Binaries, *(BuildRootDirectory / TEXT("Binaries")), TEXT("*"), true, false); // Remove all the binaries starting with a target name for(int32 BinaryIdx = 0; BinaryIdx < Binaries.Num(); BinaryIdx++) { FString BinaryName = FPaths::GetCleanFilename(Binaries[BinaryIdx]); for(int32 TargetIdx = 0; TargetIdx < TargetNames.Num(); TargetIdx++) { if(BinaryName.StartsWith(TargetNames[TargetIdx])) { const TCHAR *End = *BinaryName + TargetNames[TargetIdx].Len(); if(*End == 0 || *End == TEXT('.') || *End == TEXT('-')) { OutFileNames.Add(Binaries[BinaryIdx]); break; } } } } } // Add the intermediate folders for all of the build roots for(int32 RootIdx = 0; RootIdx < BuildRootDirectories.Num(); RootIdx++) { IFileManager::Get().FindFilesRecursive(OutFileNames, *(BuildRootDirectories[RootIdx] / TEXT("Intermediate")), TEXT("*"), true, false, false); } } #undef LOCTEXT_NAMESPACE