// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "ProjectsPrivatePCH.h" DEFINE_LOG_CATEGORY_STATIC( LogProjectManager, Log, All ); #define LOCTEXT_NAMESPACE "ProjectManager" FProject::FProject() { } FProject::FProject( const FProjectInfo& InitProjectInfo ) : ProjectInfo(InitProjectInfo) { } bool FProject::IsSignedSampleProject(const FString& FilePath) const { return ProjectInfo.EpicSampleNameHash == GetTypeHash(FPaths::GetCleanFilename(FilePath)); } void FProject::SignSampleProject(const FString& FilePath, const FString& Category) { ProjectInfo.EpicSampleNameHash = GetTypeHash(FPaths::GetCleanFilename(FilePath)); ProjectInfo.Category = Category; } void FProject::UpdateSupportedTargetPlatforms(const FName& InPlatformName, const bool bIsSupported) { if ( bIsSupported ) { ProjectInfo.TargetPlatforms.AddUnique(InPlatformName); } else { ProjectInfo.TargetPlatforms.Remove(InPlatformName); } } void FProject::ClearSupportedTargetPlatforms() { ProjectInfo.TargetPlatforms.Empty(); } bool FProject::PerformAdditionalDeserialization(const TSharedRef< FJsonObject >& FileObject) { ReadNumberFromJSON(FileObject, TEXT("EpicSampleNameHash"), ProjectInfo.EpicSampleNameHash); ProjectInfo.TargetPlatforms.Empty(); if ( FileObject->HasField(TEXT("TargetPlatforms")) ) { const TSharedPtr& TargetPlatformsValue = FileObject->GetField(TEXT("TargetPlatforms")); const TArray>& TargetPlatformsArray = TargetPlatformsValue->AsArray(); for ( const auto& TargetPlatformsEntry : TargetPlatformsArray ) { const FString PlatformName = TargetPlatformsEntry->AsString(); ProjectInfo.TargetPlatforms.Add(*PlatformName); } } return true; } void FProject::PerformAdditionalSerialization(const TSharedRef< TJsonWriter<> >& Writer) const { Writer->WriteValue(TEXT("EpicSampleNameHash"), FString::Printf(TEXT("%u"), ProjectInfo.EpicSampleNameHash)); if ( ProjectInfo.TargetPlatforms.Num() > 0 ) { Writer->WriteArrayStart(TEXT("TargetPlatforms")); for ( const FName& PlatformName : ProjectInfo.TargetPlatforms ) { Writer->WriteValue(PlatformName.ToString()); } Writer->WriteArrayEnd(); } } FProjectManager::FProjectManager() { } bool FProjectManager::LoadProjectFile( const FString& InProjectFile ) { FText FailureReason; TSharedRef NewProject = MakeShareable( new FProject() ); if ( NewProject->LoadFromFile(InProjectFile, FailureReason) ) { // Load successful. Set the loaded project file pointer. CurrentlyLoadedProject = NewProject; return true; } #if PLATFORM_IOS FString UpdatedMessage = FString::Printf(TEXT("%s\n%s"), *FailureReason.ToString(), TEXT("For troubleshooting, please go to https://docs.unrealengine.com/latest/INT/Platforms/iOS/GettingStarted/index.html")); FailureReason = FText::FromString(UpdatedMessage); #endif UE_LOG(LogProjectManager, Error, TEXT("%s"), *FailureReason.ToString()); FMessageDialog::Open(EAppMsgType::Ok, FailureReason); return false; } bool FProjectManager::LoadModulesForProject( const ELoadingPhase::Type LoadingPhase ) { DECLARE_SCOPE_CYCLE_COUNTER(TEXT("Loading Game Modules"), STAT_GameModule, STATGROUP_LoadTime); bool bSuccess = true; if ( CurrentlyLoadedProject.IsValid() ) { TMap ModuleLoadFailures; CurrentlyLoadedProject->LoadModules(LoadingPhase, ModuleLoadFailures); if ( ModuleLoadFailures.Num() > 0 ) { FText FailureMessage; for ( auto FailureIt = ModuleLoadFailures.CreateConstIterator(); FailureIt; ++FailureIt ) { const ELoadModuleFailureReason::Type FailureReason = FailureIt.Value(); if( FailureReason != ELoadModuleFailureReason::Success ) { const FText TextModuleName = FText::FromName(FailureIt.Key()); if ( FailureReason == ELoadModuleFailureReason::FileNotFound ) { FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleNotFound", "The game module '{0}' could not be found. Please ensure that this module exists and that it is compiled."), TextModuleName ); } else if ( FailureReason == ELoadModuleFailureReason::FileIncompatible ) { FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleIncompatible", "The game module '{0}' does not appear to be up to date. This may happen after updating the engine. Please recompile this module and try again."), TextModuleName ); } else if ( FailureReason == ELoadModuleFailureReason::FailedToInitialize ) { FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleFailedToInitialize", "The game module '{0}' could not be successfully initialized after it was loaded."), TextModuleName ); } else if ( FailureReason == ELoadModuleFailureReason::CouldNotBeLoadedByOS ) { FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleCouldntBeLoaded", "The game module '{0}' could not be loaded. There may be an operating system error or the module may not be properly set up."), TextModuleName ); } else { ensure(0); // If this goes off, the error handling code should be updated for the new enum values! FailureMessage = FText::Format( LOCTEXT("PrimaryGameModuleGenericLoadFailure", "The game module '{0}' failed to load for an unspecified reason. Please report this error."), TextModuleName ); } // Just report the first error break; } } FMessageDialog::Open(EAppMsgType::Ok, FailureMessage); bSuccess = false; } } return bSuccess; } bool FProjectManager::AreProjectModulesUpToDate() { return !CurrentlyLoadedProject.IsValid() || CurrentlyLoadedProject->AreModulesUpToDate(); } const FString& FProjectManager::GetAutoLoadProjectFileName() { static FString RecentProjectFileName = FPaths::Combine(*FPaths::GameAgnosticSavedDir(), TEXT("AutoLoadProject.txt")); return RecentProjectFileName; } const FString& FProjectManager::NonStaticGetProjectFileExtension() { static FString GameProjectFileExtension(TEXT("uproject")); return GameProjectFileExtension; } bool FProjectManager::GenerateNewProjectFile(const FString& NewProjectFilename, const TArray& StartupModuleNames, const FString& EngineIdentifier, FText& OutFailReason) { TSharedRef NewProject = MakeShareable( new FProject() ); NewProject->UpdateVersionToCurrent(EngineIdentifier); NewProject->ReplaceModulesInProject(&StartupModuleNames); const FString& FileContents = NewProject->SerializeToJSON(); if ( FFileHelper::SaveStringToFile(FileContents, *NewProjectFilename) ) { return true; } else { OutFailReason = FText::Format( LOCTEXT("FailedToWriteOutputFile", "Failed to write output file '{0}'. Perhaps the file is Read-Only?"), FText::FromString(NewProjectFilename) ); return false; } } bool FProjectManager::DuplicateProjectFile(const FString& SourceProjectFilename, const FString& NewProjectFilename, const FString& EngineIdentifier, FText& OutFailReason) { // Load the source project TSharedRef SourceProject = MakeShareable( new FProject() ); if ( !SourceProject->LoadFromFile(SourceProjectFilename, OutFailReason) ) { return false; } // Duplicate the project info FProjectInfo ProjectInfo = SourceProject->GetProjectInfo(); // Clear the sample hash ProjectInfo.EpicSampleNameHash = 0; // Fix up module names const FString BaseSourceName = FPaths::GetBaseFilename(SourceProjectFilename); const FString BaseNewName = FPaths::GetBaseFilename(NewProjectFilename); for ( auto ModuleIt = ProjectInfo.Modules.CreateIterator(); ModuleIt; ++ModuleIt ) { FProjectOrPluginInfo::FModuleInfo& ModuleInfo = *ModuleIt; ModuleInfo.Name = FName(*ModuleInfo.Name.ToString().Replace(*BaseSourceName, *BaseNewName)); } // Create new project, update version numbers (no need to replace modules here) TSharedRef NewProject = MakeShareable( new FProject(ProjectInfo) ); NewProject->UpdateVersionToCurrent(EngineIdentifier); // Serialize and write to disk const FString& FileContents = NewProject->SerializeToJSON(); if ( FFileHelper::SaveStringToFile(FileContents, *NewProjectFilename) ) { return true; } else { OutFailReason = FText::Format( LOCTEXT("FailedToWriteOutputFile", "Failed to write output file '{0}'. Perhaps the file is Read-Only?"), FText::FromString(NewProjectFilename) ); return false; } } bool FProjectManager::UpdateLoadedProjectFileToCurrent(const TArray* StartupModuleNames, const FString& EngineIdentifier, FText& OutFailReason) { if ( !CurrentlyLoadedProject.IsValid() ) { return false; } // Freshen version information CurrentlyLoadedProject->UpdateVersionToCurrent(EngineIdentifier); // Replace the modules names, if specified CurrentlyLoadedProject->ReplaceModulesInProject(StartupModuleNames); // Update file on disk const FString& FileContents = CurrentlyLoadedProject->SerializeToJSON(); if ( FFileHelper::SaveStringToFile(FileContents, *FPaths::GetProjectFilePath()) ) { return true; } else { // We failed to generate the file. Could be read only. OutFailReason = FText::Format( LOCTEXT("FailedToWriteOutputFile", "Failed to write output file '{0}'. Perhaps the file is Read-Only?"), FText::FromString(FPaths::GetProjectFilePath()) ); return false; } } bool FProjectManager::SignSampleProject(const FString& FilePath, const FString& Category, FText& OutFailReason) { TSharedRef NewProject = MakeShareable( new FProject() ); if ( !NewProject->LoadFromFile(FilePath, OutFailReason) ) { return false; } NewProject->SignSampleProject(FilePath, Category); const FString& FileContents = NewProject->SerializeToJSON(); if (FFileHelper::SaveStringToFile(FileContents, *FilePath)) { return true; } else { OutFailReason = FText::Format( LOCTEXT("FailedToSaveSignedProject", "Failed to save signed project file {0}"), FText::FromString(FilePath) ); return false; } } bool FProjectManager::QueryStatusForProject(const FString& FilePath, FProjectStatus& OutProjectStatus) const { TSharedRef NewProject = MakeShareable( new FProject() ); FText FailReason; if ( !NewProject->LoadFromFile(FilePath, FailReason) ) { return false; } QueryStatusForProjectImpl(*NewProject, FilePath, OutProjectStatus); return true; } bool FProjectManager::QueryStatusForCurrentProject(FProjectStatus& OutProjectStatus) const { if ( !CurrentlyLoadedProject.IsValid() ) { return false; } QueryStatusForProjectImpl(*CurrentlyLoadedProject, FPaths::GetProjectFilePath(), OutProjectStatus); return true; } void FProjectManager::QueryStatusForProjectImpl(const FProject& Project, const FString& FilePath, FProjectStatus& OutProjectStatus) { const FProjectInfo& ProjectInfo = Project.GetProjectInfo(); OutProjectStatus.Name = ProjectInfo.Name; OutProjectStatus.Description = ProjectInfo.Description; OutProjectStatus.Category = ProjectInfo.Category; OutProjectStatus.bCodeBasedProject = ProjectInfo.Modules.Num() > 0; OutProjectStatus.bSignedSampleProject = Project.IsSignedSampleProject(FilePath); OutProjectStatus.bRequiresUpdate = Project.RequiresUpdate(); OutProjectStatus.TargetPlatforms = ProjectInfo.TargetPlatforms; } void FProjectManager::UpdateSupportedTargetPlatformsForProject(const FString& FilePath, const FName& InPlatformName, const bool bIsSupported) { TSharedRef NewProject = MakeShareable( new FProject() ); FText FailReason; if ( !NewProject->LoadFromFile(FilePath, FailReason) ) { return; } NewProject->UpdateSupportedTargetPlatforms(InPlatformName, bIsSupported); const FString& FileContents = NewProject->SerializeToJSON(); FFileHelper::SaveStringToFile(FileContents, *FilePath); // Call OnTargetPlatformsForCurrentProjectChangedEvent if this project is the same as the one we currently have loaded const FString CurrentProjectPath = FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()); const FString InProjectPath = FPaths::ConvertRelativePathToFull(FilePath); if ( CurrentProjectPath == InProjectPath ) { OnTargetPlatformsForCurrentProjectChangedEvent.Broadcast(); } } void FProjectManager::UpdateSupportedTargetPlatformsForCurrentProject(const FName& InPlatformName, const bool bIsSupported) { if ( !CurrentlyLoadedProject.IsValid() ) { return; } CurrentlyLoadedProject->UpdateSupportedTargetPlatforms(InPlatformName, bIsSupported); const FString& FileContents = CurrentlyLoadedProject->SerializeToJSON(); FFileHelper::SaveStringToFile(FileContents, *FPaths::GetProjectFilePath()); OnTargetPlatformsForCurrentProjectChangedEvent.Broadcast(); } void FProjectManager::ClearSupportedTargetPlatformsForProject(const FString& FilePath) { TSharedRef NewProject = MakeShareable( new FProject() ); FText FailReason; if ( !NewProject->LoadFromFile(FilePath, FailReason) ) { return; } NewProject->ClearSupportedTargetPlatforms(); const FString& FileContents = NewProject->SerializeToJSON(); FFileHelper::SaveStringToFile(FileContents, *FilePath); // Call OnTargetPlatformsForCurrentProjectChangedEvent if this project is the same as the one we currently have loaded const FString CurrentProjectPath = FPaths::ConvertRelativePathToFull(FPaths::GetProjectFilePath()); const FString InProjectPath = FPaths::ConvertRelativePathToFull(FilePath); if ( CurrentProjectPath == InProjectPath ) { OnTargetPlatformsForCurrentProjectChangedEvent.Broadcast(); } } void FProjectManager::ClearSupportedTargetPlatformsForCurrentProject() { if ( !CurrentlyLoadedProject.IsValid() ) { return; } CurrentlyLoadedProject->ClearSupportedTargetPlatforms(); const FString& FileContents = CurrentlyLoadedProject->SerializeToJSON(); FFileHelper::SaveStringToFile(FileContents, *FPaths::GetProjectFilePath()); OnTargetPlatformsForCurrentProjectChangedEvent.Broadcast(); } IProjectManager& IProjectManager::Get() { // Single instance of manager, allocated on demand and destroyed on program exit. static FProjectManager* ProjectManager = NULL; if( ProjectManager == NULL ) { ProjectManager = new FProjectManager(); } return *ProjectManager; } #undef LOCTEXT_NAMESPACE