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);
};