253 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			253 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | using System; | |||
|  | using System.Collections.Generic; | |||
|  | using System.Collections.ObjectModel; | |||
|  | using System.Diagnostics; | |||
|  | using System.IO; | |||
|  | using System.Text; | |||
|  | 
 | |||
|  | namespace UnrealSharp.Shared; | |||
|  | 
 | |||
|  | public static class DotNetUtilities | |||
|  | { | |||
|  | 	public const string DOTNET_MAJOR_VERSION = "9.0"; | |||
|  | 	public const string DOTNET_MAJOR_VERSION_DISPLAY = "net" + DOTNET_MAJOR_VERSION; | |||
|  | 
 | |||
|  |     public static string FindDotNetExecutable() | |||
|  |     { | |||
|  | 		const string DOTNET_WIN = "dotnet.exe"; | |||
|  | 		const string DOTNET_UNIX = "dotnet"; | |||
|  | 
 | |||
|  | 		var dotnetExe = OperatingSystem.IsWindows() ? DOTNET_WIN : DOTNET_UNIX; | |||
|  | 
 | |||
|  |     	var pathVariable = Environment.GetEnvironmentVariable("PATH"); | |||
|  | 
 | |||
|  |     	if (pathVariable == null) | |||
|  |     	{ | |||
|  |     		throw new Exception($"Couldn't find {dotnetExe}!"); | |||
|  |     	} | |||
|  | 
 | |||
|  |     	var paths = pathVariable.Split(Path.PathSeparator); | |||
|  | 
 | |||
|  |     	foreach (var path in paths) | |||
|  |     	{ | |||
|  |     		// This is a hack to avoid using the dotnet.exe from the Unreal Engine installation directory. | |||
|  |     		// Can't use the dotnet.exe from the Unreal Engine installation directory because it's .NET 6.0 | |||
|  |     		if (!path.Contains(@"\dotnet\")) | |||
|  |     		{ | |||
|  |     			continue; | |||
|  |     		} | |||
|  | 
 | |||
|  |     		var dotnetExePath = Path.Combine(path, dotnetExe); | |||
|  | 
 | |||
|  |     		if (File.Exists(dotnetExePath)) | |||
|  |     		{ | |||
|  |     			return dotnetExePath; | |||
|  |     		} | |||
|  |     	} | |||
|  | 
 | |||
|  |     	if ( OperatingSystem.IsMacOS() ) { | |||
|  | 			if ( File.Exists( "/usr/local/share/dotnet/dotnet" ) ) { | |||
|  | 				return "/usr/local/share/dotnet/dotnet"; | |||
|  | 			} | |||
|  | 			if ( File.Exists( "/opt/homebrew/bin/dotnet" ) ) { | |||
|  | 				return "/opt/homebrew/bin/dotnet"; | |||
|  | 			} | |||
|  | 		} | |||
|  | 
 | |||
|  | 		throw new Exception($"Couldn't find {dotnetExe} in PATH!"); | |||
|  |     } | |||
|  | 
 | |||
|  |     public static string GetLatestDotNetSdkPath() | |||
|  |     { | |||
|  | 	    string dotNetExecutable = FindDotNetExecutable(); | |||
|  | 	    string dotNetExecutableDirectory = Path.GetDirectoryName(dotNetExecutable)!; | |||
|  | 	    string dotNetSdkDirectory = Path.Combine(dotNetExecutableDirectory!, "sdk"); | |||
|  | 
 | |||
|  | 	    string[] folderPaths = Directory.GetDirectories(dotNetSdkDirectory); | |||
|  | 
 | |||
|  | 	    string highestVersion = "0.0.0"; | |||
|  | 
 | |||
|  | 	    foreach (string folderPath in folderPaths) | |||
|  | 	    { | |||
|  | 		    string folderName = Path.GetFileName(folderPath); | |||
|  | 
 | |||
|  | 		    if (string.IsNullOrEmpty(folderName) || !char.IsDigit(folderName[0])) | |||
|  | 		    { | |||
|  | 			    continue; | |||
|  | 		    } | |||
|  | 
 | |||
|  | 		    if (string.Compare(folderName, highestVersion, StringComparison.Ordinal) > 0) | |||
|  | 		    { | |||
|  | 			    highestVersion = folderName; | |||
|  | 		    } | |||
|  | 	    } | |||
|  | 
 | |||
|  | 	    if (highestVersion == "0.0.0") | |||
|  | 	    { | |||
|  | 		    throw new Exception("Failed to find the latest .NET SDK version."); | |||
|  | 	    } | |||
|  | 
 | |||
|  | 	    if (!highestVersion.StartsWith(DOTNET_MAJOR_VERSION)) | |||
|  | 	    { | |||
|  | 		    throw new Exception($"Failed to find the latest .NET SDK version. Expected version to start with {DOTNET_MAJOR_VERSION} but found: {highestVersion}"); | |||
|  | 	    } | |||
|  | 
 | |||
|  | 	    return Path.Combine(dotNetSdkDirectory, highestVersion); | |||
|  |     } | |||
|  | 
 | |||
|  |     public static void BuildSolution(string projectRootDirectory, string managedBinariesPath) | |||
|  |     { | |||
|  |     	if (!Directory.Exists(projectRootDirectory)) | |||
|  |     	{ | |||
|  |     		throw new Exception($"Couldn't find project root directory: {projectRootDirectory}"); | |||
|  |     	} | |||
|  | 
 | |||
|  | 	    if (!Directory.Exists(managedBinariesPath)) | |||
|  | 	    { | |||
|  | 		    Directory.CreateDirectory(managedBinariesPath); | |||
|  | 	    } | |||
|  | 
 | |||
|  |     	Collection<string> arguments = new Collection<string> | |||
|  | 		{ | |||
|  | 			"publish", | |||
|  | 			$"-p:PublishDir=\"{managedBinariesPath}\"" | |||
|  | 		}; | |||
|  | 
 | |||
|  | 	    InvokeDotNet(arguments, projectRootDirectory); | |||
|  |     } | |||
|  | 
 | |||
|  |     public static bool InvokeDotNet(Collection<string> arguments, string? workingDirectory = null) | |||
|  |     { | |||
|  | 	    string dotnetPath = FindDotNetExecutable(); | |||
|  | 
 | |||
|  | 	    var startInfo = new ProcessStartInfo | |||
|  | 	    { | |||
|  | 		    FileName = dotnetPath, | |||
|  | 		    RedirectStandardOutput = true, | |||
|  | 		    RedirectStandardError = true | |||
|  | 	    }; | |||
|  | 
 | |||
|  | 	    foreach (string argument in arguments) | |||
|  | 	    { | |||
|  | 		    startInfo.ArgumentList.Add(argument); | |||
|  | 	    } | |||
|  | 
 | |||
|  | 	    if (workingDirectory != null) | |||
|  | 	    { | |||
|  | 		    startInfo.WorkingDirectory = workingDirectory; | |||
|  | 	    } | |||
|  | 
 | |||
|  | 	    // Set the MSBuild environment variables to the latest .NET SDK that U# supports. | |||
|  | 	    // Otherwise, we'll use the .NET SDK that comes with the Unreal Engine. | |||
|  | 	    { | |||
|  | 		    string latestDotNetSdkPath = GetLatestDotNetSdkPath(); | |||
|  | 		    startInfo.Environment["MSBuildExtensionsPath"] = latestDotNetSdkPath; | |||
|  | 		    startInfo.Environment["MSBUILD_EXE_PATH"] = $@"{latestDotNetSdkPath}\MSBuild.dll"; | |||
|  | 		    startInfo.Environment["MSBuildSDKsPath"] = $@"{latestDotNetSdkPath}\Sdks"; | |||
|  | 	    } | |||
|  | 
 | |||
|  | 	    using Process process = new Process(); | |||
|  | 	    process.StartInfo = startInfo; | |||
|  | 
 | |||
|  | 	    try | |||
|  | 	    { | |||
|  | 		    StringBuilder outputBuilder = new StringBuilder(); | |||
|  | 		    process.OutputDataReceived += (sender, e) => | |||
|  | 		    { | |||
|  | 			    if (e.Data != null) | |||
|  | 			    { | |||
|  | 				    outputBuilder.AppendLine(e.Data); | |||
|  | 			    } | |||
|  | 		    }; | |||
|  |              | |||
|  | 		    process.ErrorDataReceived += (sender, e) => | |||
|  | 		    { | |||
|  | 			    if (e.Data != null) | |||
|  | 			    { | |||
|  | 				    outputBuilder.AppendLine(e.Data); | |||
|  | 			    } | |||
|  | 		    }; | |||
|  |              | |||
|  | 		    if (!process.Start()) | |||
|  | 		    { | |||
|  | 			    throw new Exception("Failed to start process"); | |||
|  | 		    } | |||
|  |              | |||
|  | 		    process.BeginErrorReadLine(); | |||
|  | 		    process.BeginOutputReadLine(); | |||
|  | 		    process.WaitForExit(); | |||
|  | 
 | |||
|  | 		    if (process.ExitCode != 0) | |||
|  | 		    { | |||
|  | 			    string errorMessage = outputBuilder.ToString(); | |||
|  | 			     | |||
|  | 			    if (string.IsNullOrEmpty(errorMessage)) | |||
|  | 			    { | |||
|  | 				    errorMessage = "Process exited with non-zero exit code but no output was captured."; | |||
|  | 			    } | |||
|  | 			     | |||
|  | 			    throw new Exception($"Process failed with exit code {process.ExitCode}: {errorMessage}"); | |||
|  | 		    } | |||
|  | 	    } | |||
|  | 	    catch (Exception ex) | |||
|  | 	    { | |||
|  | 		    Console.WriteLine($"An error occurred: {ex.Message}"); | |||
|  | 		    return false; | |||
|  | 	    } | |||
|  | 	     | |||
|  | 	    return true; | |||
|  |     } | |||
|  | 
 | |||
|  |     public static bool InvokeUSharpBuildTool(string action, | |||
|  | 	    string managedBinariesPath, | |||
|  | 	    string projectName, | |||
|  | 	    string pluginDirectory, | |||
|  | 	    string projectDirectory, | |||
|  | 	    string engineDirectory, | |||
|  | 	    IEnumerable<KeyValuePair<string, string>>? additionalArguments = null) | |||
|  |     { | |||
|  | 	    string dotNetExe = FindDotNetExecutable(); | |||
|  | 	    string unrealSharpBuildToolPath = Path.Combine(managedBinariesPath, "UnrealSharpBuildTool.dll"); | |||
|  | 
 | |||
|  | 	    if (!File.Exists(unrealSharpBuildToolPath)) | |||
|  | 	    { | |||
|  | 		    throw new Exception($"Failed to find UnrealSharpBuildTool.dll at: {unrealSharpBuildToolPath}"); | |||
|  | 	    } | |||
|  | 
 | |||
|  | 	    Collection<string> arguments = new Collection<string> | |||
|  | 	    { | |||
|  | 		    unrealSharpBuildToolPath, | |||
|  | 
 | |||
|  | 		    "--Action", | |||
|  | 		    action, | |||
|  | 
 | |||
|  | 		    "--EngineDirectory", | |||
|  | 		    $"{engineDirectory}", | |||
|  | 
 | |||
|  | 		    "--ProjectDirectory", | |||
|  | 		    $"{projectDirectory}", | |||
|  | 
 | |||
|  | 		    "--ProjectName", | |||
|  | 		    projectName, | |||
|  | 
 | |||
|  | 		    "--PluginDirectory", | |||
|  | 		    $"{pluginDirectory}", | |||
|  | 
 | |||
|  | 		    "--DotNetPath", | |||
|  | 		    $"{dotNetExe}" | |||
|  | 	    }; | |||
|  | 
 | |||
|  | 	    if (additionalArguments != null) | |||
|  | 	    { | |||
|  | 		    arguments.Add("--AdditionalArgs"); | |||
|  | 
 | |||
|  | 		    foreach (KeyValuePair<string, string> argument in additionalArguments) | |||
|  | 		    { | |||
|  | 			    arguments.Add($"{argument.Key}={argument.Value}"); | |||
|  | 		    } | |||
|  | 	    } | |||
|  | 
 | |||
|  | 	    return InvokeDotNet(arguments); | |||
|  |     } | |||
|  | } |