366 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "CSProcHelper.h"
 | |
| #include "UnrealSharpProcHelper.h"
 | |
| #include "XmlFile.h"
 | |
| #include "XmlNode.h"
 | |
| #include "Misc/App.h"
 | |
| #include "Misc/Paths.h"
 | |
| #include "Interfaces/IPluginManager.h"
 | |
| #include "Misc/MessageDialog.h"
 | |
| 
 | |
| bool FCSProcHelper::InvokeCommand(const FString& ProgramPath, const FString& Arguments, int32& OutReturnCode, FString& Output, const FString* InWorkingDirectory)
 | |
| {
 | |
| 	double StartTime = FPlatformTime::Seconds();
 | |
| 	FString ProgramName = FPaths::GetBaseFilename(ProgramPath);
 | |
| 	FString WorkingDirectory = InWorkingDirectory ? *InWorkingDirectory : FPaths::GetPath(ProgramPath);
 | |
| 
 | |
| 	FString ErrorMessage;
 | |
| 	FPlatformProcess::ExecProcess(*ProgramPath, *Arguments, &OutReturnCode, &Output, &ErrorMessage, *WorkingDirectory);
 | |
| 
 | |
| 	if (OutReturnCode != 0)
 | |
| 	{
 | |
| 		UE_LOG(LogUnrealSharpProcHelper, Error, TEXT("%s task failed (Args: %s) with return code %d. Error: %s"), *ProgramName, *Arguments, OutReturnCode, *Output)
 | |
| 
 | |
| 		FText DialogText = FText::FromString(FString::Printf(TEXT("%s task failed: \n %s"), *ProgramName, *Output));
 | |
| 		FMessageDialog::Open(EAppMsgType::Ok, DialogText);
 | |
| 		return false;
 | |
| 	}
 | |
| 
 | |
| 	double EndTime = FPlatformTime::Seconds();
 | |
| 	double ElapsedTime = EndTime - StartTime;
 | |
| 	UE_LOG(LogUnrealSharpProcHelper, Log, TEXT("%s with args (%s) took %f seconds to execute."), *ProgramName, *Arguments, ElapsedTime);
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| bool FCSProcHelper::InvokeUnrealSharpBuildTool(const FString& BuildAction, const TMap<FString, FString>& AdditionalArguments)
 | |
| {
 | |
| 	FString PluginFolder = FPaths::ConvertRelativePathToFull(IPluginManager::Get().FindPlugin(UE_PLUGIN_NAME)->GetBaseDir());
 | |
| 	FString DotNetPath = GetDotNetExecutablePath();
 | |
| 
 | |
| 	FString Args;
 | |
| 	Args += FString::Printf(TEXT(" --Action %s"), *BuildAction);
 | |
| 	Args += FString::Printf(TEXT(" --EngineDirectory \"%s\""), *FPaths::ConvertRelativePathToFull(FPaths::EngineDir()));
 | |
| 	Args += FString::Printf(TEXT(" --ProjectDirectory \"%s\""), *FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()));
 | |
| 	Args += FString::Printf(TEXT(" --ProjectName %s"), FApp::GetProjectName());
 | |
| 	Args += FString::Printf(TEXT(" --PluginDirectory \"%s\""), *PluginFolder);
 | |
| 	Args += FString::Printf(TEXT(" --DotNetPath \"%s\""), *DotNetPath);
 | |
| 
 | |
| 	if (AdditionalArguments.Num())
 | |
| 	{
 | |
| 		Args += TEXT(" --AdditionalArgs");
 | |
| 		for (const TPair<FString, FString>& Argument : AdditionalArguments)
 | |
| 		{
 | |
| 			Args += FString::Printf(TEXT(" %s=%s"), *Argument.Key, *Argument.Value);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	int32 ReturnCode = 0;
 | |
| 	FString Output;
 | |
| 	FString WorkingDirectory = GetPluginAssembliesPath();
 | |
| 	return InvokeCommand(GetUnrealSharpBuildToolPath(), Args, ReturnCode, Output, &WorkingDirectory);
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetLatestHostFxrPath()
 | |
| {
 | |
| 	FString DotNetRoot = GetDotNetDirectory();
 | |
| 	FString HostFxrRoot = FPaths::Combine(DotNetRoot, "host", "fxr");
 | |
| 
 | |
| 	TArray<FString> Folders;
 | |
| 	IFileManager::Get().FindFiles(Folders, *(HostFxrRoot / "*"), true, true);
 | |
| 
 | |
| 	FString HighestVersion = "0.0.0";
 | |
| 	for (const FString &Folder : Folders)
 | |
| 	{
 | |
| 		if (Folder > HighestVersion)
 | |
| 		{
 | |
| 			HighestVersion = Folder;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (HighestVersion == "0.0.0")
 | |
| 	{
 | |
| 		UE_LOG(LogUnrealSharpProcHelper, Fatal, TEXT("Failed to find hostfxr version in %s"), *HostFxrRoot);
 | |
| 		return "";
 | |
| 	}
 | |
| 
 | |
| 	if (HighestVersion < DOTNET_MAJOR_VERSION)
 | |
| 	{
 | |
| 		UE_LOG(LogUnrealSharpProcHelper, Fatal, TEXT("Hostfxr version %s is less than the required version %s"), *HighestVersion, TEXT(DOTNET_MAJOR_VERSION));
 | |
| 		return "";
 | |
| 	}
 | |
| 
 | |
| #ifdef _WIN32
 | |
| 	return FPaths::Combine(HostFxrRoot, HighestVersion, HOSTFXR_WINDOWS);
 | |
| #elif defined(__APPLE__)
 | |
| 	return FPaths::Combine(HostFxrRoot, HighestVersion, HOSTFXR_MAC);
 | |
| #else
 | |
| 	return FPaths::Combine(HostFxrRoot, HighestVersion, HOSTFXR_LINUX);
 | |
| #endif
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetRuntimeHostPath()
 | |
| {
 | |
| #if WITH_EDITOR
 | |
| 	return GetLatestHostFxrPath();
 | |
| #else
 | |
| #ifdef _WIN32
 | |
| 	return FPaths::Combine(GetPluginAssembliesPath(), HOSTFXR_WINDOWS);
 | |
| #elif defined(__APPLE__)
 | |
| 	return FPaths::Combine(GetPluginAssembliesPath(), HOSTFXR_MAC);
 | |
| #else
 | |
| 	return FPaths::Combine(GetPluginAssembliesPath(), HOSTFXR_LINUX);
 | |
| #endif
 | |
| #endif
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetPathToSolution()
 | |
| {
 | |
| 	static FString SolutionPath = GetScriptFolderDirectory() / GetUserManagedProjectName() + ".sln";
 | |
| 	return SolutionPath;
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetPluginAssembliesPath()
 | |
| {
 | |
| #if WITH_EDITOR
 | |
| 	return FPaths::Combine(GetPluginDirectory(), "Binaries", "Managed");
 | |
| #else
 | |
| 	return GetUserAssemblyDirectory();
 | |
| #endif
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetUnrealSharpPluginsPath()
 | |
| {
 | |
| 	return GetPluginAssembliesPath() / "UnrealSharp.Plugins.dll";
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetRuntimeConfigPath()
 | |
| {
 | |
| 	return GetPluginAssembliesPath() / "UnrealSharp.runtimeconfig.json";
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetUserAssemblyDirectory()
 | |
| {
 | |
| 	return FPaths::ConvertRelativePathToFull(FPaths::Combine(FPaths::ProjectDir(), "Binaries", "Managed"));
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetUnrealSharpMetadataPath()
 | |
| {
 | |
| 	return FPaths::Combine(GetUserAssemblyDirectory(), "UnrealSharp.assemblyloadorder.json");
 | |
| }
 | |
| 
 | |
| void FCSProcHelper::GetProjectNamesByLoadOrder(TArray<FString>& UserProjectNames, const bool bIncludeGlue)
 | |
| {
 | |
| 	const FString ProjectMetadataPath = GetUnrealSharpMetadataPath();
 | |
| 
 | |
| 	if (!FPaths::FileExists(ProjectMetadataPath))
 | |
| 	{
 | |
| 		// Can be null at the start of the project.
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	FString JsonString;
 | |
| 	if (!FFileHelper::LoadFileToString(JsonString, *ProjectMetadataPath))
 | |
| 	{
 | |
| 		UE_LOG(LogUnrealSharpProcHelper, Fatal, TEXT("Failed to load UnrealSharp metadata file at: %s"), *ProjectMetadataPath);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	TSharedPtr<FJsonObject> JsonObject;
 | |
| 	if (!FJsonSerializer::Deserialize(TJsonReaderFactory<>::Create(JsonString), JsonObject) || !JsonObject.IsValid())
 | |
| 	{
 | |
| 		UE_LOG(LogUnrealSharpProcHelper, Fatal, TEXT("Failed to parse UnrealSharp metadata at: %s"), *ProjectMetadataPath);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	for (const TSharedPtr<FJsonValue>& OrderEntry : JsonObject->GetArrayField(TEXT("AssemblyLoadingOrder")))
 | |
| 	{
 | |
| 		FString ProjectName = OrderEntry->AsString();
 | |
| 
 | |
| 		if (!bIncludeGlue && ProjectName.EndsWith(TEXT("Glue")))
 | |
| 		{
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		UserProjectNames.Add(OrderEntry->AsString());
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 
 | |
| void FCSProcHelper::GetAssemblyPathsByLoadOrder(TArray<FString>& AssemblyPaths, const bool bIncludeGlue)
 | |
| {
 | |
| 	FString AbsoluteFolderPath = GetUserAssemblyDirectory();
 | |
| 
 | |
| 	TArray<FString> ProjectNames;
 | |
| 	GetProjectNamesByLoadOrder(ProjectNames, bIncludeGlue);
 | |
| 
 | |
| 	for (const FString& ProjectName : ProjectNames)
 | |
| 	{
 | |
| 		const FString AssemblyPath = FPaths::Combine(AbsoluteFolderPath, ProjectName + TEXT(".dll"));
 | |
| 		AssemblyPaths.Add(AssemblyPath);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| void FCSProcHelper::GetAllProjectPaths(TArray<FString>& ProjectPaths, bool bIncludeProjectGlue)
 | |
| {
 | |
| 	// Use the FileManager to find files matching the pattern
 | |
| 	IFileManager::Get().FindFilesRecursive(ProjectPaths,
 | |
| 		*GetScriptFolderDirectory(),
 | |
| 		TEXT("*.csproj"),
 | |
| 		true,
 | |
| 		false,
 | |
| 		false);
 | |
| 
 | |
|     TArray<FString> PluginFilePaths;
 | |
|     IPluginManager::Get().FindPluginsUnderDirectory(FPaths::ProjectPluginsDir(), PluginFilePaths);
 | |
| 	
 | |
|     for (const FString& PluginFilePath : PluginFilePaths)
 | |
|     {
 | |
|         FString ScriptDirectory = FPaths::GetPath(PluginFilePath) / "Script";
 | |
|         IFileManager::Get().FindFilesRecursive(ProjectPaths,
 | |
|             *ScriptDirectory,
 | |
|             TEXT("*.csproj"),
 | |
|             true,
 | |
|             false,
 | |
|             false);
 | |
|     }
 | |
| 
 | |
| 	for (int32 i = ProjectPaths.Num() - 1; i >= 0; i--)
 | |
| 	{
 | |
| 		if (bIncludeProjectGlue || !ProjectPaths[i].EndsWith("Glue.csproj"))
 | |
| 		{
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		ProjectPaths.RemoveAt(i);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| bool FCSProcHelper::IsProjectReloadable(FStringView ProjectPath)
 | |
| {
 | |
|     FXmlFile ProjectFile(ProjectPath.GetData());
 | |
|     if (!ProjectFile.IsValid())
 | |
|     {
 | |
|         UE_LOG(LogUnrealSharpProcHelper, Warning, TEXT("Failed to parse project file as XML: %s"),
 | |
|             ProjectPath.GetData());
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     const FXmlNode* RootNode = ProjectFile.GetRootNode();
 | |
|     if (!RootNode)
 | |
|     {
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     // Look through all PropertyGroup elements
 | |
|     for (const TArray<FXmlNode*>& ProjectNodes = RootNode->GetChildrenNodes();
 | |
|         const FXmlNode* Node : ProjectNodes)
 | |
|     {
 | |
|         if (Node->GetTag() == TEXT("PropertyGroup"))
 | |
|         {
 | |
|             if (const FXmlNode* RoslynComponentNode = Node->FindChildNode(TEXT("ExcludeFromWeaver"));
 | |
|                 RoslynComponentNode &&
 | |
|                 RoslynComponentNode->GetContent().Equals(TEXT("true"), ESearchCase::IgnoreCase))
 | |
|             {
 | |
|                 return false;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetUnrealSharpBuildToolPath()
 | |
| {
 | |
| #if PLATFORM_WINDOWS
 | |
| 	return FPaths::ConvertRelativePathToFull(GetPluginAssembliesPath() / "UnrealSharpBuildTool.exe");
 | |
| #else
 | |
| 	return FPaths::ConvertRelativePathToFull(GetPluginAssembliesPath() / "UnrealSharpBuildTool");
 | |
| #endif
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetDotNetDirectory()
 | |
| {
 | |
| 	const FString PathVariable = FPlatformMisc::GetEnvironmentVariable(TEXT("PATH"));
 | |
| 
 | |
| 	TArray<FString> Paths;
 | |
| 	PathVariable.ParseIntoArray(Paths, FPlatformMisc::GetPathVarDelimiter());
 | |
| 
 | |
| #if defined(_WIN32)
 | |
| 	FString PathDotnet = "Program Files\\dotnet\\";
 | |
| #elif defined(__APPLE__)
 | |
| 	FString PathDotnet = "/usr/local/share/dotnet/";
 | |
| 	return PathDotnet;
 | |
| #endif
 | |
| 	for (FString &Path : Paths)
 | |
| 	{
 | |
| 		if (!Path.Contains(PathDotnet))
 | |
| 		{
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		if (!FPaths::DirectoryExists(Path))
 | |
| 		{
 | |
| 			UE_LOG(LogUnrealSharpProcHelper, Warning, TEXT("Found path to DotNet, but the directory doesn't exist: %s"), *Path);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		return Path;
 | |
| 	}
 | |
| 	return "";
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetDotNetExecutablePath()
 | |
| {
 | |
| #if defined(_WIN32)
 | |
| 	return GetDotNetDirectory() + "dotnet.exe";
 | |
| #else
 | |
| 	return GetDotNetDirectory() + "dotnet";
 | |
| #endif
 | |
| }
 | |
| 
 | |
| FString& FCSProcHelper::GetPluginDirectory()
 | |
| {
 | |
| 	static FString PluginDirectory;
 | |
| 
 | |
| 	if (PluginDirectory.IsEmpty())
 | |
| 	{
 | |
| 		TSharedPtr<IPlugin> Plugin = IPluginManager::Get().FindPlugin(UE_PLUGIN_NAME);
 | |
| 		check(Plugin);
 | |
| 		PluginDirectory = Plugin->GetBaseDir();
 | |
| 	}
 | |
| 
 | |
| 	return PluginDirectory;
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetUnrealSharpDirectory()
 | |
| {
 | |
| 	return FPaths::Combine(GetPluginDirectory(), "Managed", "UnrealSharp");
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetGeneratedClassesDirectory()
 | |
| {
 | |
| 	return FPaths::Combine(GetUnrealSharpDirectory(), "UnrealSharp", "Generated");
 | |
| }
 | |
| 
 | |
| const FString& FCSProcHelper::GetScriptFolderDirectory()
 | |
| {
 | |
| 	static FString ScriptFolderDirectory = FPaths::ProjectDir() / "Script";
 | |
| 	return ScriptFolderDirectory;
 | |
| }
 | |
| 
 | |
| const FString& FCSProcHelper::GetPluginsDirectory()
 | |
| {
 | |
|     static FString PluginsDirectory = FPaths::ProjectDir() / "Plugins";
 | |
|     return PluginsDirectory;
 | |
| }
 | |
| 
 | |
| const FString& FCSProcHelper::GetProjectGlueFolderPath()
 | |
| {
 | |
| 	static FString ProjectGlueFolderPath = GetScriptFolderDirectory() / FApp::GetProjectName() + TEXT(".Glue");
 | |
| 	return ProjectGlueFolderPath;
 | |
| }
 | |
| 
 | |
| FString FCSProcHelper::GetUserManagedProjectName()
 | |
| {
 | |
| 	return FString::Printf(TEXT("Managed%s"), FApp::GetProjectName());
 | |
| }
 |