// Copyright Epic Games, Inc. All Rights Reserved. #include "Commandlets/GatherTextCommandlet.h" #include "Misc/App.h" #include "Misc/Paths.h" #include "Misc/ConfigCacheIni.h" #include "HAL/FileManager.h" #include "UObject/Class.h" #include "UObject/Package.h" #include "UObject/GCObjectScopeGuard.h" #include "SourceControlHelpers.h" DEFINE_LOG_CATEGORY_STATIC(LogGatherTextCommandlet, Log, All); /** * UGatherTextCommandlet */ UGatherTextCommandlet::UGatherTextCommandlet(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } const FString UGatherTextCommandlet::UsageText( TEXT("GatherTextCommandlet usage...\r\n") TEXT(" GatherTextCommandlet -Config=\r\n") TEXT(" \r\n") TEXT(" Full path to the .ini config file that defines what gather steps the commandlet will run.\r\n")); int32 UGatherTextCommandlet::Main(const FString& Params) { TArray Tokens; TArray Switches; TMap ParamVals; UCommandlet::ParseCommandLine(*Params, Tokens, Switches, ParamVals); // Build up the complete list of config files to process TArray GatherTextConfigPaths; if (const FString* ConfigParamPtr = ParamVals.Find(TEXT("config"))) { ConfigParamPtr->ParseIntoArray(GatherTextConfigPaths, TEXT(";")); FString ProjectBasePath; if (!FPaths::ProjectDir().IsEmpty()) { ProjectBasePath = FPaths::ProjectDir(); } else { ProjectBasePath = FPaths::EngineDir(); } for (FString& GatherTextConfigPath: GatherTextConfigPaths) { if (FPaths::IsRelative(GatherTextConfigPath)) { GatherTextConfigPath = FPaths::Combine(*ProjectBasePath, *GatherTextConfigPath); } } } if (GatherTextConfigPaths.Num() == 0) { UE_LOG(LogGatherTextCommandlet, Error, TEXT("-config not specified.\n%s"), *UsageText); return -1; } const bool bEnableSourceControl = Switches.Contains(TEXT("EnableSCC")); const bool bDisableSubmit = Switches.Contains(TEXT("DisableSCCSubmit")); TSharedPtr CommandletSourceControlInfo; if (bEnableSourceControl) { CommandletSourceControlInfo = MakeShareable(new FLocalizationSCC()); FText SCCErrorStr; if (!CommandletSourceControlInfo->IsReady(SCCErrorStr)) { UE_LOG(LogGatherTextCommandlet, Error, TEXT("Source Control error: %s"), *SCCErrorStr.ToString()); return -1; } } for (const FString& GatherTextConfigPath: GatherTextConfigPaths) { const int32 Result = ProcessGatherConfig(GatherTextConfigPath, CommandletSourceControlInfo, Tokens, Switches, ParamVals); if (Result != 0) { return Result; } } if (CommandletSourceControlInfo.IsValid() && !bDisableSubmit) { FText SCCErrorStr; if (CommandletSourceControlInfo->CheckinFiles(GetChangelistDescription(GatherTextConfigPaths), SCCErrorStr)) { UE_LOG(LogGatherTextCommandlet, Log, TEXT("Submitted Localization files.")); } else { UE_LOG(LogGatherTextCommandlet, Error, TEXT("%s"), *SCCErrorStr.ToString()); if (!CommandletSourceControlInfo->CleanUp(SCCErrorStr)) { UE_LOG(LogGatherTextCommandlet, Error, TEXT("%s"), *SCCErrorStr.ToString()); } return -1; } } return 0; } int32 UGatherTextCommandlet::ProcessGatherConfig(const FString& GatherTextConfigPath, const TSharedPtr& CommandletSourceControlInfo, const TArray& Tokens, const TArray& Switches, const TMap& ParamVals) { GConfig->LoadFile(*GatherTextConfigPath); if (!GConfig->FindConfigFile(*GatherTextConfigPath)) { UE_LOG(LogGatherTextCommandlet, Error, TEXT("Loading Config File \"%s\" failed."), *GatherTextConfigPath); return -1; } UE_LOG(LogGatherTextCommandlet, Display, TEXT("Beginning GatherText Commandlet for '%s'"), *GatherTextConfigPath); // Read in the platform split mode to use ELocTextPlatformSplitMode PlatformSplitMode = ELocTextPlatformSplitMode::None; { FString PlatformSplitModeString; if (GetStringFromConfig(TEXT("CommonSettings"), TEXT("PlatformSplitMode"), PlatformSplitModeString, GatherTextConfigPath)) { UEnum* PlatformSplitModeEnum = FindObjectChecked(ANY_PACKAGE, TEXT("ELocTextPlatformSplitMode")); const int64 PlatformSplitModeInt = PlatformSplitModeEnum->GetValueByName(*PlatformSplitModeString); if (PlatformSplitModeInt != INDEX_NONE) { PlatformSplitMode = (ELocTextPlatformSplitMode)PlatformSplitModeInt; } } } FString LocalizationTargetName; { FString ManifestName; GetStringFromConfig(TEXT("CommonSettings"), TEXT("ManifestName"), ManifestName, GatherTextConfigPath); LocalizationTargetName = FPaths::GetBaseFilename(ManifestName); } // Basic helper that can be used only to gather a new manifest for writing TSharedRef CommandletGatherManifestHelper = MakeShared(LocalizationTargetName, MakeShared(CommandletSourceControlInfo), PlatformSplitMode); CommandletGatherManifestHelper->LoadManifest(ELocTextHelperLoadFlags::Create); const FString GatherTextStepPrefix = TEXT("GatherTextStep"); // Read the list of steps from the config file (they all have the format GatherTextStep{N}) TArray StepNames; GConfig->GetSectionNames(GatherTextConfigPath, StepNames); StepNames.RemoveAllSwap([&GatherTextStepPrefix](const FString& InStepName) { return !InStepName.StartsWith(GatherTextStepPrefix); }); // Make sure the steps are sorted in ascending order (by numerical suffix) StepNames.Sort([&GatherTextStepPrefix](const FString& InStepNameOne, const FString& InStepNameTwo) { const FString NumericalSuffixOneStr = InStepNameOne.RightChop(GatherTextStepPrefix.Len()); const int32 NumericalSuffixOne = FCString::Atoi(*NumericalSuffixOneStr); const FString NumericalSuffixTwoStr = InStepNameTwo.RightChop(GatherTextStepPrefix.Len()); const int32 NumericalSuffixTwo = FCString::Atoi(*NumericalSuffixTwoStr); return NumericalSuffixOne < NumericalSuffixTwo; }); // Execute each step defined in the config file. for (const FString& StepName: StepNames) { FString CommandletClassName = GConfig->GetStr(*StepName, TEXT("CommandletClass"), GatherTextConfigPath) + TEXT("Commandlet"); UClass* CommandletClass = FindObject(ANY_PACKAGE, *CommandletClassName, false); if (!CommandletClass) { UE_LOG(LogGatherTextCommandlet, Error, TEXT("The commandlet name %s in section %s is invalid."), *CommandletClassName, *StepName); continue; } UGatherTextCommandletBase* Commandlet = NewObject(GetTransientPackage(), CommandletClass); check(Commandlet); FGCObjectScopeGuard CommandletGCGuard(Commandlet); Commandlet->Initialize(CommandletGatherManifestHelper, CommandletSourceControlInfo); // Execute the commandlet. double CommandletExecutionStartTime = FPlatformTime::Seconds(); UE_LOG(LogGatherTextCommandlet, Display, TEXT("Executing %s: %s"), *StepName, *CommandletClassName); FString GeneratedCmdLine = FString::Printf(TEXT("-Config=\"%s\" -Section=%s"), *GatherTextConfigPath, *StepName); // Add all the command params with the exception of config for (auto ParamIter = ParamVals.CreateConstIterator(); ParamIter; ++ParamIter) { const FString& Key = ParamIter.Key(); const FString& Val = ParamIter.Value(); if (Key != TEXT("config")) { GeneratedCmdLine += FString::Printf(TEXT(" -%s=%s"), *Key, *Val); } } // Add all the command switches for (auto SwitchIter = Switches.CreateConstIterator(); SwitchIter; ++SwitchIter) { const FString& Switch = *SwitchIter; GeneratedCmdLine += FString::Printf(TEXT(" -%s"), *Switch); } if (0 != Commandlet->Main(GeneratedCmdLine)) { UE_LOG(LogGatherTextCommandlet, Error, TEXT("%s-%s reported an error."), *StepName, *CommandletClassName); if (CommandletSourceControlInfo.IsValid()) { FText SCCErrorStr; if (!CommandletSourceControlInfo->CleanUp(SCCErrorStr)) { UE_LOG(LogGatherTextCommandlet, Error, TEXT("%s"), *SCCErrorStr.ToString()); } } return -1; } UE_LOG(LogGatherTextCommandlet, Display, TEXT("Completed %s: %s in %f seconds"), *StepName, *CommandletClassName, FPlatformTime::Seconds() - CommandletExecutionStartTime); } // Clean-up any stale per-platform data { FString DestinationPath; if (GetPathFromConfig(TEXT("CommonSettings"), TEXT("DestinationPath"), DestinationPath, GatherTextConfigPath)) { IFileManager& FileManager = IFileManager::Get(); auto RemoveDirectory = [&FileManager](const TCHAR* InDirectory) { FileManager.IterateDirectoryRecursively(InDirectory, [&FileManager](const TCHAR* FilenameOrDirectory, bool bIsDirectory) -> bool { if (!bIsDirectory) { if (!USourceControlHelpers::IsAvailable() || !USourceControlHelpers::MarkFileForDelete(FilenameOrDirectory)) { FileManager.Delete(FilenameOrDirectory, false, true); } } return true; }); FileManager.DeleteDirectory(InDirectory, false, true); }; const FString PlatformLocalizationPath = DestinationPath / FPaths::GetPlatformLocalizationFolderName(); if (CommandletGatherManifestHelper->ShouldSplitPlatformData()) { // Remove any stale platform sub-folders FileManager.IterateDirectory(*PlatformLocalizationPath, [&](const TCHAR* FilenameOrDirectory, bool bIsDirectory) -> bool { if (bIsDirectory) { const FString SplitPlatformName = FPaths::GetCleanFilename(FilenameOrDirectory); if (!CommandletGatherManifestHelper->GetPlatformsToSplit().Contains(SplitPlatformName)) { RemoveDirectory(FilenameOrDirectory); } } return true; }); } else { // Remove the entire Platforms folder RemoveDirectory(*PlatformLocalizationPath); } } else { UE_LOG(LogGatherTextCommandlet, Warning, TEXT("No destination path specified in the 'CommonSettings' section. Cannot check for stale per-platform data!")); } } return 0; } FText UGatherTextCommandlet::GetChangelistDescription(const TArray& GatherTextConfigPaths) { FString ProjectName = FApp::GetProjectName(); if (ProjectName.IsEmpty()) { ProjectName = TEXT("Engine"); } FString ChangeDescriptionString = FString::Printf(TEXT("[Localization Update] %s\n\n"), *ProjectName); ChangeDescriptionString += TEXT("Targets:\n"); for (const FString& GatherTextConfigPath: GatherTextConfigPaths) { const FString TargetName = FPaths::GetBaseFilename(GatherTextConfigPath, true); ChangeDescriptionString += FString::Printf(TEXT(" %s\n"), *TargetName); } return FText::FromString(MoveTemp(ChangeDescriptionString)); }