Repository: dotBunny/CLionSourceCodeAccess Branch: master Commit: 8e39692cbf8e Files: 12 Total size: 53.0 KB Directory structure: gitextract_r3vd351s/ ├── .gitignore ├── CLionSourceCodeAccess.uplugin ├── README.md ├── Resources/ │ └── CLion-Unreal-CodeStyle.xml └── Source/ └── CLionSourceCodeAccess/ ├── CLionSourceCodeAccess.Build.cs └── Private/ ├── CLionSettings.cpp ├── CLionSettings.h ├── CLionSourceCodeAccessModule.cpp ├── CLionSourceCodeAccessModule.h ├── CLionSourceCodeAccessPrivatePCH.h ├── CLionSourceCodeAccessor.cpp └── CLionSourceCodeAccessor.h ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ UE4Editor-CLionSourceCodeAccess.dylib.ready Intermediate Binaries ================================================ FILE: CLionSourceCodeAccess.uplugin ================================================ { "FileVersion" : 3, "EngineVersion" : "4.18.0", "Version" : 107, "VersionName" : "1.07", "FriendlyName" : "CLion Integration", "Description" : "Allows access to source code in CLion.", "Category" : "Programming", "CreatedBy" : "dotBunny, Inc.", "CreatedByURL" : "http://dotbunny.com", "DocsURL" : "https://github.com/dotBunny/CLionSourceCodeAccess/wiki", "MarketplaceURL" : "com.epicgames.launcher://ue/marketplace/content/7a2e6de4c71a48c7b9df5f49b73e1a09", "SupportURL" : "https://github.com/dotBunny/CLionSourceCodeAccess/issues", "EnabledByDefault" : true, "CanContainContent" : false, "IsBetaVersion" : false, "Installed" : false, "Modules" : [ { "Name" : "CLionSourceCodeAccess", "Type" : "Editor", "LoadingPhase" : "PostEngineInit", "WhitelistPlatforms" : [ "Mac", "Linux", "Win64" ] } ] } ================================================ FILE: README.md ================================================ # CLionSourceCodeAccess (UE4 <= 4.18) A CLion Plugin for Unreal Engine The plugin creates a fully flushed out CMakeList file for use with CLion, adding intellisense, compiler definitions, etc. Please visit https://github.com/dotBunny/CLionSourceCodeAccess/wiki for information on how to install and use the plugin. This will be the last release of the plugin seperate from Unreal Engine. To all those in the community that helped and contributed: **Thank You!** It is because of contributions like yours that projects like this thrive and grow. # CLionSourceCodeAccess (UE4 >= 4.19) As of Unreal Engine 4.19 the plugin has been merged into the UE4 source code and will be distributed with Unreal Engine. You can check it out the [repository](https://github.com/EpicGames/UnrealEngine/tree/master/Engine/Plugins/Developer/CLionSourceCodeAccess), and see some of the [changes](https://github.com/EpicGames/UnrealEngine/blob/master/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/CMake/CMakefileGenerator.cs) done to UBT that made it possible. ## Mentions [CLion Blog](https://blog.jetbrains.com/clion/2016/10/clion-and-ue4/) [UE Marketplace](https://www.unrealengine.com/marketplace/clion-integration) ================================================ FILE: Resources/CLion-Unreal-CodeStyle.xml ================================================ ================================================ FILE: Source/CLionSourceCodeAccess/CLionSourceCodeAccess.Build.cs ================================================ // Copyright 2017 dotBunny Inc. All Rights Reserved. namespace UnrealBuildTool.Rules { public class CLionSourceCodeAccess : ModuleRules { public CLionSourceCodeAccess(ReadOnlyTargetRules Target) : base(Target) { PrivateDependencyModuleNames.AddRange( new string[] { "Core", "SourceCodeAccess", "DesktopPlatform", "LevelEditor", "XmlParser", "HotReload", } ); PublicDependencyModuleNames.AddRange( new string[] { "Core", "Engine", "InputCore", "UnrealEd", "CoreUObject", "Slate", "SlateCore", "WorkspaceMenuStructure", "Projects", "PropertyEditor", "HotReload", "Json", } ); PCHUsage = PCHUsageMode.NoSharedPCHs; } } } ================================================ FILE: Source/CLionSourceCodeAccess/Private/CLionSettings.cpp ================================================ // Copyright 2017 dotBunny Inc. All Rights Reserved. #include "CLionSourceCodeAccessPrivatePCH.h" #include "CLionSettings.h" #if PLATFORM_WINDOWS #include "AllowWindowsPlatformTypes.h" #endif #define LOCTEXT_NAMESPACE "CLionSourceCodeAccessor" UCLionSettings::UCLionSettings(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } bool UCLionSettings::CheckSettings() { #if PLATFORM_WINDOWS if (this->CLion.FilePath.IsEmpty()) { // search from JetBrainsToolbox folder FString ToolboxBinPath; if (FWindowsPlatformMisc::QueryRegKey(HKEY_CURRENT_USER, TEXT("Software\\JetBrains s.r.o.\\JetBrainsToolbox\\"), TEXT(""), ToolboxBinPath)) { FPaths::NormalizeDirectoryName(ToolboxBinPath); FString PatternString(TEXT("(.*)/bin")); FRegexPattern Pattern(PatternString); FRegexMatcher Matcher(Pattern, ToolboxBinPath); if (Matcher.FindNext()) { FString ToolboxPath = Matcher.GetCaptureGroup(1); FString SettingJsonPath = FPaths::Combine(ToolboxPath, FString(".settings.json")); if (FPaths::FileExists(SettingJsonPath)) { FString JsonStr; FFileHelper::LoadFileToString(JsonStr, *SettingJsonPath); TSharedRef> JsonReader = TJsonReaderFactory::Create(JsonStr); TSharedPtr JsonObject = MakeShareable(new FJsonObject()); if (FJsonSerializer::Deserialize(JsonReader, JsonObject) && JsonObject.IsValid()) { FString InstallLocation; if (JsonObject->TryGetStringField(TEXT("install_location"), InstallLocation)) { if (!InstallLocation.IsEmpty()) { ToolboxPath = InstallLocation; } } } } FString CLionHome = FPaths::Combine(ToolboxPath, FString("apps"), FString("CLion")); if (FPaths::DirectoryExists(CLionHome)) { TArray IDEPaths; IFileManager::Get().FindFilesRecursive(IDEPaths, *CLionHome, TEXT("clion64.exe"), true, false); if (IDEPaths.Num() > 0) { this->CLion.FilePath = IDEPaths[0]; } } } } } if (this->CLion.FilePath.IsEmpty()) { // search from ProgID FString OpenCommand; if (!FWindowsPlatformMisc::QueryRegKey(HKEY_CURRENT_USER, TEXT("SOFTWARE\\Classes\\Applications\\clion64.exe\\shell\\open\\command\\"), TEXT(""), OpenCommand)) { FWindowsPlatformMisc::QueryRegKey(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Classes\\Applications\\clion64.exe\\shell\\open\\command\\"), TEXT(""), OpenCommand); } FString PatternString(TEXT("\"(.*)\" \".*\"")); FRegexPattern Pattern(PatternString); FRegexMatcher Matcher(Pattern, OpenCommand); if (Matcher.FindNext()) { FString IDEPath = Matcher.GetCaptureGroup(1); if (FPaths::FileExists(IDEPath)) { this->CLion.FilePath = IDEPath; } } } #elif PLATFORM_MAC if (this->CLion.FilePath.IsEmpty()) { NSURL* AppURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.CLion-EAP"]; if (AppURL != nullptr) { this->CLion.FilePath = FString([AppURL path]); } } if (this->CLion.FilePath.IsEmpty()) { NSURL* AppURL = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:@"com.jetbrains.CLion"]; if (AppURL != nullptr) { this->CLion.FilePath = FString([AppURL path]); } } if (this->Mono.FilePath.IsEmpty()) { FString MonoPath = FPaths::Combine(*FPaths::RootDir(), TEXT("Engine"), TEXT("Binaries"), TEXT("ThirdParty"), TEXT("Mono"), TEXT("Mac"), TEXT("bin"), TEXT("mono")); if (FPaths::FileExists(MonoPath)) { this->Mono.FilePath = MonoPath; } } if (this->CCompiler.FilePath.IsEmpty()) { if (FPaths::FileExists(TEXT("/usr/bin/clang"))) { this->CCompiler.FilePath = TEXT("/usr/bin/clang"); } } if (this->CXXCompiler.FilePath.IsEmpty()) { if (FPaths::FileExists(TEXT("/usr/bin/clang++"))) { this->CXXCompiler.FilePath = TEXT("/usr/bin/clang++"); } } #else if ( this->CLion.FilePath.IsEmpty()) { if(FPaths::FileExists(TEXT("/opt/clion/bin/clion.sh"))) { this->CLion.FilePath = TEXT("/opt/clion/bin/clion.sh"); } } if (this->Mono.FilePath.IsEmpty() ) { if (FPaths::FileExists(TEXT("/usr/bin/mono"))) { this->Mono.FilePath = TEXT("/usr/bin/mono"); } else if (FPaths::FileExists(TEXT("/opt/mono/bin/mono"))) { this->Mono.FilePath = TEXT("/opt/mono/bin/mono"); } } if (this->CCompiler.FilePath.IsEmpty()) { if(FPaths::FileExists(TEXT("/usr/bin/clang"))) { this->CCompiler.FilePath = TEXT("/usr/bin/clang"); } } if (this->CXXCompiler.FilePath.IsEmpty()) { if(FPaths::FileExists(TEXT("/usr/bin/clang++"))) { this->CXXCompiler.FilePath = TEXT("/usr/bin/clang++"); } } #endif // Reset the setup complete before we check things this->bSetupComplete = true; if (this->CLion.FilePath.IsEmpty()) { this->bSetupComplete = false; } #if !PLATFORM_WINDOWS if (this->Mono.FilePath.IsEmpty()) { this->bSetupComplete = false; } #endif // Update CMakeList path this->CachedCMakeListPath = FPaths::Combine(*FPaths::ConvertRelativePathToFull(*FPaths::ProjectDir()), TEXT("CMakeLists.txt")); return this->bSetupComplete; } FString UCLionSettings::GetCMakeListPath() { return this->CachedCMakeListPath; } bool UCLionSettings::IsSetup() { return this->bSetupComplete; } #if WITH_EDITOR void UCLionSettings::PreEditChange(UProperty* PropertyAboutToChange) { Super::PreEditChange(PropertyAboutToChange); // Cache our previous values this->PreviousCCompiler = this->CCompiler.FilePath; this->PreviousCLion = this->CLion.FilePath; this->PreviousCXXCompiler = this->CXXCompiler.FilePath; #if !PLATFORM_WINDOWS this->PreviousMono = this->Mono.FilePath; #endif } void UCLionSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { const FName MemberPropertyName = (PropertyChangedEvent.Property != nullptr) ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; // CLion Executable Path Check if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UCLionSettings, CLion)) { if (this->CLion.FilePath.IsEmpty()) { this->bSetupComplete = false; return; } this->CLion.FilePath = FPaths::ConvertRelativePathToFull(this->CLion.FilePath); FText FailReason; #if PLATFORM_MAC NSString *AppPath = [NSString stringWithUTF8String: TCHAR_TO_UTF8(*this->CLion.FilePath)]; NSBundle *Bundle = [NSBundle bundleWithPath: AppPath]; FString BundleId = FString([Bundle bundleIdentifier]); if (!BundleId.StartsWith(TEXT("com.jetbrains.CLion"), ESearchCase::CaseSensitive)) { FailReason = LOCTEXT("CLionSelectMacApp", "Please select the CLion app"); FMessageDialog::Open(EAppMsgType::Ok, FailReason); this->CLion.FilePath = this->PreviousCLion; return; } this->CLion.FilePath = FString([Bundle executablePath]); #endif if (this->CLion.FilePath == this->PreviousCLion) { return; } if (!FPaths::ValidatePath(this->CLion.FilePath, &FailReason)) { FMessageDialog::Open(EAppMsgType::Ok, FailReason); this->CLion.FilePath = this->PreviousCLion; if (this->CLion.FilePath.IsEmpty()) { this->bSetupComplete = false; } return; } } #if !PLATFORM_WINDOWS // Mono Path if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UCLionSettings, Mono)) { if (this->Mono.FilePath.IsEmpty()) { this->bSetupComplete = false; return; } this->Mono.FilePath = FPaths::ConvertRelativePathToFull(this->Mono.FilePath); FText FailReason; if (this->Mono.FilePath == this->PreviousMono) { return; } if (!FPaths::ValidatePath(this->Mono.FilePath, &FailReason)) { FMessageDialog::Open(EAppMsgType::Ok, FailReason); this->Mono.FilePath = this->PreviousMono; return; } } #endif // Check C Compiler Path if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UCLionSettings, CCompiler)) { // Bail out if you've wiped it out if (this->CCompiler.FilePath.IsEmpty()) { this->bRequireRefresh = true; return; } this->CCompiler.FilePath = FPaths::ConvertRelativePathToFull(this->CCompiler.FilePath); if (this->CCompiler.FilePath == this->PreviousCCompiler) { return; } if (!FPaths::FileExists(this->CCompiler.FilePath)) { this->CCompiler.FilePath = this->PreviousCCompiler; return; } this->bRequireRefresh = true; } // Check C++ Compiler Path if (MemberPropertyName == GET_MEMBER_NAME_CHECKED(UCLionSettings, CXXCompiler)) { if (this->CXXCompiler.FilePath.IsEmpty()) { this->bRequireRefresh = true; return; } this->CXXCompiler.FilePath = FPaths::ConvertRelativePathToFull(this->CXXCompiler.FilePath); if (this->CXXCompiler.FilePath == this->PreviousCXXCompiler) { return; } if (!FPaths::FileExists(CXXCompiler.FilePath)) { this->CXXCompiler.FilePath = this->PreviousCXXCompiler; return; } this->bRequireRefresh = true; } this->CheckSettings(); } #endif ================================================ FILE: Source/CLionSourceCodeAccess/Private/CLionSettings.h ================================================ // Copyright 2017 dotBunny Inc. All Rights Reserved. #pragma once #include "CLionSourceCodeAccessPrivatePCH.h" #include "CLionSettings.generated.h" UCLASS(config = EditorUserSettings, defaultconfig) class UCLionSettings : public UObject { GENERATED_UCLASS_BODY() public: /** * Does the project files require a refresh based on changes made to the settings? */ bool bRequireRefresh = false; /** * Check our settings, cache results and return * @return Can the plugin be used? */ bool CheckSettings(); /** * Get the cached CMakeList path. * @return CMakeList Path */ FString GetCMakeListPath(); /** * Are the settings for the plugin complete and usable? */ bool IsSetup(); /** * [optional] Path to a C compiler to be used in the CMakeList file. */ UPROPERTY(Config, EditAnywhere, Category = "CMake Compiler", Meta = (DisplayName = "C Compiler (Optional)")) FFilePath CCompiler; /** * Path to CLion executable, used when needing to launch CLion. */ UPROPERTY(Config, EditAnywhere, Category = "CLion", Meta = (DisplayName = "CLion Executable")) FFilePath CLion; /** * [optional] Path to a C++ compiler to be used in the CMakeList file." */ UPROPERTY(Config, EditAnywhere, Category = "CMake Compiler", Meta = (DisplayName = "C++ Compiler (Optional)")) FFilePath CXXCompiler; /** * Target Configuration Debug */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Configurations", Meta = (DisplayName = "Debug")) bool bConfigureDebug = false; /** * Target Configuration DebugGame */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Configurations", Meta = (DisplayName = "DebugGame")) bool bConfigureDebugGame = false; /** * Target Configuration Development */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Configurations", Meta = (DisplayName = "Development")) bool bConfigureDevelopment = true; /** * Target Configuration Shipping */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Configurations", Meta = (DisplayName = "Shipping")) bool bConfigureShipping = false; /** * Target Configuration Test */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Configurations", Meta = (DisplayName = "Test")) bool bConfigureTest = false; /** * General Visual Studio Code Project Files */ UPROPERTY(Config, EditAnywhere, Category = "Visual Studio Code", Meta = (DisplayName = "Generate c_cpp_properties.json File")) bool bGenerateVisualStudioCodeProject = false; /** * Include Config In Makefile */ UPROPERTY(Config, EditAnywhere, Category = "Additional Folders", Meta = (DisplayName = "Include Configs")) bool bIncludeConfigs = false; /** * Include Plugins In Makefile */ UPROPERTY(Config, EditAnywhere, Category = "Additional Folders", Meta = (DisplayName = "Include Plugins")) bool bIncludePlugins = false; /** * Include Shaders In Makefile */ UPROPERTY(Config, EditAnywhere, Category = "Additional Folders", Meta = (DisplayName = "Include Shaders")) bool bIncludeShaders = false; /** * Target Project Game Editor */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Projects", Meta = (DisplayName = "Project Game")) bool bProjectSpecificGame = false; /** * Target Project Specific Editor */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Projects", Meta = (DisplayName = "Project Editor")) bool bProjectSpecificEditor = true; /** * Target Project UE4 Editor */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Projects", Meta = (DisplayName = "UE4 Editor")) bool bProjectUE4Editor = false; /** * Target Project UE4 Game */ UPROPERTY(Config, EditAnywhere, Category = "CMake Target Projects", Meta = (DisplayName = "UE4 Game")) bool bProjectUE4Game = false; // TODO: We can't !PLATFORM_WINDOWS this as UBT/UHT barfs /** * Path to the Mono executable (Required on non-Windows platforms). */ UPROPERTY(Config, EditAnywhere, Category = "Unreal Engine", Meta = (DisplayName = "Mono Executable (Ignore On Windows)")) FFilePath Mono; protected: #if WITH_EDITOR virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; virtual void PreEditChange(UProperty* PropertyAboutToChange) override; #endif private: /** * Cached flag to tell if all settings are present needed to use the plugin. */ bool bSetupComplete = false; /** * A cached reference of the path to the CMakeList.txt file */ FString CachedCMakeListPath; /** * Cached version of C Compiler location. */ FString PreviousCCompiler; /** * Cached version of CLion location. */ FString PreviousCLion; /** * Cached version of C++ Compiler location. */ FString PreviousCXXCompiler; /** * Cached version of Mono location. */ FString PreviousMono; }; ================================================ FILE: Source/CLionSourceCodeAccess/Private/CLionSourceCodeAccessModule.cpp ================================================ // Copyright 2017 dotBunny Inc. All Rights Reserved. #include "CLionSourceCodeAccessPrivatePCH.h" #include "Runtime/Core/Public/Features/IModularFeatures.h" #include "Editor/LevelEditor/Public/LevelEditor.h" #include "ISettingsModule.h" #include "ISettingsSection.h" #include "CLionSourceCodeAccessModule.h" #include "CLionSettings.h" #define LOCTEXT_NAMESPACE "CLionSourceCodeAccessor" IMPLEMENT_MODULE(FCLionSourceCodeAccessModule, CLionSourceCodeAccess); void FCLionSourceCodeAccessModule::AddMenuOptions(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("CLionMenu", LOCTEXT("CLionMenuLabel", "CLion")); MenuBuilder.AddMenuEntry( LOCTEXT("CLionMenuGenerateCMakeListLabel", "Generate CMakeList"), LOCTEXT("CLionMenuGenerateCMakeListTooltip", "Generates the CMakeList file for the opened project."), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &FCLionSourceCodeAccessModule::HandleGenerateProjectFiles))); MenuBuilder.AddMenuEntry( LOCTEXT("CLionMenuOpenCLionLabel", "Open CLion"), LOCTEXT("CLionMenuOpenCLionTooltip", "Generates the CMakeList file, and opens CLion."), FSlateIcon(), FUIAction(FExecuteAction::CreateRaw(this, &FCLionSourceCodeAccessModule::HandleOpenCLion))); MenuBuilder.EndSection(); } void FCLionSourceCodeAccessModule::HandleGenerateProjectFiles() { CLionSourceCodeAccessor.GenerateProjectFile(); } void FCLionSourceCodeAccessModule::HandleOpenCLion() { CLionSourceCodeAccessor.OpenSolution(); } void FCLionSourceCodeAccessModule::RegisterMenu() { if (FModuleManager::Get().IsModuleLoaded("LevelEditor")) { FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked( TEXT("LevelEditor")); MainMenuExtender = MakeShareable(new FExtender); MainMenuExtender->AddMenuExtension("FileProject", EExtensionHook::After, NULL, FMenuExtensionDelegate::CreateRaw(this, &FCLionSourceCodeAccessModule::AddMenuOptions)); LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MainMenuExtender); } } void FCLionSourceCodeAccessModule::RegisterSettings() { if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) { SettingsModule->RegisterSettings("Project", "Plugins", "CLion", LOCTEXT("RuntimeSettingsName", "CLion"), LOCTEXT("RuntimeSettingsDescription", "Configure the CLion Integration"), GetMutableDefault()); } } void FCLionSourceCodeAccessModule::ShutdownModule() { CLionSourceCodeAccessor.Shutdown(); if (UObjectInitialized()) { this->UnregisterSettings(); } // unbind provider from editor IModularFeatures::Get().UnregisterModularFeature(TEXT("SourceCodeAccessor"), &CLionSourceCodeAccessor); } void FCLionSourceCodeAccessModule::StartupModule() { // Register our custom settings this->RegisterSettings(); // Register our custom menu additions this->RegisterMenu(); // Start her up CLionSourceCodeAccessor.Startup(); // Bind our source control provider to the editor IModularFeatures::Get().RegisterModularFeature(TEXT("SourceCodeAccessor"), &CLionSourceCodeAccessor); } bool FCLionSourceCodeAccessModule::SupportsDynamicReloading() { // TODO: Until we have this all fixed up return false; } void FCLionSourceCodeAccessModule::UnregisterSettings() { // Ensure to unregister all of your registered settings here, hot-reload would // otherwise yield unexpected results. if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) { SettingsModule->UnregisterSettings("Project", "CLionSettings", "General"); } } #undef LOCTEXT_NAMESPACE ================================================ FILE: Source/CLionSourceCodeAccess/Private/CLionSourceCodeAccessModule.h ================================================ // Copyright 2017 dotBunny Inc. All Rights Reserved. #pragma once #include "CLionSourceCodeAccessPrivatePCH.h" #include "CLionSourceCodeAccessor.h" class FCLionSourceCodeAccessModule : public IModuleInterface { public: /** IModuleInterface implementation */ virtual void StartupModule() override; virtual void ShutdownModule() override; virtual bool SupportsDynamicReloading() override; void AddMenuOptions(FMenuBuilder& MenuBuilder); private: TSharedPtr MainMenuExtender; FCLionSourceCodeAccessor CLionSourceCodeAccessor; void RegisterSettings(); void RegisterMenu(); void UnregisterSettings(); void HandleGenerateProjectFiles(); void HandleOpenCLion(); }; ================================================ FILE: Source/CLionSourceCodeAccess/Private/CLionSourceCodeAccessPrivatePCH.h ================================================ // Copyright 2017 dotBunny Inc. All Rights Reserved. #pragma once #include "Core.h" #include "UnrealEd.h" #include "ModuleManager.h" #include "ISourceCodeAccessModule.h" ================================================ FILE: Source/CLionSourceCodeAccess/Private/CLionSourceCodeAccessor.cpp ================================================ // Copyright 2017 dotBunny, Inc. All Rights Reserved. #include "CLionSourceCodeAccessPrivatePCH.h" #include "CLionSourceCodeAccessor.h" #define LOCTEXT_NAMESPACE "CLionSourceCodeAccessor" DEFINE_LOG_CATEGORY_STATIC(LogCLionAccessor, Log, All); bool FCLionSourceCodeAccessor::AddSourceFiles(const TArray& AbsoluteSourcePaths, const TArray& AvailableModules) { // There is no need to add the files to the make file because it already has wildcard searches of the necessary project folders return true; } bool FCLionSourceCodeAccessor::CanAccessSourceCode() const { return this->Settings->IsSetup(); } void FCLionSourceCodeAccessor::GenerateProjectFile() { if (!this->Settings->IsSetup()) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("SetupNotComplete", "The CLion plugin settings have not been completed.")); return; } if (!FPaths::IsProjectFilePathSet()) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ProjectFileNotFound", "A project file was not found.")); return; } // Due to the currently broken production of CMakeFiles in UBT, we create a CodeLite project and convert it when // a viable CMakeList generation is available this will change. if (!this->GenerateFromCodeLiteProject()) { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("ProjectGenerationFailed", "Unable to produce project files")); this->Settings->bRequireRefresh = true; } else { this->Settings->bRequireRefresh = false; } } bool FCLionSourceCodeAccessor::GenerateFromCodeLiteProject() { // Create our progress bar dialog. FScopedSlowTask ProjectGenerationTask(21, LOCTEXT("StartCMakeListGeneration", "Starting CMakeList Generation")); ProjectGenerationTask.MakeDialog(); ProjectGenerationTask.EnterProgressFrame(1, LOCTEXT("StartCMakeListGeneration", "Starting CMakeList Generation")); // Cache our path information FString UnrealBuildToolPath = *FPaths::ConvertRelativePathToFull( *FPaths::Combine(*FPaths::EngineDir(), TEXT("Binaries"), TEXT("DotNET"), TEXT("UnrealBuildTool.exe"))); FPaths::NormalizeFilename(UnrealBuildToolPath); FString ProjectFilePath = *FPaths::ConvertRelativePathToFull(*FPaths::GetProjectFilePath()); FPaths::NormalizeFilename(ProjectFilePath); FString ProjectPath = *FPaths::ConvertRelativePathToFull(*FPaths::ProjectDir()); FPaths::NormalizeFilename(ProjectPath); // Assign and filter our project name this->WorkingProjectName = FPaths::GetBaseFilename(ProjectFilePath, true); FPaths::NormalizeFilename(this->WorkingProjectName); // Reset our working folder, just incase this->WorkingMonoPath = ""; // Start our master CMakeList file FString OutputTemplate = TEXT("cmake_minimum_required (VERSION 2.6)\nproject (UE4)\n"); OutputTemplate.Append(TEXT("set(CMAKE_CXX_STANDARD 11)\n")); //Set Response files output OutputTemplate.Append(FString::Printf(TEXT("\nSET(CMAKE_CXX_USE_RESPONSE_FILE_FOR_OBJECTS 1 CACHE BOOL \"\" FORCE)"))); OutputTemplate.Append(FString::Printf(TEXT("\nSET(CMAKE_CXX_USE_RESPONSE_FILE_FOR_INCLUDES 1 CACHE BOOL \"\" FORCE)\n"))); // Handle CLang++ / CLang (If Defined) if (!this->Settings->CXXCompiler.FilePath.IsEmpty()) { OutputTemplate.Append( FString::Printf(TEXT("set(CMAKE_CXX_COMPILER \"%s\")\n"), *this->Settings->CXXCompiler.FilePath)); } if (!this->Settings->CCompiler.FilePath.IsEmpty()) { OutputTemplate.Append( FString::Printf(TEXT("set(CMAKE_C_COMPILER \"%s\")\n"), *this->Settings->CCompiler.FilePath)); } if (this->Settings->bGenerateVisualStudioCodeProject) { OutputTemplate.Append(TEXT("set(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n")); } // Add an empty line in the file, because we like organization. OutputTemplate.Append(TEXT("\n")); // Increase our progress ProjectGenerationTask.EnterProgressFrame(10, LOCTEXT("GeneratingCodLiteProject", "Generating CodeLite Project")); #if PLATFORM_WINDOWS const FString BuildProjectCommand = *UnrealBuildToolPath; const FString BuildProjectParameters = FString::Printf(TEXT("\"%s\" -Game \"%s\" -OnlyPublic -CodeLiteFiles -CurrentPlatform -NoShippingConfigs"), *ProjectFilePath, *this->WorkingProjectName); #else const FString BuildProjectCommand = *this->Settings->Mono.FilePath; const FString BuildProjectParameters = FString::Printf( TEXT("\"%s\" \"%s\" -Game \"%s\" -OnlyPublic -CodeLiteFiles -CurrentPlatform -NoShippingConfigs"), *UnrealBuildToolPath, *ProjectFilePath, *this->WorkingProjectName); #endif FProcHandle BuildProjectProcess = FPlatformProcess::CreateProc(*BuildProjectCommand, *BuildProjectParameters, true, true, false, nullptr, 0, nullptr, nullptr); if (!BuildProjectProcess.IsValid()) { UE_LOG(LogCLionAccessor, Error, TEXT("Failure to run UBT [%s %s]."), *BuildProjectCommand, *BuildProjectParameters); FPlatformProcess::CloseProc(BuildProjectProcess); return false; } // Wait for the process to finish before moving on FPlatformProcess::WaitForProc(BuildProjectProcess); // Enter next phase of generation ProjectGenerationTask.EnterProgressFrame(1, LOCTEXT("CheckingFiles", "Checking Files")); // Setup path for Includes files FString IncludeDirectoriesPath = FPaths::Combine(*ProjectPath, *FString::Printf(TEXT("%sCodeCompletionFolders.txt"), *this->WorkingProjectName)); FPaths::NormalizeFilename(IncludeDirectoriesPath); if (!FPaths::FileExists(IncludeDirectoriesPath)) { UE_LOG(LogCLionAccessor, Error, TEXT("Unable to find %s"), *IncludeDirectoriesPath); ProjectGenerationTask.EnterProgressFrame(9); return false; } // Setup path for Definitions file FString DefinitionsPath = FPaths::Combine(*ProjectPath, *FString::Printf(TEXT("%sCodeLitePreProcessor.txt"), *this->WorkingProjectName)); FPaths::NormalizeFilename(DefinitionsPath); if (!FPaths::FileExists(DefinitionsPath)) { UE_LOG(LogCLionAccessor, Error, TEXT("Unable to find %s"), *DefinitionsPath); ProjectGenerationTask.EnterProgressFrame(9); return false; } // Setup path for our master project file FString GeneratedProjectFilePath = FPaths::Combine(*ProjectPath, *FString::Printf(TEXT("%s.workspace"), *this->WorkingProjectName)); FPaths::NormalizeFilename(GeneratedProjectFilePath); if (!FPaths::FileExists(GeneratedProjectFilePath)) { UE_LOG(LogCLionAccessor, Error, TEXT("Unable to find %s"), *GeneratedProjectFilePath); ProjectGenerationTask.EnterProgressFrame(9); return false; } // Setup path for where we will output the sub CMake files FString ProjectFileOutputFolder = FPaths::Combine(*ProjectPath, TEXT("Intermediate"), TEXT("ProjectFiles")); FPaths::NormalizeFilename(ProjectFileOutputFolder); if (!FPaths::DirectoryExists(ProjectFileOutputFolder)) { FPlatformFileManager::Get().GetPlatformFile().CreateDirectoryTree(*ProjectFileOutputFolder); } ProjectGenerationTask.EnterProgressFrame(3, LOCTEXT("CreatingHelperCMakeFiles", "Creating Helper CMake Files")); // Gather our information on include directories FString IncludeDirectoriesData; FFileHelper::LoadFileToString(IncludeDirectoriesData, *IncludeDirectoriesPath); TArray IncludeDirectoriesLines; IncludeDirectoriesData.ParseIntoArrayLines(IncludeDirectoriesLines, true); FString IncludeDirectoriesContent = TEXT("set(INCLUDE_DIRECTORIES \n"); // Friendly requested helper for VS Code folks (this is not the main stay of the plugin, but it works) FString IncludeDirectoriesJSON = TEXT(""); for (FString Line : IncludeDirectoriesLines) { FPaths::NormalizeFilename(Line); IncludeDirectoriesContent.Append(FString::Printf(TEXT("\t\"%s\"\n"), *Line)); if ( this->Settings->bGenerateVisualStudioCodeProject ) { IncludeDirectoriesJSON.Append(FString::Printf(TEXT("\n\t\t\t\"%s\","), *Line)); } } IncludeDirectoriesContent.Append(TEXT(")\ninclude_directories(${INCLUDE_DIRECTORIES})\n")); // Output our Include Directories content and add an entry to the CMakeList FString IncludeDirectoriesOutputPath = FPaths::Combine(*ProjectFileOutputFolder, TEXT("IncludeDirectories.cmake")); FPaths::NormalizeFilename(IncludeDirectoriesOutputPath); if (!FFileHelper::SaveStringToFile(IncludeDirectoriesContent, *IncludeDirectoriesOutputPath, FFileHelper::EEncodingOptions::ForceAnsi)) { UE_LOG(LogCLionAccessor, Error, TEXT("Error writing %s"), *IncludeDirectoriesOutputPath); return false; } OutputTemplate.Append(FString::Printf(TEXT("include(\"%s\")\n"), *IncludeDirectoriesOutputPath)); // Output our VSCode project json file if ( this->Settings->bGenerateVisualStudioCodeProject ) { // Output Path FString IncludeJSONOutputPath = FPaths::Combine(*ProjectPath, TEXT(".vscode"), TEXT("c_cpp_properties.json")); // Build Content FString JSONOutput = TEXT(""); #if PLATFORM_MAC JSONOutput.Append(TEXT("{\n\t\"configurations\": [\n\t{\n\t\t\"name\": \"Mac\",\n\t\t\"includePath\": [")); #elif PLATFORM_LINUX JSONOutput.Append(TEXT("{\n\t\"configurations\": [\n\t{\n\t\t\"name\": \"Linux\",\n\t\t\"includePath\": [")); #else JSONOutput.Append(TEXT("{\n\t\"configurations\": [\n\t{\n\t\t\"name\": \"Windows\",\n\t\t\"includePath\": [")); #endif IncludeDirectoriesJSON.RemoveFromEnd(TEXT(","), ESearchCase::Type::IgnoreCase); JSONOutput.Append(IncludeDirectoriesJSON); JSONOutput.Append(TEXT("\n\t\t\t],\n\t\t\t\"browse\" : {\n\t\t\t\t\"limitSymbolsToIncludedHeaders\" : true, \n\t\t\t\t\"path\": [")); JSONOutput.Append(IncludeDirectoriesJSON); JSONOutput.Append(TEXT("\n\t\t\t\t]\n\t\t\t}\n\t\t}\n\t]\n}")); if (!FFileHelper::SaveStringToFile(JSONOutput, *IncludeJSONOutputPath, FFileHelper::EEncodingOptions::ForceAnsi)) { UE_LOG(LogCLionAccessor, Error, TEXT("Error writing %s"), *IncludeJSONOutputPath); return false; } } // Gather our information on definitions FString DefinitionsData; FFileHelper::LoadFileToString(DefinitionsData, *DefinitionsPath); TArray DefinitionsLines; DefinitionsData.ParseIntoArrayLines(DefinitionsLines, true); FString DefinitionsProcessed = TEXT("add_definitions(\n"); for (FString Line : DefinitionsLines) { DefinitionsProcessed.Append(FString::Printf(TEXT("\t-D%s\n"), *Line)); } DefinitionsProcessed.Append(TEXT(")\n")); // Output our Definitions content and add an entry to the CMakeList FString DefinitionsOutputPath = FPaths::Combine(*ProjectFileOutputFolder, TEXT("Definitions.cmake")); if (!FFileHelper::SaveStringToFile(DefinitionsProcessed, *DefinitionsOutputPath, FFileHelper::EEncodingOptions::ForceAnsi)) { UE_LOG(LogCLionAccessor, Error, TEXT("Error writing %s"), *DefinitionsOutputPath); return false; } OutputTemplate.Append(FString::Printf(TEXT("include(\"%s\")\n"), *DefinitionsOutputPath)); ProjectGenerationTask.EnterProgressFrame(5, LOCTEXT("CreatingProjectCMakeFiles", "Creating Project CMake Files")); // Handle finding the project file (we'll use this to determine the subprojects) FXmlFile* RootGeneratedProjectFile = new FXmlFile(); RootGeneratedProjectFile->LoadFile(*GeneratedProjectFilePath); const FXmlNode* RootProjectNode = RootGeneratedProjectFile->GetRootNode(); const TArray RootProjectNodes = RootProjectNode->GetChildrenNodes(); TArray ProjectNodes; // Iterate over nodes to come up with our project nodes for (FXmlNode* Node : RootProjectNodes) { if (Node->GetTag() == TEXT("Project")) { ProjectNodes.Add(Node); } } //WorkspaceConfiguration (DebugGame/Development/Shipping) // Iterate and create projects FXmlFile* WorkingGeneratedProjectFile = new FXmlFile(); FScopedSlowTask SubProjectGenerationTask(ProjectNodes.Num(), LOCTEXT("StartSubProjects", "Generating Sub Project File")); SubProjectGenerationTask.MakeDialog(); // This is gonna function as a storage block of what we think the mono path (inside of HandleConfiguration) for (FXmlNode* Node : ProjectNodes) { // Increment Progress Bar FString OutputProjectTemplate = ""; FString SubProjectName = Node->GetAttribute("Name"); // Determine if we want this subproject if ((!this->Settings->bProjectSpecificEditor && (SubProjectName.Contains(this->WorkingProjectName) && SubProjectName.EndsWith("Editor"))) || (!this->Settings->bProjectSpecificGame && SubProjectName.Equals(this->WorkingProjectName)) || (!this->Settings->bProjectUE4Editor && SubProjectName.Equals("UE4Editor")) || (!this->Settings->bProjectUE4Game && SubProjectName.Equals("UE4Game"))) { continue; } FString SubProjectFile = FPaths::Combine(*ProjectPath, *Node->GetAttribute("Path")); SubProjectGenerationTask.EnterProgressFrame(1); // Check the project file does exist if (!FPaths::FileExists(SubProjectFile)) { UE_LOG(LogCLionAccessor, Warning, TEXT("Cannot find %s"), *SubProjectFile); continue; } FString ProjectFilesProcessed; WorkingGeneratedProjectFile->LoadFile(*SubProjectFile); // This is painful as we're going to have to iterate through each node/child till we have none FXmlNode* CurrentNode = WorkingGeneratedProjectFile->GetRootNode(); // Call our recursive function to delve deep and get the data we need FString WorkingProjectFiles = this->GetAttributeByTagWithRestrictions(CurrentNode, TEXT("File"), TEXT("Name")); TArray WorkingProjectFilesLines; WorkingProjectFiles.ParseIntoArrayLines(WorkingProjectFilesLines, true); FString WorkingProjectFilesContent = TEXT(""); for (FString Line : WorkingProjectFilesLines) { FPaths::NormalizeFilename(Line); WorkingProjectFilesContent.Append(FString::Printf(TEXT("%s\n"), *Line)); } // Add file set to the project cmake file (this is so we split things up, so CLion does't have // any issues with the file size of one individual file. OutputProjectTemplate.Append( FString::Printf(TEXT("set(%s_FILES \n%s)\n"), *SubProjectName, *WorkingProjectFilesContent)); // Time to output this, determine the output path FString ProjectOutputPath = FPaths::Combine(*ProjectFileOutputFolder, *FString::Printf(TEXT("%s.cmake"), *SubProjectName)); if (!FFileHelper::SaveStringToFile(OutputProjectTemplate, *ProjectOutputPath, FFileHelper::EEncodingOptions::ForceAnsi)) { UE_LOG(LogCLionAccessor, Error, TEXT("Error writing %s"), *ProjectOutputPath); return false; } // Add Include Of Project Files OutputTemplate.Append(FString::Printf(TEXT("include(\"%s\")\n"), *ProjectOutputPath)); //Get working directory and build command FString CustomTargets = this->GetBuildCommands(CurrentNode, SubProjectName); OutputTemplate.Append(CustomTargets); } ProjectGenerationTask.EnterProgressFrame(1, LOCTEXT("CreatingCMakeListsFile", "Creating CMakeLists.txt File")); // Add Executable Definition To Main Template OutputTemplate.Append( FString::Printf(TEXT("add_executable(PleaseIgnoreMe ${%sEditor_FILES})"), *this->WorkingProjectName)); // Write out the file if (!FFileHelper::SaveStringToFile(OutputTemplate, *this->Settings->GetCMakeListPath(), FFileHelper::EEncodingOptions::ForceAnsi)) { UE_LOG(LogCLionAccessor, Error, TEXT("Error writing %s"), *this->Settings->GetCMakeListPath()); return false; } return true; } FString FCLionSourceCodeAccessor::GetAttributeByTagWithRestrictions(FXmlNode* CurrentNode, const FString& Tag, const FString& Attribute) { FString ReturnContent = ""; if (CurrentNode->GetTag() == Tag) { ReturnContent.Append(FString::Printf(TEXT("\t\"%s\"\n"), *CurrentNode->GetAttribute(Attribute))); } const TArray childrenNodes = CurrentNode->GetChildrenNodes(); for (FXmlNode* Node : childrenNodes) { //Don't get files from "Config", "Plugins", "Shaders" if (Node->GetTag() == TEXT("VirtualDirectory")) { const FString& name = Node->GetAttribute(TEXT("Name")); if (!this->Settings->bIncludeConfigs && name == TEXT("Config")) { continue; } if (!this->Settings->bIncludePlugins && name == TEXT("Plugins")) { continue; } if (!this->Settings->bIncludeShaders && name == TEXT("Shaders")) { continue; } } ReturnContent += FCLionSourceCodeAccessor::GetAttributeByTagWithRestrictions(Node, Tag, Attribute); } return ReturnContent; } FString FCLionSourceCodeAccessor::GetBuildCommands(FXmlNode* CurrentNode, const FString& SubprojectName) { FString ReturnContent = ""; if (CurrentNode->GetTag() != TEXT("Settings")) { const TArray childrenNodes = CurrentNode->GetChildrenNodes(); for (FXmlNode* Node : childrenNodes) { ReturnContent += FCLionSourceCodeAccessor::GetBuildCommands(Node, SubprojectName); } } else { const TArray childrenNodes = CurrentNode->GetChildrenNodes(); for (FXmlNode* Node : childrenNodes) { if (Node->GetTag() == TEXT("Configuration")) { ReturnContent += FCLionSourceCodeAccessor::HandleConfiguration(Node, SubprojectName); } } } return ReturnContent; } FString FCLionSourceCodeAccessor::HandleConfiguration(FXmlNode* CurrentNode, const FString& SubprojectName) { FString ReturnContent = ""; const FString& ConfigurationName = CurrentNode->GetAttribute(TEXT("Name")); FString WorkingDirectory = ""; FString BuildCommand = ""; FString CleanCommand = ""; const TArray childrenNodes = CurrentNode->GetChildrenNodes(); for (FXmlNode* Node : childrenNodes) { if ((Node->GetTag() == TEXT("CustomBuild")) && (Node->GetAttribute(TEXT("Enabled")) == TEXT("yes"))) { const TArray subchildrenNodes = Node->GetChildrenNodes(); for (FXmlNode* subNode : subchildrenNodes) { if (subNode->GetTag() == TEXT("WorkingDirectory")) { WorkingDirectory = subNode->GetContent(); FPaths::NormalizeFilename(WorkingDirectory); } if (subNode->GetTag() == TEXT("BuildCommand")) { BuildCommand = subNode->GetContent(); FPaths::NormalizeFilename(BuildCommand); } if (subNode->GetTag() == TEXT("CleanCommand")) { CleanCommand = subNode->GetContent(); FPaths::NormalizeFilename(CleanCommand); } } if (!this->WorkingMonoPath.Equals(WorkingDirectory)) { // Do this to avoid duplication in CMakeLists.txt ReturnContent += FString::Printf(TEXT("\nset(MONO_ROOT_PATH \"%s\")\n"), *WorkingDirectory); #if PLATFORM_WINDOWS ReturnContent += FString::Printf(TEXT("set(BUILD cd /d \"${MONO_ROOT_PATH}\")\n")); #else ReturnContent += FString::Printf(TEXT("set(BUILD cd \"${MONO_ROOT_PATH}\")\n")); #endif this->WorkingMonoPath = WorkingDirectory; } if ((ConfigurationName == TEXT("Debug") && !this->Settings->bConfigureDebug) || (ConfigurationName == TEXT("DebugGame") && !this->Settings->bConfigureDebugGame) || (ConfigurationName == TEXT("Development") && !this->Settings->bConfigureDevelopment) || (ConfigurationName == TEXT("Shipping") && !this->Settings->bConfigureShipping) || (ConfigurationName == TEXT("Test") && !this->Settings->bConfigureTest)) { } else { ReturnContent += FString::Printf(TEXT("\n# Custom target for %s project, %s configuration\n"), *SubprojectName, *ConfigurationName); ReturnContent += FString::Printf(TEXT("add_custom_target(%s-%s ${BUILD} && %s -game -progress)\n"), *SubprojectName, *ConfigurationName, *BuildCommand); ReturnContent += FString::Printf(TEXT("add_custom_target(%s-%s-CLEAN ${BUILD} && %s)\n\n"), *SubprojectName, *ConfigurationName, *CleanCommand); } } } return ReturnContent; } FText FCLionSourceCodeAccessor::GetDescriptionText() const { return LOCTEXT("CLionDisplayDesc", "Open source code files in CLion"); } FName FCLionSourceCodeAccessor::GetFName() const { return FName("CLionSourceCodeAccessor"); } FText FCLionSourceCodeAccessor::GetNameText() const { return LOCTEXT("CLionDisplayName", "CLion"); } bool FCLionSourceCodeAccessor::OpenFileAtLine(const FString& FullPath, int32 LineNumber, int32 ColumnNumber) { if (!this->Settings->IsSetup()) { UE_LOG(LogCLionAccessor, Warning, TEXT("Please configure the CLion integration in your project settings.")); return false; } if (this->Settings->bRequireRefresh || !FPaths::FileExists(*this->Settings->GetCMakeListPath())) { this->GenerateProjectFile(); } const FString Path = FString::Printf(TEXT("\"%s\" --line %d \"%s\""), *FPaths::ConvertRelativePathToFull(*FPaths::ProjectDir()), LineNumber, *FullPath); FProcHandle Proc = FPlatformProcess::CreateProc(*this->Settings->CLion.FilePath, *Path, true, true, false, nullptr, 0, nullptr, nullptr); if (!Proc.IsValid()) { UE_LOG(LogCLionAccessor, Warning, TEXT("Opening file (%s) at a specific line failed."), *Path); FPlatformProcess::CloseProc(Proc); return false; } return true; } bool FCLionSourceCodeAccessor::OpenSolution() { if (!this->Settings->IsSetup()) { UE_LOG(LogCLionAccessor, Warning, TEXT("Please configure the CLion integration in your project settings.")); return false; } // TODO: Add check for CMakeProject file, if not there generate if (this->Settings->bRequireRefresh) { this->GenerateProjectFile(); } if (this->Settings->bRequireRefresh || !FPaths::FileExists(*this->Settings->GetCMakeListPath())) { this->GenerateProjectFile(); } const FString Path = FString::Printf(TEXT("\"%s\""), *FPaths::ConvertRelativePathToFull(*FPaths::ProjectDir())); if (FPlatformProcess::CreateProc(*this->Settings->CLion.FilePath, *Path, true, true, false, nullptr, 0, nullptr, nullptr).IsValid()) { // This seems to always fail no matter what we do - could be the process type? Get rid of the warning for now //UE_LOG(LogCLionAccessor, Warning, TEXT("Opening the solution failed.")); return false; } return true; } bool FCLionSourceCodeAccessor::OpenSolutionAtPath(const FString& InSolutionPath) { // TODO: Add check for CMakeProject file, if not there generate if (this->Settings->bRequireRefresh) { this->GenerateProjectFile(); } if (this->Settings->bRequireRefresh || !FPaths::FileExists(*this->Settings->GetCMakeListPath())) { this->GenerateProjectFile(); } if (FPlatformProcess::CreateProc(*this->Settings->CLion.FilePath, *InSolutionPath, true, true, false, nullptr, 0, nullptr, nullptr).IsValid()) { // This seems to always fail no matter what we do - could be the process type? Get rid of the warning for now //UE_LOG(LogCLionAccessor, Warning, TEXT("Opening the solution failed.")); return false; } return true; } bool FCLionSourceCodeAccessor::OpenSourceFiles(const TArray& AbsoluteSourcePaths) { if (!this->Settings->IsSetup()) { UE_LOG(LogCLionAccessor, Warning, TEXT("Please configure the CLion integration in your project settings.")); return false; } if (this->Settings->bRequireRefresh || !FPaths::FileExists(*this->Settings->GetCMakeListPath())) { this->GenerateProjectFile(); } FString sourceFilesList = ""; // Build our paths based on what unreal sends to be opened for (const auto& SourcePath : AbsoluteSourcePaths) { sourceFilesList = FString::Printf(TEXT("%s \"%s\""), *sourceFilesList, *SourcePath); } // Trim any whitespace on our source file list sourceFilesList.TrimStartInline(); sourceFilesList.TrimEndInline(); FProcHandle Proc = FPlatformProcess::CreateProc(*this->Settings->CLion.FilePath, *sourceFilesList, true, false, false, nullptr, 0, nullptr, nullptr); if (!Proc.IsValid()) { UE_LOG(LogCLionAccessor, Warning, TEXT("Opening the source file (%s) failed."), *sourceFilesList); FPlatformProcess::CloseProc(Proc); return true; } return false; } bool FCLionSourceCodeAccessor::SaveAllOpenDocuments() const { // TODO: Implement saving remotely? return true; } bool FCLionSourceCodeAccessor::DoesSolutionExist() const { // TODO: check if the solution really exists return true; } void FCLionSourceCodeAccessor::Shutdown() { this->Settings = nullptr; } void FCLionSourceCodeAccessor::Startup() { // Get reference to our settings object this->Settings = GetMutableDefault(); this->Settings->CheckSettings(); } #undef LOCTEXT_NAMESPACE ================================================ FILE: Source/CLionSourceCodeAccess/Private/CLionSourceCodeAccessor.h ================================================ // Copyright 2017 dotBunny Inc. All Rights Reserved. #pragma once #include "CLionSourceCodeAccessPrivatePCH.h" #include "ISourceCodeAccessor.h" #include "XmlParser.h" #include "CLionSettings.h" class FCLionSourceCodeAccessor : public ISourceCodeAccessor { public: /** ISourceCodeAccessor implementation */ virtual void RefreshAvailability() override { } virtual bool CanAccessSourceCode() const override; virtual FName GetFName() const override; virtual FText GetNameText() const override; virtual FText GetDescriptionText() const override; virtual bool OpenSolution() override; virtual bool OpenSolutionAtPath(const FString& InSolutionPath) override; virtual bool OpenFileAtLine(const FString& FullPath, int32 LineNumber, int32 ColumnNumber = 0) override; virtual bool OpenSourceFiles(const TArray& AbsoluteSourcePaths) override; virtual bool AddSourceFiles(const TArray& AbsoluteSourcePaths, const TArray& AvailableModules) override; virtual bool SaveAllOpenDocuments() const override; virtual bool DoesSolutionExist() const override; /** * Frame Tick (Not Used) * @param DeltaTime of frame */ virtual void Tick(const float DeltaTime) override { } /** * Create the CMakeLists.txt file and all the sub *.cmake addins. */ void GenerateProjectFile(); /** * Deinitialize the accessor. */ void Shutdown(); /** * Initialize the accessor. */ void Startup(); private: /** * A local storage of the working Project name as we parse files */ FString WorkingProjectName; /** * A local reference to the Settings object. */ UCLionSettings* Settings; /** * A local storage of the working Mono path found while parsing files */ FString WorkingMonoPath; /** * Instruct UnrealBuildTool to generate a CodeLite project, then convert it to CMakeList * @return Was the project generation successful? */ bool GenerateFromCodeLiteProject(); /** * Recursively search XmlNode for children namd Tag, and grab their Attribute. * @param The root XmlNode to search from. * @param The tag of the elements to uses. * @param The attribute that we want to collect. * @return A CMakeList compatible string set of the attributes. */ FString GetAttributeByTagWithRestrictions(FXmlNode* CurrentNode, const FString& Tag, const FString& Attribute); FString GetBuildCommands(FXmlNode* CurrentNode, const FString& SubprojectName); FString HandleConfiguration(FXmlNode* CurrentNode, const FString& SubprojectName); };